diff --git a/.gitignore b/.gitignore index b085703006..deefd2c21a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,17 +2,14 @@ /data/cache/* /data/upload/* /data/preferences/* +/data/.backup/* /data/config.php /custom -/application/Espo/Resources/metadata/scopes/CustomTest.json -/application/Espo/Modules/Crm/Resources/metadata/scopes/CustomTest.json -/application/Espo/Resources/layouts/CustomTest/* -/application/Espo/Modules/Crm/Resources/layouts/CustomTest/* -/application/Espo/Resources/metadata/customTest/* -/application/Espo/Modules/Crm/Resources/metadata/customTest/* /build /node_modules /client /test.php /main.html -/tests/testData/Utils/Config/config.php +/tests/testData/cache/* +composer.phar +vendor/ diff --git a/.htaccess b/.htaccess index defe7f3b6c..db7778a520 100755 --- a/.htaccess +++ b/.htaccess @@ -4,23 +4,22 @@ DirectoryIndex index.php index.html -# PROTECTED DIRECTORIES RewriteEngine On - RewriteCond %{REQUEST_FILENAME} -d - RewriteRule (?i)(data|api) - [F] - -RedirectMatch 403 (?i)/data/config\.php$ -RedirectMatch 403 (?i)/data/logs -RedirectMatch 403 (?i)/data/cache -RedirectMatch 403 (?i)/data/upload -RedirectMatch 403 (?i)/application -RedirectMatch 403 (?i)/custom -RedirectMatch 403 (?i)/vendor -#END PROTECTED DIRECTORIES - - RewriteEngine On + # PROTECTED DIRECTORIES + RewriteCond %{REQUEST_FILENAME} -d + RewriteRule ^/?(data|api)/ - [F] + + RewriteRule ^/?data/config\.php$ - [F] + RewriteRule ^/?data/logs/ - [F] + RewriteRule ^/?data/cache/ - [F] + RewriteRule ^/?data/upload/ - [F] + RewriteRule ^/?application/ - [F] + RewriteRule ^/?custom/ - [F] + RewriteRule ^/?vendor/ - [F] + #END PROTECTED DIRECTORIES + RewriteRule .* - [E=HTTP_ESPO_CGI_AUTH:%{HTTP:Authorization}] RewriteRule reset/?$ reset.html [QSA,L] diff --git a/README.md b/README.md new file mode 100644 index 0000000000..1afcc1ed63 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +EspoCRM is an Open Source CRM (Customer Relationship Management) software that allows you to see, enter and evaluate all your company relationships regardless of the type. People, companies or opportunities - all in an easy and intuitive interface. + +### How to get started + +1. Clone repository to your local computer. +2. Change to the project's root directory. +3. Install [composer](https://getcomposer.org/doc/00-intro.md). +4. Run `composer install` if composer is installed globally or `php composer.phar install` if locally. + +Never update composer dependencies if you are going to contribute code back. + +Now you can build. + +If your repository is accessible via a web server then you can run EspoCRM by url `http://PROJECT_URL/frontend` w/o making a build. You will need to have proper data/config.php and existing database. + +### How to build + +You need to have nodejs installed. + +1. Change to the project's root directory. +2. Install project dependencies with `npm install`. +3. Run Grunt with `grunt`. + +The build will be created in the `build` directory. + +### License + +EspoCRM is published under the GNU GPLv3 [license](https://raw.githubusercontent.com/espocrm/espocrm/master/LICENSE.txt). + diff --git a/application/Espo/Controllers/Admin.php b/application/Espo/Controllers/Admin.php index f9b82cd5fd..9aa225f8a1 100644 --- a/application/Espo/Controllers/Admin.php +++ b/application/Espo/Controllers/Admin.php @@ -72,7 +72,7 @@ class Admin extends \Espo\Core\Controllers\Base { $upgradeManager = new \Espo\Core\UpgradeManager($this->getContainer()); - $upgradeManager->run($data['id']); + $upgradeManager->install($data['id']); return true; } diff --git a/application/Espo/Controllers/Extension.php b/application/Espo/Controllers/Extension.php new file mode 100644 index 0000000000..0cab3b960a --- /dev/null +++ b/application/Espo/Controllers/Extension.php @@ -0,0 +1,131 @@ +getUser()->isAdmin()) { + throw new Forbidden(); + } + } + + public function actionUpload($params, $data, $request) + { + if (!$request->isPost()) { + throw new Forbidden(); + } + + $manager = new \Espo\Core\ExtensionManager($this->getContainer()); + + $id = $manager->upload($data); + $manifest = $manager->getManifest(); + + return array( + 'id' => $id, + 'version' => $manifest['version'], + 'name' => $manifest['name'], + 'description' => $manifest['description'], + ); + } + + public function actionInstall($params, $data, $request) + { + if (!$request->isPost()) { + throw new Forbidden(); + } + + $manager = new \Espo\Core\ExtensionManager($this->getContainer()); + + $manager->install($data['id']); + + return true; + } + + public function actionUninstall($params, $data, $request) + { + if (!$request->isPost()) { + throw new Forbidden(); + } + + $manager = new \Espo\Core\ExtensionManager($this->getContainer()); + + $manager->uninstall($data['id']); + + return true; + } + + public function actionCreate() + { + throw new Forbidden(); + } + + public function actionUpdate() + { + throw new Forbidden(); + } + + public function actionPatch() + { + throw new Forbidden(); + } + + public function actionListLinked() + { + throw new Forbidden(); + } + + public function actionDelete($params, $data, $request) + { + $manager = new \Espo\Core\ExtensionManager($this->getContainer()); + + $manager->delete($params['id']); + + return true; + } + + public function actionMassUpdate() + { + throw new Forbidden(); + } + + public function actionMassDelete() + { + throw new Forbidden(); + } + + public function actionCreateLink() + { + throw new Forbidden(); + } + + public function actionRemoveLink() + { + throw new Forbidden(); + } +} + diff --git a/application/Espo/Controllers/ExternalAccount.php b/application/Espo/Controllers/ExternalAccount.php index 3c4b2c887b..38427406d1 100644 --- a/application/Espo/Controllers/ExternalAccount.php +++ b/application/Espo/Controllers/ExternalAccount.php @@ -45,35 +45,33 @@ class ExternalAccount extends \Espo\Core\Controllers\Record ); } - public function actionGetOAuthCredentials($params, $data, $request) + public function actionGetOAuth2Info($params, $data, $request) { $id = $request->get('id'); list($integration, $userId) = explode('__', $id); - if (!$this->getUser()->isAdmin()) { - if ($this->getUser()->id != $userId) { - throw new Forbidden(); - } - } + + if ($this->getUser()->id != $userId) { + throw new Forbidden(); + } $entity = $this->getEntityManager()->getEntity('Integration', $integration); if ($entity) { return array( 'clientId' => $entity->get('clientId'), - 'redirectUri' => $this->getConfig()->get('siteUrl') . '/oauthcallback' + 'redirectUri' => $this->getConfig()->get('siteUrl') . '/oauthcallback', + 'isConnected' => $this->getRecordService()->ping($integration, $userId) ); } } public function actionRead($params, $data, $request) { - list($integration, $userId) = explode('__', $params['id']); - - if (!$this->getUser()->isAdmin()) { - if ($this->getUser()->id != $userId) { - throw new Forbidden(); - } - } + list($integration, $userId) = explode('__', $params['id']); + + if ($this->getUser()->id != $userId) { + throw new Forbidden(); + } $entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']); return $entity->toArray(); @@ -88,17 +86,39 @@ class ExternalAccount extends \Espo\Core\Controllers\Record { list($integration, $userId) = explode('__', $params['id']); - if (!$this->getUser()->isAdmin()) { - if ($this->getUser()->id != $userId) { - throw new Forbidden(); - } + + if ($this->getUser()->id != $userId) { + throw new Forbidden(); } + if (isset($data['enabled']) && !$data['enabled']) { + $data['data'] = null; + } + $entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']); $entity->set($data); $this->getEntityManager()->saveEntity($entity); return $entity->toArray(); } + + public function actionAuthorizationCode($params, $data, $request) + { + if (!$request->isPost()) { + throw new Error('Bad HTTP method type.'); + } + + $id = $data['id']; + $code = $data['code']; + + list($integration, $userId) = explode('__', $id); + + if ($this->getUser()->id != $userId) { + throw new Forbidden(); + } + + $service = $this->getRecordService(); + return $service->authorizationCode($integration, $userId, $code); + } } diff --git a/application/Espo/Controllers/FieldManager.php b/application/Espo/Controllers/FieldManager.php index f6e3ceff76..eeef95b79e 100644 --- a/application/Espo/Controllers/FieldManager.php +++ b/application/Espo/Controllers/FieldManager.php @@ -70,7 +70,11 @@ class FieldManager extends \Espo\Core\Controllers\Base $fieldManager = $this->getContainer()->get('fieldManager'); $fieldManager->update($params['name'], $data, $params['scope']); - $this->getContainer()->get('dataManager')->rebuild($params['scope']); + if ($fieldManager->isChanged()) { + $this->getContainer()->get('dataManager')->rebuild($params['scope']); + } else { + $this->getContainer()->get('dataManager')->clearCache(); + } return $fieldManager->read($params['name'], $params['scope']); } diff --git a/application/Espo/Controllers/Stream.php b/application/Espo/Controllers/Stream.php index b7fe8e6669..6418c03252 100644 --- a/application/Espo/Controllers/Stream.php +++ b/application/Espo/Controllers/Stream.php @@ -33,7 +33,7 @@ class Stream extends \Espo\Core\Controllers\Base public function actionList($params, $data, $request) { $scope = $params['scope']; - $id = $params['id']; + $id = isset($params['id']) ? $params['id'] : null; $offset = intval($request->get('offset')); $maxSize = intval($request->get('maxSize')); diff --git a/application/Espo/Core/Acl.php b/application/Espo/Core/Acl.php index 8c187d2128..e9ab36dcd8 100644 --- a/application/Espo/Core/Acl.php +++ b/application/Espo/Core/Acl.php @@ -24,6 +24,8 @@ namespace Espo\Core; use \Espo\Core\Exceptions\Error; +use \Espo\ORM\Entity; + class Acl { private $data = array(); @@ -70,7 +72,7 @@ class Acl } - public function checkScope($scope, $action = null, $isOwner = null, $inTeam = null) + public function checkScope($scope, $action = null, $isOwner = null, $inTeam = null, $entity = null) { if (array_key_exists($scope, $this->data)) { if ($this->data[$scope] === false) { @@ -100,6 +102,9 @@ class Acl return true; } } + if ($inTeam === null && $entity) { + $inTeam = $this->checkInTeam($entity); + } if ($inTeam) { if ($value === 'team') { @@ -129,8 +134,10 @@ class Acl return $this->checkScope($subject, $action, $isOwner, $inTeam); } else { $entity = $subject; - $entityName = $entity->getEntityName(); - return $this->checkScope($entityName, $action, $this->checkIsOwner($entity), $this->checkInTeam($entity)); + if ($entity instanceof Entity) { + $entityName = $entity->getEntityName(); + return $this->checkScope($entityName, $action, $this->checkIsOwner($entity), $inTeam, $entity); + } } } @@ -168,7 +175,20 @@ class Acl public function checkInTeam($entity) { $userTeamIds = $this->user->get('teamsIds'); - $teamIds = $entity->get('teamsIds'); + + if (!$entity->hasRelation('teams') || !$entity->hasField('teamsIds')) { + return false; + } + + if (!$entity->has('teamsIds')) { + $entity->loadLinkMultipleField('teams'); + } + + $teamIds = $entity->get('teamsIds'); + + if (empty($teamIds)) { + return false; + } foreach ($userTeamIds as $id) { if (in_array($id, $teamIds)) { diff --git a/application/Espo/Core/Entities/Person.php b/application/Espo/Core/Entities/Person.php index 87144f091e..38de106a55 100644 --- a/application/Espo/Core/Entities/Person.php +++ b/application/Espo/Core/Entities/Person.php @@ -28,25 +28,25 @@ class Person extends \Espo\Core\ORM\Entity public function setLastName($value) { - $this->setValue('lastName', $value); + $this->_setValue('lastName', $value); $firstName = $this->get('firstName'); if (empty($firstName)) { - $this->setValue('name', $value); + $this->_setValue('name', $value); } else { - $this->setValue('name', $firstName . ' ' . $value); + $this->_setValue('name', $firstName . ' ' . $value); } } public function setFirstName($value) { - $this->setValue('firstName', $value); + $this->_setValue('firstName', $value); $lastName = $this->get('lastName'); if (empty($lastName)) { - $this->setValue('name', $value); + $this->_setValue('name', $value); } else { - $this->setValue('name', $value . ' ' . $lastName); + $this->_setValue('name', $value . ' ' . $lastName); } } } diff --git a/application/Espo/Core/ExtensionManager.php b/application/Espo/Core/ExtensionManager.php new file mode 100644 index 0000000000..d9b848c0ed --- /dev/null +++ b/application/Espo/Core/ExtensionManager.php @@ -0,0 +1,41 @@ + 'data/upload/extensions', + + 'backupPath' => 'data/.backup/extensions', + + 'scriptNames' => array( + 'before' => 'BeforeInstall', + 'after' => 'AfterInstall', + 'beforeUninstall' => 'BeforeUninstall', + 'afterUninstall' => 'AfterUninstall', + ) + ); +} diff --git a/application/Espo/Core/ExternalAccount/ClientManager.php b/application/Espo/Core/ExternalAccount/ClientManager.php new file mode 100644 index 0000000000..7d5dcd59bb --- /dev/null +++ b/application/Espo/Core/ExternalAccount/ClientManager.php @@ -0,0 +1,125 @@ +entityManager = $entityManager; + $this->metadata = $metadata; + $this->config = $config; + } + + protected function getMetadata() + { + return $this->metadata; + } + + protected function getEntityManager() + { + return $this->entityManager; + } + + protected function getConfig() + { + return $this->config; + } + + public function storeAccessToken($hash, $data) + { + if (!empty($this->clientMap[$hash]) && !empty($this->clientMap[$hash]['externalAccountEntity'])) { + $externalAccountEntity = $this->clientMap[$hash]['externalAccountEntity']; + $externalAccountEntity->set('accessToken', $data['accessToken']); + $externalAccountEntity->set('tokenType', $data['tokenType']); + $this->getEntityManager()->saveEntity($externalAccountEntity); + } + } + + public function create($integration, $userId) + { + $authMethod = $this->getMetadata()->get("integrations.{$integration}.authMethod"); + $methodName = 'create' . ucfirst($authMethod); + return $this->$methodName($integration, $userId); + } + + protected function createOAuth2($integration, $userId) + { + $integrationEntity = $this->getEntityManager()->getEntity('Integration', $integration); + $externalAccountEntity = $this->getEntityManager()->getEntity('ExternalAccount', $integration . '__' . $userId); + + $className = $this->getMetadata()->get("integrations.{$integration}.clientClassName"); + + $redirectUri = $this->getConfig()->get('siteUrl') . '/oauthcallback'; // TODO move to client class + + if (!$externalAccountEntity) { + throw new Error("External Account {$integration} not found for {$userId}"); + } + + if (!$integrationEntity->get('enabled')) { + return null; + } + if (!$externalAccountEntity->get('enabled')) { + return null; + } + + $oauth2Client = new \Espo\Core\ExternalAccount\OAuth2\Client(); + + $client = new $className($oauth2Client, array( + 'endpoint' => $this->getMetadata()->get("integrations.{$integration}.params.endpoint"), + 'tokenEndpoint' => $this->getMetadata()->get("integrations.{$integration}.params.tokenEndpoint"), + 'clientId' => $integrationEntity->get('clientId'), + 'clientSecret' => $integrationEntity->get('clientSecret'), + 'redirectUri' => $redirectUri, + 'accessToken' => $externalAccountEntity->get('accessToken'), + 'refreshToken' => $externalAccountEntity->get('refreshToken'), + 'tokenType' => $externalAccountEntity->get('tokenType'), + ), $this); + + $this->addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId); + + return $client; + } + + protected function addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId) + { + $this->clientMap[spl_object_hash($client)] = array( + 'client' => $client, + 'userId' => $userId, + 'integration' => $integrationEntity->id, + 'integrationEntity' => $integrationEntity, + 'externalAccountEntity' => $externalAccountEntity, + ); + } +} + diff --git a/application/Espo/Core/ExternalAccount/Clients/Google.php b/application/Espo/Core/ExternalAccount/Clients/Google.php new file mode 100644 index 0000000000..089b5f5e31 --- /dev/null +++ b/application/Espo/Core/ExternalAccount/Clients/Google.php @@ -0,0 +1,34 @@ +client = $client; + + $this->setParams($params); + + $this->manager = $manager; + } + + public function getParam($name) + { + if (in_array($name, $this->paramList)) { + return $this->$name; + } + } + + public function setParam($name, $value) + { + if (in_array($name, $this->paramList)) { + $methodName = 'set' . ucfirst($name); + if (method_exists($this->client, $methodName)) { + $this->client->$methodName($value); + } + $this->$name = $value; + } + } + + public function setParams(array $params) + { + foreach ($this->paramList as $name) { + if (!empty($params[$name])) { + $this->setParam($name, $params[$name]); + } + } + } + + protected function afterTokenRefreshed($data) + { + if ($this->manager) { + $this->manager->storeAccessToken(spl_object_hash($this), $data); + } + } + + public function getAccessTokenFromAuthorizationCode($code) + { + $r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_AUTHORIZATION_CODE, array( + 'code' => $code, + 'redirect_uri' => $this->getParam('redirectUri') + )); + + + if ($r['code'] == 200) { + $data = array(); + if (!empty($r['result'])) { + $data['accessToken'] = $r['result']['access_token']; + $data['tokenType'] = $r['result']['token_type']; + $data['refreshToken'] = $r['result']['refresh_token']; + } + return $data; + } + return null; + } + + abstract protected function getPingUrl(); + + public function ping() + { + if (empty($this->accessToken) || empty($this->clientId) || empty($this->clientSecret)) { + return false; + } + + $url = $this->getPingUrl(); + + try { + $this->request($url); + return true; + } catch (\Exception $e) { + return false; + } + } + + public function request($url, $params = array(), $httpMethod = Client::HTTP_METHOD_GET, $allowRenew = true) + { + $r = $this->client->request($url, $params, $httpMethod); + + $code = null; + if (!empty($r['code'])) { + $code = $r['code']; + } + + if ($code == 200) { + return $r['result']; + } else { + $handledData = $this->handleErrorResponse($r); + + if ($allowRenew && is_array($handledData)) { + if ($handledData['action'] == 'refreshToken') { + if ($this->refreshToken()) { + return $this->request($url, $params, $httpMethod, false); + } + } else if ($handledData['action'] == 'renew') { + return $this->request($url, $params, $httpMethod, false); + } + } + } + + throw new Error("Error after requesting {$httpMethod} {$url}.", $code); + } + + protected function refreshToken() + { + if (!empty($this->refreshToken)) { + $r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_REFRESH_TOKEN, array( + 'refresh_token' => $this->refreshToken, + )); + if ($r['code'] == 200) { + if (is_array($r['result'])) { + if (!empty($r['result']['access_token'])) { + $data = array(); + $data['accessToken'] = $r['result']['access_token']; + $data['tokenType'] = $r['result']['token_type']; + + $this->setParams($data); + $this->afterTokenRefreshed($data); + return true; + } + } + } + } + } + + protected function handleErrorResponse($r) + { + if ($r['code'] == 401 && !empty($r['result'])) { + $result = $r['result']; + if (strpos($r['header'], 'error=invalid_token') !== false) { + return array( + 'action' => 'refreshToken' + ); + } else { + return array( + 'action' => 'renew' + ); + } + } else if ($r['code'] == 400 && !empty($r['result'])) { + if ($r['result']['error'] == 'invalid_token') { + return array( + 'action' => 'refreshToken' + ); + } + } + } +} + diff --git a/application/Espo/Core/ExternalAccount/OAuth2/Client.php b/application/Espo/Core/ExternalAccount/OAuth2/Client.php new file mode 100644 index 0000000000..dea4caf0a8 --- /dev/null +++ b/application/Espo/Core/ExternalAccount/OAuth2/Client.php @@ -0,0 +1,248 @@ +clientId = $clientId; + } + + public function setClientSecret($clientSecret) + { + $this->clientSecret = $clientSecret; + } + + public function setAccessToken($accessToken) + { + $this->accessToken = $accessToken; + } + + public function setAuthType($authType) + { + $this->authType = $authType; + } + + public function setCertificateFile($certificateFile) + { + $this->certificateFile = $certificateFile; + } + + public function setCurlOption($option, $value) + { + $this->curlOptions[$option] = $value; + } + + public function setCurlOptions($options) + { + $this->curlOptions = array_merge($this->curlOptions, $options); + } + + public function setTokenType($tokenType) + { + $this->tokenType = $tokenType; + } + + public function setAccessTokenSecret($accessTokenSecret) + { + $this->accessTokenSecret = $accessTokenSecret; + } + + public function request($url, $params = array(), $httpMethod = self::HTTP_METHOD_GET, array $httpHeaders = array(), $contentType = self::CONTENT_TYPE_MULTIPART) + { + if ($this->accessToken) { + switch ($this->tokenType) { + case self::TOKEN_TYPE_URI: + $params[$this->accessTokenParamName] = $this->accessToken; + break; + case self::TOKEN_TYPE_BEARER: + $httpHeaders['Authorization'] = 'Bearer ' . $this->accessToken; + break; + case self::TOKEN_TYPE_OAUTH: + $httpHeaders['Authorization'] = 'OAuth ' . $this->accessToken; + break; + default: + throw new \Exception('Unknown access token type.'); + + } + } + + return $this->execute($url, $params, $httpMethod, $httpHeaders, $contentType); + } + + private function execute($url, $params = array(), $httpMethod, array $httpHeaders = array(), $contentType = self::CONTENT_TYPE_MULTIPART) + { + $curlOptions = array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_CUSTOMREQUEST => $httpMethod + ); + + switch ($httpMethod) { + case self::HTTP_METHOD_POST: + $curlOptions[CURLOPT_POST] = true; + case self::HTTP_METHOD_PUT: + case self::HTTP_METHOD_PATCH: + if (self::CONTENT_TYPE_APPLICATION === $contentType) { + $postFields = http_build_query($params, null, '&'); + } + $curlOptions[CURLOPT_POSTFIELDS] = $postFields; + break; + case self::HTTP_METHOD_HEAD: + $curlOptions[CURLOPT_NOBODY] = true; + case self::HTTP_METHOD_DELETE: + case self::HTTP_METHOD_GET: + if (strpos($url, '?') === false) { + $url .= '?'; + } + $url .= http_build_query($params, null, '&'); + break; + default: + break; + } + + $curlOptions[CURLOPT_URL] = $url; + + $curlOptHttpHeader = array(); + foreach ($httpHeaders as $key => $value) { + $curlOptHttpHeader[] = "{$key}: {$value}"; + } + $curlOptions[CURLOPT_HTTPHEADER] = $curlOptHttpHeader; + + $ch = curl_init(); + curl_setopt_array($ch, $curlOptions); + + curl_setopt($ch, CURLOPT_HEADER, 1); + + if (!empty($this->certificateFile)) { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_CAINFO, $this->certificateFile); + } else { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + } + + if (!empty($this->curlOptions)) { + curl_setopt_array($ch, $this->curlOptions); + } + + $response = curl_exec($ch); + + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + + $responceHeader = substr($response, 0, $headerSize); + $responceBody = substr($response, $headerSize); + + $resultArray = null; + + if ($curlError = curl_error($ch)) { + throw new \Exception($curlError); + } else { + $resultArray = json_decode($responceBody, true); + } + curl_close($ch); + + return array( + 'result' => (null !== $resultArray) ? $resultArray: $responceBody, + 'code' => intval($httpCode), + 'contentType' => $contentType, + 'header' => $responceHeader, + ); + } + + public function getAccessToken($url, $grantType, array $params) + { + $params['grant_type'] = $grantType; + + $httpHeaders = array(); + switch ($this->clientAuth) { + case self::AUTH_TYPE_URI: + case self::AUTH_TYPE_FORM: + $params['client_id'] = $this->clientId; + $params['client_secret'] = $this->clientSecret; + break; + case self::AUTH_TYPE_AUTHORIZATION_BASIC: + $params['client_id'] = $this->clientId; + $httpHeaders['Authorization'] = 'Basic ' . base64_encode($this->clientId . ':' . $this->clientSecret); + break; + default: + throw new \Exception(); + } + + return $this->execute($url, $params, self::HTTP_METHOD_POST, $httpHeaders, self::CONTENT_TYPE_APPLICATION); + } +} + diff --git a/application/Espo/Core/ORM/Repositories/RDB.php b/application/Espo/Core/ORM/Repositories/RDB.php index c889b778af..0023ee6f9b 100644 --- a/application/Espo/Core/ORM/Repositories/RDB.php +++ b/application/Espo/Core/ORM/Repositories/RDB.php @@ -26,6 +26,7 @@ use \Espo\ORM\EntityManager; use \Espo\ORM\EntityFactory; use \Espo\ORM\Entity; use \Espo\ORM\IEntity; +use Espo\Core\Utils\Util; use \Espo\Core\Interfaces\Injectable; @@ -63,11 +64,38 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable { $this->handleEmailAddressParams($params); $this->handlePhoneNumberParams($params); + $this->handleCurrencyParams($params); + } + + protected function handleCurrencyParams(&$params) + { + $entityName = $this->entityName; + + $metadata = $this->getMetadata(); + + if (!$metadata) { + return; + } + + $defs = $metadata->get('entityDefs.' . $entityName); + + foreach ($defs['fields'] as $field => $d) { + if (isset($d['type']) && $d['type'] == 'currency') { + if (empty($params['customJoin'])) { + $params['customJoin'] = ''; + } + $alias = Util::toUnderScore($field) . "_currency_alias"; + $params['customJoin'] .= " + LEFT JOIN currency AS `{$alias}` ON {$alias}.id = ".Util::toUnderScore($entityName).".".Util::toUnderScore($field)."_currency + "; + } + } + } protected function handleEmailAddressParams(&$params) { - $entityName = $this->entityName; + $entityName = $this->entityName; $defs = $this->getEntityManager()->getMetadata()->get($entityName); if (!empty($defs['relations']) && array_key_exists('emailAddresses', $defs['relations'])) { diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php index 746e16dbf5..b32890dfd0 100644 --- a/application/Espo/Core/SelectManagers/Base.php +++ b/application/Espo/Core/SelectManagers/Base.php @@ -39,7 +39,7 @@ class Base protected $entityName; protected $metadata; - + const MIN_LENGTH_FOR_CONTENT_SEARCH = 4; public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, $metadata) @@ -84,7 +84,7 @@ class Base } } } - + protected function getTextFilterFields() { return $this->metadata->get("entityDefs.{$this->entityName}.collection.textFilterFields", array('name')); @@ -111,9 +111,9 @@ class Base $fieldDefs = $this->entityManager->getEntity($this->entityName)->getFields(); $fieldList = $this->getTextFilterFields(); $d = array(); - foreach ($fieldList as $field) { + foreach ($fieldList as $field) { if ( - strlen($item['value']) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH + strlen($item['value']) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH && !empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text' ) { @@ -122,7 +122,7 @@ class Base $d[$field . '*'] = $item['value'] . '%'; } } - $where['OR'] = $d; + $where['OR'] = $d; } } } @@ -174,7 +174,26 @@ class Base if (empty($result['whereClause'])) { $result['whereClause'] = array(); } - $result['whereClause']['name*'] = $params['q'] . '%'; + + $fieldDefs = $this->entityManager->getEntity($this->entityName)->getFields(); + + $value = $params['q']; + + $fieldList = $this->getTextFilterFields(); + $d = array(); + foreach ($fieldList as $field) { + if ( + strlen($item['value']) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH + && + !empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text' + ) { + $d[$field . '*'] = '%' . $value . '%'; + } else { + $d[$field . '*'] = $value . '%'; + } + } + + $result['whereClause']['OR'] = $d; } } @@ -297,6 +316,57 @@ class Base case 'future': $part[$item['field'] . '>'] = date('Y-m-d'); break; + case 'currentMonth': + $dt = new \DateTime(); + $part['AND'] = array( + $item['field'] . '>=' => $dt->modify('first day of this month')->format('Y-m-d'), + $item['field'] . '<' => $dt->add(new \DateInterval('P1M'))->format('Y-m-d'), + ); + break; + case 'lastMonth': + $dt = new \DateTime(); + $part['AND'] = array( + $item['field'] . '>=' => $dt->modify('first day of last month')->format('Y-m-d'), + $item['field'] . '<' => $dt->add(new \DateInterval('P1M'))->format('Y-m-d'), + ); + break; + case 'currentQuarter': + $dt = new \DateTime(); + $quarter = ceil($dt->format('m') / 3); + $dt->modify('first day of January this year'); + $part['AND'] = array( + $item['field'] . '>=' => $dt->add(new \DateInterval('P'.(($quarter - 1) * 3).'M'))->format('Y-m-d'), + $item['field'] . '<' => $dt->add(new \DateInterval('P3M'))->format('Y-m-d'), + ); + break; + case 'lastQuarter': + $dt = new \DateTime(); + $quarter = ceil($dt->format('m') / 3); + $dt->modify('first day of January this year'); + $quarter--; + if ($quarter == 0) { + $quarter = 4; + $dt->sub('P1Y'); + } + $part['AND'] = array( + $item['field'] . '>=' => $dt->add(new \DateInterval('P'.(($quarter - 1) * 3).'M'))->format('Y-m-d'), + $item['field'] . '<' => $dt->add(new \DateInterval('P3M'))->format('Y-m-d'), + ); + break; + case 'currentYear': + $dt = new \DateTime(); + $part['AND'] = array( + $item['field'] . '>=' => $dt->modify('first day of January this year')->format('Y-m-d'), + $item['field'] . '<' => $dt->add(new \DateInterval('P1Y'))->format('Y-m-d'), + ); + break; + case 'lastYear': + $dt = new \DateTime(); + $part['AND'] = array( + $item['field'] . '>=' => $dt->modify('first day of January last year')->format('Y-m-d'), + $item['field'] . '<' => $dt->add(new \DateInterval('P1Y'))->format('Y-m-d'), + ); + break; case 'between': if (is_array($item['value'])) { $part['AND'] = array( diff --git a/application/Espo/Core/UpgradeManager.php b/application/Espo/Core/UpgradeManager.php index 3c2102c8e3..5d42afe3d7 100644 --- a/application/Espo/Core/UpgradeManager.php +++ b/application/Espo/Core/UpgradeManager.php @@ -26,11 +26,14 @@ use Espo\Core\Exceptions\Error; class UpgradeManager extends Upgrades\Base { - protected $packagePath = 'data/upload/upgrades'; + protected $name = 'Upgrade'; - protected $scriptNames = array( - 'before' => 'BeforeUpgrade', - 'after' => 'AfterUpgrade', + protected $params = array( + 'packagePath' => 'data/upload/upgrades', + + 'scriptNames' => array( + 'before' => 'BeforeUpgrade', + 'after' => 'AfterUpgrade', + ) ); - } \ No newline at end of file diff --git a/application/Espo/Core/Upgrades/ActionManager.php b/application/Espo/Core/Upgrades/ActionManager.php new file mode 100644 index 0000000000..44a0fc7b8a --- /dev/null +++ b/application/Espo/Core/Upgrades/ActionManager.php @@ -0,0 +1,102 @@ +managerName = $managerName; + $this->container = $container; + + $params['name'] = $managerName; + $this->params = $params; + } + + protected function getManagerName() + { + return $this->managerName; + } + + protected function getContainer() + { + return $this->container; + } + + public function setAction($action) + { + $this->currentAction = $action; + } + + public function getAction() + { + return $this->currentAction; + } + + public function getParams() + { + return $this->params; + } + + public function run($data) + { + $object = $this->getObject(); + + return $object->run($data); + } + + public function getManifest() + { + return $this->getObject()->getManifest(); + } + + protected function getObject() + { + $managerName = $this->getManagerName(); + $actionName = $this->getAction(); + + if (!isset($this->objects[$managerName][$actionName])) { + $class = '\Espo\Core\Upgrades\Actions\\' . ucfirst($managerName) . '\\' . ucfirst($actionName); + + if (!class_exists($class)) { + throw new Error('Could not find an action ['.ucfirst($actionName).'], class ['.$class.'].'); + } + + $this->objects[$managerName][$actionName] = new $class($this->container, $this); + } + + return $this->objects[$managerName][$actionName]; + } +} \ No newline at end of file diff --git a/application/Espo/Core/Upgrades/Actions/Base.php b/application/Espo/Core/Upgrades/Actions/Base.php new file mode 100644 index 0000000000..fb270cf0b9 --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Base.php @@ -0,0 +1,488 @@ + 'upgrade', + 'extension' => 'extension', + ); + + /** + * Default package type + */ + protected $defaultPackageType = 'extension'; + + + public function __construct(\Espo\Core\Container $container, \Espo\Core\Upgrades\ActionManager $actionManager) + { + $this->container = $container; + $this->actionManager = $actionManager; + $this->params = $actionManager->getParams(); + + $this->zipUtil = new \Espo\Core\Utils\File\ZipArchive($container->get('fileManager')); + } + + public function __destruct() + { + $this->processId = null; + $this->data = null; + } + + protected function getContainer() + { + return $this->container; + } + + protected function getActionManager() + { + return $this->actionManager; + } + + protected function getParams($name = null) + { + if (isset($this->params[$name])) { + return $this->params[$name]; + } + + return $this->params; + } + + protected function getZipUtil() + { + return $this->zipUtil; + } + + protected function getFileManager() + { + if (!isset($this->fileManager)) { + $this->fileManager = $this->getContainer()->get('fileManager'); + } + return $this->fileManager; + } + + protected function getConfig() + { + if (!isset($this->config)) { + $this->config = $this->getContainer()->get('config'); + } + return $this->config; + } + + protected function getEntityManager() + { + if (!isset($this->entityManager)) { + $this->entityManager = $this->getContainer()->get('entityManager'); + } + return $this->entityManager; + } + + protected function throwErrorAndRemovePackage($errorMessage = '') + { + $this->deletePackageFiles(); + $this->deletePackageArchive(); + throw new Error($errorMessage); + } + + abstract public function run($data); + + protected function createProcessId() + { + if (isset($this->processId)) { + throw new Error('Another installation process is currently running.'); + } + + $this->processId = uniqid(); + + return $this->processId; + } + + protected function getProcessId() + { + if (!isset($this->processId)) { + throw new Error('Installation ID was not specified.'); + } + + return $this->processId; + } + + protected function setProcessId($processId) + { + $this->processId = $processId; + } + + /** + * Check if version of upgrade/extension is acceptable to current version of EspoCRM + * + * @param string $version + * @return boolean + */ + protected function isAcceptable() + { + $res = $this->checkPackageType(); + $res &= $this->checkVersions(); + + return (bool) $res; + } + + protected function checkVersions() + { + $manifest = $this->getManifest(); + + /** check acceptable versions */ + $version = $manifest['acceptableVersions']; + if (empty($version)) { + return true; + } + + $currentVersion = $this->getConfig()->get('version'); + + if (is_string($version)) { + $version = (array) $version; + } + + foreach ($version as $strVersion) { + + $strVersion = trim($strVersion); + + if ($strVersion == $currentVersion) { + return true; + } + + $strVersion = str_replace('\\', '', $strVersion); + $strVersion = preg_quote($strVersion); + $strVersion = str_replace('\\*', '+', $strVersion); + + if (preg_match('/^'.$strVersion.'/', $currentVersion)) { + return true; + } + } + + $this->throwErrorAndRemovePackage('Your EspoCRM version doesn\'t match for this installation package.'); + } + + protected function checkPackageType() + { + $manifest = $this->getManifest(); + + /** check package type */ + $type = strtolower( $this->getParams('name') ); + $manifestType = isset($manifest['type']) ? strtolower($manifest['type']) : $this->defaultPackageType; + + if (!in_array($manifestType, $this->packageTypes)) { + $this->throwErrorAndRemovePackage('Unknown package type.'); + } + + if ($type != $manifestType) { + $this->throwErrorAndRemovePackage('Wrong package type. You cannot install '.$manifestType.' package via '.ucfirst($type).' Manager.'); + } + + return true; + } + + /** + * Run scripts by type + * @param string $type Ex. "before", "after" + * @return void + */ + protected function runScript($type) + { + $packagePath = $this->getPackagePath(); + $scriptNames = $this->getParams('scriptNames'); + + $scriptName = $scriptNames[$type]; + if (!isset($scriptName)) { + return; + } + + $beforeInstallScript = Util::concatPath( array($packagePath, self::SCRIPTS, $scriptName) ) . '.php'; + + if (file_exists($beforeInstallScript)) { + require_once($beforeInstallScript); + $script = new $scriptName(); + + try { + $script->run($this->getContainer()); + } catch (\Exception $e) { + $this->throwErrorAndRemovePackage($e->getMessage()); + } + } + } + + /** + * Get package path + * + * @param string $processId + * @return string + */ + protected function getPath($name = 'packagePath', $isPackage = false) + { + $postfix = $isPackage ? $this->packagePostfix : ''; + + $processId = $this->getProcessId(); + $path = Util::concatPath($this->getParams($name), $processId); + + return $path . $postfix; + } + + protected function getPackagePath($isPackage = false) + { + return $this->getPath('packagePath', $isPackage); + } + + /** + * Get a list of files defined in manifest.json + * + * @return [type] [description] + */ + protected function getDeleteFileList() + { + $manifest = $this->getManifest(); + + if (!empty($manifest['delete'])) { + return $manifest['delete']; + } + + return array(); + } + + /** + * Delete files defined in a manifest + * + * @return boolen + */ + protected function deleteFiles() + { + $deleteFileList = $this->getDeleteFileList(); + + if (!empty($deleteFileList)) { + return $this->getFileManager()->remove($deleteFileList); + } + + return true; + } + + protected function getCopyFileList() + { + if (!isset($this->data['fileList'])) { + $packagePath = $this->getPackagePath(); + $filesPath = Util::concatPath($packagePath, self::FILES); + + $this->data['fileList'] = $this->getFileManager()->getFileList($filesPath, true, '', 'all', true); + } + + return $this->data['fileList']; + } + + protected function copy($sourcePath, $destPath, $recursively = false, array $fileList = null, $copyOnlyFiles = false) + { + try { + $res = $this->getFileManager()->copy($sourcePath, $destPath, $recursively, $fileList, $copyOnlyFiles); + } catch (\Exception $e) { + $this->throwErrorAndRemovePackage($e->getMessage()); + } + + return $res; + } + + /** + * Copy files from upgrade/extension package + * + * @param string $processId + * @return boolean + */ + protected function copyFiles() + { + $packagePath = $this->getPackagePath(); + $filesPath = Util::concatPath($packagePath, self::FILES); + + return $this->copy($filesPath, '', true); + } + + public function getManifest() + { + if (!isset($this->data['manifest'])) { + $packagePath = $this->getPackagePath(); + + $manifestPath = Util::concatPath($packagePath, $this->manifestName); + if (!file_exists($manifestPath)) { + $this->throwErrorAndRemovePackage('It\'s not an Installation package.'); + } + + $manifestJson = $this->getFileManager()->getContents($manifestPath); + $this->data['manifest'] = Json::decode($manifestJson, true); + + if (!$this->data['manifest']) { + $this->throwErrorAndRemovePackage('Syntax error in manifest.json.'); + } + + if (!$this->checkManifest($this->data['manifest'])) { + $this->throwErrorAndRemovePackage('Unsupported package.'); + } + } + + return $this->data['manifest']; + } + + /** + * Check if the manifest is correct + * + * @param array $manifest + * @return boolean + */ + protected function checkManifest(array $manifest) + { + $requiredFields = array( + 'name', + 'version', + ); + + foreach ($requiredFields as $fieldName) { + if (empty($manifest[$fieldName])) { + return false; + } + } + + return true; + } + + /** + * Unzip a package archieve + * + * @return void + */ + protected function unzipArchive($packagePath = null) + { + $packagePath = isset($packagePath) ? $packagePath : $this->getPackagePath(); + $packageArchivePath = $this->getPackagePath(true); + + if (!file_exists($packageArchivePath)) { + throw new Error('Package Archive doesn\'t exist.'); + } + + $res = $this->getZipUtil()->unzip($packageArchivePath, $packagePath); + if ($res === false) { + throw new Error('Unnable to unzip the file - '.$packagePath.'.'); + } + } + + /** + * Delete temporary package files + * + * @return boolean + */ + protected function deletePackageFiles() + { + $packagePath = $this->getPackagePath(); + $res = $this->getFileManager()->removeInDir($packagePath, true); + + return $res; + } + + /** + * Delete temporary package archive + * + * @return boolean + */ + protected function deletePackageArchive() + { + $packageArchive = $this->getPackagePath(true); + $res = $this->getFileManager()->removeFile($packageArchive); + + return $res; + } + + protected function systemRebuild() + { + return $this->getContainer()->get('dataManager')->rebuild(); + } + + /** + * Execute an action. For ex., execute uninstall action in install + * + * @param [type] $actionName [description] + * @param [type] $data [description] + * @return [type] [description] + */ + protected function executeAction($actionName, $data) + { + $currentAction = $this->getActionManager()->getAction(); + + $this->getActionManager()->setAction($actionName); + $this->getActionManager()->run($data); + + $this->getActionManager()->setAction($currentAction); + } + + protected function beforeRunAction() + { + + } + + protected function afterRunAction() + { + + } + + +} diff --git a/application/Espo/Core/Upgrades/Actions/Base/Delete.php b/application/Espo/Core/Upgrades/Actions/Base/Delete.php new file mode 100644 index 0000000000..210dceb73d --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Base/Delete.php @@ -0,0 +1,55 @@ +debug('Delete package process ['.$processId.']: start run.'); + + if (empty($processId)) { + throw new Error('Delete package package ID was not specified.'); + } + + $this->setProcessId($processId); + + $this->beforeRunAction(); + + /* delete a package */ + $this->deletePackage(); + + $this->afterRunAction(); + + $GLOBALS['log']->debug('Delete package process ['.$processId.']: end run.'); + } + + protected function deletePackage() + { + $packageArchivePath = $this->getPackagePath(true); + $res = $this->getFileManager()->removeFile($packageArchivePath); + + return $res; + } + +} \ No newline at end of file diff --git a/application/Espo/Core/Upgrades/Actions/Base/Install.php b/application/Espo/Core/Upgrades/Actions/Base/Install.php new file mode 100644 index 0000000000..4eb235165d --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Base/Install.php @@ -0,0 +1,112 @@ +debug('Installation process ['.$processId.']: start run.'); + + if (empty($processId)) { + throw new Error('Installation package ID was not specified.'); + } + + $this->setProcessId($processId); + + $this->isCopied = false; + + /** check if an archive is unzipped, if no then unzip */ + $packagePath = $this->getPackagePath(); + if (!file_exists($packagePath)) { + $this->unzipArchive(); + $this->isAcceptable(); + } + + $this->beforeRunAction(); + + /* run before install script */ + $this->runScript('before'); + + /* remove files defined in a manifest */ + if (!$this->deleteFiles()) { + $this->throwErrorAndRemovePackage('Permission denied to delete files.'); + } + + /* copy files from directory "Files" to EspoCRM files */ + if (!$this->copyFiles()) { + $this->throwErrorAndRemovePackage('Cannot copy files.'); + } + $this->isCopied = true; + + if (!$this->systemRebuild()) { + $this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.'); + } + + /* run before install script */ + $this->runScript('after'); + + $this->afterRunAction(); + + /* delete unziped files */ + $this->deletePackageFiles(); + + $GLOBALS['log']->debug('Installation process ['.$processId.']: end run.'); + } + + protected function restoreFiles() + { + $backupPath = $this->getPath('backupPath'); + + $res = true; + if ($this->isCopied) { + $res &= $this->copy(array($backupPath, self::FILES), '', true); + $GLOBALS['log']->info('Restore: copy back'); + } + + $res &= $this->getFileManager()->removeInDir($backupPath, true); + + return $res; + } + + protected function throwErrorAndRemovePackage($errorMessage = '') + { + $this->restoreFiles(); + parent::throwErrorAndRemovePackage($errorMessage); + } +} diff --git a/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php b/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php new file mode 100644 index 0000000000..68528b50bd --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Base/Uninstall.php @@ -0,0 +1,132 @@ +debug('Uninstallation process ['.$processId.']: start run.'); + + if (empty($processId)) { + throw new Error('Uninstallation package ID was not specified.'); + } + + $this->setProcessId($processId); + + $this->beforeRunAction(); + + /* run before install script */ + $this->runScript('beforeUninstall'); + + $backupPath = $this->getPath('backupPath'); + if (file_exists($backupPath)) { + + /* remove extension files, saved in fileList */ + if (!$this->deleteFiles()) { + throw new Error('Permission denied to delete files.'); + } + + /* copy core files */ + if (!$this->copyFiles()) { + throw new Error('Cannot copy files.'); + } + } + + if (!$this->systemRebuild()) { + throw new Error('Error occurred while EspoCRM rebuild.'); + } + + /* run before install script */ + $this->runScript('afterUninstall'); + + $this->afterRunAction(); + + /* delete backup files */ + $this->deletePackageFiles(); + + $GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.'); + } + + protected function getDeleteFileList() + { + $extensionEntity = $this->getExtensionEntity(); + return $extensionEntity->get('fileList'); + } + + protected function restoreFiles() + { + $packagePath = $this->getPath('packagePath'); + $filesPath = Util::concatPath($packagePath, self::FILES); + + if (!file_exists($filesPath)) { + $this->unzipArchive($packagePath); + } + + $res = $this->copy($filesPath, '', true); + $res &= $this->getFileManager()->removeInDir($packagePath, true); + + return $res; + } + + protected function copyFiles() + { + $backupPath = $this->getPath('backupPath'); + $res = $this->copy(array($backupPath, self::FILES), '', true); + + return $res; + } + + /** + * Get backup path + * + * @param string $processId + * @return string + */ + protected function getPackagePath($isPackage = false) + { + if ($isPackage) { + return $this->getPath('packagePath', $isPackage); + } + + return $this->getPath('backupPath'); + } + + protected function deletePackageFiles() + { + $backupPath = $this->getPath('backupPath'); + $res = $this->getFileManager()->removeInDir($backupPath, true); + + return $res; + } + + protected function throwErrorAndRemovePackage($errorMessage = '') + { + $this->restoreFiles(); + throw new Error($errorMessage); + } + +} diff --git a/application/Espo/Core/Upgrades/Actions/Base/Upload.php b/application/Espo/Core/Upgrades/Actions/Base/Upload.php new file mode 100644 index 0000000000..aff8d61a84 --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Base/Upload.php @@ -0,0 +1,62 @@ +createProcessId(); + + $GLOBALS['log']->debug('Installation process ['.$processId.']: start upload the package.'); + + $packagePath = $this->getPackagePath(); + $packageArchivePath = $this->getPackagePath(true); + + if (!empty($data)) { + list($prefix, $contents) = explode(',', $data); + $contents = base64_decode($contents); + } + + $res = $this->getFileManager()->putContents($packageArchivePath, $contents); + if ($res === false) { + throw new Error('Could not upload the package.'); + } + + $this->unzipArchive(); + + $this->isAcceptable(); + + $GLOBALS['log']->debug('Installation process ['.$processId.']: end upload the package.'); + + return $processId; + } +} \ No newline at end of file diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Delete.php b/application/Espo/Core/Upgrades/Actions/Extension/Delete.php new file mode 100644 index 0000000000..4edc165261 --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Extension/Delete.php @@ -0,0 +1,69 @@ +extensionEntity; + } + + /** + * Set Extension Entity + * + * @param \Espo\Entities\Extension $extensionEntity + */ + protected function setExtensionEntity(\Espo\Entities\Extension $extensionEntity) + { + $this->extensionEntity = $extensionEntity; + } + + protected function beforeRunAction() + { + $processId = $this->getProcessId(); + + /** get extension entity */ + $extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId); + if (!isset($extensionEntity)) { + throw new Error('Extension Entity not found.'); + } + $this->setExtensionEntity($extensionEntity); + } + + protected function afterRunAction() + { + /** Delete extension entity */ + $extensionEntity = $this->getExtensionEntity(); + $this->getEntityManager()->removeEntity($extensionEntity); + } +} \ No newline at end of file diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Install.php b/application/Espo/Core/Upgrades/Actions/Extension/Install.php new file mode 100644 index 0000000000..b5425c41b3 --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Extension/Install.php @@ -0,0 +1,229 @@ +findExtension(); + if (!$this->isNew()) { + $this->compareVersion(); + $this->uninstallExtension(); + $this->deleteExtension(); + } + + $this->copyExistingFiles(); + } + + protected function afterRunAction() + { + $this->storeExtension(); + } + + /** + * Copy Existing files to backup directory + * + * @return bool + */ + protected function copyExistingFiles() + { + $fileList = $this->getCopyFileList(); + $backupPath = $this->getPath('backupPath'); + + $res = $this->copy('', array($backupPath, self::FILES), false, $fileList); + + /** copy scripts files */ + $packagePath = $this->getPackagePath(); + $res &= $this->copy(array($packagePath, self::SCRIPTS), array($backupPath, self::SCRIPTS), true); + + return $res; + } + + protected function restoreFiles() + { + $res = true; + if ($this->isCopied) { + $extensionFileList = $this->getCopyFileList(); + $res &= $this->getFileManager()->remove($extensionFileList); + } + + $res &= parent::restoreFiles(); + + return $res; + } + + protected function isNew() + { + $extensionEntity = $this->getExtensionEntity(); + + if (isset($extensionEntity)) { + $id = $this->getExtensionEntity()->get('id'); + } + + return isset($id) ? false : true; + } + + /** + * Get extension ID. It's an ID of existing entity (if available) or Installation ID + * + * @return string + */ + protected function getExtensionId() + { + $extensionEntity = $this->getExtensionEntity(); + if (isset($extensionEntity)) { + $extensionEntityId = $extensionEntity->get('id'); + } + + if (!isset($extensionEntityId)) { + return $this->getProcessId(); + } + + return $extensionEntityId; + } + + /** + * Get entity of this extension + * + * @return \Espo\Entities\Extension + */ + protected function getExtensionEntity() + { + return $this->extensionEntity; + } + + /** + * Find Extension entity + * + * @return \Espo\Entities\Extension + */ + protected function findExtension() + { + $manifest = $this->getManifest(); + + $this->extensionEntity = $this->getEntityManager()->getRepository('Extension')->where(array( + 'name' => $manifest['name'], + 'isInstalled' => true, + ))->findOne(); + + return $this->extensionEntity; + } + + /** + * Create a record of Extension Entity + * + * @return bool + */ + protected function storeExtension() + { + $entityManager = $this->getEntityManager(); + + $extensionEntity = $entityManager->getEntity('Extension', $this->getProcessId()); + if (!isset($extensionEntity)) { + $extensionEntity = $entityManager->getEntity('Extension'); + } + + $manifest = $this->getManifest(); + $fileList = $this->getCopyFileList(); + + $data = array( + 'id' => $this->getProcessId(), + 'name' => $manifest['name'], + 'isInstalled' => true, + 'version' => $manifest['version'], + 'fileList' => $fileList, + 'description' => $manifest['description'], + ); + $extensionEntity->set($data); + + return $entityManager->saveEntity($extensionEntity); + } + + /** + * Compare version between installed and a new extensions + * + * @return void + */ + protected function compareVersion() + { + $manifest = $this->getManifest(); + $extensionEntity = $this->getExtensionEntity(); + + if (isset($extensionEntity)) { + $comparedVersion = version_compare($manifest['version'], $extensionEntity->get('version')); + if ($comparedVersion <= 0) { + $this->throwErrorAndRemovePackage('You cannot install an older version of this extension.'); + } + } + } + + /** + * Throw an exception and remove package files. + * Redeclared to prevent of deleting a package of installed extension. + * + * @param string $errorMessage [description] + * @return [type] [description] + */ + protected function throwErrorAndRemovePackage($errorMessage = '') + { + if (!$this->isNew()) { + throw new Error($errorMessage); + } + + return parent::throwErrorAndRemovePackage($errorMessage); + } + + /** + * If extension already installed, uninstall an old version + * + * @return void + */ + protected function uninstallExtension() + { + $extensionEntity = $this->getExtensionEntity(); + + $this->executeAction(ExtensionManager::UNINSTALL, $extensionEntity->get('id')); + } + + /** + * Delete extension package + * + * @return void + */ + protected function deleteExtension() + { + $extensionEntity = $this->getExtensionEntity(); + + $this->executeAction(ExtensionManager::DELETE, $extensionEntity->get('id')); + } + + + + +} diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php b/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php new file mode 100644 index 0000000000..a405e6beea --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Extension/Uninstall.php @@ -0,0 +1,71 @@ +extensionEntity; + } + + /** + * Set Extension Entity + * + * @param \Espo\Entities\Extension $extensionEntity [description] + */ + protected function setExtensionEntity(\Espo\Entities\Extension $extensionEntity) + { + $this->extensionEntity = $extensionEntity; + } + + protected function beforeRunAction() + { + $processId = $this->getProcessId(); + + /** get extension entity */ + $extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId); + if (!isset($extensionEntity)) { + throw new Error('Extension Entity not found.'); + } + $this->setExtensionEntity($extensionEntity); + } + + protected function afterRunAction() + { + /** Set extension entity, isInstalled = false */ + $extensionEntity = $this->getExtensionEntity(); + + $extensionEntity->set('isInstalled', false); + $this->getEntityManager()->saveEntity($extensionEntity); + } +} \ No newline at end of file diff --git a/application/Espo/Core/Upgrades/Actions/Extension/Upload.php b/application/Espo/Core/Upgrades/Actions/Extension/Upload.php new file mode 100644 index 0000000000..e54b1a67c2 --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Extension/Upload.php @@ -0,0 +1,28 @@ +getManifest(); + + $res = $this->getConfig()->set('version', $manifest['version']); + if (method_exists($this->getConfig(), 'save')) { + $res = $this->getConfig()->save(); + } + $res &= parent::systemRebuild(); + + return $res; + } + + /** + * Delete temporary package files + * + * @return boolean + */ + protected function deletePackageFiles() + { + $res = parent::deletePackageFiles(); + $res &= $this->deletePackageArchive(); + + return $res; + } +} diff --git a/application/Espo/Core/Upgrades/Actions/Upgrade/Upload.php b/application/Espo/Core/Upgrades/Actions/Upgrade/Upload.php new file mode 100644 index 0000000000..a1ee4491be --- /dev/null +++ b/application/Espo/Core/Upgrades/Actions/Upgrade/Upload.php @@ -0,0 +1,28 @@ + 'Before', - 'after' => 'After', - ); - - protected $paths = array( - 'files' => 'files', - 'scripts' => 'scripts', - ); + const UNINSTALL = 'uninstall'; + const DELETE = 'delete'; public function __construct($container) { $this->container = $container; - $this->zipUtil = new \Espo\Core\Utils\File\ZipArchive($container->get('fileManager')); - } - - public function __destruct() - { - $this->upgradeId = null; - $this->data = null; + $this->actionManager = new ActionManager($this->name, $container, $this->params); } protected function getContainer() @@ -75,319 +54,41 @@ abstract class Base return $this->container; } - protected function getZipUtil() + protected function getActionManager() { - return $this->zipUtil; - } - - protected function getFileManager() - { - if (!isset($this->fileManager)) { - $this->fileManager = $this->getContainer()->get('fileManager'); - } - return $this->fileManager; - } - - protected function getConfig() - { - if (!isset($this->config)) { - $this->config = $this->getContainer()->get('config'); - } - return $this->config; - } - - - /** - * Upload an upgrade package - * - * @param [type] $contents - * @return string ID of upgrade process - */ - public function upload($data) - { - $upgradeId = $this->createUpgradeId(); - - $GLOBALS['log']->debug('Upgrade process ['.$upgradeId.']: start upload the package.'); - - $upgradePath = $this->getUpgradePath(); - $upgradePackagePath = $this->getUpgradePath(true); - - if (!empty($data)) { - list($prefix, $contents) = explode(',', $data); - $contents = base64_decode($contents); - } - - $res = $this->getFileManager()->putContents($upgradePackagePath, $contents); - if ($res === false) { - throw new Error('Could not upload the package.'); - } - - $res = $this->getZipUtil()->unzip($upgradePackagePath, $upgradePath); - if ($res === false) { - throw new Error('Unnable to unzip the file - '.$upgradePath.'.'); - } - - if (!$this->isAcceptable()) { - throw new Error("Your EspoCRM version doesn't match for this upgrade package."); - } - - $GLOBALS['log']->debug('Upgrade process ['.$upgradeId.']: end upload the package.'); - - return $upgradeId; - } - - /** - * Main upgrade process - * - * @param string $upgradeId Upgrade ID, gotten in upload stage - * @return bool - */ - public function run($upgradeId) - { - $GLOBALS['log']->debug('Upgrade process ['.$upgradeId.']: start run.'); - - if (empty($upgradeId)) { - throw new Error('Upgrade ID was not specified.'); - } - - $this->setUpgradeId($upgradeId); - - /* run before install script */ - $this->runScript('before'); - - /* remove files defined in a manifest */ - if (!$this->deleteFiles()) { - throw new Error('Permission denied to delete files.'); - } - - /* copy files from directory "Files" to EspoCRM files */ - if (!$this->copyFiles()) { - throw new Error('Cannot copy files.'); - } - - if (!$this->systemRebuild()) { - throw new Error('Error occurred while EspoCRM rebuild.'); - } - - /* run before install script */ - $this->runScript('after'); - - /* delete unziped files */ - $this->deletePackageFiles(); - - $GLOBALS['log']->debug('Upgrade process ['.$upgradeId.']: end run.'); - } - - - protected function createUpgradeId() - { - if (isset($this->upgradeId)) { - throw new Error('Another upgrade process is currently running.'); - } - - $this->upgradeId = uniqid(); - - return $this->upgradeId; - } - - protected function getUpgradeId() - { - if (!isset($this->upgradeId)) { - throw new Error("Upgrade ID was not specified."); - } - - return $this->upgradeId; - } - - protected function setUpgradeId($upgradeId) - { - $this->upgradeId = $upgradeId; - } - - /** - * Check if version of upgrade is acceptable to current version of EspoCRM - * - * @param string $version - * @return boolean - */ - protected function isAcceptable() - { - $manifest = $this->getManifest(); - $version = $manifest['acceptableVersions']; - - $currentVersion = $this->getConfig()->get('version'); - - if (is_string($version)) { - $version = (array) $version; - } - - foreach ($version as $strVersion) { - - $strVersion = trim($strVersion); - - if ($strVersion == $currentVersion) { - return true; - } - - $strVersion = str_replace('\\', '', $strVersion); - $strVersion = preg_quote($strVersion); - $strVersion = str_replace('\\*', '+', $strVersion); - - if (preg_match('/^'.$strVersion.'/', $currentVersion)) { - return true; - } - } - - return false; - } - - /** - * Run scripts by type - * @param string $type Ex. "before", "after" - * @return void - */ - protected function runScript($type) - { - $upgradePath = $this->getUpgradePath(); - - $scriptName = $this->scriptNames[$type]; - if (!isset($scriptName)) { - return; - } - - $beforeInstallScript = Util::concatPath( array($upgradePath, $this->paths['scripts'], $scriptName) ) . '.php'; - - if (file_exists($beforeInstallScript)) { - require_once($beforeInstallScript); - $script = new $scriptName(); - $script->run($this->getContainer()); - } - } - - /** - * Get upgrade path - * - * @param string $upgradeId - * @return string - */ - protected function getUpgradePath($isPackage = false) - { - $postfix = $isPackage ? $this->packagePostfix : ''; - - if (!isset($this->data['upgradePath'])) { - $upgradeId = $this->getUpgradeId(); - $this->data['upgradePath'] = Util::concatPath($this->packagePath, $upgradeId); - } - - return $this->data['upgradePath'] . $postfix; - } - - /** - * Delete files defined in a manifest - * - * @return boolen - */ - protected function deleteFiles() - { - $manifest = $this->getManifest(); - - if (!empty($manifest['delete'])) { - return $this->getFileManager()->remove($manifest['delete']); - } - - return true; - } - - /** - * Copy files from upgrade package - * - * @param string $upgradeId - * @return boolean - */ - protected function copyFiles() - { - $upgradePath = $this->getUpgradePath(); - $filesPath = Util::concatPath($upgradePath, $this->paths['files']); - - return $this->getFileManager()->copy($filesPath, '', true); + return $this->actionManager; } public function getManifest() { - if (!isset($this->data['manifest'])) { - $upgradePath = $this->getUpgradePath(); - - $manifestPath = Util::concatPath($upgradePath, $this->manifestName); - if (!file_exists($manifestPath)) { - throw new Error('It\'s not an upgrade package.'); - } - - $manifestJson = $this->getFileManager()->getContents($manifestPath); - $this->data['manifest'] = Json::decode($manifestJson, true); - - if (!$this->data['manifest']) { - throw new Error('Syntax error in manifest.json.'); - } - - if (!$this->checkManifest($this->data['manifest'])) { - throw new Error('Unsupported package.'); - } - } - - return $this->data['manifest']; + return $this->getActionManager()->getManifest(); } - /** - * Check if the manifest is correct - * - * @param array $manifest - * @return boolean - */ - protected function checkManifest(array $manifest) + public function upload($data) { - $requiredFields = array( - 'name', - 'version', - 'acceptableVersions', - ); + $this->getActionManager()->setAction(self::UPLOAD); - foreach ($requiredFields as $fieldName) { - if (empty($manifest[$fieldName])) { - return false; - } - } - - return true; + return $this->getActionManager()->run($data); } - /** - * Delete temporary package files - * - * @return boolean - */ - protected function deletePackageFiles() + public function install($processId) { - $upgradePath = $this->getUpgradePath(); - $upgradePackagePath = $this->getUpgradePath(true); + $this->getActionManager()->setAction(self::INSTALL); - $res = $this->getFileManager()->removeInDir($upgradePath, true); - $res &= $this->getFileManager()->removeFile($upgradePackagePath); - - return $res; + return $this->getActionManager()->run($processId); } - protected function systemRebuild() + public function uninstall($processId) { - $manifest = $this->getManifest(); + $this->getActionManager()->setAction(self::UNINSTALL); - $res = $this->getConfig()->set('version', $manifest['version']); - if (method_exists($this->getConfig(), 'save')) { - $res = $this->getConfig()->save(); - } - $res &= $this->getContainer()->get('dataManager')->rebuild(); - - return $res; + return $this->getActionManager()->run($processId); } + public function delete($processId) + { + $this->getActionManager()->setAction(self::DELETE); + return $this->getActionManager()->run($processId); + } } diff --git a/application/Espo/Core/Utils/Config.php b/application/Espo/Core/Utils/Config.php index 19270a24a0..09cde9ef46 100644 --- a/application/Espo/Core/Utils/Config.php +++ b/application/Espo/Core/Utils/Config.php @@ -150,7 +150,7 @@ class Config $removeData = empty($this->removeData) ? null : $this->removeData; - $result = $this->getFileManager()->mergeContentsPHP($this->configPath, $values, 1, $removeData); + $result = $this->getFileManager()->mergeContentsPHP($this->configPath, $values, $removeData); if ($result) { $this->changedData = array(); $this->removeData = array(); diff --git a/application/Espo/Core/Utils/Database/DBAL/FieldTypes/JsonArray.php b/application/Espo/Core/Utils/Database/DBAL/FieldTypes/JsonArray.php index ad228d1568..cd822e27a0 100644 --- a/application/Espo/Core/Utils/Database/DBAL/FieldTypes/JsonArray.php +++ b/application/Espo/Core/Utils/Database/DBAL/FieldTypes/JsonArray.php @@ -36,4 +36,4 @@ class JsonArray extends \Doctrine\DBAL\Types\JsonArrayType return 'TEXT'; } -} \ No newline at end of file +} diff --git a/application/Espo/Core/Utils/Database/DBAL/FieldTypes/JsonObject.php b/application/Espo/Core/Utils/Database/DBAL/FieldTypes/JsonObject.php new file mode 100644 index 0000000000..f2f4f756ff --- /dev/null +++ b/application/Espo/Core/Utils/Database/DBAL/FieldTypes/JsonObject.php @@ -0,0 +1,39 @@ +getType() != $column2->getType() ) { + { + $changedProperties = array(); + if ( $column1->getType() != $column2->getType() ) { //espo: fix problem with executing query for custom types $column1DbTypeName = method_exists($column1->getType(), 'getDbTypeName') ? $column1->getType()->getDbTypeName() : $column1->getType()->getName(); $column2DbTypeName = method_exists($column2->getType(), 'getDbTypeName') ? $column2->getType()->getDbTypeName() : $column2->getType()->getName(); if (strtolower($column1DbTypeName) != strtolower($column2DbTypeName)) { - $changedProperties[] = 'type'; + $changedProperties[] = 'type'; } //END: espo - } + } - if ($column1->getNotnull() != $column2->getNotnull()) { - $changedProperties[] = 'notnull'; - } + if ($column1->getNotnull() != $column2->getNotnull()) { + $changedProperties[] = 'notnull'; + } - if ($column1->getDefault() != $column2->getDefault()) { - $changedProperties[] = 'default'; - } + if ($column1->getDefault() != $column2->getDefault()) { + $changedProperties[] = 'default'; + } - if ($column1->getUnsigned() != $column2->getUnsigned()) { - $changedProperties[] = 'unsigned'; - } + if ($column1->getUnsigned() != $column2->getUnsigned()) { + $changedProperties[] = 'unsigned'; + } - if ($column1->getType() instanceof \Doctrine\DBAL\Types\StringType) { - // check if value of length is set at all, default value assumed otherwise. - $length1 = $column1->getLength() ?: 255; - $length2 = $column2->getLength() ?: 255; - if ($length1 != $length2) { - $changedProperties[] = 'length'; - } + if ($column1->getType() instanceof \Doctrine\DBAL\Types\StringType) { + // check if value of length is set at all, default value assumed otherwise. + $length1 = $column1->getLength() ?: 255; + $length2 = $column2->getLength() ?: 255; - if ($column1->getFixed() != $column2->getFixed()) { - $changedProperties[] = 'fixed'; - } - } + /** Espo: column length can be increased only */ + /*if ($length1 != $length2) { + $changedProperties[] = 'length'; + }*/ + if ($length2 > $length1) { + $changedProperties[] = 'length'; + } + /** Espo: end */ - if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) { - if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) { - $changedProperties[] = 'precision'; - } - if ($column1->getScale() != $column2->getScale()) { - $changedProperties[] = 'scale'; - } - } + if ($column1->getFixed() != $column2->getFixed()) { + $changedProperties[] = 'fixed'; + } + } - if ($column1->getAutoincrement() != $column2->getAutoincrement()) { - $changedProperties[] = 'autoincrement'; - } + if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) { + if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) { + $changedProperties[] = 'precision'; + } + if ($column1->getScale() != $column2->getScale()) { + $changedProperties[] = 'scale'; + } + } - // only allow to delete comment if its set to '' not to null. - if ($column1->getComment() !== null && $column1->getComment() != $column2->getComment()) { - $changedProperties[] = 'comment'; - } + if ($column1->getAutoincrement() != $column2->getAutoincrement()) { + $changedProperties[] = 'autoincrement'; + } - $options1 = $column1->getCustomSchemaOptions(); - $options2 = $column2->getCustomSchemaOptions(); + // only allow to delete comment if its set to '' not to null. + if ($column1->getComment() !== null && $column1->getComment() != $column2->getComment()) { + $changedProperties[] = 'comment'; + } - $commonKeys = array_keys(array_intersect_key($options1, $options2)); + $options1 = $column1->getCustomSchemaOptions(); + $options2 = $column2->getCustomSchemaOptions(); - foreach ($commonKeys as $key) { - if ($options1[$key] !== $options2[$key]) { - $changedProperties[] = $key; - } - } + $commonKeys = array_keys(array_intersect_key($options1, $options2)); - $diffKeys = array_keys(array_diff_key($options1, $options2) + array_diff_key($options2, $options1)); + foreach ($commonKeys as $key) { + if ($options1[$key] !== $options2[$key]) { + $changedProperties[] = $key; + } + } - $changedProperties = array_merge($changedProperties, $diffKeys); + $diffKeys = array_keys(array_diff_key($options1, $options2) + array_diff_key($options2, $options1)); - return $changedProperties; - } + $changedProperties = array_merge($changedProperties, $diffKeys); + + return $changedProperties; + } } \ No newline at end of file diff --git a/application/Espo/Core/Utils/Database/Orm/Converter.php b/application/Espo/Core/Utils/Database/Orm/Converter.php index 083502015d..5527ad3e09 100644 --- a/application/Espo/Core/Utils/Database/Orm/Converter.php +++ b/application/Espo/Core/Utils/Database/Orm/Converter.php @@ -164,8 +164,9 @@ class Converter { $entityDefs = $this->getEntityDefs(); + $currentOrmMeta = $ormMeta; //load custom field definitions and customCodes - foreach($ormMeta as $entityName => &$entityParams) { + foreach($currentOrmMeta as $entityName => $entityParams) { foreach($entityParams['fields'] as $fieldName => $fieldParams) { //load custom field definitions @@ -184,15 +185,15 @@ class Converter } $ormMeta = Util::merge($ormMeta, $fieldResult); - } //END: load custom field definitions + } //END: load custom field definitions //todo move to separate file //add a field 'isFollowed' for scopes with 'stream => true' $scopeDefs = $this->getMetadata()->get('scopes.'.$entityName); if (isset($scopeDefs['stream']) && $scopeDefs['stream']) { if (!isset($entityParams['fields']['isFollowed'])) { - $entityParams['fields']['isFollowed'] = array( + $ormMeta[$entityName]['fields']['isFollowed'] = array( 'type' => 'varchar', 'notStorable' => true, ); @@ -302,7 +303,7 @@ class Converter { /** set default type if exists */ if (!isset($fieldParams['type']) || empty($fieldParams['type'])) { - $GLOBALS['log']->warning('Field type does not exist for '.$entityName.':'.$fieldName.'. Use default type ['.$this->defaultFieldType.']'); + $GLOBALS['log']->debug('Field type does not exist for '.$entityName.':'.$fieldName.'. Use default type ['.$this->defaultFieldType.']'); $fieldParams['type'] = $this->defaultFieldType; } /** END: set default type if exists */ @@ -353,19 +354,11 @@ class Converter //if empty field name, then use the main field if (trim($subFieldName) == '') { - if (!isset($fieldTypeMeta['fieldDefs'])) { - $GLOBALS['log']->critical('Empty field defs for ['.$entityName.':'.$fieldName.'] using "actualFields". Main field ['.$fieldName.']'); - } - $subField['name'] = $fieldName; $subField['naming'] = $fieldName; } else { - if (!isset($fieldTypeMeta['fields'][$subFieldName])) { - $GLOBALS['log']->critical('Empty field defs for ['.$entityName.':'.$subFieldName.'] using "actualFields". Main field ['.$fieldName.']'); - } - $namingType = isset($fieldTypeMeta['naming']) ? $fieldTypeMeta['naming'] : $this->defaultNaming; $subField['name'] = $subFieldName; diff --git a/application/Espo/Core/Utils/Database/Orm/Fields/Currency.php b/application/Espo/Core/Utils/Database/Orm/Fields/Currency.php new file mode 100644 index 0000000000..f3d56094a2 --- /dev/null +++ b/application/Espo/Core/Utils/Database/Orm/Fields/Currency.php @@ -0,0 +1,64 @@ + array( + 'fields' => array( + $fieldName => array( + "type" => "float", + "orderBy" => $converedFieldName . " {direction}" + ), + $fieldName . 'Converted' => array( + 'type' => 'float', + 'select' => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate" , + 'where' => + array ( + "=" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate = {value}", + ">" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate > {value}", + "<" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate < {value}", + ">=" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate >= {value}", + "<=" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate <= {value}", + "<>" => Util::toUnderScore($entityName) . "." . $currencyColumnName . " * {$alias}.rate <> {value}" + ), + 'notStorable' => true, + 'orderBy' => $converedFieldName . " {direction}" + ), + ), + ), + ); + } + +} diff --git a/application/Espo/Core/Utils/FieldManager.php b/application/Espo/Core/Utils/FieldManager.php index 547009a3c2..8ccae1fcdb 100644 --- a/application/Espo/Core/Utils/FieldManager.php +++ b/application/Espo/Core/Utils/FieldManager.php @@ -33,6 +33,8 @@ class FieldManager private $metadataUtils; + protected $isChanged = null; + protected $metadataType = 'entityDefs'; protected $customOptionName = 'isCustom'; @@ -61,7 +63,6 @@ class FieldManager return $this->metadataUtils; } - public function read($name, $scope) { $fieldDef = $this->getFieldDef($name, $scope); @@ -91,10 +92,11 @@ class FieldManager $res = true; if (isset($fieldDef['label'])) { $res &= $this->setLabel($name, $fieldDef['label'], $scope); - unset($fieldDef['label']); } - $res &= $this->setEntityDefs($name, $fieldDef, $scope); + if ($this->isDefsChanged($name, $fieldDef, $scope)) { + $res &= $this->setEntityDefs($name, $fieldDef, $scope); + } return (bool) $res; } @@ -109,6 +111,7 @@ class FieldManager 'fields.'.$name, 'links.'.$name, ); + $res = $this->getMetadata()->delete($unsets, $this->metadataType, $scope); $this->deleteLabel($name, $scope); @@ -146,6 +149,42 @@ class FieldManager return $this->getMetadata()->get($this->metadataType.'.'.$scope.'.links.'.$name); } + /** + * Prepare input fieldDefs, remove unnecessary fields + * + * @param string $fieldName + * @param array $fieldDef + * @param string $scope + * @return array + */ + protected function prepareFieldDef($name, $fieldDef, $scope) + { + $unnecessaryFields = array( + 'name', + 'label', + ); + + foreach ($unnecessaryFields as $fieldName) { + if (isset($fieldDef[$fieldName])) { + unset($fieldDef[$fieldName]); + } + } + + if (isset($fieldDef['linkDefs'])) { + $linkDefs = $fieldDef['linkDefs']; + unset($fieldDef['linkDefs']); + } + + $currentOptionList = array_keys((array) $this->getFieldDef($name, $scope)); + foreach ($fieldDef as $defName => $defValue) { + if ( (!isset($defValue) || $defValue === '') && !in_array($defName, $currentOptionList) ) { + unset($fieldDef[$defName]); + } + } + + return $fieldDef; + } + /** * Add all needed block for a field defenition * @@ -156,20 +195,7 @@ class FieldManager */ protected function normalizeDefs($fieldName, array $fieldDef, $scope) { - if (isset($fieldDef['name'])) { - unset($fieldDef['name']); - } - - if (isset($fieldDef['linkDefs'])) { - $linkDefs = $fieldDef['linkDefs']; - unset($fieldDef['linkDefs']); - } - - foreach ($fieldDef as $defName => $defValue) { - if (!isset($defValue) || (is_string($defValue) && $defValue == '') ) { - unset($fieldDef[$defName]); - } - } + $fieldDef = $this->prepareFieldDef($fieldName, $fieldDef, $scope); $metaFieldDef = $this->getMetadataUtils()->getFieldDefsInFieldMeta($fieldDef); if (isset($metaFieldDef)) { @@ -194,6 +220,38 @@ class FieldManager return $defs; } + /** + * Check if changed metadata defenition for a field except 'label' + * + * @return boolean + */ + protected function isDefsChanged($name, $fieldDef, $scope) + { + $fieldDef = $this->prepareFieldDef($name, $fieldDef, $scope); + $currentFieldDef = $this->getFieldDef($name, $scope); + + $this->isChanged = Util::isEquals($fieldDef, $currentFieldDef) ? false : true; + + return $this->isChanged; + } + + /** + * Only for update method + * + * @return boolean + */ + public function isChanged() + { + return $this->isChanged; + } + + /** + * Check if a field is core field + * + * @param string $name + * @param string $scope + * @return boolean + */ protected function isCore($name, $scope) { $existingField = $this->getFieldDef($name, $scope); diff --git a/application/Espo/Core/Utils/File/Manager.php b/application/Espo/Core/Utils/File/Manager.php index 7534e9e45b..2dbe3cf034 100644 --- a/application/Espo/Core/Utils/File/Manager.php +++ b/application/Espo/Core/Utils/File/Manager.php @@ -225,13 +225,12 @@ class Manager * @param string | array $path * @param string $content JSON string * @param bool $isJSON - * @param string | array $mergeOptions * @param string | array $removeOptions - List of unset keys from content * @param bool $isReturn - Is result to be returned or stored * * @return bool | array */ - public function mergeContents($path, $content, $isJSON = false, $mergeOptions = null, $removeOptions = null, $isReturn = false) + public function mergeContents($path, $content, $isJSON = false, $removeOptions = null, $isReturn = false) { $fileContent = $this->getContents($path); @@ -243,7 +242,7 @@ class Manager $newDataArray = Utils\Util::unsetInArray($newDataArray, $removeOptions); } - $data = Utils\Util::merge($savedDataArray, $newDataArray, $mergeOptions); + $data = Utils\Util::merge($savedDataArray, $newDataArray); if ($isJSON) { $data = Utils\Json::encode($data, JSON_PRETTY_PRINT); } @@ -260,13 +259,12 @@ class Manager * * @param string | array $path * @param string $content JSON string - * @param string | array $mergeOptions * @param string | array $removeOptions - List of unset keys from content * @return bool */ - public function mergeContentsPHP($path, $content, $mergeOptions = null, $removeOptions = null) + public function mergeContentsPHP($path, $content, $removeOptions = null) { - $data = $this->mergeContents($path, $content, false, $mergeOptions, $removeOptions, true); + $data = $this->mergeContents($path, $content, false, $removeOptions, true); return $this->putContentsPHP($path, $data); } @@ -362,40 +360,71 @@ class Manager /** * Copy files from one direcoty to another + * Ex. $sourcePath = 'data/uploads/extensions/file.json', $destPath = 'data/uploads/backup', result will be data/uploads/backup/data/uploads/backup/file.json. * * @param string $sourcePath * @param string $destPath * @param boolean $recursively + * @param array $fileList - list of files that should be copied + * @param boolean $copyOnlyFiles - copy only files, instead of full path with directories, Ex. $sourcePath = 'data/uploads/extensions/file.json', $destPath = 'data/uploads/backup', result will be 'data/uploads/backup/file.json' * @return boolen */ - public function copy($sourcePath, $destPath, $recursively = false) + public function copy($sourcePath, $destPath, $recursively = false, array $fileList = null, $copyOnlyFiles = false) { $sourcePath = $this->concatPaths($sourcePath); $destPath = $this->concatPaths($destPath); - if (is_file($sourcePath)) { - $fileList = (array) $sourcePath; + if (isset($fileList)) { + if (!empty($sourcePath)) { + foreach ($fileList as &$fileName) { + $fileName = $this->concatPaths(array($sourcePath, $fileName)); + } + } } else { - $fileList = $this->getFileList($sourcePath, $recursively, '', 'all', true); + $fileList = is_file($sourcePath) ? (array) $sourcePath : $this->getFileList($sourcePath, $recursively, '', 'file', true); + } + + /** Check permission before copying */ + $permissionDeniedList = array(); + foreach ($fileList as $file) { + + if ($copyOnlyFiles) { + $file = pathinfo($file, PATHINFO_BASENAME); + } + + $destFile = $this->concatPaths(array($destPath, $file)); + + $isFileExists = file_exists($destFile); + + if ($this->checkCreateFile($destFile) === false) { + $permissionDeniedList[] = $destFile; + } else if (!$isFileExists) { + $this->removeFile($destFile); + } + } + /** END */ + + if (!empty($permissionDeniedList)) { + $betterPermissionList = $this->getPermissionUtils()->arrangePermissionList($permissionDeniedList); + throw new Error("Permission denied in
". implode(",
", $betterPermissionList)); } $res = true; foreach ($fileList as $file) { - $sourceFile = $this->concatPaths(array($sourcePath, $file)); - $destFile = $this->concatPaths(array($destPath, $file)); - - if ($this->checkCreateFile($destFile) === false) { - throw new Error('Permission denied in '. $destFile); + if ($copyOnlyFiles) { + $file = pathinfo($file, PATHINFO_BASENAME); } + $sourceFile = is_file($sourcePath) ? $sourcePath : $this->concatPaths(array($sourcePath, $file)); + $destFile = $this->concatPaths(array($destPath, $file)); + $res &= copy($sourceFile, $destFile); } return $res; } - /** * Create a new file if not exists with all folders in the path. * @@ -471,17 +500,21 @@ class Manager $fileList = $this->getFileList($dirPath, false); $result = true; - foreach ($fileList as $file) { - $fullPath = Utils\Util::concatPath($dirPath, $file); - if (is_dir($fullPath)) { - $result &= $this->removeInDir($fullPath, true); - } else { - $result &= unlink($fullPath); + if (is_array($fileList)) { + foreach ($fileList as $file) { + $fullPath = Utils\Util::concatPath($dirPath, $file); + if (is_dir($fullPath)) { + $result &= $this->removeInDir($fullPath, true); + } else if (file_exists($fullPath)) { + $result &= unlink($fullPath); + } } } if ($removeWithDir) { - rmdir($dirPath); + if (file_exists($dirPath)) { + rmdir($dirPath); + } } return $result; @@ -566,7 +599,6 @@ class Manager return $pathInfo['dirname']; } - /** * Return content of PHP file * diff --git a/application/Espo/Core/Utils/File/Permission.php b/application/Espo/Core/Utils/File/Permission.php index b1fc85554f..efcc763d91 100644 --- a/application/Espo/Core/Utils/File/Permission.php +++ b/application/Espo/Core/Utils/File/Permission.php @@ -420,7 +420,7 @@ class Permission $owner = $defaultPermissions['user']; if (empty($owner) && $usePosix) { - $owner = posix_getuid(); + $owner = function_exists('posix_getuid') ? posix_getuid() : null; } if (empty($owner)) { @@ -441,7 +441,7 @@ class Permission $group = $defaultPermissions['group']; if (empty($group) && $usePosix) { - $group = posix_getegid(); + $group = function_exists('posix_getegid') ? posix_getegid() : null; } if (empty($group)) { @@ -535,6 +535,71 @@ class Permission return $this->permissionErrorRules; } + /** + * Arrange permission file list + * e.g. array('application/Espo/Controllers/Email.php', 'application/Espo/Controllers/Import.php'), result is array('application/Espo/Controllers') + * + * @param array $fileList + * @return array + */ + public function arrangePermissionList($fileList) + { + $betterList = array(); + foreach ($fileList as $fileName) { + + $pathInfo = pathinfo($fileName); + $dirname = $pathInfo['dirname']; + + $currentPath = $fileName; + if ($this->getSearchCount($dirname, $fileList) > 1) { + $currentPath = $dirname; + } + + if (!$this->isItemIncludes($currentPath, $betterList)) { + $betterList[] = $currentPath; + } + } + + return $betterList; + } + + /** + * Get count of a search string in a array + * + * @param string $search + * @param array $array + * @return bool + */ + protected function getSearchCount($search, array $array) + { + $search = $this->getPregQuote($search); + + $number = 0; + foreach ($array as $value) { + if (preg_match('/^'.$search.'/', $value)) { + $number++; + } + } + + return $number; + } + + protected function isItemIncludes($item, $array) + { + foreach ($array as $value) { + $value = $this->getPregQuote($value); + if (preg_match('/^'.$value.'/', $item)) { + return true; + } + } + + return false; + } + + protected function getPregQuote($string) + { + return preg_quote($string, '/-+=.'); + } } diff --git a/application/Espo/Core/Utils/File/Unifier.php b/application/Espo/Core/Utils/File/Unifier.php index c0b365eeee..355a3c215f 100644 --- a/application/Espo/Core/Utils/File/Unifier.php +++ b/application/Espo/Core/Utils/File/Unifier.php @@ -38,24 +38,21 @@ class Unifier $this->fileManager = $fileManager; } - protected function getFileManager() { return $this->fileManager; } - /** * Unite file content to the file * - * @param [type] $name [description] - * @param [type] $paths [description] + * @param string $name + * @param array $paths * @param boolean $recursively Note: only for first level of sub directory, other levels of sub directories will be ignored - * @param [type] $mergeLevel - merge level, see Espo\Core\Utils\Util::merge() * * @return array */ - public function unify($name, $paths, $recursively = false, $mergeLevel = null, $mergeKeyName = null) + public function unify($name, $paths, $recursively = false) { $content = $this->unifySingle($paths['corePath'], $name, $recursively); @@ -65,19 +62,17 @@ class Unifier foreach ($dirList as $dirName) { $curPath = str_replace('{*}', $dirName, $paths['modulePath']); - $content = Utils\Util::merge($content, $this->unifySingle($curPath, $name, $recursively, $dirName), $mergeLevel, $mergeKeyName); + $content = Utils\Util::merge($content, $this->unifySingle($curPath, $name, $recursively, $dirName)); } } if (!empty($paths['customPath'])) { - $content = Utils\Util::merge($content, $this->unifySingle($paths['customPath'], $name, $recursively), $mergeLevel, $mergeKeyName); + $content = Utils\Util::merge($content, $this->unifySingle($paths['customPath'], $name, $recursively)); } return $content; } - - /** * Unite file content to the file for one directory [NOW ONLY FOR METADATA, NEED TO CHECK FOR LAYOUTS AND OTHERS] * @@ -161,7 +156,7 @@ class Unifier * * @return array */ - protected function loadDefaultValues($name, $type='metadata') + protected function loadDefaultValues($name, $type = 'metadata') { $defaultPath = $this->params['defaultsPath']; diff --git a/application/Espo/Core/Utils/Language.php b/application/Espo/Core/Utils/Language.php index c5f6de1d19..abe599192b 100644 --- a/application/Espo/Core/Utils/Language.php +++ b/application/Espo/Core/Utils/Language.php @@ -241,7 +241,7 @@ class Language $i18nCacheFile = str_replace('{*}', $i18nName, $this->cacheFile); if ($i18nName != $this->defaultLanguage) { - $i18nData = Util::merge($this->fullData[$this->defaultLanguage], $i18nData, null, null, true); + $i18nData = Util::merge($this->fullData[$this->defaultLanguage], $i18nData); } $result &= $this->getFileManager()->putContentsPHP($i18nCacheFile, $i18nData); } diff --git a/application/Espo/Core/Utils/Metadata.php b/application/Espo/Core/Utils/Metadata.php index 9a55bf4a28..06acc37400 100644 --- a/application/Espo/Core/Utils/Metadata.php +++ b/application/Espo/Core/Utils/Metadata.php @@ -186,7 +186,7 @@ class Metadata { $data = false; if (!file_exists($this->cacheFile) || $reload) { - $data = $this->getUnifier()->unify($this->name, $this->paths, true, 5, 'options'); + $data = $this->getUnifier()->unify($this->name, $this->paths, true); if ($data === false) { $GLOBALS['log']->emergency('Metadata:getMetadata() - metadata unite file cannot be created'); @@ -246,7 +246,7 @@ class Metadata { $path = $this->paths['customPath']; - $result = $this->getFileManager()->mergeContents(array($path, $type, $scope.'.json'), $data, true, array(3, 'options')); + $result = $this->getFileManager()->mergeContents(array($path, $type, $scope.'.json'), $data, true); if ($result === false) { throw new Error("Error saving metadata. See log file for details."); } diff --git a/application/Espo/Core/Utils/System.php b/application/Espo/Core/Utils/System.php index 1752668150..dfc1d84dae 100644 --- a/application/Espo/Core/Utils/System.php +++ b/application/Espo/Core/Utils/System.php @@ -33,7 +33,7 @@ class System { $serverSoft = $_SERVER['SERVER_SOFTWARE']; - preg_match('/^(.*)\//i', $serverSoft, $match); + preg_match('/^(.*?)\//i', $serverSoft, $match); if (empty($match[1])) { preg_match('/^(.*)\/?/i', $serverSoft, $match); } diff --git a/application/Espo/Core/Utils/Util.php b/application/Espo/Core/Utils/Util.php index 35c715da70..ecf05a7f57 100644 --- a/application/Espo/Core/Utils/Util.php +++ b/application/Espo/Core/Utils/Util.php @@ -109,107 +109,51 @@ class Util } /** - * Merge arrays (default PHP function is not suitable) + * Merge arrays recursively (default PHP function is not suitable) * - * @param array $array - * @param array $mainArray - chief array (priority is same as for array_merge()) - * @param array $rewriteLevel - Merge by rewrite level, level numering starts from 1. Ex. - * array( - * 'level1' => array( - * 'level2' => array( - * 'level3' => array( - * 'key1' => 'value', - * 'key2' => 'value', - * ), - * ), - * ), - * ) - * @param $rewriteKeyName string - Rewrite key name. It is ignored if $rewriteLevel is NULL. - * @param $rewriteArrays bool - Rewrite single arrays. Examples: - * TRUE: array is [0, 1, 2], main array is [3, 4, 5], Result is [3, 4, 5]. - * FALSE: array is [0, 1, 2], main array is [3, 4, 5], Result is [0, 1, 2, 3, 4, 5]. + * @param array $currentArray + * @param array $newArray - chief array (priority is same as for array_merge()) * * @return array */ - public static function merge($array, $mainArray, $rewriteLevel = null, $rewriteKeyName = null, $rewriteArrays = false) + public static function merge($currentArray, $newArray) { - if (is_array($array) && !is_array($mainArray)) { - return $array; - } else if (!is_array($array) && is_array($mainArray)) { - return $mainArray; - } else if (!is_array($array) && !is_array($mainArray)) { + $mergeIdentifier = '__APPEND__'; + + if (is_array($currentArray) && (!is_array($newArray) || empty($newArray))) { + return $currentArray; + } else if ((!is_array($currentArray) || empty($currentArray)) && is_array($newArray)) { + return $newArray; + } else if ((!is_array($currentArray) || empty($currentArray)) && (!is_array($newArray) || empty($newArray))) { return array(); } - if (is_array($rewriteLevel)) { - if (isset($rewriteLevel[1])) { - $rewriteKeyName = $rewriteLevel[1]; - } - if (isset($rewriteLevel[0])) { - $rewriteLevel = $rewriteLevel[0]; - } - } + /** add root items from currentArray */ + foreach ($currentArray as $currentName => $currentValue) { - foreach($mainArray as $mainKey => $mainValue) { + if (!array_key_exists($currentName, $newArray)) { - $found = false; - foreach($array as $key => $value) { + $newArray[$currentName] = $currentValue; - if ((string)$mainKey == (string)$key) { + } else if (is_array($currentValue) && is_array($newArray[$currentName])) { - $found = true; - if (is_array($mainValue) || is_array($value)) { - - $rowRewriteLevel = $rewriteLevel; - - /** check the $rewriteKeyName */ - if (isset($rowRewriteLevel) && $rowRewriteLevel == 1 && isset($rewriteKeyName)) { - $rewriteKeyName = is_array($rewriteKeyName) ? $rewriteKeyName : (array) $rewriteKeyName; - - if (!in_array((string)$key, $rewriteKeyName)) { - $rowRewriteLevel = null; - } - } /** END: check the $rewriteKeyName */ - - if (!isset($rowRewriteLevel) || $rowRewriteLevel != 1) { - $array[$mainKey] = static::merge((array) $value, (array) $mainValue, --$rowRewriteLevel, $rewriteKeyName, $rewriteArrays); - continue; - } - - $mergeValue = array('mergeLevel' => (array) $value); - $mergeMainValue = array('mergeLevel' => (array) $mainValue); - $mergeRes = array_merge($mergeValue, $mergeMainValue); - $array[$mainKey] = $mergeRes['mergeLevel']; - continue; - } - - /** merge logic */ - if (!is_numeric($mainKey)) { - $array[$mainKey] = $mainValue; - } - elseif (!in_array($mainValue, $array)) { - if ($rewriteArrays) { - $array[$mainKey] = $mainValue; - } else { - $array[] = $mainValue; - } - } /** END: merge logic */ - - break; + /** check __APPEND__ identifier */ + $appendKey = array_search($mergeIdentifier, $newArray[$currentName], true); + if ($appendKey !== false) { + unset($newArray[$currentName][$appendKey]); + $newArray[$currentName] = array_merge($currentValue, $newArray[$currentName]); + } else if (!static::isSingleArray($newArray[$currentName])) { + $newArray[$currentName] = static::merge($currentValue, $newArray[$currentName]); } - } - /** add an item if key not found */ - if (!$found) { - $array[$mainKey] = $mainValue; - } + } } - return $array; + return $newArray; } /** - * Get a full path of the file + * Get a full path of the file * * @param string | array $folderPath - Folder path, Ex. myfolder * @param string $filePath - File path, Ex. file.json @@ -239,7 +183,6 @@ class Util return $folderPath . static::getSeparator() . $filePath; } - /** * Convert array to object format recursively * @@ -255,7 +198,6 @@ class Util } } - /** * Convert object to array format recursively * @@ -285,7 +227,6 @@ class Util return $name; } - /** * Get Naming according to prefix or postfix type * @@ -306,7 +247,6 @@ class Util return null; } - /** * Replace $search in array recursively * @@ -343,19 +283,23 @@ class Util * @param array $content * @param string | array $unsets in format * array( - * 'EntityName1' => array( 'unset1', 'unset2' ), - * 'EntityName2' => array( 'unset1', 'unset2' ), + * 'EntityName1' => array( 'unset1', 'unset2' ), + * 'EntityName2' => array( 'unset1', 'unset2' ), * ) - * OR - * array('EntityName1.unset1', 'EntityName1.unset2', .....) - * OR - * 'EntityName1.unset1' + * OR + * array('EntityName1.unset1', 'EntityName1.unset2', .....) + * OR + * 'EntityName1.unset1' * * @return array */ public static function unsetInArray(array $content, $unsets) { - if (is_string($unsets)) { + if (empty($unsets)) { + return $content; + } + + if (is_string($unsets)) { $unsets = (array) $unsets; } @@ -373,10 +317,11 @@ class Util } $unsetElem = $currVal . "['{$lastKey}']"; + $currVal = " - if (isset({$unsetElem}) || array_key_exists({$lastKey}, {$currVal})) { - unset({$unsetElem}); - } "; + if (isset({$unsetElem}) || ( is_array({$currVal}) && array_key_exists({$lastKey}, {$currVal}) )) { + unset({$unsetElem}); + } "; eval($currVal); } } @@ -385,7 +330,6 @@ class Util return $content; } - /** * Get class name from the file path * @@ -402,7 +346,6 @@ class Util return $className; } - /** * Return values of defined $key. * @@ -431,6 +374,55 @@ class Util return $lastItem; } + /** + * Check if two variables are equals + * + * @param mixed $var1 + * @param mixed $var2 + * @return boolean + */ + public static function isEquals($var1, $var2) + { + if (is_array($var1)) { + static::ksortRecursive($var1); + } + if (is_array($var2)) { + static::ksortRecursive($var2); + } + + return ($var1 === $var2); + } + + /** + * Sort array recursively + * @param array $array + * @return bool + */ + public static function ksortRecursive(&$array) + { + if (!is_array($array)) { + return false; + } + + ksort($array); + foreach ($array as $key => $value) { + static::ksortRecursive($array[$key]); + } + + return true; + } + + public static function isSingleArray(array $array) + { + foreach ($array as $key => $value) { + if (!is_int($key)) { + return false; + } + } + + return true; + } + } diff --git a/application/Espo/Core/defaults/config.php b/application/Espo/Core/defaults/config.php index 149dbbbc0b..31e6e67053 100644 --- a/application/Espo/Core/defaults/config.php +++ b/application/Espo/Core/defaults/config.php @@ -41,7 +41,7 @@ return array ( 'weekStart' => 0, 'thousandSeparator' => ',', 'decimalMark' => '.', - 'exportDelimiter' => ',', + 'exportDelimiter' => ';', 'currencyList' => array ( ), @@ -66,6 +66,7 @@ return array ( 'nl_NL', 'tr_TR', 'ro_RO', + 'ru_RU', 'pl_PL', 'pt_BR', 'vi_VN' diff --git a/application/Espo/Entities/Extension.php b/application/Espo/Entities/Extension.php new file mode 100644 index 0000000000..abad6e82e9 --- /dev/null +++ b/application/Espo/Entities/Extension.php @@ -0,0 +1,29 @@ +get('data')) { - $data = json_decode($this->get('data'), true); + $data = $this->get('data'); } else { - $data = array(); + $data = new \stdClass(); } - if (isset($data[$name])) { - return $data[$name]; + if (isset($data->$name)) { + return $data->$name; } } return null; } + public function clear($name) + { + parent::clear($name); + + $data = $this->get('data'); + if (empty($data)) { + $data = new \stdClass(); + } + unset($data->$name); + $this->set('data', $data); + } + public function set($p1, $p2) { if (is_array($p1)) { @@ -68,12 +80,15 @@ class Integration extends \Espo\Core\ORM\Entity if ($this->hasField($name)) { $this->valuesContainer[$name] = $value; } else { - $data = json_decode($this->get('data'), true); - if (empty($data)) { - $data = array(); + if (!$this->get('enabled')) { + return; } - $data[$name] = $value; - $this->set('data', json_encode($data)); + $data = $this->get('data'); + if (empty($data)) { + $data = new \stdClass(); + } + $data->$name = $value; + $this->set('data', $data); } } @@ -85,7 +100,7 @@ class Integration extends \Espo\Core\ORM\Entity foreach ($arr as $field => $value) { if (is_string($field)) { - if (is_array($value)) { + if (is_array($value) || ($value instanceof \stdClass)) { $value = json_encode($value); } @@ -112,6 +127,12 @@ class Integration extends \Espo\Core\ORM\Entity $value = null; } break; + case self::JSON_OBJECT: + $value = is_string($value) ? json_decode($value) : $value; + if (!($value instanceof \stdClass) && !is_array($value)) { + $value = null; + } + break; default: break; } @@ -139,12 +160,14 @@ class Integration extends \Espo\Core\ORM\Entity } } - $data = json_decode($this->get('data'), true); + $data = $this->get('data'); if (empty($data)) { - $data = array(); + $data = new \stdClass(); } + + $dataArr = get_object_vars($data); - $arr = array_merge($arr, $data); + $arr = array_merge($arr, $dataArr); return $arr; } diff --git a/application/Espo/EntryPoints/Image.php b/application/Espo/EntryPoints/Image.php index c91703575c..976a768ce0 100644 --- a/application/Espo/EntryPoints/Image.php +++ b/application/Espo/EntryPoints/Image.php @@ -72,7 +72,7 @@ class Image extends \Espo\Core\EntryPoints\Base if ($attachment->get('parentId') && $attachment->get('parentType')) { $parent = $this->getEntityManager()->getEntity($attachment->get('parentType'), $attachment->get('parentId')); - if (!$this->getAcl()->check($parent)) { + if ($parent && !$this->getAcl()->check($parent)) { throw new Forbidden(); } } diff --git a/application/Espo/Hooks/Note/Mentions.php b/application/Espo/Hooks/Note/Mentions.php new file mode 100644 index 0000000000..aca031423b --- /dev/null +++ b/application/Espo/Hooks/Note/Mentions.php @@ -0,0 +1,109 @@ +dependencies[] = 'serviceFactory'; + } + + protected function getServiceFactory() + { + return $this->getInjection('serviceFactory'); + } + + protected function addMentionData($entity) + { + $post = $entity->get('post'); + + $mentionData = new \stdClass(); + + $previousMentionList = array(); + if ($entity->isFetched()) { + $data = $entity->get('data'); + if (!empty($data) && !empty($data->mentions)) { + $previousMentionList = array_keys(get_object_vars($data->mentions)); + } + } + + preg_match_all('/(@\w+)/', $post, $matches); + + if (is_array($matches) && !empty($matches[0]) && is_array($matches[0])) { + foreach ($matches[0] as $item) { + $userName = substr($item, 1); + $user = $this->getEntityManager()->getRepository('User')->where(array('userName' => $userName))->findOne(); + if ($user) { + $m = array( + 'id' => $user->id, + 'name' => $user->get('name'), + 'userName' => $user->get('userName'), + '_scope' => $user->getEntityName() + ); + $mentionData->$item = (object) $m; + if (!in_array($item, $previousMentionList)) { + $this->notifyAboutMention($entity, $user); + } + } + } + } + + $data = $entity->get('data'); + if (empty($data)) { + $data = new \stdClass(); + } + $data->mentions = $mentionData; + + $entity->set('data', $data); + } + + public function beforeSave(Entity $entity) + { + if ($entity->get('type') == 'Post') { + $post = $entity->get('post'); + + $this->addMentionData($entity); + } + } + + protected function notifyAboutMention(Entity $entity, \Espo\Entities\User $user) + { + $this->getNotificationService()->notifyAboutMentionInPost($user->id, $entity->id); + } + + protected function getNotificationService() + { + if (empty($this->notificationService)) { + $this->notificationService = $this->getServiceFactory()->create('Notification'); + } + return $this->notificationService; + } +} + diff --git a/application/Espo/Hooks/Note/Notifications.php b/application/Espo/Hooks/Note/Notifications.php index 705be830c5..4d59d77e9d 100644 --- a/application/Espo/Hooks/Note/Notifications.php +++ b/application/Espo/Hooks/Note/Notifications.php @@ -28,6 +28,8 @@ class Notifications extends \Espo\Core\Hooks\Base { protected $notificationService = null; + public static $order = 14; + protected function init() { $this->dependencies[] = 'serviceFactory'; @@ -38,6 +40,19 @@ class Notifications extends \Espo\Core\Hooks\Base return $this->getInjection('serviceFactory'); } + protected function getMentionedUserList($entity) + { + $mentionedUserList = array(); + $data = $entity->get('data'); + if (($data instanceof \stdClass) && ($data->mentions instanceof \stdClass)) { + $mentions = get_object_vars($data->mentions); + foreach ($mentions as $d) { + $mentionedUserList[] = $d->id; + } + } + return $mentionedUserList; + } + public function afterSave(Entity $entity) { if (!$entity->isFetched()) { @@ -46,6 +61,9 @@ class Notifications extends \Espo\Core\Hooks\Base $parentId = $entity->get('parentId'); if ($parentType && $parentId) { + + $mentionedUserList = $this->getMentionedUserList($entity); + $pdo = $this->getEntityManager()->getPDO(); $sql = " SELECT user_id AS userId @@ -55,7 +73,7 @@ class Notifications extends \Espo\Core\Hooks\Base $sth->execute(); $userIdList = array(); while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) { - if ($this->getUser()->id != $row['userId']) { + if ($this->getUser()->id != $row['userId'] && !in_array($row['userId'], $mentionedUserList)) { $userIdList[] = $row['userId']; } } @@ -64,10 +82,10 @@ class Notifications extends \Espo\Core\Hooks\Base $job->set(array( 'serviceName' => 'Notification', 'method' => 'notifyAboutNoteFromJob', - 'data' => json_encode(array( + 'data' => array( 'userIdList' => $userIdList, 'noteId' => $entity->id - )), + ), 'executeTime' => date('Y-m-d H:i:s'), )); $this->getEntityManager()->saveEntity($job); diff --git a/application/Espo/Modules/Crm/Business/Event/Invitations.php b/application/Espo/Modules/Crm/Business/Event/Invitations.php index 5df6823797..ee042db6c5 100644 --- a/application/Espo/Modules/Crm/Business/Event/Invitations.php +++ b/application/Espo/Modules/Crm/Business/Event/Invitations.php @@ -79,6 +79,8 @@ class Invitations $bodyTpl = file_get_contents($bodyTplFileName); $subject = $this->parseInvitationTemplate($subjectTpl, $entity, $invitee, $uid); + $subject = str_replace(array("\n", "\r"), '', $subject); + $body = $this->parseInvitationTemplate($bodyTpl, $entity, $invitee, $uid); $email->set('subject', $subject); diff --git a/application/Espo/Modules/Crm/Controllers/Contract.php b/application/Espo/Modules/Crm/Controllers/Contract.php new file mode 100644 index 0000000000..4273e62de4 --- /dev/null +++ b/application/Espo/Modules/Crm/Controllers/Contract.php @@ -0,0 +1,28 @@ +": "lead.opportunity_amount * currency.rate > {value}", - "<": "lead.opportunity_amount * currency.rate < {value}", - ">=": "lead.opportunity_amount * currency.rate >= {value}", - "<=": "lead.opportunity_amount * currency.rate <= {value}", - "<>": "lead.opportunity_amount * currency.rate <> {value}" - } + "type": "currencyConverted", + "readOnly": true }, "website": { "type": "url" diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Opportunity.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Opportunity.json index ba90ec0f6a..ec966bc7fd 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Opportunity.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Opportunity.json @@ -7,22 +7,27 @@ "amount": { "type": "currency", "required": true, - "audited": true, - "orderBy": "amountConverted {direction}" + "audited": true }, "amountConverted": { + "type": "currencyConverted", + "readOnly": true + }, + "amountWeightedConverted": { "type": "float", - "notStorable": true, "readOnly": true, - "select": "opportunity.amount * currency.rate", + "notStorable": true, + "select": "opportunity.amount * amount_currency_alias.rate * opportunity.probability / 100", "where": { - "=": "opportunity.amount * currency.rate = {value}", - ">": "opportunity.amount * currency.rate > {value}", - "<": "opportunity.amount * currency.rate < {value}", - ">=": "opportunity.amount * currency.rate >= {value}", - "<=": "opportunity.amount * currency.rate <= {value}", - "<>": "opportunity.amount * currency.rate <> {value}" - } + "=": "(opportunity.amount * amount_currency_alias.rate * opportunity.probability / 100) = {value}", + "<": "(opportunity.amount * amount_currency_alias.rate * opportunity.probability / 100) < {value}", + ">": "(opportunity.amount * amount_currency_alias.rate * opportunity.probability / 100) > {value}", + "<=": "(opportunity.amount * amount_currency_alias.rate * opportunity.probability / 100) <= {value}", + ">=": "(opportunity.amount * amount_currency_alias.rate * opportunity.probability / 100) >= {value}", + "<>": "(opportunity.amount * amount_currency_alias.rate * opportunity.probability / 100) <> {value}" + }, + "orderBy": "amountWeightedConverted {direction}", + "view": "Fields.CurrencyConverted" }, "account": { "type": "link", @@ -136,6 +141,11 @@ "type": "hasChildren", "entity": "Email", "foreign": "parent" + }, + "documents": { + "type": "hasMany", + "entity": "Document", + "foreign": "opportunities" } }, "collection": { diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Task.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Task.json index 18b2227153..e51cb7f187 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Task.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Task.json @@ -9,8 +9,7 @@ "options": ["Not Started", "Started", "Completed", "Canceled"], "view": "Fields.EnumStyled", "style": { - "Completed": "success", - "Canceled": "danger" + "Completed": "success" } }, "priority": { @@ -24,11 +23,13 @@ }, "dateEnd": { "type": "datetime", - "after": "dateStart" + "after": "dateStart", + "view": "Crm:Task.Fields.DateEnd" }, "isOverdue": { - "type": "base", - "db": false, + "type": "bool", + "readOnly": true, + "notStorable": true, "view": "Crm:Task.Fields.IsOverdue" }, "description": { diff --git a/application/Espo/Modules/Crm/Resources/metadata/scopes/Document.json b/application/Espo/Modules/Crm/Resources/metadata/scopes/Document.json new file mode 100644 index 0000000000..4bb51bf381 --- /dev/null +++ b/application/Espo/Modules/Crm/Resources/metadata/scopes/Document.json @@ -0,0 +1,9 @@ +{ + "entity": true, + "layouts": false, + "tab": true, + "acl": true, + "module": "Crm", + "customizable": false, + "importable": false +} diff --git a/application/Espo/Modules/Crm/Services/Activities.php b/application/Espo/Modules/Crm/Services/Activities.php index a18ef0aaa3..7ac901fd43 100644 --- a/application/Espo/Modules/Crm/Services/Activities.php +++ b/application/Espo/Modules/Crm/Services/Activities.php @@ -396,8 +396,16 @@ class Activities extends \Espo\Core\Services\Base FROM `task` WHERE task.deleted = 0 AND - task.date_start >= ".$pdo->quote($from)." AND - task.date_start < ".$pdo->quote($to)." AND + ( + ( + task.date_start >= ".$pdo->quote($from)." AND + task.date_start < ".$pdo->quote($to)." + ) OR ( + (task.date_start IS NULL OR task.date_start = '') AND + task.date_end >= ".$pdo->quote($from)." AND + task.date_end < ".$pdo->quote($to)." + ) + ) AND task.assigned_user_id = ".$pdo->quote($userId)." "; diff --git a/application/Espo/ORM/DB/Mapper.php b/application/Espo/ORM/DB/Mapper.php index ae6fc720cf..a052918ae9 100644 --- a/application/Espo/ORM/DB/Mapper.php +++ b/application/Espo/ORM/DB/Mapper.php @@ -37,6 +37,8 @@ abstract class Mapper implements IMapper public $pdo; protected $entityFactroy; + + protected $query; protected $fieldsMapCache = array(); protected $aliasesCache = array(); @@ -60,7 +62,6 @@ abstract class Mapper implements IMapper '=' => '=', ); - // @todo whereClause ? protected static $selectParamList = array( 'offset', 'limit', @@ -74,8 +75,9 @@ abstract class Mapper implements IMapper 'joinConditions', ); - public function __construct(PDO $pdo, \Espo\ORM\EntityFactory $entityFactory) { + public function __construct(PDO $pdo, \Espo\ORM\EntityFactory $entityFactory, Query $query) { $this->pdo = $pdo; + $this->query = $query; $this->entityFactory = $entityFactory; } @@ -88,7 +90,7 @@ abstract class Mapper implements IMapper $params['whereClause']['id'] = $id; $params['whereClause']['deleted'] = 0; - $sql = $this->createSelectQuery($entity, $params); + $sql = $this->query->createSelectQuery($entity->getEntityName(), $params); $ps = $this->pdo->query($sql); @@ -123,7 +125,7 @@ abstract class Mapper implements IMapper public function select(IEntity $entity, $params = array()) { - $sql = $this->createSelectQuery($entity, $params); + $sql = $this->query->createSelectQuery($entity->getEntityName(), $params); $dataArr = array(); $ps = $this->pdo->query($sql); @@ -145,8 +147,11 @@ abstract class Mapper implements IMapper if (empty($aggregation) || !isset($entity->fields[$aggregationBy])) { return false; } + + $params['aggregation'] = $aggregation; + $params['aggregationBy'] = $aggregationBy; - $sql = $this->createSelectQuery($entity, $params, $aggregation, $aggregationBy, $deleted); + $sql = $this->query->createSelectQuery($entity->getEntityName(), $params, $deleted); $ps = $this->pdo->query($sql); @@ -158,102 +163,6 @@ abstract class Mapper implements IMapper return false; } - protected function createSelectQuery(IEntity $entity, $params = array(), $aggregation = null, $aggregationBy = null, $deleted = false) - { - $whereClause = array(); - if (array_key_exists('whereClause', $params)) { - $whereClause = $params['whereClause']; - } - - if (!$deleted) { - $whereClause = $whereClause + array('deleted' => 0); - } - - foreach (self::$selectParamList as $k) { - $$k = array_key_exists($k, $params) ? $params[$k] : null; - } - - if (empty($aggregation)) { - $selectPart = $this->getSelect($entity); - $orderPart = $this->getOrder($entity, $orderBy, $order); - } else { - $aggDist = false; - if ($distinct && $aggregation == 'COUNT') { - $aggDist = true; - } - $selectPart = $this->getAggregationSelect($entity, $aggregation, $aggregationBy, $aggDist); - } - $joinsPart = $this->getBelongsToJoins($entity); - $wherePart = $this->getWhere($entity, $whereClause); - - if (!empty($customWhere)) { - $wherePart .= ' ' . $customWhere; - } - - if (!empty($customJoin)) { - $joinsPart .= ' ' . $customJoin . ' '; - } - - if (!empty($joins) && is_array($joins)) { - $joinsRelated = $this->getJoins($entity, $joins, false, $joinConditions); - if (!empty($joinsRelated)) { - if (!empty($joinsPart)) { - $joinsPart .= ' '; - } - $joinsPart .= $joinsRelated; - } - } - - if (!empty($leftJoins) && is_array($leftJoins)) { - $joinsRelated = $this->getJoins($entity, $leftJoins, true, $joinConditions); - if (!empty($joinsRelated)) { - if (!empty($joinsPart)) { - $joinsPart .= ' '; - } - $joinsPart .= $joinsRelated; - } - } - - if (empty($aggregation)) { - return $this->composeSelectQuery($this->toDb($entity->getEntityName()), $selectPart, $joinsPart, $wherePart, $orderPart, $offset, $limit, $distinct); - } else { - return $this->composeSelectQuery($this->toDb($entity->getEntityName()), $selectPart, $joinsPart, $wherePart, null, null, null, false, $aggregation = true); - } - } - - protected function getAggregationSelect(IEntity $entity, $aggregation, $aggregationBy, $distinct = false) - { - if (!isset($entity->fields[$aggregationBy])) { - return false; - } - - $aggregation = strtoupper($aggregation); - - $distinctPart = ''; - if ($distinct) { - $distinctPart = 'DISTINCT '; - } - - $selectPart = "{$aggregation}({$distinctPart}" . $this->toDb($entity->getEntityName()) . "." . $this->toDb($aggregationBy) . ") AS AggregateValue"; - return $selectPart; - } - - protected function getJoins(IEntity $entity, array $joins, $left = false, $joinConditions = array()) - { - $joinsArr = array(); - foreach ($joins as $relationName) { - $conditions = array(); - if (!empty($joinConditions[$relationName])) { - $conditions = $joinConditions[$relationName]; - } - if ($joinRelated = $this->getJoinRelated($entity, $relationName, $left, $conditions)) { - $joinsArr[] = $joinRelated; - } - } - return implode(' ', $joinsArr); - } - - public function selectRelated(IEntity $entity, $relationName, $params = array(), $totalCount = false) { $relOpt = $entity->relations[$relationName]; @@ -268,80 +177,35 @@ abstract class Mapper implements IMapper if (!$relEntity) { return null; } - - $whereClause = array(); - if (array_key_exists('whereClause', $params)) { - $whereClause = $params['whereClause']; + + if ($totalCount) { + $params['aggregation'] = 'COUNT'; + $params['aggregationBy'] = 'id'; } - $whereClause = $whereClause + array('deleted' => 0); - - foreach (self::$selectParamList as $k) { - $$k = array_key_exists($k, $params) ? $params[$k] : null; - if (is_null($$k) && isset($relOpt[$k])) { - $$k = $relOpt[$k]; - } - } - - if (!$totalCount) { - $selectPart = $this->getSelect($relEntity); - $joinsPart = $this->getBelongsToJoins($relEntity); - $orderPart = $this->getOrder($relEntity, $orderBy, $order); - - } else { - $selectPart = $this->getAggregationSelect($relEntity, 'COUNT', 'id'); - $joinsPart = ''; - $orderPart = ''; - $offset = null; - $limit = null; - } - - if (!empty($joins) && is_array($joins)) { - $joinsRelated = $this->getJoins($relEntity, $joins, false, $joinConditions); - if (!empty($joinsRelated)) { - if (!empty($joinsPart)) { - $joinsPart .= ' '; - } - $joinsPart .= $joinsRelated; - } - } - - if (!empty($customJoin)) { - $joinsPart .= ' ' . $customJoin . ' '; - } - - if (!empty($leftJoins) && is_array($leftJoins)) { - $joinsRelated = $this->getJoins($relEntity, $leftJoins, true, $joinConditions); - if (!empty($joinsRelated)) { - if (!empty($joinsPart)) { - $joinsPart .= ' '; - } - $joinsPart .= $joinsRelated; - } + + if (empty($params['whereClause'])) { + $params['whereClause'] = array(); } $relType = $relOpt['type']; - $keySet = $this->getKeys($entity, $relationName); + $keySet = $this->query->getKeys($entity, $relationName); $key = $keySet['key']; $foreignKey = $keySet['foreignKey']; switch ($relType) { - case IEntity::BELONGS_TO: - - $whereClause[$foreignKey] = $entity->get($key); - $wherePart = $this->getWhere($relEntity, $whereClause); - - if (!empty($customWhere)) { - $wherePart .= ' ' . $customWhere; - } - - $sql = $this->composeSelectQuery($this->toDb($relEntity->getEntityName()), $selectPart, $joinsPart, $wherePart, $orderPart, 0, 1); + case IEntity::BELONGS_TO: + $params['whereClause'][$foreignKey] = $entity->get($key); + $params['offset'] = 0; + $params['limit'] = 1; + + $sql = $this->query->createSelectQuery($relEntity->getEntityName(), $params); + $ps = $this->pdo->query($sql); - if ($ps) { foreach ($ps as $row) { if (!$totalCount) { @@ -356,23 +220,17 @@ abstract class Mapper implements IMapper case IEntity::HAS_MANY: case IEntity::HAS_CHILDREN: - - $whereClause[$foreignKey] = $entity->get($key); + + $params['whereClause'][$foreignKey] = $entity->get($key); if ($relType == IEntity::HAS_CHILDREN) { $foreignType = $keySet['foreignType']; - $whereClause[$foreignType] = $entity->getEntityName(); + $params['whereClause'][$foreignType] = $entity->getEntityName(); } - $wherePart = $this->getWhere($relEntity, $whereClause); - - if (!empty($customWhere)) { - $wherePart .= ' ' . $customWhere; - } $dataArr = array(); - - $sql = $this->composeSelectQuery($this->toDb($relEntity->getEntityName()), $selectPart, $joinsPart, $wherePart, $orderPart, $offset, $limit); + $sql = $this->query->createSelectQuery($relEntity->getEntityName(), $params); $ps = $this->pdo->query($sql); if ($ps) { @@ -395,21 +253,25 @@ abstract class Mapper implements IMapper case IEntity::MANY_MANY: - $MMJoinPart = $this->getMMJoin($entity, $relationName, $keySet); - $wherePart = $this->getWhere($relEntity, $whereClause); - if ($joinsPart != '') { - $MMJoinPart = ' ' . $MMJoinPart; + $MMJoinPart = $this->getMMJoin($entity, $relationName, $keySet); + + if (empty($params['customJoin'])) { + $params['customJoin'] = ''; + } else { + $params['customJoin'] .= ' '; } + $params['customJoin'] .= $MMJoinPart; + + + $params['relationName'] = $relOpt['relationName']; + // TODO total + + + $sql = $this->query->createSelectQuery($relEntity->getEntityName(), $params); + $dataArr = array(); - if (!empty($params['additionalColumns']) && is_array($params['additionalColumns'])) { - foreach ($params['additionalColumns'] as $column => $field) { - $selectPart .= ", `" . $this->toDb($relOpt['relationName']) . "`." . $this->toDb($column) . " AS `{$field}`"; - } - } - - $sql = $this->composeSelectQuery($this->toDb($relEntity->getEntityName()), $selectPart, $joinsPart . $MMJoinPart, $wherePart, $orderPart, $offset, $limit); $ps = $this->pdo->query($sql); if ($ps) { @@ -434,82 +296,6 @@ abstract class Mapper implements IMapper return false; } - protected function getKeys(IEntity $entity, $relationName) - { - $relOpt = $entity->relations[$relationName]; - $relType = $relOpt['type']; - - switch ($relType) { - - case IEntity::BELONGS_TO: - $key = $this->toDb($entity->getEntityName()) . 'Id'; - if (isset($relOpt['key'])) { - $key = $relOpt['key']; - } - $foreignKey = 'id'; - if(isset($relOpt['foreignKey'])){ - $foreignKey = $relOpt['foreignKey']; - } - return array( - 'key' => $key, - 'foreignKey' => $foreignKey, - ); - - case IEntity::HAS_MANY: - $key = 'id'; - if (isset($relOpt['key'])){ - $key = $relOpt['key']; - } - $foreignKey = $this->toDb($entity->getEntityName()) . 'Id'; - if (isset($relOpt['foreignKey'])) { - $foreignKey = $relOpt['foreignKey']; - } - return array( - 'key' => $key, - 'foreignKey' => $foreignKey, - ); - case IEntity::HAS_CHILDREN: - $key = 'id'; - if (isset($relOpt['key'])){ - $key = $relOpt['key']; - } - $foreignKey = 'parentId'; - if (isset($relOpt['foreignKey'])) { - $foreignKey = $relOpt['foreignKey']; - } - $foreignType = 'parentType'; - if (isset($relOpt['foreignType'])) { - $foreignType = $relOpt['foreignType']; - } - return array( - 'key' => $key, - 'foreignKey' => $foreignKey, - 'foreignType' => $foreignType, - ); - - case IEntity::MANY_MANY: - $key = 'id'; - if(isset($relOpt['key'])){ - $key = $relOpt['key']; - } - $foreignKey = 'id'; - if(isset($relOpt['foreignKey'])){ - $foreignKey = $relOpt['foreignKey']; - } - $nearKey = $this->toDb($entity->getEntityName()) . 'Id'; - $distantKey = $this->toDb($relOpt['entity']) . 'Id'; - if (isset($relOpt['midKeys']) && is_array($relOpt['midKeys'])){ - $nearKey = $relOpt['midKeys'][0]; - $distantKey = $relOpt['midKeys'][1]; - } - return array( - 'key' => $key, - 'foreignKey' => $foreignKey, - 'nearKey' => $nearKey, - 'distantKey' => $distantKey, - ); - } - } public function countRelated(IEntity $entity, $relationName, $params = array()) { @@ -533,7 +319,7 @@ abstract class Mapper implements IMapper } $relOpt = $entity->relations[$relationName]; - $keySet = $this->getKeys($entity, $relationName); + $keySet = $this->query->getKeys($entity, $relationName); $relType = $relOpt['type']; @@ -550,6 +336,9 @@ abstract class Mapper implements IMapper foreach ($columnData as $column => $value) { $setArr[] = $this->toDb($column) . " = " . $this->pdo->quote($value); } + if (empty($setArr)) { + return true; + } $setPart = implode(', ', $setArr); $wherePart = @@ -599,7 +388,7 @@ abstract class Mapper implements IMapper $relEntity->id = $id; } - $keySet = $this->getKeys($entity, $relationName); + $keySet = $this->query->getKeys($entity, $relationName); switch ($relType) { case IEntity::BELONGS_TO: @@ -621,7 +410,7 @@ abstract class Mapper implements IMapper $setPart .= ", " . $this->toDb($foreignType) . " = " . $this->pdo->quote($entity->getEntityName()); } - $wherePart = $this->getWhere($relEntity, array('id' => $id, 'deleted' => 0)); + $wherePart = $this->query->getWhere($relEntity, array('id' => $id, 'deleted' => 0)); $sql = $this->composeUpdateQuery($this->toDb($relEntity->getEntityName()), $setPart, $wherePart); if ($this->pdo->query($sql)) { @@ -650,7 +439,7 @@ abstract class Mapper implements IMapper } } - $sql = $this->composeSelectQuery($relTable, '*', '', $wherePart); + $sql = $this->query->composeSelectQuery($relTable, '*', '', $wherePart); $ps = $this->pdo->query($sql); @@ -739,7 +528,7 @@ abstract class Mapper implements IMapper $relEntity->id = $id; } - $keySet = $this->getKeys($entity, $relationName); + $keySet = $this->query->getKeys($entity, $relationName); switch ($relType) { @@ -772,7 +561,7 @@ abstract class Mapper implements IMapper $whereClause[$foreignType] = $entity->getEntityName(); } - $wherePart = $this->getWhere($relEntity, $whereClause); + $wherePart = $this->query->getWhere($relEntity, $whereClause); $sql = $this->composeUpdateQuery($this->toDb($relEntity->getEntityName()), $setPart, $wherePart); if ($this->pdo->query($sql)) { return true; @@ -835,13 +624,7 @@ abstract class Mapper implements IMapper $type = $entity->fields[$field]['type']; - if ($type == IEntity::JSON_ARRAY && is_array($value)) { - $value = json_encode($value); - } - - if (is_bool($value)) { - $value = (int) $value; - } + $value = $this->prepareValueForInsert($type, $value); $valArr[] = $this->quote($value); } @@ -870,19 +653,13 @@ abstract class Mapper implements IMapper if ($type == IEntity::FOREIGN) { continue; - } + } - if ($entity->getFetched($field) === $value) { + if ($entity->getFetched($field) === $value && $type != IEntity::JSON_ARRAY && $type != IEntity::JSON_OBJECT) { continue; } - - if ($type == IEntity::JSON_ARRAY && is_array($value)) { - $value = json_encode($value); - } - - if (is_bool($value)) { - $value = (int) $value; - } + + $value = $this->prepareValueForInsert($type, $value); $setArr[] = "`" . $this->toDb($field) . "` = " . $this->quote($value); } @@ -891,7 +668,7 @@ abstract class Mapper implements IMapper } $setPart = implode(', ', $setArr); - $wherePart = $this->getWhere($entity, array('id' => $entity->id, 'deleted' => 0)); + $wherePart = $this->query->getWhere($entity, array('id' => $entity->id, 'deleted' => 0)); $sql = $this->composeUpdateQuery($this->toDb($entity->getEntityName()), $setPart, $wherePart); @@ -901,6 +678,19 @@ abstract class Mapper implements IMapper return false; } + + protected function prepareValueForInsert($type, $value) { + if ($type == IEntity::JSON_ARRAY && is_array($value)) { + $value = json_encode($value); + } else if ($type == IEntity::JSON_OBJECT && (is_array($value) || $value instanceof \stdClass)) { + $value = json_encode($value); + } + + if (is_bool($value)) { + $value = (int) $value; + } + return $value; + } public function deleteFromDb($entityName, $id) { @@ -942,297 +732,12 @@ abstract class Mapper implements IMapper return $entity; } - protected function getAlias(IEntity $entity, $key) - { - if (!isset($this->aliasesCache[$entity->getEntityName()])) { - $this->aliasesCache[$entity->getEntityName()] = $this->getTableAliases($entity); - } - - if (isset($this->aliasesCache[$entity->getEntityName()][$key])) { - return $this->aliasesCache[$entity->getEntityName()][$key]; - } else { - return false; - } - } - - protected function getTableAliases(IEntity $entity) - { - $aliases = array(); - $c = 0; - - $occuranceHash = array(); - - foreach ($entity->relations as $name => $r) { - if ($r['type'] == IEntity::BELONGS_TO) { - $key = $r['key']; - $table = $this->toDb($r['entity']); - - if (!array_key_exists($key, $aliases)) { - if (array_key_exists($table, $occuranceHash)) { - $occuranceHash[$table]++; - } else { - $occuranceHash[$table] = 0; - } - $suffix = '_f'; - if ($occuranceHash[$table] > 0) { - $suffix .= '_' . $occuranceHash[$table]; - } - - $aliases[$key] = $table . $suffix; - } - } - } - - return $aliases; - } - - protected function getFieldPath(IEntity $entity, $field) - { - if (isset($entity->fields[$field])) { - $f = $entity->fields[$field]; - - if (isset($f['source'])) { - if ($f['source'] != 'db') { - return false; - } - } - - if (!empty($f['notStorable'])) { - return false; - } - - $fieldPath = ''; - - switch($f['type']) { - case 'foreign': - if (isset($f['relation'])) { - $relationName = $f['relation']; - - $keySet = $this->getKeys($entity, $relationName); - $key = $keySet['key']; - - $foreigh = $f['foreign']; - - if (is_array($foreigh)) { - foreach ($foreigh as $i => $value) { - if ($value == ' ') { - $foreigh[$i] = '\' \''; - } else { - $foreigh[$i] = $this->getAlias($entity, $key) . '.' . $this->toDb($value); - } - } - $fieldPath = 'TRIM(CONCAT(' . implode(', ', $foreigh). '))'; - } else { - $fieldPath = $this->getAlias($entity, $key) . '.' . $this->toDb($foreigh); - } - } - break; - default: - $fieldPath = $this->toDb($entity->getEntityName()) . '.' . $this->toDb($field) ; - } - - return $fieldPath; - } - - return false; - } - - protected function getWhere(IEntity $entity, $whereClause, $sqlOp = 'AND') - { - $whereParts = array(); - - foreach ($whereClause as $field => $value) { - - if (is_int($field)) { - $field = 'AND'; - } - - if (!in_array($field, self::$sqlOperators)) { - - $inRelated = false; - - if (strpos($field, '.') !== false) { - list($entityName, $field) = array_map('trim', explode('.', $field)); - $entityName = preg_replace('/[^A-Za-z0-9_]+/', '', $entityName); - $field = preg_replace('/[^A-Za-z0-9_]+/', '', $field); - $inRelated = true; - } - - $operator = '='; - - if (!preg_match('/^[a-z0-9]+$/i', $field)) { - foreach (self::$comparisonOperators as $op => $opDb) { - if (strpos($field, $op) !== false) { - $field = trim(str_replace($op, '', $field)); - $operator = $opDb; - break; - } - } - } - - if (!$inRelated) { - - if (!isset($entity->fields[$field])) { - continue; - } - - $fieldDefs = $entity->fields[$field]; - - if (!empty($fieldDefs['where']) && !empty($fieldDefs['where'][$operator])) { - $whereParts[] = str_replace('{value}', $this->pdo->quote($value), $fieldDefs['where'][$operator]); - } else { - if ($fieldDefs['type'] == IEntity::FOREIGN) { - $leftPart = ''; - if (isset($fieldDefs['relation'])) { - $relationName = $fieldDefs['relation']; - if (isset($entity->relations[$relationName])) { - $keySet = $this->getKeys($entity, $relationName); - $key = $keySet['key']; - - $alias = $this->getAlias($entity, $key); - if ($alias) { - $leftPart = $alias . '.' . $this->toDb($fieldDefs['foreign']); - } - } - } - } else { - $leftPart = $this->toDb($entity->getEntityName()) . '.' . $this->toDb($field); - } - } - } else { - $leftPart = $this->toDb($entityName) . '.' . $this->toDb($field); - } - - if (!empty($leftPart)) { - if (!is_array($value)) { - $whereParts[] = $leftPart . " " . $operator . " " . $this->pdo->quote($value); - - } else { - $valArr = $value; - foreach ($valArr as $k => $v) { - $valArr[$k] = $this->pdo->quote($valArr[$k]); - } - $oppose = ''; - if ($operator == '<>') { - $oppose = 'NOT'; - } - if (!empty($valArr)) { - $whereParts[] = $leftPart . " {$oppose} IN " . "(" . implode(',', $valArr) . ")"; - } - } - } - } else { - $whereParts[] = "(" . $this->getWhere($entity, $value, $field) . ")"; - } - } - return implode(" " . $sqlOp . " ", $whereParts); - } - - protected function getBelongsToJoins(IEntity $entity) - { - $joinsArr = array(); - - foreach ($entity->relations as $relationName => $r) { - if ($r['type'] == IEntity::BELONGS_TO) { - $keySet = $this->getKeys($entity, $relationName); - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - - $alias = $this->getAlias($entity, $key); - - if ($alias) { - $joinsArr[] = - "LEFT JOIN `" . $this->toDb($r['entity']) . "` AS `" . $alias . "` ON ". - $this->toDb($entity->getEntityName()) . "." . $this->toDb($key) . " = " . $alias . "." . $this->toDb($foreignKey); - } - } - } - - return implode(' ', $joinsArr); - } - - protected function getJoinRelated(IEntity $entity, $relationName, $left = false, $conditions = array()) - { - $relOpt = $entity->relations[$relationName]; - $keySet = $this->getKeys($entity, $relationName); - - $pre = ($left) ? 'LEFT ' : ''; - - if ($relOpt['type'] == IEntity::MANY_MANY) { - - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - $nearKey = $keySet['nearKey']; - $distantKey = $keySet['distantKey']; - - $relTable = $this->toDb($relOpt['relationName']); - $distantTable = $this->toDb($relOpt['entity']); - - - /*$join = "{$pre}JOIN (SELECT DISTINCT * FROM `{$relTable}` WHERE"; - $join .= " {$relTable}.deleted = " . $this->pdo->quote(0); - - if (!empty($relOpt['conditions']) && is_array($relOpt['conditions'])) { - $conditions = array_merge($conditions, $relOpt['conditions']); - } - foreach ($conditions as $f => $v) { - $join .= " AND {$relTable}." . $this->toDb($f) . " = " . $this->pdo->quote($v); - } - - $join .= " GROUP BY {$relTable}." . $this->toDb($nearKey); - - $join .= ") AS {$relTable} ON {$this->toDb($entity->getEntityName())}." . $this->toDb($key) . " = {$relTable}." . $this->toDb($nearKey); - - - $join .= " {$pre}JOIN `{$distantTable}` ON {$distantTable}." . $this->toDb($foreignKey) . " = {$relTable}." . $this->toDb($distantKey) - . " AND " - . "{$distantTable}.deleted = " . $this->pdo->quote(0) . ""; - - return $join;*/ - - $join = - "{$pre}JOIN `{$relTable}` ON {$this->toDb($entity->getEntityName())}." . $this->toDb($key) . " = {$relTable}." . $this->toDb($nearKey) - . " AND " - . "{$relTable}.deleted = " . $this->pdo->quote(0); - - if (!empty($relOpt['conditions']) && is_array($relOpt['conditions'])) { - $conditions = array_merge($conditions, $relOpt['conditions']); - } - foreach ($conditions as $f => $v) { - $join .= " AND {$relTable}." . $this->toDb($f) . " = " . $this->pdo->quote($v); - } - - $join .= " {$pre}JOIN `{$distantTable}` ON {$distantTable}." . $this->toDb($foreignKey) . " = {$relTable}." . $this->toDb($distantKey) - . " AND " - . "{$distantTable}.deleted = " . $this->pdo->quote(0) . ""; - - return $join; - } - - if ($relOpt['type'] == IEntity::HAS_MANY) { - - $foreignKey = $keySet['foreignKey']; - $distantTable = $this->toDb($relOpt['entity']); - - // TODO conditions - - $join = - "{$pre}JOIN `{$distantTable}` ON {$this->toDb($entity->getEntityName())}." . $this->toDb('id') . " = {$distantTable}." . $this->toDb($foreignKey) - . " AND " - . "{$distantTable}.deleted = " . $this->pdo->quote(0) . ""; - - return $join; - } - - return false; - } - protected function getMMJoin(IEntity $entity, $relationName, $keySet = false) { $relOpt = $entity->relations[$relationName]; if (empty($keySet)) { - $keySet = $this->getKeys($entity, $relationName); + $keySet = $this->query->getKeys($entity, $relationName); } $key = $keySet['key']; @@ -1259,67 +764,6 @@ abstract class Mapper implements IMapper return $join; } - protected function getSelect(IEntity $entity, $fields = null) - { - $select = ""; - $arr = array(); - $specifiedList = is_array($fields) ? true : false; - - foreach ($entity->fields as $field => $fieldDefs) { - if ($specifiedList) { - if (!in_array($field, $fields)) { - continue; - } - } - - if (!empty($fieldDefs['select'])) { - $fieldPath = $fieldDefs['select']; - } else { - if (!empty($fieldDefs['notStorable'])) { - continue; - } - $fieldPath = $this->getFieldPath($entity, $field); - } - - $arr[] = $fieldPath . ' AS `' . $field . '`'; - } - - $select = implode(', ', $arr); - - return $select; - } - - protected function getOrder(IEntity $entity, $orderBy = null, $order = null) - { - $orderStr = ""; - - if (!is_null($orderBy)) { - if (!is_null($order)) { - $order = strtoupper($order); - if (!in_array($order, array('ASC', 'DESC'))) { - $order = 'ASC'; - } - } else { - $order = 'ASC'; - } - - $fieldDefs = $entity->fields[$orderBy]; - if (!empty($fieldDefs['orderBy'])) { - $orderPart = str_replace('{direction}', $order, $fieldDefs['orderBy']); - $orderStr .= "ORDER BY {$orderPart}"; - } else { - $fieldPath = $this->getFieldPath($entity, $orderBy); - if ($fieldDefs['type'] == iEntity::FOREIGN) { - - } else { - - } - $orderStr .= "ORDER BY {$fieldPath} " . $order; - } - } - - return $orderStr; - } protected function composeInsertQuery($table, $fields, $values) { @@ -1341,8 +785,6 @@ abstract class Mapper implements IMapper return $sql; } - abstract protected function composeSelectQuery($table, $select, $joins = '', $where = '', $order = '', $offset = null, $limit = null, $distinct = null); - abstract protected function toDb($field); public function setReturnCollection($returnCollection) diff --git a/application/Espo/ORM/DB/MysqlMapper.php b/application/Espo/ORM/DB/MysqlMapper.php index d6d37f9046..76688d7c63 100644 --- a/application/Espo/ORM/DB/MysqlMapper.php +++ b/application/Espo/ORM/DB/MysqlMapper.php @@ -33,62 +33,10 @@ use PDO; */ class MysqlMapper extends Mapper { - protected function composeSelectQuery($table, $select, $joins = '', $where = '', $order = '', $offset = null, $limit = null, $distinct = null, $aggregation = false) - { - $sql = "SELECT"; - - /*if (!empty($distinct)) { - $sql .= " DISTINCT"; - }*/ - - $sql .= " {$select} FROM `{$table}`"; - - if (!empty($joins)) { - $sql .= " {$joins}"; - } - - if (!empty($where)) { - $sql .= " WHERE {$where}"; - } - - if (!empty($distinct)) { - $sql .= " GROUP BY `{$table}`.id"; - } - - if (!empty($order)) { - $sql .= " {$order}"; - } - - if (is_null($offset) && !is_null($limit)) { - $offset = 0; - } - - if (!is_null($offset) && !is_null($limit)) { - $offset = intval($offset); - $limit = intval($limit); - $sql .= " LIMIT {$offset}, {$limit}"; - } - - return $sql; - } - protected function toDb($field) { - if (array_key_exists($field, $this->fieldsMapCache)) { - return $this->fieldsMapCache[$field]; - - } else { - $field[0] = strtolower($field[0]); - $dbField = preg_replace_callback('/([A-Z])/', array($this, 'toDbConversion'), $field); - - $this->fieldsMapCache[$field] = $dbField; - return $dbField; - } + return $this->query->toDb($field); } - protected function toDbConversion($matches) - { - return "_" . strtolower($matches[1]); - } } -?> + diff --git a/application/Espo/ORM/DB/Query.php b/application/Espo/ORM/DB/Query.php new file mode 100644 index 0000000000..4898ee2689 --- /dev/null +++ b/application/Espo/ORM/DB/Query.php @@ -0,0 +1,808 @@ + '<>', + '*' => 'LIKE', + '>=' => '>=', + '<=' => '<=', + '>' => '>', + '<' => '<', + '=' => '=', + ); + + protected $entityFactory; + + protected $pdo; + + protected $fieldsMapCache = array(); + + protected $aliasesCache = array(); + + protected $seedCache = array(); + + public function __construct(PDO $pdo, EntityFactory $entityFactory) + { + $this->entityFactory = $entityFactory; + $this->pdo = $pdo; + } + + protected function getSeed($entityName) + { + if (empty($this->seedCache[$entityName])) { + $this->seedCache[$entityName] = $this->entityFactory->create($entityName); + } + return $this->seedCache[$entityName]; + } + + public function createSelectQuery($entityName, array $params = array(), $deleted = false) + { + $entity = $this->getSeed($entityName); + + foreach (self::$selectParamList as $k) { + $params[$k] = array_key_exists($k, $params) ? $params[$k] : null; + } + + $whereClause = $params['whereClause']; + if (empty($whereClause)) { + $whereClause = array(); + } + + if (!$deleted) { + $whereClause = $whereClause + array('deleted' => 0); + } + + if (empty($params['aggregation'])) { + $selectPart = $this->getSelect($entity, $params['select'], $params['distinct']); + $orderPart = $this->getOrder($entity, $params['orderBy'], $params['order']); + + if (!empty($params['additionalColumns']) && is_array($params['additionalColumns']) && !empty($params['relationName'])) { + foreach ($params['additionalColumns'] as $column => $field) { + $selectPart .= ", `" . $this->toDb($params['relationName']) . "`." . $this->toDb($column) . " AS `{$field}`"; + } + } + + } else { + $aggDist = false; + if ($params['distinct'] && $params['aggregation'] == 'COUNT') { + $aggDist = true; + } + $selectPart = $this->getAggregationSelect($entity, $params['aggregation'], $params['aggregationBy'], $aggDist); + } + + if (empty($params['joins'])) { + $params['joins'] = array(); + } + if (empty($params['leftJoins'])) { + $params['leftJoins'] = array(); + } + + $joinsPart = $this->getBelongsToJoins($entity, $params['select'], $params['joins'] + $params['leftJoins']); + + $wherePart = $this->getWhere($entity, $whereClause); + + if (!empty($params['customWhere'])) { + $wherePart .= ' ' . $params['customWhere']; + } + + if (!empty($params['customJoin'])) { + if (!empty($joinsPart)) { + $joinsPart .= ' '; + } + $joinsPart .= '' . $params['customJoin'] . ''; + } + + if (!empty($params['joins']) && is_array($params['joins'])) { + $joinsRelated = $this->getJoins($entity, $params['joins'], false, $params['joinConditions']); + if (!empty($joinsRelated)) { + if (!empty($joinsPart)) { + $joinsPart .= ' '; + } + $joinsPart .= $joinsRelated; + } + } + + if (!empty($params['leftJoins']) && is_array($params['leftJoins'])) { + $joinsRelated = $this->getJoins($entity, $params['leftJoins'], true, $params['joinConditions']); + if (!empty($joinsRelated)) { + if (!empty($joinsPart)) { + $joinsPart .= ' '; + } + $joinsPart .= $joinsRelated; + } + } + + $groupByPart = null; + if (!empty($params['groupBy']) && is_array($params['groupBy'])) { + $arr = array(); + foreach ($params['groupBy'] as $field) { + $arr[] = $this->convertComplexExpression($entity, $field, $entity->getEntityName()); + } + $groupByPart = implode(', ', $arr); + } + + if (empty($params['aggregation'])) { + return $this->composeSelectQuery($this->toDb($entity->getEntityName()), $selectPart, $joinsPart, $wherePart, $orderPart, $params['offset'], $params['limit'], $params['distinct'], null, $groupByPart); + } else { + return $this->composeSelectQuery($this->toDb($entity->getEntityName()), $selectPart, $joinsPart, $wherePart, null, null, null, false, $params['aggregation']); + } + } + + protected function getFunctionPart($function, $part, $entityName, $distinct = false) + { + switch ($function) { + case 'MONTH': + return "DATE_FORMAT({$part}, '%Y-%m')"; + case 'DAY': + return "DATE_FORMAT({$part}, '%Y-%m-%d')"; + } + if ($distinct) { + $idPart = $this->toDb($entityName) . ".id"; + switch ($function) { + case 'SUM': + case 'COUNT': + return $function . "({$part}) * COUNT(DISTINCT {$idPart}) / COUNT({$idPart})"; + } + } + return $function . '(' . $part . ')'; + } + + + protected function convertComplexExpression($entity, $field, $entityName = null, $distinct = false) + { + $function = null; + $relName = null; + + if (strpos($field, ':')) { + list($function, $field) = explode(':', $field); + } + if (strpos($field, '.')) { + list($relName, $field) = explode('.', $field); + } + + $part = $this->toDb($field); + if ($relName) { + $part = $this->toDb($relName) . '.' . $part; + } else { + if (!empty($entity->fields[$field]['select'])) { + $part = $entity->fields[$field]['select']; + } else { + if ($entityName) { + $part = $this->toDb($entityName) . '.' . $part; + } + } + } + if ($function) { + $part = $this->getFunctionPart(strtoupper($function), $part, $entityName, $distinct); + } + return $part; + } + + protected function getSelect(IEntity $entity, $fields = null, $distinct = false) + { + $select = ""; + $arr = array(); + $specifiedList = is_array($fields) ? true : false; + + if (empty($fields)) { + $fieldList = array_keys($entity->fields); + } else { + $fieldList = $fields; + } + + foreach ($fieldList as $field) { + if (array_key_exists($field, $entity->fields)) { + $fieldDefs = $entity->fields[$field]; + } else { + $part = $this->convertComplexExpression($entity, $field, $entity->getEntityName(), $distinct); + $arr[] = $part . ' AS `' . $field . '`'; + continue; + } + + if (!empty($fieldDefs['select'])) { + $fieldPath = $fieldDefs['select']; + } else { + if (!empty($fieldDefs['notStorable'])) { + continue; + } + $fieldPath = $this->getFieldPath($entity, $field); + } + + $arr[] = $fieldPath . ' AS `' . $field . '`'; + } + + $select = implode(', ', $arr); + + return $select; + } + + protected function getBelongsToJoin(IEntity $entity, $relationName, $r = null) + { + if (empty($r)) { + $r = $entity->relations[$relationName]; + } + + $keySet = $this->getKeys($entity, $relationName); + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + + $alias = $this->getAlias($entity, $relationName); + + if ($alias) { + return "JOIN `" . $this->toDb($r['entity']) . "` AS `" . $alias . "` ON ". + $this->toDb($entity->getEntityName()) . "." . $this->toDb($key) . " = " . $alias . "." . $this->toDb($foreignKey); + } + } + + protected function getBelongsToJoins(IEntity $entity, $select = null, $skipList = array()) + { + $joinsArr = array(); + + $fieldDefs = $entity->fields; + + $relationsToJoin = array(); + if (is_array($select)) { + foreach ($select as $field) { + if (!empty($fieldDefs[$field]) && $fieldDefs[$field]['type'] == 'foreign' && !empty($fieldDefs[$field]['relation'])) { + $relationsToJoin[] = $fieldDefs[$field]['relation']; + } + } + } + + foreach ($entity->relations as $relationName => $r) { + if ($r['type'] == IEntity::BELONGS_TO) { + if (in_array($relationName, $skipList)) { + continue; + } + + if (!empty($select)) { + if (!in_array($relationName, $relationsToJoin)) { + continue; + } + } + + $join = $this->getBelongsToJoin($entity, $relationName, $r); + if ($join) { + $joinsArr[] = 'LEFT ' . $join; + } + } + } + + return implode(' ', $joinsArr); + } + + protected function getOrderPart(IEntity $entity, $orderBy = null, $order = null) { + + if (!is_null($orderBy)) { + if (is_array($orderBy)) { + $arr = array(); + + foreach ($orderBy as $item) { + if (is_array($item)) { + $orderByInternal = $item[0]; + $orderInternal = null; + if (!empty($item[1])) { + $orderInternal = $item[1]; + } + $arr[] = $this->getOrderPart($entity, $orderByInternal, $orderInternal); + } + } + return implode(", ", $arr); + } + + if (strpos($orderBy, 'LIST:') === 0) { + list($l, $field, $list) = explode(':', $orderBy); + $fieldPath = $this->getFieldPathForOrderBy($entity, $field); + return "FIELD(" . $fieldPath . ", '" . implode("', '", explode(",", $list)) . "')"; + } + + if (!is_null($order)) { + $order = strtoupper($order); + if (!in_array($order, array('ASC', 'DESC'))) { + $order = 'ASC'; + } + } else { + $order = 'ASC'; + } + + if (is_integer($orderBy)) { + return "{$orderBy} " . $order; + } + + if (!empty($entity->fields[$orderBy])) { + $fieldDefs = $entity->fields[$orderBy]; + } + if (!empty($fieldDefs) && !empty($fieldDefs['orderBy'])) { + $orderPart = str_replace('{direction}', $order, $fieldDefs['orderBy']); + return "{$orderPart}"; + } else { + $fieldPath = $this->getFieldPathForOrderBy($entity, $orderBy); + + return "{$fieldPath} " . $order; + } + } + } + + protected function getOrder(IEntity $entity, $orderBy = null, $order = null) + { + $orderPart = $this->getOrderPart($entity, $orderBy, $order); + if ($orderPart) { + return "ORDER BY " . $orderPart; + } + + } + + protected function getFieldPathForOrderBy($entity, $orderBy) + { + if (strpos($orderBy, '.') !== false) { + list($alias, $field) = explode('.', $orderBy); + $fieldPath = $this->toDb($alias) . '.' . $this->toDb($field); + } else { + $fieldPath = $this->getFieldPath($entity, $orderBy); + } + return $fieldPath; + } + + protected function getAggregationSelect(IEntity $entity, $aggregation, $aggregationBy, $distinct = false) + { + if (!isset($entity->fields[$aggregationBy])) { + return false; + } + + $aggregation = strtoupper($aggregation); + + $distinctPart = ''; + if ($distinct) { + $distinctPart = 'DISTINCT '; + } + + $selectPart = "{$aggregation}({$distinctPart}" . $this->toDb($entity->getEntityName()) . "." . $this->toDb($aggregationBy) . ") AS AggregateValue"; + return $selectPart; + } + + public function toDb($field) + { + if (array_key_exists($field, $this->fieldsMapCache)) { + return $this->fieldsMapCache[$field]; + + } else { + $field[0] = strtolower($field[0]); + $dbField = preg_replace_callback('/([A-Z])/', array($this, 'toDbConversion'), $field); + + $this->fieldsMapCache[$field] = $dbField; + return $dbField; + } + } + + protected function toDbConversion($matches) + { + return "_" . strtolower($matches[1]); + } + + protected function getAlias(IEntity $entity, $relationName) + { + if (!isset($this->aliasesCache[$entity->getEntityName()])) { + $this->aliasesCache[$entity->getEntityName()] = $this->getTableAliases($entity); + } + + if (isset($this->aliasesCache[$entity->getEntityName()][$relationName])) { + return $this->aliasesCache[$entity->getEntityName()][$relationName]; + } else { + return false; + } + } + + protected function getTableAliases(IEntity $entity) + { + $aliases = array(); + $c = 0; + + $occuranceHash = array(); + + foreach ($entity->relations as $name => $r) { + if ($r['type'] == IEntity::BELONGS_TO) { + $table = $this->toDb($r['entity']); + + + if (!array_key_exists($name, $aliases)) { + if (array_key_exists($name, $occuranceHash)) { + $occuranceHash[$name]++; + } else { + $occuranceHash[$name] = 0; + } + $suffix = ''; + if ($occuranceHash[$name] > 0) { + $suffix .= '_' . $occuranceHash[$name]; + } + + $aliases[$name] = $this->toDb($name) . $suffix; + } + } + } + + return $aliases; + } + + protected function getFieldPath(IEntity $entity, $field) + { + if (isset($entity->fields[$field])) { + $f = $entity->fields[$field]; + + if (isset($f['source'])) { + if ($f['source'] != 'db') { + return false; + } + } + + if (!empty($f['notStorable'])) { + return false; + } + + $fieldPath = ''; + + switch($f['type']) { + case 'foreign': + if (isset($f['relation'])) { + $relationName = $f['relation']; + + $foreigh = $f['foreign']; + + if (is_array($foreigh)) { + foreach ($foreigh as $i => $value) { + if ($value == ' ') { + $foreigh[$i] = '\' \''; + } else { + $foreigh[$i] = $this->getAlias($entity, $relationName) . '.' . $this->toDb($value); + } + } + $fieldPath = 'TRIM(CONCAT(' . implode(', ', $foreigh). '))'; + } else { + $fieldPath = $this->getAlias($entity, $relationName) . '.' . $this->toDb($foreigh); + } + } + break; + default: + $fieldPath = $this->toDb($entity->getEntityName()) . '.' . $this->toDb($field) ; + } + + return $fieldPath; + } + + return false; + } + + public function getWhere(IEntity $entity, $whereClause, $sqlOp = 'AND') + { + $whereParts = array(); + + foreach ($whereClause as $field => $value) { + + if (is_int($field)) { + $field = 'AND'; + } + + if (!in_array($field, self::$sqlOperators)) { + + $inRelated = false; + + if (strpos($field, '.') !== false) { + list($entityName, $field) = array_map('trim', explode('.', $field)); + $entityName = preg_replace('/[^A-Za-z0-9_]+/', '', $entityName); + $field = preg_replace('/[^A-Za-z0-9_]+/', '', $field); + $inRelated = true; + } + + $operator = '='; + + if (!preg_match('/^[a-z0-9]+$/i', $field)) { + foreach (self::$comparisonOperators as $op => $opDb) { + if (strpos($field, $op) !== false) { + $field = trim(str_replace($op, '', $field)); + $operator = $opDb; + break; + } + } + } + + if (!$inRelated) { + + if (!isset($entity->fields[$field])) { + continue; + } + + $fieldDefs = $entity->fields[$field]; + + if (!empty($fieldDefs['where']) && !empty($fieldDefs['where'][$operator])) { + $whereParts[] = str_replace('{value}', $this->pdo->quote($value), $fieldDefs['where'][$operator]); + } else { + if ($fieldDefs['type'] == IEntity::FOREIGN) { + $leftPart = ''; + if (isset($fieldDefs['relation'])) { + $relationName = $fieldDefs['relation']; + if (isset($entity->relations[$relationName])) { + + $alias = $this->getAlias($entity, $relationName); + if ($alias) { + $leftPart = $alias . '.' . $this->toDb($fieldDefs['foreign']); + } + } + } + } else { + $leftPart = $this->toDb($entity->getEntityName()) . '.' . $this->toDb($field); + } + } + } else { + $leftPart = $this->toDb($entityName) . '.' . $this->toDb($field); + } + + if (!empty($leftPart)) { + if (!is_array($value)) { + $whereParts[] = $leftPart . " " . $operator . " " . $this->pdo->quote($value); + + } else { + $valArr = $value; + foreach ($valArr as $k => $v) { + $valArr[$k] = $this->pdo->quote($valArr[$k]); + } + $oppose = ''; + if ($operator == '<>') { + $oppose = 'NOT'; + } + if (!empty($valArr)) { + $whereParts[] = $leftPart . " {$oppose} IN " . "(" . implode(',', $valArr) . ")"; + } + } + } + } else { + $whereParts[] = "(" . $this->getWhere($entity, $value, $field) . ")"; + } + } + return implode(" " . $sqlOp . " ", $whereParts); + } + + protected function getJoins(IEntity $entity, array $joins, $left = false, $joinConditions = array()) + { + $joinsArr = array(); + foreach ($joins as $relationName) { + $conditions = array(); + if (!empty($joinConditions[$relationName])) { + $conditions = $joinConditions[$relationName]; + } + if ($joinRelated = $this->getJoinRelated($entity, $relationName, $left, $conditions)) { + $joinsArr[] = $joinRelated; + } + } + return implode(' ', $joinsArr); + } + + protected function getJoinRelated(IEntity $entity, $relationName, $left = false, $conditions = array()) + { + $relOpt = $entity->relations[$relationName]; + $keySet = $this->getKeys($entity, $relationName); + + $pre = ($left) ? 'LEFT ' : ''; + + if ($relOpt['type'] == IEntity::MANY_MANY) { + + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + $nearKey = $keySet['nearKey']; + $distantKey = $keySet['distantKey']; + + $relTable = $this->toDb($relOpt['relationName']); + $distantTable = $this->toDb($relOpt['entity']); + + $join = + "{$pre}JOIN `{$relTable}` ON {$this->toDb($entity->getEntityName())}." . $this->toDb($key) . " = {$relTable}." . $this->toDb($nearKey) + . " AND " + . "{$relTable}.deleted = " . $this->pdo->quote(0); + + if (!empty($relOpt['conditions']) && is_array($relOpt['conditions'])) { + $conditions = array_merge($conditions, $relOpt['conditions']); + } + foreach ($conditions as $f => $v) { + $join .= " AND {$relTable}." . $this->toDb($f) . " = " . $this->pdo->quote($v); + } + + $join .= " {$pre}JOIN `{$distantTable}` ON {$distantTable}." . $this->toDb($foreignKey) . " = {$relTable}." . $this->toDb($distantKey) + . " AND " + . "{$distantTable}.deleted = " . $this->pdo->quote(0) . ""; + + return $join; + } + + if ($relOpt['type'] == IEntity::HAS_MANY) { + + $foreignKey = $keySet['foreignKey']; + $distantTable = $this->toDb($relOpt['entity']); + + // TODO conditions + + $join = + "{$pre}JOIN `{$distantTable}` ON {$this->toDb($entity->getEntityName())}." . $this->toDb('id') . " = {$distantTable}." . $this->toDb($foreignKey) + . " AND " + . "{$distantTable}.deleted = " . $this->pdo->quote(0) . ""; + + return $join; + } + + if ($relOpt['type'] == IEntity::BELONGS_TO) { + return $pre . $this->getBelongsToJoin($entity, $relationName); + } + + return false; + } + + public function composeSelectQuery($table, $select, $joins = '', $where = '', $order = '', $offset = null, $limit = null, $distinct = null, $aggregation = false, $groupBy = null) + { + $sql = "SELECT"; + + /*if (!empty($distinct)) { + $sql .= " DISTINCT"; + }*/ + + $sql .= " {$select} FROM `{$table}`"; + + if (!empty($joins)) { + $sql .= " {$joins}"; + } + + if (!empty($where)) { + $sql .= " WHERE {$where}"; + } + + if (!empty($groupBy)) { + $sql .= " GROUP BY {$groupBy}"; + } else { + if (!empty($distinct)) { + $sql .= " GROUP BY `{$table}`.id"; + } + } + + if (!empty($order)) { + $sql .= " {$order}"; + } + + if (is_null($offset) && !is_null($limit)) { + $offset = 0; + } + + if (!is_null($offset) && !is_null($limit)) { + $offset = intval($offset); + $limit = intval($limit); + $sql .= " LIMIT {$offset}, {$limit}"; + } + + return $sql; + } + + public function getKeys(IEntity $entity, $relationName) + { + $relOpt = $entity->relations[$relationName]; + $relType = $relOpt['type']; + + switch ($relType) { + + case IEntity::BELONGS_TO: + $key = $this->toDb($entity->getEntityName()) . 'Id'; + if (isset($relOpt['key'])) { + $key = $relOpt['key']; + } + $foreignKey = 'id'; + if(isset($relOpt['foreignKey'])){ + $foreignKey = $relOpt['foreignKey']; + } + return array( + 'key' => $key, + 'foreignKey' => $foreignKey, + ); + + case IEntity::HAS_MANY: + $key = 'id'; + if (isset($relOpt['key'])){ + $key = $relOpt['key']; + } + $foreignKey = $this->toDb($entity->getEntityName()) . 'Id'; + if (isset($relOpt['foreignKey'])) { + $foreignKey = $relOpt['foreignKey']; + } + return array( + 'key' => $key, + 'foreignKey' => $foreignKey, + ); + case IEntity::HAS_CHILDREN: + $key = 'id'; + if (isset($relOpt['key'])){ + $key = $relOpt['key']; + } + $foreignKey = 'parentId'; + if (isset($relOpt['foreignKey'])) { + $foreignKey = $relOpt['foreignKey']; + } + $foreignType = 'parentType'; + if (isset($relOpt['foreignType'])) { + $foreignType = $relOpt['foreignType']; + } + return array( + 'key' => $key, + 'foreignKey' => $foreignKey, + 'foreignType' => $foreignType, + ); + + case IEntity::MANY_MANY: + $key = 'id'; + if(isset($relOpt['key'])){ + $key = $relOpt['key']; + } + $foreignKey = 'id'; + if(isset($relOpt['foreignKey'])){ + $foreignKey = $relOpt['foreignKey']; + } + $nearKey = $this->toDb($entity->getEntityName()) . 'Id'; + $distantKey = $this->toDb($relOpt['entity']) . 'Id'; + if (isset($relOpt['midKeys']) && is_array($relOpt['midKeys'])){ + $nearKey = $relOpt['midKeys'][0]; + $distantKey = $relOpt['midKeys'][1]; + } + return array( + 'key' => $key, + 'foreignKey' => $foreignKey, + 'nearKey' => $nearKey, + 'distantKey' => $distantKey, + ); + } + } + +} + diff --git a/application/Espo/ORM/Entity.php b/application/Espo/ORM/Entity.php index 2d27bbcb23..748b24ab2c 100644 --- a/application/Espo/ORM/Entity.php +++ b/application/Espo/ORM/Entity.php @@ -94,7 +94,7 @@ abstract class Entity implements IEntity $this->valuesContainer = array(); } - protected function setValue($name, $value) + protected function _setValue($name, $value) { $this->valuesContainer[$name] = $value; } @@ -195,6 +195,12 @@ abstract class Entity implements IEntity $value = null; } break; + case self::JSON_OBJECT: + $value = is_string($value) ? json_decode($value) : $value; + if (!($value instanceof \stdClass) && !is_array($value)) { + $value = null; + } + break; default: break; } diff --git a/application/Espo/ORM/EntityManager.php b/application/Espo/ORM/EntityManager.php index bfb1a523b3..6adcb8cd1f 100644 --- a/application/Espo/ORM/EntityManager.php +++ b/application/Espo/ORM/EntityManager.php @@ -38,6 +38,8 @@ class EntityManager protected $repositoryHash = array(); protected $params = array(); + + protected $query; public function __construct($params) { @@ -55,7 +57,6 @@ class EntityManager } $this->entityFactory = new $entityFactoryClassName($this, $this->metadata); - $repositoryFactoryClassName = '\\Espo\\ORM\\RepositoryFactory'; if (!empty($params['repositoryFactoryClassName'])) { $repositoryFactoryClassName = $params['repositoryFactoryClassName']; @@ -64,11 +65,21 @@ class EntityManager $this->init(); } + + public function getQuery() + { + if (empty($this->query)) { + $this->query = new DB\Query($this->getPDO(), $this->entityFactory); + } + return $this->query; + } public function getMapper($className) { if (empty($this->mappers[$className])) { - $this->mappers[$className] = new $className($this->getPDO(), $this->entityFactory); + // TODO use factory + + $this->mappers[$className] = new $className($this->getPDO(), $this->entityFactory, $this->getQuery()); } return $this->mappers[$className]; } diff --git a/application/Espo/ORM/IEntity.php b/application/Espo/ORM/IEntity.php index eac8c566af..cb81fe712e 100644 --- a/application/Espo/ORM/IEntity.php +++ b/application/Espo/ORM/IEntity.php @@ -39,6 +39,7 @@ interface IEntity const DATE = 'date'; const DATETIME = 'datetime'; const JSON_ARRAY = 'jsonArray'; + const JSON_OBJECT = 'jsonObject'; const MANY_MANY = 'manyMany'; const HAS_MANY = 'hasMany'; diff --git a/application/Espo/Resources/i18n/en_US/Admin.json b/application/Espo/Resources/i18n/en_US/Admin.json index 9a772eb985..29d4ad92c7 100644 --- a/application/Espo/Resources/i18n/en_US/Admin.json +++ b/application/Espo/Resources/i18n/en_US/Admin.json @@ -31,7 +31,19 @@ "Auth Tokens": "Auth Tokens", "Authentication": "Authentication", "Currency": "Currency", - "Integrations": "Integrations" + "Integrations": "Integrations", + "Extensions": "Extensions", + "Upload": "Upload", + "Installing...": "Installing...", + "Upgrading...": "Upgrading...", + "Upgraded successfully": "Upgraded successfully", + "Installed successfully": "Installed successfully", + "Ready for upgrade": "Ready for upgrade", + "Run Upgrade": "Run Upgrade", + "Install": "Install", + "Ready for installation": "Ready for installation", + "Uninstalling...": "Uninstalling...", + "Uninstalled": "Uninstalled" }, "layouts": { "list": "List", @@ -96,7 +108,12 @@ "userHasNoEmailAddress": "User has not email address.", "selectEntityType": "Select entity type in the left menu.", "selectUpgradePackage": "Select upgrade package", - "selectLayout": "Select needed layout in the left menu and edit it." + "downloadUpgradePackage": "Download needed upgrade package(s) here.", + "selectLayout": "Select needed layout in the left menu and edit it.", + "selectExtensionPackage": "Select extension package", + "extensionInstalled": "Extension {name} {version} has been installed.", + "installExtension": "Extension {name} {version} is ready for an istallation.", + "uninstallConfirmation": "Are you really want to uninstall the extension?" }, "descriptions": { "settings": "System settings of application.", @@ -116,7 +133,8 @@ "userInterface": "Configure UI.", "authTokens": "Active auth sessions. IP address and last access date.", "authentication": "Authentication settings.", - "currency": "Currency settings and rates." + "currency": "Currency settings and rates.", + "extensions": "Install or uninstall extensions." }, "options": { "previewSize": { diff --git a/application/Espo/Resources/i18n/en_US/DashletOptions.json b/application/Espo/Resources/i18n/en_US/DashletOptions.json new file mode 100644 index 0000000000..1bb364da83 --- /dev/null +++ b/application/Espo/Resources/i18n/en_US/DashletOptions.json @@ -0,0 +1,10 @@ +{ + "fields": { + "title": "Title", + "dateFrom": "Date From", + "dateTo": "Date To", + "autorefreshInterval": "Auto-refresh Interval", + "displayRecords": "Display Records", + "isDoubleHeight": "Height 2x" + } +} diff --git a/application/Espo/Resources/i18n/en_US/Extension.json b/application/Espo/Resources/i18n/en_US/Extension.json new file mode 100644 index 0000000000..dd3af846a0 --- /dev/null +++ b/application/Espo/Resources/i18n/en_US/Extension.json @@ -0,0 +1,15 @@ +{ + "fields": { + "name": "Name", + "version": "Version", + "description": "Description", + "isInstalled": "Installed" + }, + "labels": { + "Uninstall": "Uninstall", + "Install": "Install" + }, + "messages": { + "uninstalled": "Extension {name} has been uninstalled" + } +} diff --git a/application/Espo/Resources/i18n/en_US/ExternalAccount.json b/application/Espo/Resources/i18n/en_US/ExternalAccount.json index 1478eede4d..841a36bcd5 100644 --- a/application/Espo/Resources/i18n/en_US/ExternalAccount.json +++ b/application/Espo/Resources/i18n/en_US/ExternalAccount.json @@ -1,6 +1,7 @@ { "labels": { - "Connect": "Connect" + "Connect": "Connect", + "Connected": "Connected" }, "help": { } diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index b61bb915cd..480c6318c7 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -8,7 +8,8 @@ "EmailAccount": "Email Account", "OutboundEmail": "Outbound Email", "ScheduledJob": "Scheduled Job", - "ExternalAccount": "External Account" + "ExternalAccount": "External Account", + "Extension": "Extension" }, "scopeNamesPlural": { "Email": "Emails", @@ -19,7 +20,8 @@ "EmailAccount": "Email Accounts", "OutboundEmail": "Outbound Emails", "ScheduledJob": "Scheduled Jobs", - "ExternalAccount": "External Accounts" + "ExternalAccount": "External Accounts", + "Extension": "Extensions" }, "labels": { "Misc": "Misc", @@ -120,7 +122,10 @@ "Duplicate": "Duplicate", "Notifications": "Notifications", "Mark all read": "Mark all read", - "See more": "See more" + "See more": "See more", + "Today": "Today", + "Tomorrow": "Tomorrow", + "Yesterday": "Yesterday" }, "messages": { "notModified": "You have not modified the record", @@ -161,12 +166,7 @@ "createdAt": "Created At", "modifiedAt": "Modified At", "createdBy": "Created By", - "modifiedBy": "Modified By", - "title": "Title", - "dateFrom": "Date From", - "dateTo": "Date To", - "autorefreshInterval": "Auto-refresh Interval", - "displayRecords": "Display Records" + "modifiedBy": "Modified By" }, "links": { "teams": "Teams", @@ -185,6 +185,7 @@ "update": "{user} updated {entityType} {entity}", "createRelated": "{user} created {relatedEntityType} {relatedEntity} linked to {entityType} {entity}", "emailReceived": "{entity} has been received for {entityType} {entity}", + "mentionInPost": "{user} mentioned {mentioned} on {entityType} {entity}", "createThis": "{user} created this {entityType}", "createAssignedThis": "{user} created this {entityType} assigned to {assignee}", @@ -291,7 +292,13 @@ "between": "Between", "today": "Today", "past": "Past", - "future": "Future" + "future": "Future", + "currentMonth": "Current Month", + "lastMonth": "Last Month", + "currentQuarter": "Current Quarter", + "lastQuarter": "Last Quarter", + "currentYear": "Current Year", + "lastYear": "Last Year" }, "intSearchRanges": { "equals": "Equals", diff --git a/application/Espo/Resources/i18n/en_US/Import.json b/application/Espo/Resources/i18n/en_US/Import.json new file mode 100644 index 0000000000..0eb8be2a83 --- /dev/null +++ b/application/Espo/Resources/i18n/en_US/Import.json @@ -0,0 +1,15 @@ +{ + "labels": { + "Revert": "Revert", + "Return to Import": "Return to Import", + "Run Import": "Run Import", + "Back": "Back", + "Field Mapping": "Field Mapping", + "Default Values": "Default Values", + "Add Field": "Add Field", + "Created": "Created", + "Updated": "Updated", + "Result": "Result", + "Show records": "Show records" + } +} diff --git a/application/Espo/Resources/i18n/en_US/User.json b/application/Espo/Resources/i18n/en_US/User.json index 5c1024abfb..0f47fb9def 100644 --- a/application/Espo/Resources/i18n/en_US/User.json +++ b/application/Espo/Resources/i18n/en_US/User.json @@ -24,7 +24,8 @@ "Change Password": "Change Password" }, "tooltips": { - "defaultTeam": "All records created by this user will be related to this team by default." + "defaultTeam": "All records created by this user will be related to this team by default.", + "userName": "Letters a-z, numbers 0-9 and underscores are allowed." }, "messages": { "passwordWillBeSent": "Password will be sent to user's email address.", diff --git a/application/Espo/Resources/i18n/pt_BR/Admin.json b/application/Espo/Resources/i18n/pt_BR/Admin.json index cc0d87d63f..d5908ba1cd 100644 --- a/application/Espo/Resources/i18n/pt_BR/Admin.json +++ b/application/Espo/Resources/i18n/pt_BR/Admin.json @@ -1,119 +1,129 @@ { - "labels": { - "Enabled": "Habilitado", - "Disabled": "Desabilitado", - "System": "Sistema", - "Users": "Usuários", - "Email": "E-mail", - "Data": "Data", - "Customization": "Customização", - "Available Fields": "Campos Disponíveis", - "Layout": "Layout", - "Add Panel": "Adicionar Painel", - "Add Field": "Adicionar Campo", - "Settings": "Preferências", - "Scheduled Jobs": "Atividades agendadas", - "Upgrade": "Atualização", - "Clear Cache": "Limpar Cache", - "Rebuild": "Reconstruir", - "Teams": "Times", - "Roles": "Regras", - "Outbound Emails": "E-mails de Saída", - "Inbound Emails": "E-mails de Entrada", - "Email Templates": "Templates dos E-mails", - "Import": "Importar", - "Layout Manager": "Gerenciar Layout", - "Field Manager": "Gerenciar Campos", - "User Interface": "Interface do Usuário" - }, - "layouts": { - "list": "Lista", - "detail": "Detalhe", - "listSmall": "List (Pequeno)", - "detailSmall": "Detalhe (Pequeno)", - "filters": "Filtros de Busca", - "massUpdate": "Atualização em massa", - "relationships": "Relacionamentos" - }, - "fieldTypes": { - "address": "Endereço", - "array": "Array", - "foreign": "Estrangeiro", - "duration": "Duração", - "password": "Password", - "parsonName": "Nome da Pessoa", - "autoincrement": "Auto-increment", - "bool": "Bool", - "currency": "Monetário", - "date": "Data", - "datetime": "Data/Hora", - "email": "E-mail", - "enum": "Enum", - "enumInt": "Enum Integer", - "enumFloat": "Enum Float", - "float": "Float", - "int": "Int", - "link": "Link", - "linkMultiple": "Link Multiple", - "linkParent": "Link Parent", - "multienim": "Multienum", - "phone": "Telefone", - "text": "Texto", - "url": "Url", - "varchar": "Varchar", - "file": "Arquivo", - "image": "Imagem" - }, - "fields": { - "type": "Tipe", - "name": "Nome", - "label": "Rótulo", - "required": "Obrigatório", - "default": "Padrão", - "maxLength": "Tamanho máximo", - "options": "Opções (valores raw, não traduzíveis)", - "after": "Antes (field)", - "before": "Após (field)", - "link": "Link", - "field": "Campo", - "min": "Mín", - "max": "Máx", - "translation": "Tradução", - "previewSize": "Tamanho do Preview" - }, - "messages": { - "upgradeVersion": "Your EspoCRM will be upgraded to version {version}. This can take some time.", - "upgradeDone": "Your EspoCRM has been upgraded to version {version}. Refresh your browser window.", - "upgradeBackup": "We recommend you to make backup of your EspoCRM files and data before upgrade.", - "thousandSeparatorEqualsDecimalMark": "Thousand separator can not be same as decimal mark", - "userHasNoEmailAddress": "User has not email address.", - "selectEntityType": "Select entity type in the left menu.", - "selectUpgradePackage": "Select uprade package", - "selectLayout": "Select needed layout in the left menu and edit it." - }, - "descriptions": { - "settings": "Configurações gerais do aplicativo.", - "scheduledJob": "Tarefas agendadas que serão executadas pelo cron.", - "upgrade": "Atualizar o EspoCRM.", - "clearCache": "Limpar todo o cache do backend.", - "rebuild": "Reconstruir o backend e limpar o cache.", - "users": "Manutenção de usuários.", - "teams": "Manutenção de Times.", - "roles": "Manutenção de Regras.", - "outboundEmails": "Configuração SMTP para envio de e-mails.", - "inboundEmails": "Group IMAP email accouts. Email import and Email-to-Case.", - "emailTemplates": "Templates para envio de e-mails.", - "import": "Importar dados de arquivo CSV.", - "layoutManager": "Customizar layouts (listas, detalhes, edição, busca, atualização em massa).", - "fieldManager": "Criar novos campos ou personalizar os existentes.", - "userInterface": "Configurar UI." - }, - "options": { - "previewSize": { - "x-small": "X-Small", - "small": "Small", - "medium": "Medium", - "large": "Large" - } - } + "labels": { + "Enabled": "Habilitado", + "Disabled": "Desabilitado", + "System": "Sistema", + "Users": "Usuários", + "Email": "E-mail", + "Data": "Data", + "Customization": "Customização", + "Available Fields": "Campos Disponíveis", + "Layout": "Layout", + + + "Add Panel": "Adicionar Painel", + "Add Field": "Adicionar Campo", + "Settings": "Preferências", + "Scheduled Jobs": "Tarefas agendadas", + "Upgrade": "Atualização", + "Clear Cache": "Limpar Cache", + "Rebuild": "Reconstruir", + + "Teams": "Times", + "Roles": "Regras", + "Outbound Emails": "E-mails de Saída", + "Inbound Emails": "E-mails de Entrada", + "Email Templates": "Templates dos E-mails", + "Import": "Importar", + "Layout Manager": "Gerenciar Layout", + "Field Manager": "Gerenciar Campos", + "User Interface": "Interface do Usuário", + "Auth Tokens": "Tokens de Autenticação", + "Authentication": "Autenticação", + "Currency": "Moeda", + "Integrations": "Integrações" + }, + "layouts": { + "list": "Lista", + "detail": "Detalhe", + "listSmall": "List (Pequeno)", + "detailSmall": "Detalhe (Pequeno)", + "filters": "Filtros de Busca", + "massUpdate": "Atualização em massa", + "relationships": "Relacionamentos" + }, + "fieldTypes": { + "address": "Endereço", + "array": "Matriz", + "foreign": "Relacionamento", + "duration": "Duração", + "password": "Senha", + "parsonName": "Nome da Pessoa", + "autoincrement": "Auto-incremento", + "bool": "Booleano", + "currency": "Monetário", + "date": "Data", + "datetime": "Data/Hora", + "email": "E-mail", + "enum": "Lista", + "enumInt": "Lista (Número)", + "enumFloat": "Lista (Float)", + "float": "Float", + "int": "Número", + "link": "Link", + "linkMultiple": "Link Multiplo", + "linkParent": "Link Pai", + "multienim": "Lista Múltipla", + "phone": "Telefone", + "text": "Texto", + "url": "Url", + "varchar": "Varchar", + "file": "Arquivo", + "image": "Imagem" + }, + "fields": { + "type": "Tipe", + "name": "Nome", + "label": "Rótulo", + "required": "Obrigatório", + "default": "Padrão", + "maxLength": "Tamanho máximo", + "options": "Opções (valores raw, não traduzíveis)", + "after": "Antes (field)", + "before": "Após (field)", + "link": "Link", + "field": "Campo", + "min": "Mín", + "max": "Máx", + "translation": "Tradução", + "previewSize": "Tamanho do Preview" + }, + "messages": { + "upgradeVersion": "Sua instalação do EspoCRM será atualizada para a versão {version}. Isto pode levar algum tempo.", + "upgradeDone": "Sua instalação do EspoCRM foi atualizada para a versão {version}. Atualize a janela do navegador.", + "upgradeBackup": "Nós recomendamos que você faça um backup dos arquivos e dados do EspoCRM antes de atualizar.", + "thousandSeparatorEqualsDecimalMark": "O separador de milhar não pode ser o mesmo do separador decimal", + "userHasNoEmailAddress": "Usuário não possui endereço de e-mail.", + "selectEntityType": "Escolha o tipo de entidade no menu a esquerda.", + "selectUpgradePackage": "Selecione o pacote de atualização", + "selectLayout": "Selecione o layout necessário no meu a esquerda e edite ele." + }, + "descriptions": { + "settings": "Configurações gerais do aplicativo.", + "scheduledJob": "Tarefas agendadas que serão executadas pelo cron.", + "upgrade": "Atualizar o EspoCRM.", + "clearCache": "Limpar todo o cache do backend.", + "rebuild": "Reconstruir o backend e limpar o cache.", + "users": "Manutenção de usuários.", + "teams": "Manutenção de Times.", + "roles": "Manutenção de Regras.", + "outboundEmails": "Configuração SMTP para envio de e-mails.", + "inboundEmails": "Group IMAP email accouts. Email import and Email-to-Case.", + "emailTemplates": "Templates para envio de e-mails.", + "import": "Importar dados de arquivo CSV.", + "layoutManager": "Customizar layouts (listas, detalhes, edição, busca, atualização em massa).", + "fieldManager": "Criar novos campos ou personalizar os existentes.", + "userInterface": "Configurar UI.", + "authTokens": "Sessões autenticadas ativas. Endereço de IP e última data de acesso.", + "authentication": "Configurações de autenticação.", + "currency": "Configurações de moeda e taxas." + }, + "options": { + "previewSize": { + "x-small": "Mínimo", + "small": "Pequeno", + "medium": "Médio", + "large": "Grande" + } + } } diff --git a/application/Espo/Resources/i18n/pt_BR/AuthToken.json b/application/Espo/Resources/i18n/pt_BR/AuthToken.json new file mode 100644 index 0000000000..9ac0742ce3 --- /dev/null +++ b/application/Espo/Resources/i18n/pt_BR/AuthToken.json @@ -0,0 +1,9 @@ +{ + "fields": { + "user": "Usuário", + "ipAddress": "Endereço de IP", + "lastAccess": "Último acesso", + "createdAt": "Data do login" + + } +} diff --git a/application/Espo/Resources/i18n/pt_BR/Email.json b/application/Espo/Resources/i18n/pt_BR/Email.json index b860f3857a..8597139b6a 100644 --- a/application/Espo/Resources/i18n/pt_BR/Email.json +++ b/application/Espo/Resources/i18n/pt_BR/Email.json @@ -15,7 +15,8 @@ "selectTemplate": "Escolher Template", "fromEmailAddress": "E-mail do rementente", "toEmailAddresses": "E-mail do destinatário", - "emailAddress": "Endereço de E-mail" + "emailAddress": "Endereço de E-mail", + "deliveryDate": "Data de envio" }, "links": { }, @@ -27,6 +28,17 @@ }, "labels": { "Create Email": "Criar e-mail", - "Compose": "Compor" + "Archive Email": "Arquivar e-mail", + "Compose": "Compor", + "Reply": "Responder", + "Reply to All": "Responder a Todos", + "Forward": "Encaminhar", + "Original message": "Mensagem original", + "Forwarded message": "Mensagem encaminhada", + "Email Accounts": "Contas de e-mail" + }, + "presetFilters": { + "sent": "Enviado", + "archived": "Arquivado" } } diff --git a/application/Espo/Resources/i18n/pt_BR/EmailAccount.json b/application/Espo/Resources/i18n/pt_BR/EmailAccount.json new file mode 100644 index 0000000000..ae036851c7 --- /dev/null +++ b/application/Espo/Resources/i18n/pt_BR/EmailAccount.json @@ -0,0 +1,29 @@ +{ + "fields": { + "name": "Nome", + "status": "Status", + "host": "Host", + "username": "Usuário", + "password": "Senha", + "port": "Porta", + "monitoredFolders": "Pastas monitoradas", + "ssl": "SSL", + "fetchSince": "Buscar desde" + }, + "links": { + }, + "options": { + "status": { + "Active": "Ativa", + "Inactive": "Inativa" + } + }, + "labels": { + "Create EmailAccount": "Criar conta de e-mail", + "IMAP": "IMAP", + "Main": "Principal" + }, + "messages": { + "couldNotConnectToImap": "Não foi possível conectar ao servidor IMAP" + } +} diff --git a/application/Espo/Resources/i18n/pt_BR/EmailAddress.json b/application/Espo/Resources/i18n/pt_BR/EmailAddress.json index 69f5f7743c..58582e9f51 100644 --- a/application/Espo/Resources/i18n/pt_BR/EmailAddress.json +++ b/application/Espo/Resources/i18n/pt_BR/EmailAddress.json @@ -1,7 +1,7 @@ { "labels": { "Primary": "Primário", - "Opted Out": "Cancelado", + "Opted Out": "Cancelou (opt-out)", "Invalid": "Inválido" } } diff --git a/application/Espo/Resources/i18n/pt_BR/ExternalAccount.json b/application/Espo/Resources/i18n/pt_BR/ExternalAccount.json new file mode 100644 index 0000000000..b62b91cd3a --- /dev/null +++ b/application/Espo/Resources/i18n/pt_BR/ExternalAccount.json @@ -0,0 +1,7 @@ +{ + "labels": { + "Connect": "Conectar" + }, + "help": { + } +} diff --git a/application/Espo/Resources/i18n/pt_BR/Global.json b/application/Espo/Resources/i18n/pt_BR/Global.json index 199b89dd8d..8f00fc4e34 100644 --- a/application/Espo/Resources/i18n/pt_BR/Global.json +++ b/application/Espo/Resources/i18n/pt_BR/Global.json @@ -5,8 +5,10 @@ "Team": "Time", "Role": "Regra", "EmailTemplate": "Template de E-mail", + "EmailAccount": "Conta de e-mail", "OutboundEmail": "E-mail de Saída", - "ScheduledJob": "Atividade Agendada" + "ScheduledJob": "Tarefa Agendada", + "ExternalAccount": "Conta externa" }, "scopeNamesPlural": { "Email": "E-mails", @@ -14,8 +16,10 @@ "Team": "Times", "Role": "Regras", "EmailTemplate": "Templates de E-mail", + "EmailAccount": "Contas de e-mail", "OutboundEmail": "E-mails de Saída", - "ScheduledJob": "Tarefas Agendadas" + "ScheduledJob": "Tarefas Agendadas", + "ExternalAccount": "Contas externas" }, "labels": { "Misc": "Misc", @@ -28,9 +32,10 @@ "Not valid": "Inválido", "Please wait...": "Aguarde...", "Please wait": "Aguarde", - "Loading...": "carregando...", + "Loading...": "Carregando...", "Uploading...": "Enviando...", "Sending...": "Enviando...", + "Removed": "Removido", "Posted": "Postado", "Linked": "Relacionado", "Unlinked": "Relacionamento removido", @@ -43,7 +48,7 @@ "Removing...": "Removendo...", "Unlinking...": "Removendo relacionamento...", "Posting...": "Postando...", - "Username can not be empty!": "O usuário não pode estar vazio!", + "Username can not be empty!": "O nome de usuário não pode estar vazio!", "Cache is not enabled": "O cache não está habilitado", "Cache has been cleared": "Cache limpo", "Rebuild has been done": "Reconstrução concluída", @@ -99,7 +104,7 @@ "Stream": "Fluxo", "Show more": "Exibir mais", "Dashlet Options": "Opções do Dashlet", - "Full Form": "Formulári Completo", + "Full Form": "Formulário Completo", "Insert": "Inserir", "Person": "Pessoa", "First Name": "Nome", @@ -107,7 +112,15 @@ "Original": "Original", "You": "Você", "you": "você", - "change": "modificar" + "change": "modificar", + "Primary": "Primário", + "Save Filters": "Salvar Filtros", + "Administration": "Administração", + "Run Import": "Executar Importação", + "Duplicate": "Duplicar", + "Notifications": "Notificações", + "Mark all read": "Marcar tudo como lido", + "See more": "Ver mais" }, "messages": { "notModified": "Você não modificou o registro", @@ -123,12 +136,18 @@ "fieldShouldBeBetween": "{field} deve estar entre {min} e {max}", "fieldShouldBeLess": "{field} deve ser menos que {value}", "fieldShouldBeGreater": "{field} deve ser maior que {value}", - "fieldBadPasswordConfirm": "{field} confirmado impropriamente" + "fieldBadPasswordConfirm": "{field} confirmado impropriamente", + "assignmentEmailNotificationSubject": "EspoCRM {entityType}: {Entity.name}", + "assignmentEmailNotificationBody": "{assignerUserName} designou {entityType} '{Entity.name}' para você\n\n{recordUrl}", + "confirmation": "Você tem certeza?", + "removeRecordConfirmation": "Você gostaria mesmo de remover este registro?", + "unlinkRecordConfirmation": "Você gostaria mesmo de desfazer este relacionamento?", + "removeSelectedRecordsConfirmation": "Você gostaria mesmo de remover os registros selecionados?" }, "boolFilters": { - "onlyMy": "Apenas meus", - "open": "Abertos", - "active": "Ativos" + "onlyMy": "Meus", + "open": "Aberto", + "active": "Ativo" }, "fields": { "name": "Nome", @@ -269,7 +288,10 @@ "notOn": "Menos em", "after": "Anterior", "before": "Posterior", - "between": "Entre" + "between": "Entre", + "today": "Hoje", + "past": "Passado", + "future": "Futuro" }, "intSearchRanges": { "equals": "Igual", @@ -287,109 +309,114 @@ "2": "2 minutos", "5": "5 minutos", "10": "10 minutos" - } + }, + "phoneNumber": { + "Mobile": "Celular", + "Office": "Escritório", + "Fax": "Fax", + "Home": "Residencial", + "Other": "Outro" + } }, "sets": { "summernote": { "NOTICE": "You can find translation here: https://github.com/HackerWins/summernote/tree/master/lang", "font": { - "bold": "Bold", - "italic": "Italic", - "underline": "Underline", - "strike": "Strike", - "clear": "Remove Font Style", - "height": "Line Height", - "name": "Font Family", - "size": "Font Size" + "bold": "Negrito", + "italic": "Itálico", + "underline": "Sublinhado", + "strikethrough": "Riscado", + "clear": "Remover estilo da fonte", + "height": "Altura da linha", + "size": "Tamanho da fonte" }, "image": { - "image": "Picture", - "insert": "Insert Image", + "image": "Imagem", + "insert": "Inserir imagem", "resizeFull": "Resize Full", "resizeHalf": "Resize Half", "resizeQuarter": "Resize Quarter", "floatLeft": "Float Left", "floatRight": "Float Right", "floatNone": "Float None", - "dragImageHere": "Drag an image here", - "selectFromFiles": "Select from files", - "url": "Image URL", - "remove": "Remove Image" + "dragImageHere": "Arraste uma imagem para cá", + "selectFromFiles": "Selecione a partir dos arquivos", + "url": "URL da image" }, "link": { "link": "Link", - "insert": "Insert Link", - "unlink": "Unlink", - "edit": "Edit", - "textToDisplay": "Text to display", - "url": "To what URL should this link go?", - "openInNewWindow": "Open in new window" + "insert": "Inserir link", + "unlink": "Remover link", + "edit": "Editar", + "textToDisplay": "Texto para exibir", + "url": "Para qual URL esse link leva?", + "openInNewWindow": "Abrir em uma nova janela" }, "video": { - "video": "Video", - "videoLink": "Video Link", - "insert": "Insert Video", - "url": "Video URL?", - "providers": "(YouTube, Vimeo, Vine, Instagram, or DailyMotion)" + "video": "Vídeo", + "videoLink": "Link para vídeo", + "insert": "Inserir vídeo", + "url": "URL do vídeo?", + "providers": "(YouTube, Vimeo, Vine, Instagram, DailyMotion, ou Youku)" }, "table": { - "table": "Table" + "table": "Tabela" }, "hr": { - "insert": "Insert Horizontal Rule" + "insert": "Inserir linha horizontal" }, "style": { - "style": "Style", + "style": "Estilo", "normal": "Normal", - "blockquote": "Quote", - "pre": "Code", - "h1": "Header 1", - "h2": "Header 2", - "h3": "Header 3", - "h4": "Header 4", - "h5": "Header 5", - "h6": "Header 6" + "blockquote": "Citação", + "pre": "Código", + "h1": "Título 1", + "h2": "Título 2", + "h3": "Título 3", + "h4": "Título 4", + "h5": "Título 5", + "h6": "Título 6" }, "lists": { - "unordered": "Unordered list", - "ordered": "Ordered list" + "unordered": "Lista com marcadores", + "ordered": "Lista numerada" }, "options": { - "help": "Help", - "fullscreen": "Full Screen", - "codeview": "Code View" + "help": "Ajuda", + "fullscreen": "Tela cheia", + "codeview": "Ver código-fonte" }, - "paragraph": { - "paragraph": "Paragraph", - "outdent": "Outdent", - "indent": "Indent", - "left": "Align left", - "center": "Align center", - "right": "Align right", - "justify": "Justify full" + " paragraph": { + "paragraph": "Parágrafo", + "outdent": "Menor tabulação", + "indent": "Maior tabulação", + "left": "Alinhar à esquerda", + "center": "Alinhar ao centro", + "right": "Alinha à direita", + "justify": "Justificado" }, "color": { - "recent": "Recent Color", - "more": "More Color", - "background": "BackColor", - "foreground": "FontColor", - "transparent": "Transparent", - "setTransparent": "Set transparent", - "reset": "Reset", - "resetToDefault": "Reset to default" + "recent": "Cor recente", + "more": "Mais cores", + "background": "Fundo", + "foreground": "Fonte", + "transparent": "Transparente", + "setTransparent": "Fundo transparente", + "reset": "Restaurar", + "resetToDefault": "Restaurar padrão" }, "shortcut": { - "shortcuts": "Keyboard shortcuts", - "close": "Close", - "textFormatting": "Text formatting", - "action": "Action", - "paragraphFormatting": "Paragraph formatting", - "documentStyle": "Document Style" + "shortcuts": "Atalhos do teclado", + "close": "Fechar", + "textFormatting": "Formatação de texto", + "action": "Ação", + "paragraphFormatting": "Formatação de parágrafo", + "documentStyle": "Estilo de documento" }, "history": { - "undo": "Undo", - "redo": "Redo" + "undo": "Desfazer", + "redo": "Refazer" } } } -} +} \ No newline at end of file diff --git a/application/Espo/Resources/i18n/pt_BR/Integration.json b/application/Espo/Resources/i18n/pt_BR/Integration.json new file mode 100644 index 0000000000..821b4bf8a0 --- /dev/null +++ b/application/Espo/Resources/i18n/pt_BR/Integration.json @@ -0,0 +1,14 @@ +{ + "fields": { + "enabled": "Habilitado", + "clientId": "Client ID", + "clientSecret": "Client Secret", + "redirectUri": "URL de Redirecionamento" + }, + "messages": { + "selectIntegration": "Selecione uma integração no menu." + }, + "help": { + "Google": "

Obtenha as credenciais OAuth 2.0 do Google Developers Console.

Visite o Google Developers Console para obter as credenciais OAuth 2.0 como Client ID e Client Secret comuns ao Google e ao EspoCRM.

" + } +} diff --git a/application/Espo/Resources/i18n/pt_BR/Preferences.json b/application/Espo/Resources/i18n/pt_BR/Preferences.json index d699c8b01b..384afd6292 100644 --- a/application/Espo/Resources/i18n/pt_BR/Preferences.json +++ b/application/Espo/Resources/i18n/pt_BR/Preferences.json @@ -19,7 +19,9 @@ "smtpPassword": "Senha", "smtpEmailAddress": "Endereço de E-mail", - "exportDelimiter": "Delimitador de Exportação" + "exportDelimiter": "Delimitador de Exportação", + + "receiveAssignmentEmailNotifications": "Receber e-mail de notificação quando designado" }, "links": { }, @@ -28,5 +30,8 @@ "0": "Domingo", "1": "Segunda" } - } + }, + "labels": { + "Notifications": "Notificações" + } } diff --git a/application/Espo/Resources/i18n/pt_BR/Role.json b/application/Espo/Resources/i18n/pt_BR/Role.json index 765afa8705..49fd2c2f78 100644 --- a/application/Espo/Resources/i18n/pt_BR/Role.json +++ b/application/Espo/Resources/i18n/pt_BR/Role.json @@ -20,7 +20,7 @@ "levelList": { "all": "tudo", "team": "time", - "own": "proprio", + "own": "próprio", "no": "nenhum" } }, @@ -28,5 +28,8 @@ "read": "Ler", "edit": "Editar", "delete": "Excluir" - } + }, + "messages": { + "changesAfterClearCache": "Todas as modificações no controle de acesso serão aplicadas após a limpeza do cache." + } } diff --git a/application/Espo/Resources/i18n/pt_BR/ScheduledJob.json b/application/Espo/Resources/i18n/pt_BR/ScheduledJob.json index 358e89d62d..768add92b6 100644 --- a/application/Espo/Resources/i18n/pt_BR/ScheduledJob.json +++ b/application/Espo/Resources/i18n/pt_BR/ScheduledJob.json @@ -17,9 +17,9 @@ "Cleanup": "Limpar" }, "cronSetup": { - "linux": "Nota: Adicione esta linha ao arquivo de crontab para executar as tarefas agendadas do Espo:", - "mac": "Nota: Adicione esta linha ao arquivo de crontab para executar as tarefas agendadas do Espo:", - "windows": "Nota: Crie um arquivo em lote pcom os seguintes comandos para executar as tarefas agendadas do Espo no agendador de tarefas do Windows:", + "linux": "Nota: Adicione esta linha ao arquivo de crontab para executar as tarefas agendadas do EspoCRM:", + "mac": "Nota: Adicione esta linha ao arquivo de crontab para executar as tarefas agendadas do EspoCRM:", + "windows": "Nota: Crie um arquivo em lote com os seguintes comandos para executar as tarefas agendadas do EspoCRM no agendador de tarefas do Windows:", "default": "Nota: Adicione este comando Cron Job (Tarefa agendada):" }, "status": { diff --git a/application/Espo/Resources/i18n/pt_BR/Settings.json b/application/Espo/Resources/i18n/pt_BR/Settings.json index edebfc3137..467a291612 100644 --- a/application/Espo/Resources/i18n/pt_BR/Settings.json +++ b/application/Espo/Resources/i18n/pt_BR/Settings.json @@ -8,6 +8,10 @@ "thousandSeparator": "Separador de Milhar", "decimalMark": "Deparador Decimal", "defaultCurrency": "Moeda Padrão", + "baseCurrency": "Moeda Base", + + "currencyRates": "Conversão de Moedas", + "currencyList": "Moedas Disponíveis", "language": "Idioma", @@ -29,7 +33,27 @@ "tabList": "Lista de abas", "quickCreateList": "Lista de Criação Rápida", - "exportDelimiter": "Delimitador de exportação" + "exportDelimiter": "Delimitador de exportação", + + "authenticationMethod": "Método de Autenticação", + "ldapHost": "Host", + "ldapPort": "Porta", + "ldapAuth": "Auth", + "ldapUsername": "Usuário", + "ldapPassword": "Senha", + "ldapBindRequiresDn": "Ligação exige Dn", + "ldapBaseDn": "Dn Base", + "ldapAccountCanonicalForm": "Formulário Canônico de Conta", + "ldapAccountDomainName": "Nome de Domínio da Conta", + "ldapTryUsernameSplit": "Tentar dividir o Nome de Usuário", + "ldapCreateEspoUser": "Criar usuário no EspoCRM", + "ldapSecurity": "Segurança", + "ldapUserLoginFilter": "Filtro para Login de Usuário", + "ldapAccountDomainNameShort": "Nome curto do Domínio da Conta", + "ldapOptReferrals": "Referências Opt", + "disableExport": "Desabilitar exportação (permitido apenas para administradores)", + "assignmentEmailNotifications": "Enviar notificações sobre as designações por e-mail", + "assignmentEmailNotificationsEntityList": "Entidades para notificar" }, "options": { "weekStart": { @@ -37,10 +61,16 @@ "1": "Segunda" } }, + "tooltips": { + "recordsPerPageSmall": "Contar registros nos painéis de relacionamento." + }, "labels": { "System": "Sistema", "Locale": "Idioma", "SMTP": "SMTP", - "Configuration": "Configuração" + "Configuration": "Configuração", + "Notifications": "Notificações", + "Currency Settings": "Configurações de Moeda", + "Currency Rates": "Conversão de moedas" } } diff --git a/application/Espo/Resources/i18n/pt_BR/Team.json b/application/Espo/Resources/i18n/pt_BR/Team.json index b4d938c991..305642a089 100644 --- a/application/Espo/Resources/i18n/pt_BR/Team.json +++ b/application/Espo/Resources/i18n/pt_BR/Team.json @@ -6,6 +6,9 @@ "links": { "users": "Usuários" }, + "tooltips": { + "roles": "Todos os usuários deste time terão acesso as configurações das regras selecionadas." + }, "labels": { "Create Team": "Criar Time" } diff --git a/application/Espo/Resources/i18n/pt_BR/User.json b/application/Espo/Resources/i18n/pt_BR/User.json index dc13798d26..d4f8e5390a 100644 --- a/application/Espo/Resources/i18n/pt_BR/User.json +++ b/application/Espo/Resources/i18n/pt_BR/User.json @@ -6,7 +6,7 @@ "isAdmin": "Administrador", "defaultTeam": "Time Padrão", "emailAddress": "E-mail", - "phone": "Telefone", + "phoneNumber": "Telefone", "roles": "Regras", "password": "Senha", "passwordConfirm": "Confirmação da Senha", @@ -23,6 +23,9 @@ "Preferences": "Preferências", "Change Password": "Trocar Senha" }, + "tooltips": { + "defaultTeam": "Todos os registros criados por este usuário serão relacionados e este time por padrão." + }, "messages": { "passwordWillBeSent": "A senha será enviada para o email do usuário.", "accountInfoEmailSubject": "Informações da Conta", diff --git a/application/Espo/Resources/i18n/ru_RU/Admin.json b/application/Espo/Resources/i18n/ru_RU/Admin.json new file mode 100644 index 0000000000..2e0e205d9a --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/Admin.json @@ -0,0 +1,128 @@ +{ + "labels": { + "Enabled": "Enabled", + "Disabled": "Disabled", + "System": "System", + "Users": "Users", + "Email": "Email", + "Data": "Data", + "Customization": "Customization", + "Available Fields": "Available Fields", + "Layout": "Layout", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Add Panel": "Add Panel", + "Add Field": "Add Field", + "Settings": "Settings", + "Scheduled Jobs": "Scheduled Jobs", + "Upgrade": "Upgrade", + "Clear Cache": "Clear Cache", + "Rebuild": "Rebuild", + "Users": "Users", + "Teams": "Teams", + "Roles": "Roles", + "Outbound Emails": "Outbound Emails", + "Inbound Emails": "Inbound Emails", + "Email Templates": "Email Templates", + "Import": "Import", + "Layout Manager": "Layout Manager", + "Field Manager": "Field Manager", + "User Interface": "User Interface", + "Auth Tokens": "Auth Tokens", + "Authentication": "Authentication", + "Currency": "Currency" + }, + "layouts": { + "list": "List", + "detail": "Detail", + "listSmall": "List (Small)", + "detailSmall": "Detail (Small)", + "filters": "Search Filters", + "massUpdate": "Mass Update", + "relationships": "Relationships" + }, + "fieldTypes": { + "address": "Address", + "array": "Array", + "foreign": "Foreign", + "duration": "Duration", + "password": "Password", + "parsonName": "Person Name", + "autoincrement": "Auto-increment", + "bool": "Bool", + "currency": "Currency", + "date": "Date", + "datetime": "DateTime", + "email": "Email", + "enum": "Enum", + "enumInt": "Enum Integer", + "enumFloat": "Enum Float", + "float": "Float", + "int": "Int", + "link": "Link", + "linkMultiple": "Link Multiple", + "linkParent": "Link Parent", + "multienim": "Multienum", + "phone": "Phone", + "text": "Text", + "url": "Url", + "varchar": "Varchar", + "file": "File", + "image": "Image" + }, + "fields": { + "type": "Type", + "name": "Name", + "label": "Label", + "required": "Required", + "default": "Default", + "maxLength": "Max Length", + "options": "Options (raw values, not translated)", + "after": "After (field)", + "before": "Before (field)", + "link": "Link", + "field": "Field", + "min": "Min", + "max": "Max", + "translation": "Translation", + "previewSize": "Preview Size" + }, + "messages": { + "upgradeVersion": "Your EspoCRM will be upgraded to version {version}. This can take some time.", + "upgradeDone": "Your EspoCRM has been upgraded to version {version}. Refresh your browser window.", + "upgradeBackup": "We recommend you to make backup of your EspoCRM files and data before upgrade.", + "thousandSeparatorEqualsDecimalMark": "Thousand separator can not be same as decimal mark", + "userHasNoEmailAddress": "User has not email address.", + "selectEntityType": "Select entity type in the left menu.", + "selectUpgradePackage": "Select uprade package", + "selectLayout": "Select needed layout in the left menu and edit it." + }, + "descriptions": { + "settings": "System settings of application.", + "scheduledJob": "Jobs which are executed by cron.", + "upgrade": "Upgrade EspoCRM.", + "clearCache": "Clear all backend cache.", + "rebuild": "Rebuild backend and clear cache.", + "users": "Users management.", + "teams": "Teams management.", + "roles": "Roles management.", + "outboundEmails": "SMTP settings for outgoing emails.", + "inboundEmails": "Group IMAP email accouts. Email import and Email-to-Case.", + "emailTemplates": "Templates for outbound emails.", + "import": "Import data from CSV file.", + "layoutManager": "Customize layouts (list, detail, edit, search, mass update).", + "fieldManager": "Create new fields or customize existing ones.", + "userInterface": "Configure UI.", + "authTokens": "Active auth sessions. IP address and last access date.", + "authentication": "Authentication settings.", + "currency": "Currency settings and rates." + }, + "options": { + "previewSize": { + "x-small": "X-Small", + "small": "Small", + "medium": "Medium", + "large": "Large" + } + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/AuthToken.json b/application/Espo/Resources/i18n/ru_RU/AuthToken.json new file mode 100644 index 0000000000..7dbbd53cf7 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/AuthToken.json @@ -0,0 +1,9 @@ +{ + "fields": { + "user": "Пользователь", + "ipAddress": "IP -адрес", + "lastAccess": "Дата последнего подключения", + "createdAt": "Дата входа" + + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/Email.json b/application/Espo/Resources/i18n/ru_RU/Email.json new file mode 100644 index 0000000000..39b0f3b030 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/Email.json @@ -0,0 +1,36 @@ +{ + "fields": { + "name": "Тема/субъект", + "parent": "Источник", + "status": "статус", + "dateSent": "Дата отправки", + "from": "От", + "to": "К", + "cc": "CC", + "bcc": "BCC", + "isHtml": "Is Html", + "body": "Тело", + "subject": "Тема", + "attachments": "Вложения", + "selectTemplate": "Выбрать шаблон", + "fromEmailAddress": "С адреса", + "toEmailAddresses": "На андрес", + "emailAddress": "E-mail адрес" + }, + "links": { + }, + "options": { + "Draft": "Черновик", + "Sending": "Отправляется", + "Sent": "Отправлено", + "Archived": "В архиве" + }, + "labels": { + "Create Email": "Отправить письмо в архив", + "Compose": "Новое сообщение" + }, + "presetFilters": { + "sent": "Отправлено", + "archived": "В архиве" + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/EmailAddress.json b/application/Espo/Resources/i18n/ru_RU/EmailAddress.json new file mode 100644 index 0000000000..ed1ab64ad4 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/EmailAddress.json @@ -0,0 +1,7 @@ +{ + "labels": { + "Primary": "Главные", + "Opted Out": "Отписка", + "Invalid": "Неверный адрес" + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/EmailTemplate.json b/application/Espo/Resources/i18n/ru_RU/EmailTemplate.json new file mode 100644 index 0000000000..378dfcd6f5 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/EmailTemplate.json @@ -0,0 +1,16 @@ +{ + "fields": { + "name": "Имя", + "status": "Статус", + "isHtml": "Html", + "body": "Основная часть", + "subject": "Тема", + "attachments": "Вложения", + "insertField": "Вставить поле" + }, + "links": { + }, + "labels": { + "Create EmailTemplate": "Создать шаблон письма" + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/Global.json b/application/Espo/Resources/i18n/ru_RU/Global.json new file mode 100644 index 0000000000..f693948c1f --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/Global.json @@ -0,0 +1,422 @@ +{ + "scopeNames": { + "Email": "Письмо", + "User": "Пользователь", + "Team": "Группа", + "Role": "Роль", + "EmailTemplate": "Шаблон письма", + "OutboundEmail": "Исходящее письмо", + "ScheduledJob": "Запланированная работа" + }, + "scopeNamesPlural": { + "Email": "Письма", + "User": "Пользователи", + "Team": "Группы", + "Role": "Роль", + "EmailTemplate": "Шаблоны писем", + "OutboundEmail": "Исходящие письма", + "ScheduledJob": "Запланированные задачи" + }, + "labels": {"Misc": "Разное", + "Merge": "Объединить", + "None": "Нет", + "by": "по", + "Saved": "Сохранено", + "Error": "Ошибка", + "Select": "Выбрать", + "Not valid": "Неправильный", + "Please wait...": "Пожалуйста, подождите...", + "Please wait": "Пожалуйста, подождите", + "Loading...": "Загрузка...", + "Uploading...": "Загружается...", + "Sending...": "Отправляется...", + "Removed": "Удалено", + "Posted": "Добавлено", + "Linked": "Ссылка добавлена", + "Unlinked": "Ссылка удалена", + "Access denied": "В доступе отказано", + "Access": "Доступ", + "Are you sure?": "Вы уверены?", + "Record has been removed": "Запись удалена", + "Wrong username/password": "Неверное имя пользователя/пароль", + "Post cannot be empty": "Сообщение не может быть пустым", + "Removing...": "Удаляется...", + "Unlinking...": "Ссылка удаляется...", + "Posting...": "Размещается...", + "Username can not be empty!": "Имя пользователя не может быть пустым!", + "Cache is not enabled": "Кэш не подключен", + "Cache has been cleared": "Кэш очищен", + "Rebuild has been done": "Восстановление выполнено", + "Saving...": "Сохраняется...", + "Modified": "Изменено", + "Created": "Создано", + "Create": "Создать", + "create": "создать", + "Overview": "Обзор", + "Details": "Описание", + "Add Filter": "Добавить фильтр", + "Add Dashlet": "Добавить панель", + "Add": "Добавить", + "Reset": "Сбросить", + "Menu": "Меню", + "More": "Больше", + "Search": "Искать", + "Only My": "Тлько мои", + "Open": "Открыть", + "Admin": "Администратор", + "About": "О программе", + "Refresh": "Обновить", + "Remove": "Удалить", + "Options": "Настройки", + "Username": "Имя пользователя", + "Password": "Пароль", + "Login": "Войти", + "Log Out": "Выйти", + "Preferences": "Настройки", + "State": "Регион", + "Street": "Улица", + "Country": "Страна", + "City": "Город", + "PostalCode": "Почтовый индекс", + "Followed": "Отслеживаемый", + "Follow": "Перейти", + "Clear Local Cache": "Очистить локальный кэш", + "Actions": "Действия", + "Delete": "Удалить", + "Update": "Обновить", + "Save": "Сохранить", + "Edit": "Редактировать", + "Cancel": "Отменить", + "Unlink": "Убрать ссылку", + "Mass Update": "Глобальное редактирование", + "Export": "Экспортировать", + "No Data": "Нет данных", + "All": "Все", + "Active": "Активный", + "Inactive": "Неактивный", + "Write your comment here": "Оставьте свою заметку здесь", + "Post": "Разместить", + "Stream": "Лента", + "Show more": "Загрузить еще", + "Dashlet Options": "Настройки панели", + "Full Form": "Раширенная форма", + "Insert": "Вставить", + "Person": "Личность", + "First Name": "Имя", + "Last Name": "Фамилия", + "Original": "Оригинальный", + "You": "Вы", + "you": "вы", + "change": "изменить", + "Primary": "Главная", + "Save Filters": "Сохранить фильтры", + "Administration": "Администрирование", + "Run Import": "Импортировать", + "Duplicate": "Копия", + "Notifications": "Оповещения", + "Mark all read": "Пометить все как прочитанное", + "See more": "Подробнее" + }, + "messages": { + "notModified": "Вы не внесли изменения в запись", + "duplicate": "Созданная вами запись дублировалась", + "fieldIsRequired": "{field} необходимы", + "fieldShouldBeEmail": "{field} должно быть правильным e-mail", + "fieldShouldBeFloat": "{field} должно быть верное дробное число", + "fieldShouldBeInt": "{field} должно быть верное целое число", + "fieldShouldBeDate": "{field} должна быть верная дата", + "fieldShouldBeDatetime": "{field} должна быть верная дата/время", + "fieldShouldAfter": "{field} должно быть после {otherField}", + "fieldShouldBefore": "{field} должно быть до {otherField}", + "fieldShouldBeBetween": "{field} должно быть между {min} и {max}", + "fieldShouldBeLess": "{field} должно быть меньше чем {value}", + "fieldShouldBeGreater": "{field} должно быть больше чем {value}", + "fieldBadPasswordConfirm": "{field} подтверждено неверно", + "assignmentEmailNotificationSubject": "EspoCRM {entityType}: {Entity.name}", + "assignmentEmailNotificationBody": "{assignerUserName} назначил {entityType} '{Entity.name}' на вас\n\n{recordUrl}", + "confirmation": "Вы уверены?", + "removeRecordConfirmation": "Удалить запись?", + "unlinkRecordConfirmation": "Убрать связь?", + "removeSelectedRecordsConfirmation": "Удалить выбранные записи?" + }, + "boolFilters": { + "onlyMy": "Только мои", + "open": "Открыть", + "active": "Активный" + }, + "fields": { + "name": "Полное имя", + "firstName": "Имя", + "lastName": "Фамилия", + "salutationName": "Пол", + "assignedUser": "Выбранный пользователь", + "emailAddress": "E-mail адрес", + "assignedUserName": "Имя выбранного пользователя", + "teams": "Группы", + "createdAt": "Создан в", + "modifiedAt": "Изменен в", + "createdBy": "Создан (кем)", + "modifiedBy": "Изменен (кем)", + "title": "Название", + "dateFrom": "Дата от", + "dateTo": "Дата к", + "autorefreshInterval": "Интервал авто-обновления", + "displayRecords": "Отобразить записи" + }, + "links": { + "teams": "Группы", + "users": "Пользователи" + }, + "dashlets": { + "Stream": "Лента" + }, + "streamMessages": { + "create": "{user} создал {entityType} {entity}", + "createAssigned": "{user} создал {entityType} {entity} присвоен {assignee}", + "assign": "{user} присвоено {entityType} {entity} к {assignee}", + "post": "{user} опубликован на {entityType} {entity}", + "attach": "{user} прикреплен {entityType} {entity}", + "status": "{user} обновлен {field} на {entityType} {entity}", + "update": "{user} обновлен {entityType} {entity}", + "createRelated": "{user} создан {relatedEntityType} {relatedEntity} привязан к {entityType} {entity}", + "emailReceived": "{entity} было получено для {entityType} {entity}", + + "createThis": "{user} создал это {entityType}", + "createAssignedThis": "{user} создал это {entityType} присвоен {assignee}", + "assignThis": "{user} присвоено это {entityType} к {assignee}", + "postThis": "{user} добавил", + "attachThis": "{user} добавил", + "statusThis": "{user} обновил {field}", + "updateThis": "{user} обновил это {entityType}", + "createRelatedThis": "{user} создал {relatedEntityType} {relatedEntity} привязал к этому {entityType}", + "emailReceivedThis": "{entity} было получено" + }, + "lists": { + "monthNames": ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"], + "monthNamesShort": ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"], + "dayNames": ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"], + "dayNamesShort": ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"], + "dayNamesMin": ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"] + }, + "options": { + "salutationName": { + "Mr.": "Г-н.", + "Mrs.": "Г-жа.", + "Dr.": "Д-р.", + "Drs.": "Д-р.", + "male": "Муж. ", + "female": "Жен. " + }, + "language": { + "af_ZA":"Африкаанс", + "az_AZ":"Азербайджанский", + "be_BY":"Белорусский", + "bg_BG":"Болгарский", + "bn_IN":"Бенгальский", + "bs_BA":"Боснийский", + "ca_ES":"Каталонский", + "cs_CZ":"Чешский", + "cy_GB":"Валлийский", + "da_DK":"Датский", + "de_DE":"Немецкий", + "el_GR":"Греческий", + "en_GB":"Английский (UK)", + "en_US":"Английский (US)", + "es_ES":"Испанский (Испания)", + "et_EE":"Эстонский", + "eu_ES":"Баскский", + "fa_IR":"Персидский", + "fi_FI":"Финский", + "fo_FO":"Фарерский", + "fr_CA":"Французский (Канада)", + "fr_FR":"Французский (Франция)", + "ga_IE":"Ирландский", + "gl_ES":"Галицкий", + "gn_PY":"Гуарани", + "he_IL":"Иврит", + "hi_IN":"Хинди", + "hr_HR":"Хорватский", + "hu_HU":"Венгерский", + "hy_AM":"Армянский", + "id_ID":"Индонезийский", + "is_IS":"Исландский", + "it_IT":"Итальянский", + "ja_JP":"Японский", + "ka_GE":"Грузинский", + "km_KH":"Кхмерийский", + "ko_KR":"Корейский", + "ku_TR":"Курдский", + "lt_LT":"Литовский", + "lv_LV":"Латвийский", + "mk_MK":"Македонский", + "ml_IN":"Малайялам", + "ms_MY":"Малайский", + "nb_NO":"Норвежский букмол", + "nn_NO":"Норвежский нюнорск", + "ne_NP":"Непальский", + "nl_NL":"Нидерландский", + "pa_IN":"Панджабский", + "pl_PL":"Польский", + "ps_AF":"Пушту", + "pt_BR":"Португальский (Бразилия)", + "pt_PT":"Португальский (Португалия)", + "ro_RO":"Румынский", + "ru_RU":"Русский", + "sk_SK":"Словацкий", + "sl_SI":"Словенский", + "sq_AL":"Албанский", + "sr_RS":"Сербский", + "sv_SE":"Шведский", + "sw_KE":"Суахили", + "ta_IN":"Тамильский", + "te_IN":"Телугу", + "th_TH":"Тайский", + "tl_PH":"Тагальский", + "tr_TR":"Турецкий", + "uk_UA":"Украинский", + "ur_PK":"Урду", + "vi_VN":"Вьетнамский", + "zh_CN":"Упрощенный китайский (Китай)", + "zh_HK":"Традиционный китайский (Гонконг)", + "zh_TW":"Традиционный китайский (Тайвань)" + }, + "dateSearchRanges": { + "on": "На", + "notOn": "Не на", + "after": "После", + "before": "До", + "between": "Между", + "today": "Сегодня", + "past": "Прошлое", + "future": "Будущее" + }, + "intSearchRanges": { + "equals": "Равняется", + "notEquals": "Не равняется", + "greaterThan": "Больше чем", + "lessThan": "Меньше чем", + "greaterThanOrEquals": "Больше чем или равняется", + "lessThanOrEquals": "Меньше чем или равняется", + "between": "Между" + }, + "autorefreshInterval": { + "0": "Нет", + "0.5": "30 секунд", + "1": "1 минута", + "2": "2 минуты", + "5": "5 минут", + "10": "10 минут" + }, + "phoneNumber": { + "Mobile": "Мобильный", + "Office": "Офисный", + "Fax": "Факс", + "Home": "Домашний", + "Other": "Дополнительно" + } + }, + "sets": { + "summernote": { + "NOTICE": "You can find translation here: https://github.com/HackerWins/summernote/tree/master/lang", + "font":{ + "bold": "Полужирный", + "italic": "Курсив", + "underline": "Подчёркнутый", + "strike": "Зачеркнутый", + "clear": "Убрать стили шрифта", + "height": "Высота линии", + "name": "Название шрифта", + "size": "Размер шрифта" + }, + "image":{ + "image": "Изображение", + "insert": "Вставить изображение", + "resizeFull": "Восстановить размер", + "resizeHalf": "Уменьшить до 50%", + "resizeQuarter": "Уменьшить до 25%", + "floatLeft": "Расположить слева", + "floatRight": "Расположить справа", + "floatNone": "Расположение по-умолчанию", + "dragImageHere": "Перетащите изображение сюда", + "selectFromFiles": "Выбрать из файлов", + "url": "URL изображения", + "remove": "Удалить изображение" + }, + "link":{ + "link": "Ссылка", + "insert": "Вставить ссылку", + "unlink": "Убрать ссылку", + "edit": "Редактировать", + "textToDisplay": "Отображаемый текст", + "url": "URL для перехода", + "openInNewWindow": "Открывать в новом окне" + }, + "video":{ + "video": "Видео", + "videoLink": "Ссылка на видео", + "insert": "Вставить видео", + "url": "URL видео", + "providers": "(YouTube, Vimeo, Vine, Instagram или DailyMotion)" + }, + "table":{ + "table":"Таблица" + }, + "hr":{ + "insert":"Вставить горизонтальную линию" + }, + "style":{ + "style":"Стиль", + "normal":"Нормальный", + "blockquote":"Цитата", + "pre":"Код", + "h1":"Заголовок 1", + "h2":"Заголовок 2", + "h3":"Заголовок 3", + "h4":"Заголовок 4", + "h5":"Заголовок 5", + "h6":"Заголовок 6" + }, + "lists":{ + "unordered":"Маркированный список", + "ordered":"Нумерованный список" + }, + "options":{ + "help":"Помощь", + "fullscreen":"На весь экран", + "codeview":"Исходный код" + }, + "paragraph":{ + "paragraph": "Параграф", + "outdent": "Уменьшить отступ", + "indent": "Увеличить отступ", + "left": "Выровнять по левому краю", + "center": "Выровнять по центру", + "right": "Выровнять по правому краю", + "justify": "Растянуть по ширине" + }, + "color":{ + "recent": "Последний цвет", + "more": "Еще цвета", + "background": "Цвет фона", + "foreground": "Цвет шрифта", + "transparent": "Прозрачный", + "setTransparent": "Сделать прозрачным", + "reset": "Сброс", + "resetToDefault": "Восстановить умолчания" + }, + "shortcut":{ + "shortcuts": "Сочетания клавиш", + "close": "Закрыть", + "textFormatting": "Форматирование текста", + "action": "Действие", + "paragraphFormatting": "Форматирование параграфа", + "documentStyle": "Стиль документа", + "extraKeys": "Дополнительные комбинации" + }, + "history":{ + "undo":"Отменить", + "redo":"Повтор" + } + } + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/Note.json b/application/Espo/Resources/i18n/ru_RU/Note.json new file mode 100644 index 0000000000..80a1a57a97 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/Note.json @@ -0,0 +1,6 @@ +{ + "fields": { + "post": "Сообщение", + "attachments": "Вложения" + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/Preferences.json b/application/Espo/Resources/i18n/ru_RU/Preferences.json new file mode 100644 index 0000000000..6f9b33a1bf --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/Preferences.json @@ -0,0 +1,34 @@ +{ + "fields": { + "dateFormat": "Формат даты", + "timeFormat": "Формат времени", + "timeZone": "Часовой пояс", + "weekStart": "Первый день недели", + "thousandSeparator": "Разделитель разрядов(тысячные)", + "decimalMark": "Десятичный знак", + "defaultCurrency": "Валюта по умолчанию", + "currencyList": "Список валют", + "language": "Язык", + + "smtpServer": "Сервер", + "smtpPort": "Порт", + "smtpAuth": "Авторизация", + "smtpSecurity": "Безопасность", + "smtpUsername": "Имя пользователя", + "emailAddress": "E-mail", + "smtpPassword": "Пароль", + "smtpEmailAddress": "Адрес e-mail", + + "exportDelimiter": "Разделитель (Перемещение)", + + "receiveAssignmentEmailNotifications": "Получать уведомления при назначении" + }, + "links": { + }, + "options": { + "weekStart": { + "0": "Воскресенье", + "1": "Понедельник" + } + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/Role.json b/application/Espo/Resources/i18n/ru_RU/Role.json new file mode 100644 index 0000000000..159d026c77 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/Role.json @@ -0,0 +1,35 @@ +{ + "fields": { + "name": "Имя", + "roles": "Функции" + }, + "links": { + "users": "Пользователи", + "teams": "Группы" + }, + "labels": { + "Access": "Доступ", + "Create Role": "Создать роль" + }, + "options": { + "accessList": { + "not-set": "не установлен", + "enabled": "включен", + "disabled": "отключен" + }, + "levelList": { + "all": "все", + "team": "группа", + "own": "принадлежит", + "no": "нет" + } + }, + "actions": { + "read": "Чтение", + "edit": "Редактирование", + "delete": "Удаление" + }, + "messages": { + "changesAfterClearCache": "Все изменения применятся только после очистки кэша." + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/ScheduledJob.json b/application/Espo/Resources/i18n/ru_RU/ScheduledJob.json new file mode 100644 index 0000000000..ccb69230f8 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/ScheduledJob.json @@ -0,0 +1,30 @@ +{ + "fields": { + "name": "Имя", + "status": "Статус", + "job": "Работа", + "scheduling": "Планирование (crontab notation)" + }, + "links": { + "log": "Лог" + }, + "labels": { + "Create ScheduledJob": "Создать периодическую работу" + }, + "options": { + "job": { + "CheckInboundEmails": "Проверить входящую почту", + "Cleanup": "Очистить" + }, + "cronSetup": { + "linux": "Заметка: Добавьте эту строку в crontab - файл для запуска Планировщика Работ Espo:", + "mac": "Заметка: Добавьте эту строку в crontab - файл для запуска Планировщика Работ Espo:", + "windows": "Заметка: Создайте пакетный файл со следующими командами для запуска Планировщика Работ Espo, используя Планировщик задач Windows:", + "default": "Заметка: Добавьте эту команду в Cron-Работа (Планировщик задач):" + }, + "status": { + "Active": "Активный", + "Inactive": "Неактивный" + } + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/ScheduledJobLogRecord.json b/application/Espo/Resources/i18n/ru_RU/ScheduledJobLogRecord.json new file mode 100644 index 0000000000..4a63acaba3 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/ScheduledJobLogRecord.json @@ -0,0 +1,6 @@ +{ + "fields": { + "status": "Статус", + "executionTime": "Время запуска" + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/Settings.json b/application/Espo/Resources/i18n/ru_RU/Settings.json new file mode 100644 index 0000000000..0be82c0b03 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/Settings.json @@ -0,0 +1,76 @@ +{ + "fields": { + "useCache": "Использовать кэш", + "dateFormat": "Формат даты", + "timeFormat": "Формат времени", + "timeZone": "Часовой пояс", + "weekStart": "Первый день недели", + "thousandSeparator": "Разделитель разрядов(тысячные)", + "decimalMark": "Десятичный знак", + "defaultCurrency": "Валюта по умолчанию", + "baseCurrency": "Базовая валюта", + "baseCurrency": "Базовая валюта", + "currencyRates": "Курсы обмена", + + "currencyList": "Список валют", + "language": "Язык", + + "companyLogo": "Логотип компании", + + "smtpServer": "Сервер", + "smtpPort": "Порт", + "smtpAuth": "Авторизация", + "smtpSecurity": "Безопасность", + "smtpUsername": "Имя пользователя", + "emailAddress": "E-mail", + "smtpPassword": "Пароль", + "outboundEmailFromName": "От имени", + "outboundEmailFromAddress": "От адреса", + "outboundEmailIsShared": "Может использоваться всеми пользователями", + + "recordsPerPage": "Показывать по страницам", + "recordsPerPageSmall": "Показывать по страницам (Мелкий)", + "tabList": "Вкладки", + "quickCreateList": "Быстрое создание списка", + + "exportDelimiter": "Экспортировать разделитель", + + "authenticationMethod": "Метод аутентификации", + "ldapHost": "Хост", + "ldapPort": "Порт", + "ldapAuth": "Авторизация", + "ldapUsername": "Имя пользователя", + "ldapPassword": "Пароль", + "ldapBindRequiresDn": "Привязка по домену", + "ldapBaseDn": "Базовый домен", + "ldapAccountCanonicalForm": "Стандартная форма учетной записи", + "ldapAccountDomainName": "Доменное имя учетной записи", + "ldapTryUsernameSplit": "Попробовать имя пользователя Split", + "ldapCreateEspoUser": "Создать пользователя в EspoCRM", + "ldapSecurity": "Безопасность", + "ldapUserLoginFilter": "Фильтер пользовательской авторизации", + "ldapAccountDomainNameShort": "Краткая учетная запись домена", + "ldapOptReferrals": "Opt Referrals", + "disableExport": "Отмена экпортирования (доступно только администратору)", + "assignmentEmailNotifications": "Оповещать по email при назначении", + "assignmentEmailNotificationsEntityList": "Список вещей для оповещения" + }, + "options": { + "weekStart": { + "0": "Воскресенье", + "1": "Понедельник" + } + }, + "tooltips": { + "recordsPerPageSmall": "Число записей в панелях связей." + }, + "labels": { + "System": "Система", + "Locale": "Локальные настройки", + "SMTP": "SMTP", + "Configuration": "Конфигурация", + "Notifications": "Оповещения", + "Currency Settings": "Настройки валюты", + "Currency Rtes": "Курсы обмена валюты" + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/Team.json b/application/Espo/Resources/i18n/ru_RU/Team.json new file mode 100644 index 0000000000..92695035bc --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/Team.json @@ -0,0 +1,15 @@ +{ + "fields": { + "name": "Имя", + "roles": "Роли" + }, + "links": { + "users": "Пользователи" + }, + "tooltips": { + "roles": "Все пользователи этой команды получат настройки доступа из выбранных ролей." + }, + "labels": { + "Create Team": "Создать группу" + } +} diff --git a/application/Espo/Resources/i18n/ru_RU/User.json b/application/Espo/Resources/i18n/ru_RU/User.json new file mode 100644 index 0000000000..ece2697562 --- /dev/null +++ b/application/Espo/Resources/i18n/ru_RU/User.json @@ -0,0 +1,35 @@ +{ + "fields": { + "name": "Имя", + "userName": "Имя пользователя", + "title": "Название", + "isAdmin": "Администратор", + "defaultTeam": "Группа по умолчанию", + "emailAddress": "E-mail", + "phoneNumber": "Телефон", + "roles": "Роли", + "password": "Пароль", + "passwordConfirm": "Подтвердите пароль", + "newPassword": "Новый пароль" + }, + "links": { + "teams": "Группы", + "roles": "Роли" + }, + "labels": { + "Create User": "Создать пользователя", + "Generate": "Сгенерировать", + "Access": "Доступ", + "Preferences": "Преимущества", + "Change Password": "Изменить пароль" + }, + "tooltips": { + "defaultTeam": "Все записи, созданные этим пользователем, по умолчанию будут относиться к этой команде." + }, + "messages": { + "passwordWillBeSent": "Пароль будет выслан на почтовый адрес пользователя", + "accountInfoEmailSubject": "Информация об учетной записи", + "accountInfoEmailBody": "Информация о Вашей учетной записи:\n\nUsername: {userName}\nPassword: {password}\n\n{siteUrl}", + "passwordChanged": "Пароль изменен" + } +} diff --git a/application/Espo/Resources/layouts/Extension/list.json b/application/Espo/Resources/layouts/Extension/list.json new file mode 100644 index 0000000000..e75712b7ab --- /dev/null +++ b/application/Espo/Resources/layouts/Extension/list.json @@ -0,0 +1,6 @@ +[ + {"name":"name","width":35,"notSortable": true}, + {"name":"version","notSortable": true, "width":15}, + {"name":"description","notSortable": true}, + {"name":"isInstalled","notSortable": true, "width":5} +] diff --git a/application/Espo/Resources/metadata/app/adminPanel.json b/application/Espo/Resources/metadata/app/adminPanel.json index 678376f374..3214279871 100644 --- a/application/Espo/Resources/metadata/app/adminPanel.json +++ b/application/Espo/Resources/metadata/app/adminPanel.json @@ -111,6 +111,11 @@ "url":"#Admin/userInterface", "label":"User Interface", "description":"userInterface" + }, + { + "url":"#Admin/extensions", + "label":"Extensions", + "description":"extensions" } ] } diff --git a/application/Espo/Resources/metadata/app/jsLibs.json b/application/Espo/Resources/metadata/app/jsLibs.json index 659bcbd8d7..478f6a178f 100644 --- a/application/Espo/Resources/metadata/app/jsLibs.json +++ b/application/Espo/Resources/metadata/app/jsLibs.json @@ -8,5 +8,11 @@ "path": "client/lib/summernote.min.js", "exportsTo": "$", "exportsAs": "summernote" + }, + "Textcomplete": { + "path": "client/lib/jquery.textcomplete.js", + "exportsTo": "$", + "exportsAs": "textcomplete" + } } diff --git a/application/Espo/Resources/metadata/entityDefs/Extension.json b/application/Espo/Resources/metadata/entityDefs/Extension.json new file mode 100644 index 0000000000..f77c822fc1 --- /dev/null +++ b/application/Espo/Resources/metadata/entityDefs/Extension.json @@ -0,0 +1,41 @@ +{ + "fields": { + "name": { + "type": "varchar", + "required": true + }, + "version": { + "type": "varchar", + "required": true, + "maxLength": 50 + }, + "fileList": { + "type": "jsonArray" + }, + "description": { + "type": "text" + }, + "isInstalled": { + "type": "bool", + "default": false + }, + "createdAt": { + "type": "datetime", + "readOnly": true + }, + "createdBy": { + "type": "link", + "readOnly": true + } + }, + "links": { + "createdBy": { + "type": "belongsTo", + "entity": "User" + } + }, + "collection": { + "sortBy": "createdAt", + "asc": false + } +} diff --git a/application/Espo/Resources/metadata/entityDefs/ExternalAccount.json b/application/Espo/Resources/metadata/entityDefs/ExternalAccount.json index 3dcdfa336b..4bb1ec7c78 100644 --- a/application/Espo/Resources/metadata/entityDefs/ExternalAccount.json +++ b/application/Espo/Resources/metadata/entityDefs/ExternalAccount.json @@ -1,7 +1,7 @@ { "fields": { "data": { - "type": "text" + "type": "jsonObject" }, "enabled": { "type": "bool" diff --git a/application/Espo/Resources/metadata/entityDefs/Integration.json b/application/Espo/Resources/metadata/entityDefs/Integration.json index 3dcdfa336b..4bb1ec7c78 100644 --- a/application/Espo/Resources/metadata/entityDefs/Integration.json +++ b/application/Espo/Resources/metadata/entityDefs/Integration.json @@ -1,7 +1,7 @@ { "fields": { "data": { - "type": "text" + "type": "jsonObject" }, "enabled": { "type": "bool" diff --git a/application/Espo/Resources/metadata/entityDefs/Job.json b/application/Espo/Resources/metadata/entityDefs/Job.json index caced072e3..f9a6276bc6 100644 --- a/application/Espo/Resources/metadata/entityDefs/Job.json +++ b/application/Espo/Resources/metadata/entityDefs/Job.json @@ -24,7 +24,7 @@ "len":100 }, "data": { - "type": "text" + "type": "jsonObject" }, "scheduledJob": { "type": "link" diff --git a/application/Espo/Resources/metadata/entityDefs/Note.json b/application/Espo/Resources/metadata/entityDefs/Note.json index 516ece665b..008ab4275d 100644 --- a/application/Espo/Resources/metadata/entityDefs/Note.json +++ b/application/Espo/Resources/metadata/entityDefs/Note.json @@ -4,7 +4,7 @@ "type": "text" }, "data": { - "type": "text" + "type": "jsonObject" }, "type": { "type": "varchar" diff --git a/application/Espo/Resources/metadata/entityDefs/Notification.json b/application/Espo/Resources/metadata/entityDefs/Notification.json index c32f990706..f55f92bf47 100644 --- a/application/Espo/Resources/metadata/entityDefs/Notification.json +++ b/application/Espo/Resources/metadata/entityDefs/Notification.json @@ -1,7 +1,11 @@ { "fields": { "data": { - "type": "text" + "type": "jsonObject" + }, + "noteData": { + "type": "jsonObject", + "notStorable": true }, "type": { "type": "varchar" diff --git a/application/Espo/Resources/metadata/entityDefs/User.json b/application/Espo/Resources/metadata/entityDefs/User.json index a0d306621f..0c746f061e 100644 --- a/application/Espo/Resources/metadata/entityDefs/User.json +++ b/application/Espo/Resources/metadata/entityDefs/User.json @@ -6,7 +6,9 @@ "userName": { "type": "varchar", "maxLength": 50, - "required": true + "required": true, + "view": "User.Fields.UserName", + "tooltip": true }, "name": { "type": "personName" @@ -95,6 +97,7 @@ }, "collection": { "sortBy": "userName", - "asc": true + "asc": true, + "textFilterFields": ["name", "userName"] } } diff --git a/application/Espo/Resources/metadata/fields/currency.json b/application/Espo/Resources/metadata/fields/currency.json index f7e2cf57c0..92581559d4 100644 --- a/application/Espo/Resources/metadata/fields/currency.json +++ b/application/Espo/Resources/metadata/fields/currency.json @@ -16,16 +16,18 @@ ], "actualFields":[ "currency", + "converted", "" ], "fields":{ "currency":{ "type":"varchar", "disabled": true + }, + "converted":{ + "type":"currencyConverted", + "readOnly": true } }, - "filter": true, - "fieldDefs":{ - "type":"float" - } + "filter": true } diff --git a/application/Espo/Resources/metadata/fields/currencyConverted.json b/application/Espo/Resources/metadata/fields/currencyConverted.json new file mode 100644 index 0000000000..124118157c --- /dev/null +++ b/application/Espo/Resources/metadata/fields/currencyConverted.json @@ -0,0 +1,9 @@ +{ + "params":[ + ], + "filter": true, + "notCreatable": true, + "fieldDefs":{ + "skip": true + } +} diff --git a/application/Espo/Resources/metadata/fields/foreign.json b/application/Espo/Resources/metadata/fields/foreign.json index 8d5e4c7145..8cc033630d 100644 --- a/application/Espo/Resources/metadata/fields/foreign.json +++ b/application/Espo/Resources/metadata/fields/foreign.json @@ -9,9 +9,9 @@ "type":"varchar" } ], - "filter": false, + "filter": true, "notCreatable": true, "fieldDefs":{ - "skip":true + "skip": false } } diff --git a/application/Espo/Resources/metadata/integrations/Google.json b/application/Espo/Resources/metadata/integrations/Google.json index c16428360d..26b2502c36 100644 --- a/application/Espo/Resources/metadata/integrations/Google.json +++ b/application/Espo/Resources/metadata/integrations/Google.json @@ -12,9 +12,11 @@ } }, "params": { - "url": "https://accounts.google.com/o/oauth2/auth", - "scope": "https://www.googleapis.com/auth/calendar" + "endpoint": "https://accounts.google.com/o/oauth2/auth", + "tokenEndpoint": "https://accounts.google.com/o/oauth2/token", + "scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/calendar" }, "allowUserAccounts": true, - "authMethod": "OAuth2" + "authMethod": "OAuth2", + "clientClassName": "\\Espo\\Core\\ExternalAccount\\Clients\\Google" } diff --git a/application/Espo/Resources/metadata/scopes/Extension.json b/application/Espo/Resources/metadata/scopes/Extension.json new file mode 100644 index 0000000000..55066c1a9f --- /dev/null +++ b/application/Espo/Resources/metadata/scopes/Extension.json @@ -0,0 +1,7 @@ +{ + "entity": true, + "layouts": false, + "tab": false, + "acl": false, + "customizable": false +} diff --git a/application/Espo/Services/EmailTemplate.php b/application/Espo/Services/EmailTemplate.php index e10b5048e7..1acfe1a153 100644 --- a/application/Espo/Services/EmailTemplate.php +++ b/application/Espo/Services/EmailTemplate.php @@ -33,6 +33,7 @@ class EmailTemplate extends Record protected function init() { $this->dependencies[] = 'fileManager'; + $this->dependencies[] = 'dateTime'; } protected function getFileManager() @@ -40,6 +41,11 @@ class EmailTemplate extends Record return $this->injections['fileManager']; } + protected function getDateTime() + { + return $this->injections['dateTime']; + } + public function parse($id, array $params = array(), $copyAttachments = false) { $emailTemplate = $this->getEntity($id); @@ -147,7 +153,13 @@ class EmailTemplate extends Record { $fields = array_keys($entity->getFields()); foreach ($fields as $field) { - $text = str_replace('{' . $type . '.' . $field . '}', $entity->get($field), $text); + $value = $entity->get($field); + if ($entity->fields[$field]['type'] == 'date') { + $value = $this->getDateTime()->convertSystemDateToGlobal($value); + } else if ($entity->fields[$field]['type'] == 'datetime') { + $value = $this->getDateTime()->convertSystemDateTimeToGlobal($value); + } + $text = str_replace('{' . $type . '.' . $field . '}', $value, $text); } return $text; } diff --git a/application/Espo/Services/ExternalAccount.php b/application/Espo/Services/ExternalAccount.php new file mode 100644 index 0000000000..4c1b446fb4 --- /dev/null +++ b/application/Espo/Services/ExternalAccount.php @@ -0,0 +1,95 @@ +getEntityManager()->getEntity('Integration', $integration); + + if (!$integrationEntity) { + throw new NotFound(); + } + $d = $integrationEntity->toArray(); + + if (!$integrationEntity->get('enabled')) { + throw new Error("{$integration} is disabled."); + } + + $factory = new \Espo\Core\ExternalAccount\ClientManager($this->getEntityManager(), $this->getMetadata(), $this->getConfig()); + return $factory->create($integration, $id); + } + + public function getExternalAccountEntity($integration, $userId) + { + return $this->getEntityManager()->getEntity('ExternalAccount', $integration . '__' . $userId); + } + + public function ping($integration, $userId) + { + $entity = $this->getExternalAccountEntity($integration, $userId); + try { + $client = $this->getClient($integration, $userId); + if ($client) { + return $client->ping(); + } + } catch (\Exception $e) {} + } + + public function authorizationCode($integration, $userId, $code) + { + $entity = $this->getExternalAccountEntity($integration, $userId); + if (!$entity) { + throw new NotFound(); + } + $entity->set('enabled', true); + $this->getEntityManager()->saveEntity($entity); + + $client = $this->getClient($integration, $userId); + if ($client instanceof \Espo\Core\ExternalAccount\Clients\OAuth2Abstract) { + $result = $client->getAccessTokenFromAuthorizationCode($code); + if (!empty($result) && !empty($result['accessToken'])) { + $entity->clear('accessToken'); + $entity->clear('refreshToken'); + $entity->clear('tokenType'); + foreach ($result as $name => $value) { + $entity->set($name, $value); + } + $this->getEntityManager()->saveEntity($entity); + return true; + } else { + throw new Error("Could not get access token for {$integration}."); + } + } else { + throw new Error("Could not load client for {$integration}."); + } + } +} + diff --git a/application/Espo/Services/Import.php b/application/Espo/Services/Import.php index d30a82d862..31b9908e99 100644 --- a/application/Espo/Services/Import.php +++ b/application/Espo/Services/Import.php @@ -329,14 +329,17 @@ class Import extends \Espo\Core\Services\Base $a = $entity->toArray(); - if ($this->getEntityManager()->saveEntity($entity)) { - $result['id'] = $entity->id; - if (empty($id)) { - $result['imported'] = true; - } else { - $result['updated'] = true; + try { + if ($this->getEntityManager()->saveEntity($entity)) { + $result['id'] = $entity->id; + if (empty($id)) { + $result['imported'] = true; + } else { + $result['updated'] = true; + } } - } + } catch (\Exception $e) {} + return $result; } diff --git a/application/Espo/Services/Note.php b/application/Espo/Services/Note.php index 8a83feb81a..bcae8b4dd4 100644 --- a/application/Espo/Services/Note.php +++ b/application/Espo/Services/Note.php @@ -42,7 +42,7 @@ class Note extends Record { if (!empty($data['parentType']) && !empty($data['parentId'])) { $entity = $this->getEntityManager()->getEntity($data['parentType'], $data['parentId']); - if ($entity) { + if ($entity) { if (!$this->getAcl()->check($entity, 'read')) { throw new Forbidden(); } @@ -52,5 +52,6 @@ class Note extends Record return parent::createEntity($data); } + } diff --git a/application/Espo/Services/Notification.php b/application/Espo/Services/Notification.php index fb282af174..2a1394a0af 100644 --- a/application/Espo/Services/Notification.php +++ b/application/Espo/Services/Notification.php @@ -50,12 +50,23 @@ class Notification extends \Espo\Core\Services\Base return $this->injections['metadata']; } + public function notifyAboutMentionInPost($userId, $noteId) + { + $notification = $this->getEntityManager()->getEntity('Notification'); + $notification->set(array( + 'type' => 'MentionInPost', + 'data' => array('noteId' => $noteId), + 'userId' => $userId + )); + $this->getEntityManager()->saveEntity($notification); + } + public function notifyAboutNote($userId, $noteId) { $notification = $this->getEntityManager()->getEntity('Notification'); $notification->set(array( 'type' => 'Note', - 'data' => json_encode(array('noteId' => $noteId)), + 'data' => array('noteId' => $noteId), 'userId' => $userId )); $this->getEntityManager()->saveEntity($notification); @@ -113,9 +124,13 @@ class Notification extends \Espo\Core\Services\Base $ids = array(); foreach ($collection as $k => $entity) { $ids[] = $entity->id; - $data = json_decode($entity->get('data')); + $data = $entity->get('data'); + if (empty($data)) { + continue; + } switch ($entity->get('type')) { - case 'Note': + case 'Note': + case 'MentionInPost': $note = $this->getEntityManager()->getEntity('Note', $data->noteId); if ($note) { if ($note->get('parentId') && $note->get('parentType')) { @@ -124,9 +139,10 @@ class Notification extends \Espo\Core\Services\Base $note->set('parentName', $parent->get('name')); } } - $entity->set('data', $note->toArray()); + $entity->set('noteData', $note->toArray()); } else { unset($collection[$k]); + $count--; $this->getEntityManager()->removeEntity($entity); } break; diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php index 4194da6878..66786ad659 100644 --- a/application/Espo/Services/Record.php +++ b/application/Espo/Services/Record.php @@ -169,7 +169,7 @@ class Record extends \Espo\Core\Services\Base { $fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityName() . '.fields', array()); foreach ($fieldDefs as $field => $defs) { - if ($defs['type'] == 'linkMultiple') { + if (isset($defs['type']) && $defs['type'] == 'linkMultiple') { $columns = null; if (!empty($defs['columns'])) { $columns = $defs['columns']; @@ -183,7 +183,7 @@ class Record extends \Espo\Core\Services\Base { $fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityName() . '.fields', array()); foreach ($fieldDefs as $field => $defs) { - if ($defs['type'] == 'linkParent') { + if (isset($defs['type']) && $defs['type'] == 'linkParent') { $id = $entity->get($field . 'Id'); $scope = $entity->get($field . 'Type'); @@ -325,16 +325,12 @@ class Record extends \Espo\Core\Services\Base if (!$this->getAcl()->check($entity, 'edit')) { throw new Forbidden(); } - - - + $entity->set($data); if (!$this->isValid($entity)) { throw new BadRequest(); - } - - $d = $entity->get('attachmentsIds'); + } if ($this->storeEntity($entity)) { return $entity; @@ -570,7 +566,7 @@ class Record extends \Espo\Core\Services\Base if (empty($defs['notStorable'])) { $fields[] = $field; } else { - if ($defs['type'] == 'email') { + if (in_array($defs['type'], array('email', 'phone'))) { $fields[] = $field; } else if ($defs['name'] == 'name') { $fields[] = $field; diff --git a/application/Espo/Services/Stream.php b/application/Espo/Services/Stream.php index 6ee24a7be9..081ceb834c 100644 --- a/application/Espo/Services/Stream.php +++ b/application/Espo/Services/Stream.php @@ -278,7 +278,7 @@ class Stream extends \Espo\Core\Services\Base $data['emailId'] = $email->id; $data['emailName'] = $email->get('name'); - $note->set('data', json_encode($data)); + $note->set('data', $data); $this->getEntityManager()->saveEntity($note); } @@ -317,7 +317,7 @@ class Stream extends \Espo\Core\Services\Base } } - $note->set('data', json_encode($data)); + $note->set('data', $data); $this->getEntityManager()->saveEntity($note); } @@ -330,12 +330,12 @@ class Stream extends \Espo\Core\Services\Base $note->set('parentId', $id); $note->set('parentType', $entityType); - $note->set('data', json_encode(array( + $note->set('data', array( 'action' => $action, 'entityType' => $entity->getEntityName(), 'entityId' => $entity->id, 'entityName' => $entity->get('name') - ))); + )); $this->getEntityManager()->saveEntity($note); } @@ -351,10 +351,10 @@ class Stream extends \Espo\Core\Services\Base if (!$entity->has('assignedUserName')) { $this->loadAssignedUserName($entity); } - $note->set('data', json_encode(array( + $note->set('data', array( 'assignedUserId' => $entity->get('assignedUserId'), 'assignedUserName' => $entity->get('assignedUserName'), - ))); + )); $this->getEntityManager()->saveEntity($note); } @@ -375,11 +375,11 @@ class Stream extends \Espo\Core\Services\Base $style = $this->statusDefs[$entityName]['style'][$value]; } - $note->set('data', json_encode(array( + $note->set('data', array( 'field' => $field, 'value' => $value, 'style' => $style, - ))); + )); $this->getEntityManager()->saveEntity($note); } @@ -449,13 +449,13 @@ class Stream extends \Espo\Core\Services\Base $note->set('parentId', $entity->id); $note->set('parentType', $entity->getEntityName()); - $note->set('data', json_encode(array( + $note->set('data', array( 'fields' => $updatedFields, 'attributes' => array( 'was' => $was, 'became' => $became, ) - ))); + )); $this->getEntityManager()->saveEntity($note); } diff --git a/application/Espo/Services/User.php b/application/Espo/Services/User.php index bd404a46e1..5556e6551c 100644 --- a/application/Espo/Services/User.php +++ b/application/Espo/Services/User.php @@ -125,7 +125,9 @@ class User extends Record $user = parent::updateEntity($id, $data); if (!is_null($newPassword)) { - $this->sendPassword($user, $newPassword); + try { + $this->sendPassword($user, $newPassword); + } catch (\Exception $e) {} } return $user; @@ -141,6 +143,10 @@ class User extends Record $email = $this->getEntityManager()->getEntity('Email'); + if (!$this->getConfig()->get('smtpServer')) { + return; + } + $subject = $this->getLanguage()->translate('accountInfoEmailSubject', 'messages', 'User'); $body = $this->getLanguage()->translate('accountInfoEmailBody', 'messages', 'User'); diff --git a/composer.json b/composer.json index 175bc32491..c4d04d6465 100644 --- a/composer.json +++ b/composer.json @@ -1,21 +1,21 @@ { - "require": { - "doctrine/dbal": "2.*", + "require": { + "doctrine/dbal": "2.*", "slim/slim": "2.*", "mtdowling/cron-expression": "1.0.*", "zendframework/zend-validator": "2.*", - "zendframework/zend-mail": "2.*", + "zendframework/zend-mail": "2.*", "zendframework/zend-ldap": "2.*", "monolog/monolog": "1.*" - }, - "autoload": { - "psr-0": { - "": "application/", - "tests": "", - "Espo\\Custom": "custom/" - } - }, + }, + "autoload": { + "psr-0": { + "": "application/", + "tests": "", + "Espo\\Custom": "custom/" + } + }, "require-dev": { - "phpunit/phpunit": "3.7.*" - } + "phpunit/phpunit": "3.7.*" + } } diff --git a/composer.phar b/composer.phar deleted file mode 100755 index f3231ad72a..0000000000 Binary files a/composer.phar and /dev/null differ diff --git a/diff.js b/diff.js index 73fb3b1f62..012871bc22 100644 --- a/diff.js +++ b/diff.js @@ -95,6 +95,7 @@ execute('git diff --name-only ' + versionFrom, function (stdout) { var manifest = { "name": "EspoCRM Upgrade "+acceptedVersionName+" to "+version, + "type": "upgrade", "version": version, "acceptableVersions": versionList, "releaseDate": date, diff --git a/frontend/client/css/bootstrap.css b/frontend/client/css/bootstrap.css index 3079cc53e3..97e5557387 100644 --- a/frontend/client/css/bootstrap.css +++ b/frontend/client/css/bootstrap.css @@ -1 +1 @@ -/*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!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) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:'Open Sans',sans-serif;font-size:14px;line-height:1.36;color:#333;background-color:#fefefe}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#5184b0;text-decoration:none}a:hover,a:focus{color:#385d7c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:0}.img-thumbnail{padding:4px;line-height:1.36;background-color:#fefefe;border:1px solid #e8eced;border-radius:0;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;width:100% \9;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:19px;margin-bottom:19px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Open Sans',sans-serif;font-weight:500;line-height:1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:19px;margin-bottom:9.5px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:9.5px;margin-bottom:9.5px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 9.5px}.lead{margin-bottom:19px;font-size:16px;font-weight:300;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999}.text-primary{color:#5184b0}a.text-primary:hover{color:#406a8e}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#9e7328}a.text-warning:hover{color:#75551e}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#5184b0}a.bg-primary:hover{background-color:#406a8e}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#cee6ed}a.bg-info:hover{background-color:#a9d3df}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:8.5px;margin:38px 0 19px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:9.5px}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;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:19px}dt,dd{line-height:1.36}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:9.5px 19px;margin:0 0 19px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.36;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:19px;font-style:normal;line-height:1.36}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:0}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:0;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;box-shadow:none}pre{display:block;padding:9px;margin:0 0 9.5px;font-size:13px;line-height:1.36;word-break:break-all;word-wrap:break-word;color:#333;background-color:#e8eced;border:1px solid #ccc;border-radius:0}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-left:8px;padding-right:8px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media(min-width:768px){.container{width:none}}@media(min-width:992px){.container{width:none}}@media(min-width:1200px){.container{width:none}}.container-fluid{padding-left:8px;padding-right:8px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-8px;margin-right:-8px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:8px;padding-right:8px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666666666666%}.col-xs-10{width:83.33333333333334%}.col-xs-9{width:75%}.col-xs-8{width:66.66666666666666%}.col-xs-7{width:58.333333333333336%}.col-xs-6{width:50%}.col-xs-5{width:41.66666666666667%}.col-xs-4{width:33.33333333333333%}.col-xs-3{width:25%}.col-xs-2{width:16.666666666666664%}.col-xs-1{width:8.333333333333332%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666666666666%}.col-xs-pull-10{right:83.33333333333334%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666666666666%}.col-xs-pull-7{right:58.333333333333336%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666666666667%}.col-xs-pull-4{right:33.33333333333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.666666666666664%}.col-xs-pull-1{right:8.333333333333332%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666666666666%}.col-xs-push-10{left:83.33333333333334%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666666666666%}.col-xs-push-7{left:58.333333333333336%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666666666667%}.col-xs-push-4{left:33.33333333333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.666666666666664%}.col-xs-push-1{left:8.333333333333332%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666666666666%}.col-xs-offset-10{margin-left:83.33333333333334%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666666666666%}.col-xs-offset-7{margin-left:58.333333333333336%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666666666667%}.col-xs-offset-4{margin-left:33.33333333333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.666666666666664%}.col-xs-offset-1{margin-left:8.333333333333332%}.col-xs-offset-0{margin-left:0}@media(min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666666666666%}.col-sm-10{width:83.33333333333334%}.col-sm-9{width:75%}.col-sm-8{width:66.66666666666666%}.col-sm-7{width:58.333333333333336%}.col-sm-6{width:50%}.col-sm-5{width:41.66666666666667%}.col-sm-4{width:33.33333333333333%}.col-sm-3{width:25%}.col-sm-2{width:16.666666666666664%}.col-sm-1{width:8.333333333333332%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666666666666%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666666666666%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-0{margin-left:0}}@media(min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666666666666%}.col-md-10{width:83.33333333333334%}.col-md-9{width:75%}.col-md-8{width:66.66666666666666%}.col-md-7{width:58.333333333333336%}.col-md-6{width:50%}.col-md-5{width:41.66666666666667%}.col-md-4{width:33.33333333333333%}.col-md-3{width:25%}.col-md-2{width:16.666666666666664%}.col-md-1{width:8.333333333333332%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666666666666%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666666666666%}.col-md-push-10{left:83.33333333333334%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666666666666%}.col-md-push-7{left:58.333333333333336%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666666666667%}.col-md-push-4{left:33.33333333333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.666666666666664%}.col-md-push-1{left:8.333333333333332%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666666666666%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-0{margin-left:0}}@media(min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666666666666%}.col-lg-10{width:83.33333333333334%}.col-lg-9{width:75%}.col-lg-8{width:66.66666666666666%}.col-lg-7{width:58.333333333333336%}.col-lg-6{width:50%}.col-lg-5{width:41.66666666666667%}.col-lg-4{width:33.33333333333333%}.col-lg-3{width:25%}.col-lg-2{width:16.666666666666664%}.col-lg-1{width:8.333333333333332%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666666666666%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666666666666%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:19px}.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.36;vertical-align:top;border-top:1px solid #e8eced}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #e8eced}.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 #e8eced}.table .table{background-color:#fefefe}.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 #e8eced}.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 #e8eced}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#e8eced}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.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:#e8eced}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#dae0e2}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#cee6ed}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#bbdce6}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:14.25px;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #e8eced;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:19px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.36;color:#555}.form-control{display:block;width:100%;height:33px;padding:6px 10px;font-size:14px;line-height:1.36;color:#555;background-color:#fff;background-image:none;border:1px solid #d1d5d6;border-radius:0;-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;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{line-height:33px;line-height:1.36 \0}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm{line-height:30px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:19px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:0}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:0}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:41.25px}.form-control-feedback{position:absolute;top:24px;right:0;z-index:2;display:block;width:33px;height:33px;line-height:33px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#9e7328}.has-warning .form-control{border-color:#9e7328;-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:#75551e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d5a757;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d5a757}.has-warning .input-group-addon{color:#9e7328;border-color:#9e7328;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#9e7328}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only ~ .form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:26px}.form-horizontal .form-group{margin-left:-8px;margin-right:-8px}@media(min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:8px}@media(min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media(min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 10px;font-size:14px;line-height:1.36;border-radius:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-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{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#e8eced;border-color:#e8eced}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#cbd4d7;border-color:#c6d0d2}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#e8eced;border-color:#e8eced}.btn-default .badge{color:#e8eced;background-color:#333}.btn-primary{color:#fff;background-color:#5184b0;border-color:#5184b0}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#406a8e;border-color:#3d6587}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#5184b0;border-color:#5184b0}.btn-primary .badge{color:#5184b0;background-color:#fff}.btn-success{color:#fff;background-color:#87c956;border-color:#87c956}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#6db339;border-color:#68ab37}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#87c956;border-color:#87c956}.btn-success .badge{color:#87c956;background-color:#fff}.btn-info{color:#fff;background-color:#cee6ed;border-color:#cee6ed}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#a9d3df;border-color:#a1cfdd}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#cee6ed;border-color:#cee6ed}.btn-info .badge{color:#cee6ed;background-color:#fff}.btn-warning{color:#fff;background-color:#f59c0c;border-color:#f59c0c}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#c67d08;border-color:#bc7708}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f59c0c;border-color:#f59c0c}.btn-warning .badge{color:#f59c0c;background-color:#fff}.btn-danger{color:#fff;background-color:#cf605d;border-color:#cf605d}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c03c39;border-color:#b83a37}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#cf605d;border-color:#cf605d}.btn-danger .badge{color:#cf605d;background-color:#fff}.btn-link{color:#5184b0;font-weight:normal;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:#385d7c;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:#999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:0}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:0}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:0}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:0;-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:8.5px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.36;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#5184b0}.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;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.36;color:#999;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top: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;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-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-bottom-right-radius:0;border-top-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-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right: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-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{position:absolute;z-index:-1;opacity:0;filter:alpha(opacity=0)}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:0}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:0}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 10px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #d1d5d6;border-radius:0}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:0}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:0}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#5184b0}.nav .nav-divider{height:1px;margin:8.5px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #e8eced}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.36;border:1px solid transparent;border-radius:0}.nav-tabs>li>a:hover{border-color:#eee #eee #e8eced}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fefefe;border:1px solid #e8eced;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #e8eced}@media(min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #e8eced;border-radius:0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fefefe}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:0}.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:#5184b0}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e8eced}@media(min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e8eced;border-radius:0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fefefe}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:44px;margin-bottom:19px;border:1px solid transparent}@media(min-width:768px){.navbar{border-radius:0}}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:8px;padding-left:8px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:none}@media(max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-8px;margin-left:-8px}@media(min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media(min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:12.5px 8px;font-size:18px;line-height:19px;height:44px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-8px}}.navbar-toggle{position:relative;float:right;margin-right:8px;padding:9px 10px;margin-top:5px;margin-bottom:5px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:0}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:6.25px -8px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:19px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:19px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:12.5px;padding-bottom:12.5px}.navbar-nav.navbar-right:last-child{margin-right:-8px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{margin-left:-8px;margin-right:-8px;padding:10px 8px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:5.5px;margin-bottom:5.5px}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-8px}}.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-btn{margin-top:5.5px;margin-bottom:5.5px}.navbar-btn.btn-sm{margin-top:7px;margin-bottom:7px}.navbar-btn.btn-xs{margin-top:11px;margin-bottom:11px}.navbar-text{margin-top:12.5px;margin-bottom:12.5px}@media(min-width:768px){.navbar-text{float:left;margin-left:8px;margin-right:8px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#e8eced}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#e8eced}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#4a6492;border-color:transparent}.navbar-inverse .navbar-brand{color:#dfdfdf}.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:#dfdfdf}.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:#394d70}.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:#394d70}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#3e547a}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#394d70;color:#fff}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#dfdfdf}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#394d70}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#dfdfdf}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#dfdfdf}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:19px;list-style:none;background-color:#e8eced;border-radius:0}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:19px 0;border-radius:0}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 10px;line-height:1.36;text-decoration:none;color:#5184b0;background-color:#fff;border:1px solid #e8eced;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:0;border-top-left-radius:0}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#385d7c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#5184b0;border-color:#5184b0;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pager{padding-left:0;margin:19px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #e8eced;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#5184b0}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#406a8e}.label-success{background-color:#87c956}.label-success[href]:hover,.label-success[href]:focus{background-color:#6db339}.label-info{background-color:#cee6ed}.label-info[href]:hover,.label-info[href]:focus{background-color:#a9d3df}.label-warning{background-color:#f59c0c}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#c67d08}.label-danger{background-color:#cf605d}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c03c39}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#5184b0;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:0}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:19px;line-height:1.36;background-color:#fefefe;border:1px solid #e8eced;border-radius:0;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#5184b0}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:19px;border:1px solid transparent;border-radius:0}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#cee6ed;border-color:#b4e1e3;color:#31708f}.alert-info hr{border-top-color:#a1d9dd}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#9e7328}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#75551e}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:19px;margin-bottom:19px;background-color:#e8eced;border-radius:0;-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;line-height:19px;color:#fff;text-align:center;background-color:#5184b0;-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;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,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);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{color:#999;min-width:30px;background-color:transparent;background-image:none;box-shadow:none}.progress-bar-success{background-color:#87c956}.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-info{background-color:#cee6ed}.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-warning{background-color:#f59c0c}.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-danger{background-color:#cf605d}.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)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #e8eced}.list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;color:#555;background-color:#e8eced}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#999}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#5184b0;border-color:#5184b0}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#dde7f0}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#cee6ed}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#bbdce6}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#9e7328;background-color:#fcf8e3}a.list-group-item-warning{color:#9e7328}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#9e7328;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#9e7328;border-color:#9e7328}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:19px;background-color:#fff;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:-1;border-top-left-radius:-1}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#e8eced;border-top:1px solid #e8eced;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:-1;border-top-left-radius:-1}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:-1;border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:-1}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:-1}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #e8eced}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:19px}.panel-group .panel{margin-bottom:0;border-radius:0}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #e8eced}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #e8eced}.panel-default{border-color:#e8eced}.panel-default>.panel-heading{color:#333;background-color:#e8eced;border-color:#e8eced}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#e8eced}.panel-default>.panel-heading .badge{color:#e8eced;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#e8eced}.panel-primary{border-color:#5184b0}.panel-primary>.panel-heading{color:#fff;background-color:#5184b0;border-color:#5184b0}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#5184b0}.panel-primary>.panel-heading .badge{color:#5184b0;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#5184b0}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#b4e1e3}.panel-info>.panel-heading{color:#31708f;background-color:#cee6ed;border-color:#b4e1e3}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#b4e1e3}.panel-info>.panel-heading .badge{color:#cee6ed;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#b4e1e3}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#9e7328;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#9e7328}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#e8eced;border:1px solid #d4dbdd;border-radius:0;-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-lg{padding:24px;border-radius:0}.well-sm{padding:9px;border-radius:0}.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}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0);-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.in .modal-dialog{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius: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;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#dbdbdb}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:10px;border-bottom:1px solid #e5e5e5;min-height:11.36px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.36}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media(min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media(min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1600;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:0}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1600;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:0;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:-1 -1 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{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-image:-o-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{left:auto;right:0;background-image:-webkit-linear-gradient(left,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-image:-o-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{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;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-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(max-width:767px){.visible-xs-block{display:block!important}}@media(max-width:767px){.visible-xs-inline{display:inline!important}}@media(max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media(min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media(min-width:1200px){.visible-lg-block{display:block!important}}@media(min-width:1200px){.visible-lg-inline{display:inline!important}}@media(min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media(max-width:767px){.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}.btn{border-radius:0}.panel-heading{padding:6px 10px}.panel-body{padding:14px}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination-sm>li>a{line-height:1.5}.modal-header{padding:10px 14px;border-bottom:1px solid #e5e5e5;min-height:11.36px}.modal-footer{margin-top:10px;padding:15px 15px 15px}.table thead>tr>th{border-bottom:1px solid #e8eced;font-weight:normal}.form-group{margin-bottom:12px}.panel-group .panel+.panel{margin-top:4px}.alert{padding:5px 18px}label{margin-bottom:3px}.table{margin-bottom:10px}.page-header{margin:10px 0;border-bottom:0;padding-bottom:2px}.modal-header{background-color:#e8eced}div.list-expanded>ul>li>div.expanded-row>.cell{padding-left:6px;padding-right:3px;border-left:1px solid #e8eced}div.list-expanded>ul>li>div.expanded-row>.cell:first-child{padding-left:0;border-left:0}table.table>thead th a{color:#999}table.table>thead th{color:#999}.cell>label,.filter label{color:#999;font-weight:normal;margin-bottom:2px}.btn.active{box-shadow:none}.navbar-inverse{border-bottom-width:0!important}@media(min-width:1200px){.container{max-width:1366px}}body{padding-top:55px}.navbar-brand{padding:3px 8px}#global-search-input{width:165px;margin-top:1px}.progress.pre-loading{margin-top:-50px;margin-bottom:31px}.edit form{margin:0}.button-container{padding:0 0 10px}.margin{margin:8px 0}ul.dropdown-menu>li.checkbox{margin-left:20px}.page-header h3{margin-top:0;margin-bottom:8px}.list-buttons-container>div{float:left;margin-right:15px}.list-container .pagination{margin:0}.floated-row>div{float:left;margin-right:15px}.search-row{margin-left:-3px!important;margin-right:-3px!important}.search-row>div{padding-left:2px!important;padding-right:2px!important}.btn-icon>span{margin:0 20px}.field>.row{margin-left:-5px!important;margin-right:-5px!important}.field .row>div{padding-left:5px!important;padding-right:5px!important;float:left}.field .form-control,.field .btn{margin-bottom:3px}.field .input-group .form-control,.field .input-group .btn{margin-bottom:0}.field .input-group{margin-bottom:3px}.field .link-container{margin-bottom:-1px}.field .link-container .list-group-item>div{margin:-3px 0 -3px}.field .link-container .list-group-item>div>div{margin:6px 0}.field .link-container .list-group-item .form-control,.field .link-container .list-group-item .btn{margin-top:1px;margin-bottom:1px}.field .link-container .list-group-item .form-control{width:140px!important}.filter .field .link-container .list-group-item a{margin-top:1px}.field .link-container .list-group-item a{margin-top:1px}.field .link-container .list-group-item.link-with-role a{margin-top:6px}#login.panel>.panel-heading{background-color:#4a6492;padding:3px 10px}@media screen and (min-width:768px){.modal-dialog{width:740px!important}}@media screen and (min-width:1024px){.modal-dialog{width:900px!important}}@media screen and (max-width:768px){.navbar-fixed-top{position:static}body{padding-top:0}}.list-row-buttons span.caret{border-top-color:#999}.list>table{margin-bottom:0}.list>ul{margin-bottom:0}.list>table td input[type="checkbox"]{margin:0;position:relative;top:3px}.list>table th span.caret{border-top-color:#999}.list>table th span.caret-up{border-bottom-color:#999}.filter a.remove-filter{display:none}.filter:hover a.remove-filter{display:block}select[multiple].input-sm{height:90px!important}.input-group-btn>select.form-control{position:relative;display:inline-block;width:auto;vertical-align:middle;margin-right:-1px}.input-group-btn:last-child>select.form-control{margin-right:0;margin-left:-1px}.input-group .form-control:focus{z-index:3}.panel-body>.list-container>.list-expanded{margin-left:-15px;margin-right:-15px}.panel-body>.list-container>.list{margin-left:-15px;margin-right:-15px}.panel-body>.list-container>.list>table td:first-child,.panel-body>.list-container>.list>table th:first-child{padding-left:15px}.panel-body>.list-container>.list>table td:last-child,.panel-body>.list-container>.list>table th:last-child{padding-right:15px}.list-expanded>.show-more{padding:0;margin-top:-1px}.list-expanded>li.show-more>.btn{margin-left:-1px;margin-right:-4px}.list-expanded>ul>li .list-row-buttons{margin-right:-12px;margin-top:-10px}.show-more>.btn{border-radius:0;border-color:#e0e0e0}.panel-body>.list-container:first-child>.list-expanded:first-child{margin-top:-15px}.panel-body>.list-container:last-child>.list-expanded:last-child{margin-bottom:-15px}.panel-body>.list-container:first-child>.list:first-child{margin-top:-15px}.panel-body>.list-container:last-child>.list:last-child{margin-bottom:-15px}.expanded-row{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}#notifications-panel .expanded-row{white-space:normal;overflow:hidden}#notifications-panel .right{padding-top:5px}.expanded-row .cell{display:inline-block}.caret-up{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-bottom:4px solid #000;border-right:4px solid transparent;border-top:0 dotted;border-left:4px solid transparent;content:""}.list>table{table-layout:fixed}.list>table td,table.list th{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.list>table td.cell-buttons{overflow:visible}.panel.dashlet>.panel-heading>.menu-container{margin-top:-8px;right:-10px}.panel.dashlet>.panel-body{height:295px;overflow-y:auto;overflow-x:hidden}.panel-heading>.btn-group{top:-7px;right:-11px}.list-container+.button-container{margin-top:6px;padding-bottom:0}td.cell-buttons>.btn-group{margin-top:-8px;margin-bottom:-8px;margin-right:-10px}.list-expanded>li>.right>.btn-group{top:-7px;right:-11px}.panel{border-width:2px}.panel-heading>.btn-group>.btn{border-radius:0!important}.list>table thead>th{border-top-width:1px!important}.show-more>.btn-block{text-align:left;padding-left:15px}.autocomplete-suggestions{border:1px solid #999;background:#FFF;cursor:default;overflow:auto;-webkit-box-shadow:1px 4px 3px rgba(50,50,50,0.64);-moz-box-shadow:1px 4px 3px rgba(50,50,50,0.64);box-shadow:1px 4px 3px rgba(50,50,50,0.64)}.autocomplete-suggestion{padding:2px 5px;white-space:nowrap;overflow:hidden}.autocomplete-selected{background:#f0f0f0}.autocomplete-suggestions strong{font-weight:normal;color:#39f}.gray-box{background-color:#eee;margin:0 5px 3px 0;padding:3px 3px 3px 5px}.gray-box .preview{overflow:hidden;display:inline-block}.attachment-preview{display:inline-block;margin:3px 6px 3px 0}.field>.attachment-preview{display:block;overflow:hidden}.stick-sub{position:fixed;top:0;margin:44px 0 0;z-index:4}.stick-sub.button-container{background-color:#fefefe;padding:3px 3px 3px 0;width:100%}@media screen and (max-width:768px){.stick-sub{margin-top:0}}@font-face{font-family:Open Sans;src:url('../fonts/open-sans-regular.eot?') format('eot'),url('../fonts/open-sans-regular.woff') format('woff'),url('../fonts/open-sans-regular.ttf') format('truetype'),url('../fonts/open-sans-regular.svg#svgFontName') format('svg')}.badge-circle{width:8px;height:8px;border-radius:4px;display:table-cell;font-size:5px;line-height:8px;text-align:center;background-color:#e8eced}.badge-circle.badge-circle-danger{background-color:#cf605d}.badge-circle.badge-circle-warning{background-color:#f59c0c}.danger.glyphicon{color:#cf605d}.warning.glyphicon{color:#f59c0c}html,body{height:100%}body>.content{height:auto;min-height:100%;padding-bottom:35px}body>footer{clear:both;position:relative;height:26px;margin-top:-28px;background-color:#eee}body>footer>p{padding:6px 15px}body>footer>p.credit{margin:0}body>footer>p,body>footer>p a,body>footer>p a:hover,body>footer>p a:active,body>footer>p a:visited{color:#555}.navbar-collapse{overflow-x:hidden!important}#global-search-panel>.panel>.panel-body{max-height:294px;overflow-y:auto;overflow-x:hidden}#notifications-panel>.panel>.panel-body{max-height:294px;overflow-y:auto;overflow-x:hidden}@media screen and (max-width:768px){.list{width:auto;overflow-y:hidden;overflow-x:scroll}.list.list-expanded{width:auto;overflow-x:hidden}.list>table{min-width:768px}}.modal.in .modal.in{overflow:visible!important;overflow-y:visible!important}.record>.panel:first-child{margin-top:0}.record>.panel{margin-top:-20px}.detail .bottom>.panel-stream:first-child{margin-top:-20px}.stream-post-container,.stream-attachments-container{padding:5px 0}.link-container .list-group-item{padding:6px 10px}.note-editable blockquote{font-size:14px}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} \ No newline at end of file +/*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!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) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:'Open Sans',sans-serif;font-size:14px;line-height:1.36;color:#333;background-color:#fefefe}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#5184b0;text-decoration:none}a:hover,a:focus{color:#385d7c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:0}.img-thumbnail{padding:4px;line-height:1.36;background-color:#fefefe;border:1px solid #e8eced;border-radius:0;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;width:100% \9;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:19px;margin-bottom:19px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Open Sans',sans-serif;font-weight:500;line-height:1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:19px;margin-bottom:9.5px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:9.5px;margin-bottom:9.5px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 9.5px}.lead{margin-bottom:19px;font-size:16px;font-weight:300;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999}.text-primary{color:#5184b0}a.text-primary:hover{color:#406a8e}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#9e7328}a.text-warning:hover{color:#75551e}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#5184b0}a.bg-primary:hover{background-color:#406a8e}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#cee6ed}a.bg-info:hover{background-color:#a9d3df}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:8.5px;margin:38px 0 19px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:9.5px}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;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:19px}dt,dd{line-height:1.36}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:9.5px 19px;margin:0 0 19px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.36;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:19px;font-style:normal;line-height:1.36}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:0}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:0;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;box-shadow:none}pre{display:block;padding:9px;margin:0 0 9.5px;font-size:13px;line-height:1.36;word-break:break-all;word-wrap:break-word;color:#333;background-color:#e8eced;border:1px solid #ccc;border-radius:0}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-left:8px;padding-right:8px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}.container:before,.container:after{content:" ";display:table}.container:after{clear:both}@media(min-width:768px){.container{width:none}}@media(min-width:992px){.container{width:none}}@media(min-width:1200px){.container{width:none}}.container-fluid{padding-left:8px;padding-right:8px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.container-fluid:before,.container-fluid:after{content:" ";display:table}.container-fluid:after{clear:both}.row{margin-left:-8px;margin-right:-8px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:8px;padding-right:8px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666666666666%}.col-xs-10{width:83.33333333333334%}.col-xs-9{width:75%}.col-xs-8{width:66.66666666666666%}.col-xs-7{width:58.333333333333336%}.col-xs-6{width:50%}.col-xs-5{width:41.66666666666667%}.col-xs-4{width:33.33333333333333%}.col-xs-3{width:25%}.col-xs-2{width:16.666666666666664%}.col-xs-1{width:8.333333333333332%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666666666666%}.col-xs-pull-10{right:83.33333333333334%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666666666666%}.col-xs-pull-7{right:58.333333333333336%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666666666667%}.col-xs-pull-4{right:33.33333333333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.666666666666664%}.col-xs-pull-1{right:8.333333333333332%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666666666666%}.col-xs-push-10{left:83.33333333333334%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666666666666%}.col-xs-push-7{left:58.333333333333336%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666666666667%}.col-xs-push-4{left:33.33333333333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.666666666666664%}.col-xs-push-1{left:8.333333333333332%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666666666666%}.col-xs-offset-10{margin-left:83.33333333333334%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666666666666%}.col-xs-offset-7{margin-left:58.333333333333336%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666666666667%}.col-xs-offset-4{margin-left:33.33333333333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.666666666666664%}.col-xs-offset-1{margin-left:8.333333333333332%}.col-xs-offset-0{margin-left:0}@media(min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666666666666%}.col-sm-10{width:83.33333333333334%}.col-sm-9{width:75%}.col-sm-8{width:66.66666666666666%}.col-sm-7{width:58.333333333333336%}.col-sm-6{width:50%}.col-sm-5{width:41.66666666666667%}.col-sm-4{width:33.33333333333333%}.col-sm-3{width:25%}.col-sm-2{width:16.666666666666664%}.col-sm-1{width:8.333333333333332%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666666666666%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666666666666%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-0{margin-left:0}}@media(min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666666666666%}.col-md-10{width:83.33333333333334%}.col-md-9{width:75%}.col-md-8{width:66.66666666666666%}.col-md-7{width:58.333333333333336%}.col-md-6{width:50%}.col-md-5{width:41.66666666666667%}.col-md-4{width:33.33333333333333%}.col-md-3{width:25%}.col-md-2{width:16.666666666666664%}.col-md-1{width:8.333333333333332%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666666666666%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666666666666%}.col-md-push-10{left:83.33333333333334%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666666666666%}.col-md-push-7{left:58.333333333333336%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666666666667%}.col-md-push-4{left:33.33333333333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.666666666666664%}.col-md-push-1{left:8.333333333333332%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666666666666%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-0{margin-left:0}}@media(min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666666666666%}.col-lg-10{width:83.33333333333334%}.col-lg-9{width:75%}.col-lg-8{width:66.66666666666666%}.col-lg-7{width:58.333333333333336%}.col-lg-6{width:50%}.col-lg-5{width:41.66666666666667%}.col-lg-4{width:33.33333333333333%}.col-lg-3{width:25%}.col-lg-2{width:16.666666666666664%}.col-lg-1{width:8.333333333333332%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666666666666%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666666666666%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:19px}.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.36;vertical-align:top;border-top:1px solid #e8eced}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #e8eced}.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 #e8eced}.table .table{background-color:#fefefe}.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 #e8eced}.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 #e8eced}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#e8eced}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.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:#e8eced}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#dae0e2}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#cee6ed}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#bbdce6}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:14.25px;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #e8eced;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:19px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.36;color:#555}.form-control{display:block;width:100%;height:33px;padding:6px 10px;font-size:14px;line-height:1.36;color:#555;background-color:#fff;background-image:none;border:1px solid #d1d5d6;border-radius:0;-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;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{line-height:33px;line-height:1.36 \0}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm{line-height:30px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:19px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:0}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:0}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:41.25px}.form-control-feedback{position:absolute;top:24px;right:0;z-index:2;display:block;width:33px;height:33px;line-height:33px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#9e7328}.has-warning .form-control{border-color:#9e7328;-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:#75551e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d5a757;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d5a757}.has-warning .input-group-addon{color:#9e7328;border-color:#9e7328;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#9e7328}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only ~ .form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:26px}.form-horizontal .form-group{margin-left:-8px;margin-right:-8px}@media(min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:8px}@media(min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media(min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 10px;font-size:14px;line-height:1.36;border-radius:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-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{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#e8eced;border-color:#e8eced}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#cbd4d7;border-color:#c6d0d2}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#e8eced;border-color:#e8eced}.btn-default .badge{color:#e8eced;background-color:#333}.btn-primary{color:#fff;background-color:#5184b0;border-color:#5184b0}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#406a8e;border-color:#3d6587}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#5184b0;border-color:#5184b0}.btn-primary .badge{color:#5184b0;background-color:#fff}.btn-success{color:#fff;background-color:#87c956;border-color:#87c956}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#6db339;border-color:#68ab37}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#87c956;border-color:#87c956}.btn-success .badge{color:#87c956;background-color:#fff}.btn-info{color:#fff;background-color:#cee6ed;border-color:#cee6ed}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#a9d3df;border-color:#a1cfdd}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#cee6ed;border-color:#cee6ed}.btn-info .badge{color:#cee6ed;background-color:#fff}.btn-warning{color:#fff;background-color:#f59c0c;border-color:#f59c0c}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#c67d08;border-color:#bc7708}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f59c0c;border-color:#f59c0c}.btn-warning .badge{color:#f59c0c;background-color:#fff}.btn-danger{color:#fff;background-color:#cf605d;border-color:#cf605d}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c03c39;border-color:#b83a37}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#cf605d;border-color:#cf605d}.btn-danger .badge{color:#cf605d;background-color:#fff}.btn-link{color:#5184b0;font-weight:normal;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:#385d7c;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:#999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:0}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:0}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:0}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:0;-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:8.5px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.36;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#5184b0}.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;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.36;color:#999;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top: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;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-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-bottom-right-radius:0;border-top-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-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right: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-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{position:absolute;z-index:-1;opacity:0;filter:alpha(opacity=0)}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:0}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:0}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 10px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #d1d5d6;border-radius:0}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:0}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:0}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#5184b0}.nav .nav-divider{height:1px;margin:8.5px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #e8eced}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.36;border:1px solid transparent;border-radius:0}.nav-tabs>li>a:hover{border-color:#eee #eee #e8eced}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fefefe;border:1px solid #e8eced;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #e8eced}@media(min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #e8eced;border-radius:0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fefefe}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:0}.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:#5184b0}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #e8eced}@media(min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #e8eced;border-radius:0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fefefe}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:44px;margin-bottom:19px;border:1px solid transparent}@media(min-width:768px){.navbar{border-radius:0}}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:8px;padding-left:8px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:none}@media(max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-8px;margin-left:-8px}@media(min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media(min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:12.5px 8px;font-size:18px;line-height:19px;height:44px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-8px}}.navbar-toggle{position:relative;float:right;margin-right:8px;padding:9px 10px;margin-top:5px;margin-bottom:5px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:0}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:6.25px -8px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:19px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:19px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:12.5px;padding-bottom:12.5px}.navbar-nav.navbar-right:last-child{margin-right:-8px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{margin-left:-8px;margin-right:-8px;padding:10px 8px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:5.5px;margin-bottom:5.5px}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-8px}}.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-btn{margin-top:5.5px;margin-bottom:5.5px}.navbar-btn.btn-sm{margin-top:7px;margin-bottom:7px}.navbar-btn.btn-xs{margin-top:11px;margin-bottom:11px}.navbar-text{margin-top:12.5px;margin-bottom:12.5px}@media(min-width:768px){.navbar-text{float:left;margin-left:8px;margin-right:8px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#e8eced}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#e8eced}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#4a6492;border-color:transparent}.navbar-inverse .navbar-brand{color:#dfdfdf}.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:#dfdfdf}.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:#394d70}.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:#394d70}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#3e547a}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#394d70;color:#fff}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#dfdfdf}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#394d70}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#dfdfdf}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#dfdfdf}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:19px;list-style:none;background-color:#e8eced;border-radius:0}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:19px 0;border-radius:0}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 10px;line-height:1.36;text-decoration:none;color:#5184b0;background-color:#fff;border:1px solid #e8eced;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:0;border-top-left-radius:0}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#385d7c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#5184b0;border-color:#5184b0;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pager{padding-left:0;margin:19px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #e8eced;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#5184b0}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#406a8e}.label-success{background-color:#87c956}.label-success[href]:hover,.label-success[href]:focus{background-color:#6db339}.label-info{background-color:#cee6ed}.label-info[href]:hover,.label-info[href]:focus{background-color:#a9d3df}.label-warning{background-color:#f59c0c}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#c67d08}.label-danger{background-color:#cf605d}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c03c39}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#5184b0;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:0}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:19px;line-height:1.36;background-color:#fefefe;border:1px solid #e8eced;border-radius:0;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#5184b0}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:19px;border:1px solid transparent;border-radius:0}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#cee6ed;border-color:#b4e1e3;color:#31708f}.alert-info hr{border-top-color:#a1d9dd}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#9e7328}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#75551e}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:19px;margin-bottom:19px;background-color:#e8eced;border-radius:0;-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;line-height:19px;color:#fff;text-align:center;background-color:#5184b0;-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;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,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);background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{color:#999;min-width:30px;background-color:transparent;background-image:none;box-shadow:none}.progress-bar-success{background-color:#87c956}.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-info{background-color:#cee6ed}.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-warning{background-color:#f59c0c}.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-danger{background-color:#cf605d}.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)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #e8eced}.list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;color:#555;background-color:#e8eced}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eee;color:#999}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#5184b0;border-color:#5184b0}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#dde7f0}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#cee6ed}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#bbdce6}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#9e7328;background-color:#fcf8e3}a.list-group-item-warning{color:#9e7328}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#9e7328;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#9e7328;border-color:#9e7328}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:19px;background-color:#fff;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:-1;border-top-left-radius:-1}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#e8eced;border-top:1px solid #e8eced;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:-1;border-top-left-radius:-1}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:-1;border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:-1}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:-1}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #e8eced}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:19px}.panel-group .panel{margin-bottom:0;border-radius:0}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #e8eced}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #e8eced}.panel-default{border-color:#e8eced}.panel-default>.panel-heading{color:#333;background-color:#e8eced;border-color:#e8eced}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#e8eced}.panel-default>.panel-heading .badge{color:#e8eced;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#e8eced}.panel-primary{border-color:#5184b0}.panel-primary>.panel-heading{color:#fff;background-color:#5184b0;border-color:#5184b0}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#5184b0}.panel-primary>.panel-heading .badge{color:#5184b0;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#5184b0}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#b4e1e3}.panel-info>.panel-heading{color:#31708f;background-color:#cee6ed;border-color:#b4e1e3}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#b4e1e3}.panel-info>.panel-heading .badge{color:#cee6ed;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#b4e1e3}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#9e7328;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#9e7328}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#e8eced;border:1px solid #d4dbdd;border-radius:0;-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-lg{padding:24px;border-radius:0}.well-sm{padding:9px;border-radius:0}.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}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0);-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.in .modal-dialog{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius: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;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#dbdbdb}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:10px;border-bottom:1px solid #e5e5e5;min-height:11.36px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.36}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media(min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media(min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1600;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:0}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1600;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:0;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:-1 -1 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{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-image:-o-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{left:auto;right:0;background-image:-webkit-linear-gradient(left,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-image:-o-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{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;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-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(max-width:767px){.visible-xs-block{display:block!important}}@media(max-width:767px){.visible-xs-inline{display:inline!important}}@media(max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media(min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media(min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media(min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media(min-width:1200px){.visible-lg-block{display:block!important}}@media(min-width:1200px){.visible-lg-inline{display:inline!important}}@media(min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media(max-width:767px){.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}.btn{border-radius:0}.panel-heading{padding:6px 10px}.panel-body{padding:14px}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{content:" ";display:table}.panel-body:after{clear:both}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination-sm>li>a{line-height:1.5}.modal-header{padding:10px 14px;border-bottom:1px solid #e5e5e5;min-height:11.36px}.modal-footer{margin-top:10px;padding:15px 15px 15px}.table thead>tr>th{border-bottom:1px solid #e8eced;font-weight:normal}.form-group{margin-bottom:12px}.panel-group .panel+.panel{margin-top:4px}.alert{padding:5px 18px}label{margin-bottom:3px}.table{margin-bottom:10px}.page-header{margin:10px 0;border-bottom:0;padding-bottom:2px}.modal-header{background-color:#e8eced}div.list-expanded>ul>li>div.expanded-row>.cell{padding-left:6px;padding-right:3px;border-left:1px solid #e8eced}div.list-expanded>ul>li>div.expanded-row>.cell:first-child{padding-left:0;border-left:0}table.table>thead th a{color:#999}table.table>thead th{color:#999}.cell>label,.filter label{color:#999;font-weight:normal;margin-bottom:2px}.btn.active{box-shadow:none}.navbar-inverse{border-bottom-width:0!important}@media(min-width:1200px){.container{max-width:1366px}}body{padding-top:55px}.navbar-brand{padding:3px 8px}#global-search-input{width:165px;margin-top:1px}.progress.pre-loading{margin-top:-50px;margin-bottom:31px}.edit form{margin:0}.button-container{padding:0 0 10px}.margin{margin:8px 0}ul.dropdown-menu>li.checkbox{margin-left:20px}.page-header h3{margin-top:0;margin-bottom:8px}.list-buttons-container>div{float:left;margin-right:15px}.list-container .pagination{margin:0}.floated-row>div{float:left;margin-right:15px}.search-row{margin-left:-3px!important;margin-right:-3px!important}.search-row>div{padding-left:2px!important;padding-right:2px!important}.btn-icon>span{margin:0 20px}.field>.row{margin-left:-5px!important;margin-right:-5px!important}.field .row>div{padding-left:5px!important;padding-right:5px!important;float:left}.field .form-control,.field .btn{margin-bottom:3px}.field .input-group .form-control,.field .input-group .btn{margin-bottom:0}.field .input-group{margin-bottom:3px}.field .link-container{margin-bottom:-1px}.field .link-container .list-group-item>div{margin:-3px 0 -3px}.field .link-container .list-group-item>div>div{margin:6px 0}.field .link-container .list-group-item .form-control,.field .link-container .list-group-item .btn{margin-top:1px;margin-bottom:1px}.field .link-container .list-group-item .form-control{width:140px!important}.filter .field .link-container .list-group-item a{margin-top:1px}.field .link-container .list-group-item a{margin-top:1px}.field .link-container .list-group-item.link-with-role a{margin-top:6px}#login.panel>.panel-heading{background-color:#4a6492;padding:3px 10px}@media screen and (min-width:768px){.modal-dialog{width:740px!important}}@media screen and (min-width:1024px){.modal-dialog{width:900px!important}}@media screen and (max-width:768px){.navbar-fixed-top{position:static}body{padding-top:0}}.list-row-buttons span.caret{border-top-color:#999}.list>table{margin-bottom:0}.list>ul{margin-bottom:0}.list>table td input[type="checkbox"]{margin:0;position:relative;top:3px}.list>table th span.caret{border-top-color:#999}.list>table th span.caret-up{border-bottom-color:#999}.filter a.remove-filter{display:none}.filter:hover a.remove-filter{display:block}select[multiple].input-sm{height:90px!important}.input-group-btn>select.form-control{position:relative;display:inline-block;width:auto;vertical-align:middle;margin-right:-1px}.input-group-btn:last-child>select.form-control{margin-right:0;margin-left:-1px}.input-group .form-control:focus{z-index:3}.panel-body>.list-container>.list-expanded{margin-left:-15px;margin-right:-15px}.panel-body>.list-container>.list{margin-left:-15px;margin-right:-15px}.panel-body>.list-container>.list>table td:first-child,.panel-body>.list-container>.list>table th:first-child{padding-left:15px}.panel-body>.list-container>.list>table td:last-child,.panel-body>.list-container>.list>table th:last-child{padding-right:15px}.list-expanded>.show-more{padding:0;margin-top:-1px}.list-expanded>li.show-more>.btn{margin-left:-1px;margin-right:-4px}.list-expanded>ul>li .list-row-buttons{margin-right:-12px;margin-top:-10px}.show-more>.btn{border-radius:0;border-color:#e0e0e0}.panel-body>.list-container:first-child>.list-expanded:first-child{margin-top:-15px}.panel-body>.list-container:last-child>.list-expanded:last-child{margin-bottom:-15px}.panel-body>.list-container:first-child>.list:first-child{margin-top:-15px}.panel-body>.list-container:last-child>.list:last-child{margin-bottom:-15px}.expanded-row{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}#notifications-panel .expanded-row{white-space:normal;overflow:hidden}#notifications-panel .right{padding-top:5px}.expanded-row .cell{display:inline-block}.caret-up{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-bottom:4px solid #000;border-right:4px solid transparent;border-top:0 dotted;border-left:4px solid transparent;content:""}.list>table{table-layout:fixed}.list>table td,table.list th{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.list>table td.cell-buttons{overflow:visible}.panel.dashlet>.panel-heading>.menu-container{margin-top:-8px;right:-10px}.panel.dashlet>.panel-body{height:295px;overflow-y:auto;overflow-x:hidden}.panel.dashlet.double-height>.panel-body{height:642px}.panel-heading>.btn-group{top:-7px;right:-11px}.list-container+.button-container{margin-top:6px;padding-bottom:0}td.cell-buttons>.btn-group{margin-top:-8px;margin-bottom:-8px;margin-right:-10px}.list-expanded>li>.right>.btn-group{top:-7px;right:-11px}.panel{border-width:2px}.panel-heading>.btn-group>.btn{border-radius:0!important}.list>table thead>th{border-top-width:1px!important}.show-more>.btn-block{text-align:left;padding-left:15px}.autocomplete-suggestions{border:1px solid #999;background:#FFF;cursor:default;overflow:auto;-webkit-box-shadow:1px 4px 3px rgba(50,50,50,0.64);-moz-box-shadow:1px 4px 3px rgba(50,50,50,0.64);box-shadow:1px 4px 3px rgba(50,50,50,0.64)}.autocomplete-suggestion{padding:2px 5px;white-space:nowrap;overflow:hidden}.autocomplete-selected{background:#f0f0f0}.autocomplete-suggestions strong{font-weight:normal;color:#39f}.gray-box{background-color:#eee;margin:0 5px 3px 0;padding:3px 3px 3px 5px}.gray-box .preview{overflow:hidden;display:inline-block}.attachment-preview{display:inline-block;margin:3px 6px 3px 0}.field>.attachment-preview{display:block;overflow:hidden}.stick-sub{position:fixed;top:0;margin:44px 0 0;z-index:4}.stick-sub.button-container{background-color:#fefefe;padding:3px 3px 3px 0;width:100%}@media screen and (max-width:768px){.stick-sub{margin-top:0}}@font-face{font-family:Open Sans;src:url('../fonts/open-sans-regular.eot?') format('eot'),url('../fonts/open-sans-regular.woff') format('woff'),url('../fonts/open-sans-regular.ttf') format('truetype'),url('../fonts/open-sans-regular.svg#svgFontName') format('svg')}.badge-circle{width:8px;height:8px;border-radius:4px;display:table-cell;font-size:5px;line-height:8px;text-align:center;background-color:#e8eced}.badge-circle.badge-circle-danger{background-color:#cf605d}.badge-circle.badge-circle-warning{background-color:#f59c0c}.danger.glyphicon{color:#cf605d}.warning.glyphicon{color:#f59c0c}html,body{height:100%}body>.content{height:auto;min-height:100%;padding-bottom:35px}body>footer{clear:both;position:relative;height:26px;margin-top:-28px;background-color:#eee}body>footer>p{padding:6px 15px}body>footer>p.credit{margin:0}body>footer>p,body>footer>p a,body>footer>p a:hover,body>footer>p a:active,body>footer>p a:visited{color:#555}.navbar-collapse{overflow-x:hidden!important}#global-search-panel>.panel>.panel-body{max-height:294px;overflow-y:auto;overflow-x:hidden}#notifications-panel>.panel>.panel-body{max-height:294px;overflow-y:auto;overflow-x:hidden}@media screen and (max-width:768px){.list{width:auto;overflow-y:hidden;overflow-x:scroll}.list.list-expanded{width:auto;overflow-x:hidden}.list>table{min-width:768px}}.modal.in .modal.in{overflow:visible!important;overflow-y:visible!important}.record>.panel:first-child{margin-top:0}.record>.panel{margin-top:-21px}.detail .bottom>.panel-stream:first-child{margin-top:-21px}.stream-post-container,.stream-attachments-container{padding:5px 0}.link-container .list-group-item{padding:6px 10px}.note-editable blockquote{font-size:14px}.legend-container table td{padding:2px 2px;line-height:1em}.textcomplete-item a{cursor:default}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} \ No newline at end of file diff --git a/frontend/client/custom/.data b/frontend/client/custom/.data new file mode 100644 index 0000000000..0519ecba6e --- /dev/null +++ b/frontend/client/custom/.data @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/client/img/favicon.ico b/frontend/client/img/favicon.ico new file mode 100644 index 0000000000..159cf8468f Binary files /dev/null and b/frontend/client/img/favicon.ico differ diff --git a/frontend/client/lib/jquery.textcomplete.js b/frontend/client/lib/jquery.textcomplete.js new file mode 100644 index 0000000000..be60e60976 --- /dev/null +++ b/frontend/client/lib/jquery.textcomplete.js @@ -0,0 +1,1023 @@ +/*! + * jQuery.textcomplete + * + * Repository: https://github.com/yuku-t/jquery-textcomplete + * License: MIT (https://github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE) + * Author: Yuku Takahashi + */ + +if (typeof jQuery === 'undefined') { + throw new Error('jQuery.textcomplete requires jQuery'); +} + ++function ($) { + 'use strict'; + + var warn = function (message) { + if (console.warn) { console.warn(message); } + }; + + $.fn.textcomplete = function (strategies, option) { + var args = Array.prototype.slice.call(arguments); + return this.each(function () { + var $this = $(this); + var completer = $this.data('textComplete'); + if (!completer) { + completer = new $.fn.textcomplete.Completer(this, option || {}); + $this.data('textComplete', completer); + } + if (typeof strategies === 'string') { + args.shift() + completer[strategies].apply(completer, args); + } else { + // For backward compatibility. + // TODO: Remove at v0.4 + $.each(strategies, function (obj) { + $.each(['header', 'footer', 'placement', 'maxCount'], function (name) { + if (obj[name]) { + completer.option[name] = obj[name]; + warn(name + 'as a strategy param is deplicated. Use option.'); + delete obj[name]; + } + }); + }); + completer.register($.fn.textcomplete.Strategy.parse(strategies)); + } + }); + }; + +}(jQuery); + ++function ($) { + 'use strict'; + + // Exclusive execution control utility. + // + // func - The function to be locked. It is executed with a function named + // `free` as the first argument. Once it is called, additional + // execution are ignored until the free is invoked. Then the last + // ignored execution will be replayed immediately. + // + // Examples + // + // var lockedFunc = lock(function (free) { + // setTimeout(function { free(); }, 1000); // It will be free in 1 sec. + // console.log('Hello, world'); + // }); + // lockedFunc(); // => 'Hello, world' + // lockedFunc(); // none + // lockedFunc(); // none + // // 1 sec past then + // // => 'Hello, world' + // lockedFunc(); // => 'Hello, world' + // lockedFunc(); // none + // + // Returns a wrapped function. + var lock = function (func) { + var locked, queuedArgsToReplay; + + return function () { + // Convert arguments into a real array. + var args = Array.prototype.slice.call(arguments); + if (locked) { + // Keep a copy of this argument list to replay later. + // OK to overwrite a previous value because we only replay + // the last one. + queuedArgsToReplay = args; + return; + } + locked = true; + var self = this; + args.unshift(function replayOrFree() { + if (queuedArgsToReplay) { + // Other request(s) arrived while we were locked. + // Now that the lock is becoming available, replay + // the latest such request, then call back here to + // unlock (or replay another request that arrived + // while this one was in flight). + var replayArgs = queuedArgsToReplay; + queuedArgsToReplay = undefined; + replayArgs.unshift(replayOrFree); + func.apply(self, replayArgs); + } else { + locked = false; + } + }); + func.apply(this, args); + }; + }; + + var isString = function (obj) { + return Object.prototype.toString.call(obj) === '[object String]'; + }; + + var uniqueId = 0; + + function Completer(element, option) { + this.$el = $(element); + this.id = 'textcomplete' + uniqueId++; + this.strategies = []; + this.views = []; + this.option = $.extend({}, Completer.DEFAULTS, option); + + if (!this.$el.is('textarea') && !element.isContentEditable) { + throw new Error('textcomplete must be called to a Textarea or a ContentEditable.'); + } + + if (element === document.activeElement) { + // element has already been focused. Initialize view objects immediately. + this.initialize() + } else { + // Initialize view objects lazily. + var self = this; + this.$el.one('focus.' + this.id, function () { self.initialize(); }); + } + } + + Completer.DEFAULTS = { + appendTo: $('body'), + zIndex: '100' + }; + + $.extend(Completer.prototype, { + // Public properties + // ----------------- + + id: null, + option: null, + strategies: null, + adapter: null, + dropdown: null, + $el: null, + + // Public methods + // -------------- + + initialize: function () { + var element = this.$el.get(0); + // Initialize view objects. + this.dropdown = new $.fn.textcomplete.Dropdown(element, this, this.option); + var Adapter; + if (this.option.adapter) { + Adapter = this.option.adapter; + } else { + var viewName = element.isContentEditable ? 'ContentEditable' : 'Textarea'; + Adapter = $.fn.textcomplete[viewName]; + } + this.adapter = new Adapter(element, this, this.option); + }, + + destroy: function () { + this.$el.off('.' + this.id); + this.adapter.destroy(); + this.dropdown.destroy(); + this.$el = this.adapter = this.dropdown = null; + }, + + // Invoke textcomplete. + trigger: function (text, skipUnchangedTerm) { + if (!this.dropdown) { this.initialize(); } + var searchQuery = this._extractSearchQuery(text); + if (searchQuery.length) { + var term = searchQuery[1]; + // Ignore shift-key, ctrl-key and so on. + if (skipUnchangedTerm && this._term === term) { return; } + this._term = term; + this._search.apply(this, searchQuery); + } else { + this._term = null; + this.dropdown.deactivate(); + } + }, + + fire: function (eventName) { + this.$el.trigger(eventName); + return this; + }, + + register: function (strategies) { + Array.prototype.push.apply(this.strategies, strategies); + }, + + // Insert the value into adapter view. It is called when the dropdown is clicked + // or selected. + // + // value - The selected element of the array callbacked from search func. + // strategy - The Strategy object. + select: function (value, strategy) { + this.adapter.select(value, strategy); + this.fire('change').fire('textComplete:select', value, strategy); + this.adapter.focus(); + }, + + // Private properties + // ------------------ + + _clearAtNext: true, + _term: null, + + // Private methods + // --------------- + + // Parse the given text and extract the first matching strategy. + // + // Returns an array including the strategy and the query term if the + // text matches an strategy; otherwise returns an empty array.. + _extractSearchQuery: function (text) { + for (var i = 0; i < this.strategies.length; i++) { + var strategy = this.strategies[i]; + var context = strategy.context(text); + if (context || context === '') { + if (isString(context)) { text = context; } + var match = text.match(strategy.match); + if (match) { return [strategy, match[strategy.index]]; } + } + } + return [] + }, + + // Call the search method of selected strategy.. + _search: lock(function (free, strategy, term) { + var self = this; + strategy.search(term, function (data, stillSearching) { + if (!self.dropdown.shown) { + self.dropdown.activate(); + self.dropdown.setPosition(self.adapter.getCaretPosition()); + } + if (self._clearAtNext) { + // The first callback in the current lock. + self.dropdown.clear(); + self._clearAtNext = false; + } + self.dropdown.render(self._zip(data, strategy)); + if (!stillSearching) { + // The last callback in the current lock. + free(); + self._clearAtNext = true; // Call dropdown.clear at the next time. + } + }); + }), + + // Build a parameter for Dropdown#render. + // + // Examples + // + // this._zip(['a', 'b'], 's'); + // //=> [{ value: 'a', strategy: 's' }, { value: 'b', strategy: 's' }] + _zip: function (data, strategy) { + return $.map(data, function (value) { + return { value: value, strategy: strategy }; + }); + } + }); + + $.fn.textcomplete.Completer = Completer; +}(jQuery); + ++function ($) { + 'use strict'; + + var include = function (zippedData, datum) { + var i, elem; + for (i = 0; i < zippedData.length; i++) { + elem = zippedData[i]; + if (elem.value === datum.value && elem.strategy === datum.strategy) return true; + } + return false; + }; + + var dropdownViews = {}; + $(document).on('click', function (e) { + var id = e.originalEvent.keepTextCompleteDropdown; + $.each(dropdownViews, function (key, view) { + if (key !== id) { view.deactivate(); } + }); + }); + + // Dropdown view + // ============= + + // Construct Dropdown object. + // + // element - Textarea or contenteditable element. + function Dropdown(element, completer, option) { + this.$el = Dropdown.findOrCreateElement(option); + this.completer = completer; + this.id = completer.id + 'dropdown'; + this._data = []; // zipped data. + this.$inputEl = $(element); + + // Override setPosition method. + if (option.listPosition) { this.setPosition = option.listPosition; } + if (option.height) { this.$el.height(option.height); } + var self = this; + $.each(['maxCount', 'placement', 'footer', 'header'], function (_i, name) { + if (option[name] != null) { self[name] = option[name]; } + }); + this._bindEvents(element); + dropdownViews[this.id] = this; + } + + $.extend(Dropdown, { + // Class methods + // ------------- + + findOrCreateElement: function (option) { + var $parent = option.appendTo; + if (!($parent instanceof $)) { $parent = $($parent); } + var $el = $parent.children('.dropdown-menu') + if (!$el.length) { + $el = $('').css({ + display: 'none', + left: 0, + position: 'absolute', + zIndex: option.zIndex + }).appendTo($parent); + } + return $el; + } + }); + + $.extend(Dropdown.prototype, { + // Public properties + // ----------------- + + $el: null, // jQuery object of ul.dropdown-menu element. + $inputEl: null, // jQuery object of target textarea. + completer: null, + footer: null, + header: null, + id: null, + maxCount: 10, + placement: '', + shown: false, + data: [], // Shown zipped data. + + // Public methods + // -------------- + + destroy: function () { + this.$el.off('.' + this.id); + this.$inputEl.off('.' + this.id); + this.clear(); + this.$el = this.$inputEl = this.completer = null; + delete dropdownViews[this.id] + }, + + render: function (zippedData) { + var contentsHtml = this._buildContents(zippedData); + var unzippedData = $.map(this.data, function (d) { return d.value; }); + if (this.data.length) { + this._renderHeader(unzippedData); + this._renderFooter(unzippedData); + if (contentsHtml) { + this._renderContents(contentsHtml); + this._activateIndexedItem(); + } + this._setScroll(); + } else if (this.shown) { + this.deactivate(); + } + }, + + setPosition: function (position) { + this.$el.css(this._applyPlacement(position)); + return this; + }, + + clear: function () { + this.$el.html(''); + this.data = []; + this._index = 0; + this._$header = this._$footer = null; + }, + + activate: function () { + if (!this.shown) { + this.clear(); + this.$el.show(); + this.completer.fire('textComplete:show'); + this.shown = true; + } + return this; + }, + + deactivate: function () { + if (this.shown) { + this.$el.hide(); + this.completer.fire('textComplete:hide'); + this.shown = false; + } + return this; + }, + + isUp: function (e) { + return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P + }, + + isDown: function (e) { + return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N + }, + + isEnter: function (e) { + var modifiers = e.ctrlKey || e.altKey || e.metaKey || e.shiftKey; + return !modifiers && (e.keyCode === 13 || e.keyCode === 9) // ENTER, TAB + }, + + isPageup: function (e) { + return e.keyCode === 33; // PAGEUP + }, + + isPagedown: function (e) { + return e.keyCode === 34; // PAGEDOWN + }, + + // Private properties + // ------------------ + + _data: null, // Currently shown zipped data. + _index: null, + _$header: null, + _$footer: null, + + // Private methods + // --------------- + + _bindEvents: function () { + this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)) + this.$el.on('mouseover.' + this.id, '.textcomplete-item', $.proxy(this._onMouseover, this)); + this.$inputEl.on('keydown.' + this.id, $.proxy(this._onKeydown, this)); + }, + + _onClick: function (e) { + var $el = $(e.target); + e.preventDefault(); + e.originalEvent.keepTextCompleteDropdown = this.id; + if (!$el.hasClass('textcomplete-item')) { + $el = $el.closest('.textcomplete-item'); + } + var datum = this.data[parseInt($el.data('index'), 10)]; + this.completer.select(datum.value, datum.strategy); + var self = this; + // Deactive at next tick to allow other event handlers to know whether + // the dropdown has been shown or not. + setTimeout(function () { self.deactivate(); }, 0); + }, + + // Activate hovered item. + _onMouseover: function (e) { + var $el = $(e.target); + e.preventDefault(); + if (!$el.hasClass('textcomplete-item')) { + $el = $el.closest('.textcomplete-item'); + } + this._index = parseInt($el.data('index'), 10); + this._activateIndexedItem(); + }, + + _onKeydown: function (e) { + if (!this.shown) { return; } + if (this.isUp(e)) { + e.preventDefault(); + this._up(); + } else if (this.isDown(e)) { + e.preventDefault(); + this._down(); + } else if (this.isEnter(e)) { + e.preventDefault(); + this._enter(); + } else if (this.isPageup(e)) { + e.preventDefault(); + this._pageup(); + } else if (this.isPagedown(e)) { + e.preventDefault(); + this._pagedown(); + } + }, + + _up: function () { + if (this._index === 0) { + this._index = this.data.length - 1; + } else { + this._index -= 1; + } + this._activateIndexedItem(); + this._setScroll(); + }, + + _down: function () { + if (this._index === this.data.length - 1) { + this._index = 0; + } else { + this._index += 1; + } + this._activateIndexedItem(); + this._setScroll(); + }, + + _enter: function () { + var datum = this.data[parseInt(this._getActiveElement().data('index'), 10)]; + this.completer.select(datum.value, datum.strategy); + this._setScroll(); + }, + + _pageup: function () { + var target = 0; + var threshold = this._getActiveElement().position().top - this.$el.innerHeight(); + this.$el.children().each(function (i) { + if ($(this).position().top + $(this).outerHeight() > threshold) { + target = i; + return false; + } + }); + this._index = target; + this._activateIndexedItem(); + this._setScroll(); + }, + + _pagedown: function () { + var target = this.data.length - 1; + var threshold = this._getActiveElement().position().top + this.$el.innerHeight(); + this.$el.children().each(function (i) { + if ($(this).position().top > threshold) { + target = i; + return false + } + }); + this._index = target; + this._activateIndexedItem(); + this._setScroll(); + }, + + _activateIndexedItem: function () { + this.$el.find('.textcomplete-item.active').removeClass('active'); + this._getActiveElement().addClass('active'); + }, + + _getActiveElement: function () { + return this.$el.children('.textcomplete-item:nth(' + this._index + ')'); + }, + + _setScroll: function () { + var $activeEl = this._getActiveElement(); + var itemTop = $activeEl.position().top; + var itemHeight = $activeEl.outerHeight(); + var visibleHeight = this.$el.innerHeight(); + var visibleTop = this.$el.scrollTop(); + if (this._index === 0 || this._index == this.data.length - 1 || itemTop < 0) { + this.$el.scrollTop(itemTop + visibleTop); + } else if (itemTop + itemHeight > visibleHeight) { + this.$el.scrollTop(itemTop + itemHeight + visibleTop - visibleHeight); + } + }, + + _buildContents: function (zippedData) { + var datum, i, index; + var html = ''; + for (i = 0; i < zippedData.length; i++) { + if (this.data.length === this.maxCount) break; + datum = zippedData[i]; + if (include(this.data, datum)) { continue; } + index = this.data.length; + this.data.push(datum); + html += '
  • '; + html += datum.strategy.template(datum.value); + html += '
  • '; + } + return html; + }, + + _renderHeader: function (unzippedData) { + if (this.header) { + if (!this._$header) { + this._$header = $('
  • ').prependTo(this.$el); + } + var html = $.isFunction(this.header) ? this.header(unzippedData) : this.header; + this._$header.html(html); + } + }, + + _renderFooter: function (unzippedData) { + if (this.footer) { + if (!this._$footer) { + this._$footer = $('').appendTo(this.$el); + } + var html = $.isFunction(this.footer) ? this.footer(unzippedData) : this.footer; + this._$footer.html(html); + } + }, + + _renderContents: function (html) { + if (this._$footer) { + this._$footer.before(html); + } else { + this.$el.append(html); + } + }, + + _applyPlacement: function (position) { + // If the 'placement' option set to 'top', move the position above the element. + if (this.placement.indexOf('top') !== -1) { + // Overwrite the position object to set the 'bottom' property instead of the top. + position = { + top: 'auto', + bottom: this.$el.parent().height() - position.top + position.lineHeight, + left: position.left + }; + } else { + position.bottom = 'auto'; + delete position.lineHeight; + } + if (this.placement.indexOf('absleft') !== -1) { + position.left = 0; + } else if (this.placement.indexOf('absright') !== -1) { + position.right = 0; + position.left = 'auto'; + } + return position; + } + }); + + $.fn.textcomplete.Dropdown = Dropdown; +}(jQuery); + ++function ($) { + 'use strict'; + + // Memoize a search function. + var memoize = function (func) { + var memo = {}; + return function (term, callback) { + if (memo[term]) { + callback(memo[term]); + } else { + func.call(this, term, function (data) { + memo[term] = (memo[term] || []).concat(data); + callback.apply(null, arguments); + }); + } + }; + }; + + function Strategy(options) { + $.extend(this, options); + if (this.cache) { this.search = memoize(this.search); } + } + + Strategy.parse = function (optionsArray) { + return $.map(optionsArray, function (options) { + return new Strategy(options); + }); + }; + + $.extend(Strategy.prototype, { + // Public properties + // ----------------- + + // Required + match: null, + replace: null, + search: null, + + // Optional + cache: false, + context: function () { return true; }, + index: 2, + template: function (obj) { return obj; } + }); + + $.fn.textcomplete.Strategy = Strategy; + +}(jQuery); + ++function ($) { + 'use strict'; + + var now = Date.now || function () { return new Date().getTime(); }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // `wait` msec. + // + // This utility function was originally implemented at Underscore.js. + var debounce = function (func, wait) { + var timeout, args, context, timestamp, result; + var later = function () { + var last = now() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + result = func.apply(context, args); + context = args = null; + } + }; + + return function () { + context = this; + args = arguments; + timestamp = now(); + if (!timeout) { + timeout = setTimeout(later, wait); + } + return result; + }; + }; + + function Adapter () {} + + $.extend(Adapter.prototype, { + // Public properties + // ----------------- + + id: null, // Identity. + completer: null, // Completer object which creates it. + el: null, // Textarea element. + $el: null, // jQuery object of the textarea. + option: null, + + // Public methods + // -------------- + + initialize: function (element, completer, option) { + this.el = element; + this.$el = $(element); + this.id = completer.id + this.constructor.name; + this.completer = completer; + this.option = option; + + if (this.option.debounce) { + this._onKeyup = debounce(this._onKeyup, this.option.debounce); + } + + this._bindEvents(); + }, + + destroy: function () { + this.$el.off('.' + this.id); // Remove all event handlers. + this.$el = this.el = this.completer = null; + }, + + // Update the element with the given value and strategy. + // + // value - The selected object. It is one of the item of the array + // which was callbacked from the search function. + // strategy - The Strategy associated with the selected value. + select: function (/* value, strategy */) { + throw new Error('Not implemented'); + }, + + // Returns the caret's relative coordinates from body's left top corner. + // + // FIXME: Calculate the left top corner of `this.option.appendTo` element. + getCaretPosition: function () { + var position = this._getCaretRelativePosition(); + var offset = this.$el.offset(); + position.top += offset.top; + position.left += offset.left; + return position; + }, + + // Focus on the element. + focus: function () { + this.$el.focus(); + }, + + // Private methods + // --------------- + + _bindEvents: function () { + this.$el.on('keyup.' + this.id, $.proxy(this._onKeyup, this)); + }, + + _onKeyup: function (e) { + if (this._skipSearch(e)) { return; } + this.completer.trigger(this._getTextFromHeadToCaret(), true); + }, + + // Suppress searching if it returns true. + _skipSearch: function (clickEvent) { + switch (clickEvent.keyCode) { + case 40: // DOWN + case 38: // UP + return true; + } + if (clickEvent.ctrlKey) switch (clickEvent.keyCode) { + case 78: // Ctrl-N + case 80: // Ctrl-P + return true; + } + } + }); + + $.fn.textcomplete.Adapter = Adapter; +}(jQuery); + ++function ($) { + 'use strict'; + + // Textarea adapter + // ================ + // + // Managing a textarea. It doesn't know a Dropdown. + function Textarea(element, completer, option) { + this.initialize(element, completer, option); + } + + Textarea.DIV_PROPERTIES = { + left: -9999, + position: 'absolute', + top: 0, + whiteSpace: 'pre-wrap' + } + + Textarea.COPY_PROPERTIES = [ + 'border-width', 'font-family', 'font-size', 'font-style', 'font-variant', + 'font-weight', 'height', 'letter-spacing', 'word-spacing', 'line-height', + 'text-decoration', 'text-align', 'width', 'padding-top', 'padding-right', + 'padding-bottom', 'padding-left', 'margin-top', 'margin-right', + 'margin-bottom', 'margin-left', 'border-style', 'box-sizing', 'tab-size' + ]; + + Textarea.prototype = new $.fn.textcomplete.Adapter(); + + $.extend(Textarea.prototype, { + // Public methods + // -------------- + + // Update the textarea with the given value and strategy. + select: function (value, strategy) { + var pre = this._getTextFromHeadToCaret(); + var selectionEnd = this.el.selectionEnd; + if (typeof selectionEnd === 'number') { + var post = this.el.value.substring(selectionEnd); + var newSubstr = strategy.replace(value); + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.selectionStart = this.el.selectionEnd = pre.length; + } else if (document.selection) { // IE + + } + }, + + // Private methods + // --------------- + + // Returns the caret's relative coordinates from textarea's left top corner. + // + // Browser native API does not provide the way to know the position of + // caret in pixels, so that here we use a kind of hack to accomplish + // the aim. First of all it puts a dummy div element and completely copies + // the textarea's style to the element, then it inserts the text and a + // span element into the textarea. + // Consequently, the span element's position is the thing what we want. + _getCaretRelativePosition: function () { + var dummyDiv = $('
    ').css(this._copyCss()) + .text(this._getTextFromHeadToCaret()); + var span = $('').text('.').appendTo(dummyDiv); + this.$el.before(dummyDiv); + var position = span.position(); + position.top += span.height() - this.$el.scrollTop(); + position.lineHeight = span.height(); + dummyDiv.remove(); + return position; + }, + + _copyCss: function () { + return $.extend({ + // Set 'scroll' if a scrollbar is being shown; otherwise 'auto'. + overflow: this.el.scrollHeight > this.el.offsetHeight ? 'scroll' : 'auto' + }, Textarea.DIV_PROPERTIES, this._getStyles()); + }, + + _getStyles: (function ($) { + var color = $('
    ').css(['color']).color; + if (typeof color !== 'undefined') { + return function () { + return this.$el.css(Textarea.COPY_PROPERTIES); + }; + } else { // jQuery < 1.8 + return function () { + var $el = this.$el; + var styles = {}; + $.each(Textarea.COPY_PROPERTIES, function (i, property) { + styles[property] = $el.css(property); + }); + return styles; + }; + } + })($), + + _getTextFromHeadToCaret: function () { + var selectionEnd = this.el.selectionEnd; + if (typeof selectionEnd === 'number') { + return this.el.value.substring(0, selectionEnd); + } else if (document.selection) { // IE + var range = this.el.createTextRange(); + range.moveStart('character', 0); + range.moveEnd('textedit'); + return range.text; + } + } + }); + + $.fn.textcomplete.Textarea = Textarea; +}(jQuery); + +// NOTE: TextComplete plugin has contenteditable support but it does not work +// fine especially on old IEs. +// Any pull requests are REALLY welcome. + ++function ($) { + 'use strict'; + + // ContentEditable adapter + // ======================= + // + // Adapter for contenteditable elements. + function ContentEditable (element, completer, option) { + this.initialize(element, completer, option); + } + + ContentEditable.prototype = new $.fn.textcomplete.Adapter(); + + $.extend(ContentEditable.prototype, { + // Public methods + // -------------- + + // Update the content with the given value and strategy. + // When an dropdown item is selected, it is executed. + select: function (value, strategy) { + var pre = this._getTextFromHeadToCaret(); + var sel = window.getSelection() + var range = sel.getRangeAt(0); + var selection = range.cloneRange(); + selection.selectNodeContents(range.startContainer); + var content = selection.toString(); + var post = content.substring(range.startOffset); + var newSubstr = strategy.replace(value); + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + range.selectNodeContents(range.startContainer); + range.deleteContents(); + var node = document.createTextNode(pre + post); + range.insertNode(node); + range.setStart(node, pre.length); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + }, + + // Private methods + // --------------- + + // Returns the caret's relative position from the contenteditable's + // left top corner. + // + // Examples + // + // this._getCaretRelativePosition() + // //=> { top: 18, left: 200, lineHeight: 16 } + // + // Dropdown's position will be decided using the result. + _getCaretRelativePosition: function () { + var range = window.getSelection().getRangeAt(0).cloneRange(); + var node = document.createElement('span'); + range.insertNode(node); + range.selectNodeContents(node); + range.deleteContents(); + var $node = $(node); + var position = $node.offset(); + position.left -= this.$el.offset().left; + position.top += $node.height() - this.$el.offset().top; + position.lineHeight = $node.height(); + var dir = this.$el.attr('dir') || this.$el.css('direction'); + if (dir === 'rtl') { position.left -= this.listView.$el.width(); } + return position; + }, + + // Returns the string between the first character and the caret. + // Completer will be triggered with the result for start autocompleting. + // + // Example + // + // // Suppose the html is 'hello wor|ld' and | is the caret. + // this._getTextFromHeadToCaret() + // // => ' wor' // not 'hello wor' + _getTextFromHeadToCaret: function () { + var range = window.getSelection().getRangeAt(0); + var selection = range.cloneRange(); + selection.selectNodeContents(range.startContainer); + return selection.toString().substring(0, range.startOffset); + } + }); + + $.fn.textcomplete.ContentEditable = ContentEditable; +}(jQuery); diff --git a/frontend/client/modules/crm/res/templates/task/fields/date-end/detail.tpl b/frontend/client/modules/crm/res/templates/task/fields/date-end/detail.tpl new file mode 100644 index 0000000000..f2d80efd07 --- /dev/null +++ b/frontend/client/modules/crm/res/templates/task/fields/date-end/detail.tpl @@ -0,0 +1,7 @@ +{{#if isOverdue}} + +{{/if}} +{{value}} +{{#if isOverdue}} + +{{/if}} diff --git a/frontend/client/modules/crm/src/views/dashlets/abstract/chart.js b/frontend/client/modules/crm/src/views/dashlets/abstract/chart.js index bf901e499d..24349963aa 100644 --- a/frontend/client/modules/crm/src/views/dashlets/abstract/chart.js +++ b/frontend/client/modules/crm/src/views/dashlets/abstract/chart.js @@ -29,7 +29,9 @@ Espo.define('Crm:Views.Dashlets.Abstract.Chart', ['Views.Dashlets.Abstract.Base' thousandSeparator: ',', - colors: ['#6FA8D6', '#4E6CAD', '#EDC555', '#ED8F42', '#5ABD37', '#DE6666', '#9666DE', '#ABC478'], + colors: ['#6FA8D6', '#4E6CAD', '#EDC555', '#ED8F42', '#DE6666', '#7CC4A4', '#8A7CC2', '#D4729B'], + + successColor: '#5ABD37', init: function () { Dep.prototype.init.call(this); diff --git a/frontend/client/modules/crm/src/views/dashlets/opportunities-by-stage.js b/frontend/client/modules/crm/src/views/dashlets/opportunities-by-stage.js index d5b997ac81..ce9139c119 100644 --- a/frontend/client/modules/crm/src/views/dashlets/opportunities-by-stage.js +++ b/frontend/client/modules/crm/src/views/dashlets/opportunities-by-stage.js @@ -64,10 +64,14 @@ Espo.define('Crm:Views.Dashlets.OpportunitiesByStage', 'Crm:Views.Dashlets.Abstr var data = []; var i = 0; d.forEach(function (item) { - data.push({ + var o = { data: [[item.value, d.length - i]], label: this.getLanguage().translateOption(item.stage, 'stage', 'Opportunity'), - }); + } + if (item.stage == 'Closed Won') { + o.color = this.successColor; + } + data.push(o); this.stageList.push(this.getLanguage().translateOption(item.stage, 'stage', 'Opportunity')); i++; }, this); @@ -103,8 +107,11 @@ Espo.define('Crm:Views.Dashlets.OpportunitiesByStage', 'Crm:Views.Dashlets.Abstr }, xaxis: { min: 0, - tickFormatter: function (value) { - return self.formatNumber(value) + ' ' + self.currency; + tickFormatter: function (value) { + if (value != 0) { + return self.formatNumber(value) + ' ' + self.currency; + } + return ''; }, }, mouse: { diff --git a/frontend/client/modules/crm/src/views/dashlets/sales-by-month.js b/frontend/client/modules/crm/src/views/dashlets/sales-by-month.js index 0eecb7685d..97d5d5a169 100644 --- a/frontend/client/modules/crm/src/views/dashlets/sales-by-month.js +++ b/frontend/client/modules/crm/src/views/dashlets/sales-by-month.js @@ -70,7 +70,7 @@ Espo.define('Crm:Views.Dashlets.SalesByMonth', 'Crm:Views.Dashlets.Abstract.Char values.forEach(function (value, i) { data.push({ data: [[i, value]], - color: (value >= mid) ? this.colorGood : this.colorBad + color: (value >= mid) ? this.successColor : this.colorBad }); }, this); @@ -81,8 +81,7 @@ Espo.define('Crm:Views.Dashlets.SalesByMonth', 'Crm:Views.Dashlets.Abstract.Char this.currency = this.getConfig().get('defaultCurrency'); this.currencySymbol = ''; - this.colorGood = '#5ABD37'; - this.colorBad = '#5ABD37'; + this.colorBad = this.successColor; }, drow: function () { diff --git a/frontend/client/modules/crm/src/views/dashlets/sales-pipeline.js b/frontend/client/modules/crm/src/views/dashlets/sales-pipeline.js index 5a679cbf2a..f5eae726aa 100644 --- a/frontend/client/modules/crm/src/views/dashlets/sales-pipeline.js +++ b/frontend/client/modules/crm/src/views/dashlets/sales-pipeline.js @@ -125,10 +125,12 @@ Espo.define('Crm:Views.Dashlets.SalesPipeline', 'Crm:Views.Dashlets.Abstract.Cha var item = data[i]; var value = item.value; var nextValue = ((i + 1) < data.length) ? data[i + 1].value : value; - this.chartData.push({ + var o = { data: [[i, value], [i + 1, nextValue]], label: item.stage - }); + }; + + this.chartData.push(o); } this.maxY = 1000; @@ -139,8 +141,21 @@ Espo.define('Crm:Views.Dashlets.SalesPipeline', 'Crm:Views.Dashlets.Abstract.Cha drow: function () { var self = this; + + var colors = Espo.Utils.clone(this.colors); + + this.chartData.forEach(function (item, i) { + if (i + 1 > colors.length) { + colors.push('#164'); + } + if (this.chartData.length == i + 1) { + colors[i] = this.successColor; + } + }, this); + + this.flotr.draw(this.$container.get(0), this.chartData, { - colors: this.colors, + colors: colors, shadowSize: false, lines: { show: true, @@ -166,7 +181,11 @@ Espo.define('Crm:Views.Dashlets.SalesPipeline', 'Crm:Views.Dashlets.Abstract.Cha mouse: { track: true, relative: true, + position: 'ne', trackFormatter: function (obj) { + if (obj.x >= self.chartData.length) { + return null; + } return self.formatNumber(obj.y) + ' ' + self.currency; }, }, diff --git a/frontend/client/modules/crm/src/views/dashlets/tasks.js b/frontend/client/modules/crm/src/views/dashlets/tasks.js index 345ca49b1a..abc03443af 100644 --- a/frontend/client/modules/crm/src/views/dashlets/tasks.js +++ b/frontend/client/modules/crm/src/views/dashlets/tasks.js @@ -57,10 +57,7 @@ Espo.define('Crm:Views.Dashlets.Tasks', 'Views.Dashlets.Abstract.RecordList', fu { name: 'name', link: true, - }, - { - name: 'isOverdue', - }, + } ], [ { diff --git a/frontend/client/modules/crm/src/views/document/fields/file-short.js b/frontend/client/modules/crm/src/views/document/fields/file-short.js new file mode 100644 index 0000000000..d497f67193 --- /dev/null +++ b/frontend/client/modules/crm/src/views/document/fields/file-short.js @@ -0,0 +1,42 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Crm:Views.Document.Fields.FileShort', 'Views.Fields.File', function (Dep) { + + return Dep.extend({ + + getValueForDisplay: function () { + if (this.mode == 'detail' || this.mode == 'list') { + var name = this.model.get(this.nameName); + var type = this.model.get(this.typeName) || this.defaultType; + var id = this.model.get(this.idName); + + if (!id) { + return false; + } + + return ''; + } + }, + + }); + +}); diff --git a/frontend/client/modules/crm/src/views/document/fields/name.js b/frontend/client/modules/crm/src/views/document/fields/name.js new file mode 100644 index 0000000000..23a33e9a5a --- /dev/null +++ b/frontend/client/modules/crm/src/views/document/fields/name.js @@ -0,0 +1,37 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Crm:Views.Document.Fields.Name', 'Views.Fields.Varchar', function (Dep) { + + return Dep.extend({ + + setup: function () { + Dep.prototype.setup.call(this); + if (this.model.isNew()) { + this.listenTo(this.model, 'change:fileName', function () { + this.model.set('name', this.model.get('fileName')); + }, this); + } + }, + + }); + +}); diff --git a/frontend/client/modules/crm/src/views/task/fields/date-end.js b/frontend/client/modules/crm/src/views/task/fields/date-end.js new file mode 100644 index 0000000000..5345478778 --- /dev/null +++ b/frontend/client/modules/crm/src/views/task/fields/date-end.js @@ -0,0 +1,51 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Crm:Views.Task.Fields.DateEnd', 'Views.Fields.Datetime', function (Dep) { + + return Dep.extend({ + + detailTemplate: 'crm:task.fields.date-end.detail', + + listTemplate: 'crm:task.fields.date-end.detail', + + data: function () { + var data = Dep.prototype.data.call(this); + + if (!~['Completed', 'Canceled'].indexOf(this.model.get('status'))) { + if (this.mode == 'list' || this.mode == 'detail') { + var value = this.model.get(this.name); + if (value) { + var d = this.getDateTime().toMoment(value); + var now = moment().tz(this.getDateTime().timeZone || 'UTC'); + if (d.unix() < now.unix()) { + data.isOverdue = true; + } + } + } + } + + return data; + }, + + }); +}); + diff --git a/frontend/client/res/templates/admin/extensions/done.tpl b/frontend/client/res/templates/admin/extensions/done.tpl new file mode 100644 index 0000000000..5bebd05fc6 --- /dev/null +++ b/frontend/client/res/templates/admin/extensions/done.tpl @@ -0,0 +1,5 @@ + +

    + {{{text}}} +

    + diff --git a/frontend/client/res/templates/admin/extensions/index.tpl b/frontend/client/res/templates/admin/extensions/index.tpl new file mode 100644 index 0000000000..fd3f20f1e0 --- /dev/null +++ b/frontend/client/res/templates/admin/extensions/index.tpl @@ -0,0 +1,21 @@ + + +
    +
    +

    {{translate 'selectExtensionPackage' category='messages' scope='Admin'}}

    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + + + +
    {{{list}}}
    + diff --git a/frontend/client/res/templates/admin/extensions/ready.tpl b/frontend/client/res/templates/admin/extensions/ready.tpl new file mode 100644 index 0000000000..030262da6d --- /dev/null +++ b/frontend/client/res/templates/admin/extensions/ready.tpl @@ -0,0 +1,5 @@ + +

    + {{{text}}} +

    + diff --git a/frontend/client/res/templates/admin/upgrade/index.tpl b/frontend/client/res/templates/admin/upgrade/index.tpl index 88c8269f7e..1792c4ecb5 100644 --- a/frontend/client/res/templates/admin/upgrade/index.tpl +++ b/frontend/client/res/templates/admin/upgrade/index.tpl @@ -13,6 +13,10 @@

    {{translate 'selectUpgradePackage' scope='Admin' category="messages"}}

    + +

    + {{{translate 'downloadUpgradePackage' scope='Admin' category="messages"}}} +

    diff --git a/frontend/client/res/templates/dashlet.tpl b/frontend/client/res/templates/dashlet.tpl index 75fa8c66a6..7c9f1e42e9 100644 --- a/frontend/client/res/templates/dashlet.tpl +++ b/frontend/client/res/templates/dashlet.tpl @@ -1,4 +1,4 @@ -
    +
    - + + {{translate 'Connected' scope='ExternalAccount'}}
    diff --git a/frontend/client/res/templates/import/step-2.tpl b/frontend/client/res/templates/import/step-2.tpl index 9e8c12f840..71e05e7d77 100644 --- a/frontend/client/res/templates/import/step-2.tpl +++ b/frontend/client/res/templates/import/step-2.tpl @@ -14,7 +14,7 @@
    - - + +
    diff --git a/frontend/client/res/templates/import/step-3.tpl b/frontend/client/res/templates/import/step-3.tpl index 45ee31a39e..68e261e7d4 100644 --- a/frontend/client/res/templates/import/step-3.tpl +++ b/frontend/client/res/templates/import/step-3.tpl @@ -9,6 +9,8 @@
    + {{translate 'Return to Import' scope='Import'}} + {{translate 'Show records' scope='Import'}} {{#if result.countCreated}} {{/if}} @@ -16,5 +18,3 @@
    - - diff --git a/frontend/client/src/controllers/admin.js b/frontend/client/src/controllers/admin.js index 28471db75c..960443dd6e 100644 --- a/frontend/client/src/controllers/admin.js +++ b/frontend/client/src/controllers/admin.js @@ -159,6 +159,10 @@ Espo.define('Controllers.Admin', 'Controller', function (Dep) { this.main('Admin.Integrations.Index', {integration: integration}); }, + extensions: function (options) { + this.main('Admin.Extensions.Index'); + }, + rebuild: function (options) { var master = this.get('master'); Espo.Ui.notify(master.translate('Please wait...')); diff --git a/frontend/client/src/search-manager.js b/frontend/client/src/search-manager.js index 301377bf6e..ea1c4608d7 100644 --- a/frontend/client/src/search-manager.js +++ b/frontend/client/src/search-manager.js @@ -143,7 +143,7 @@ var where = { field: field }; - if (!value && !~['today', 'past', 'future'].indexOf(type)) { + if (!value && ~['on', 'before', 'after'].indexOf(type)) { return null; } @@ -188,7 +188,9 @@ var to = moment(value[1], this.dateTime.internalDateFormat, this.timeZone).utc().format(this.dateTime.internalDateTimeFormat); where.value = [from, to]; } - break; + break; + default: + where.type = type; } return where; diff --git a/frontend/client/src/view-helper.js b/frontend/client/src/view-helper.js index f11e71faf4..8571cfadd3 100644 --- a/frontend/client/src/view-helper.js +++ b/frontend/client/src/view-helper.js @@ -22,9 +22,24 @@ (function (Espo, _, Handlebars) { Espo.ViewHelper = function (options) { - this.urlRegex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; - + this.urlRegex = /(^|[^\[])(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; this._registerHandlebarsHelpers(); + + this.mdSearch = [ + /\("?(.*?)"?\)\[(.*?)\]/g, + /\&\#x60;(([\s\S]*?)\&\#x60;)/, + /(\*\*|__)(.*?)\1/, + /(\*|_)(.*?)\1/, + /\~\~(.*?)\~\~/ + ]; + this.mdReplace = [ + '$1', + '$2', + '$2', + '$2', + '$1', + ]; + } _.extend(Espo.ViewHelper.prototype, { @@ -135,10 +150,15 @@ Handlebars.registerHelper('complexText', function (text) { text = Handlebars.Utils.escapeExpression(text || ''); - text = text.replace(/(\r\n|\n|\r)/gm, '
    '); - text = text.replace(self.urlRegex, function (url) { - return '' + url + ''; + + text = text.replace(self.urlRegex, '$1($2)[$2]'); + + self.mdSearch.forEach(function (re, i) { + text = text.replace(re, self.mdReplace[i]); }); + + text = text.replace(/(\r\n|\n|\r)/gm, '
    '); + text = text.replace('[#see-more-text]', ' ' + self.language.translate('See more')) + ''; return new Handlebars.SafeString(text); }); diff --git a/frontend/client/src/views/admin/extensions/done.js b/frontend/client/src/views/admin/extensions/done.js new file mode 100644 index 0000000000..8ecbca0822 --- /dev/null +++ b/frontend/client/src/views/admin/extensions/done.js @@ -0,0 +1,60 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Views.Admin.Extensions.Done', 'Views.Modal', function (Dep) { + + return Dep.extend({ + + cssName: 'done-modal', + + header: false, + + template: 'admin.extensions.done', + + createButton: true, + + data: function () { + return { + version: this.options.version, + name: this.options.name, + text: this.translate('extensionInstalled', 'messages', 'Admin').replace('{version}', this.options.version) + .replace('{name}', this.options.name) + }; + }, + + setup: function () { + this.buttons = [ + { + name: 'close', + label: 'Close', + onClick: function (dialog) { + dialog.close(); + }.bind(this) + } + ]; + + this.header = this.getLanguage().translate('Installed successfully', 'labels', 'Admin'); + + }, + + }); +}); + diff --git a/frontend/client/src/views/admin/extensions/index.js b/frontend/client/src/views/admin/extensions/index.js new file mode 100644 index 0000000000..de7c8b3cfe --- /dev/null +++ b/frontend/client/src/views/admin/extensions/index.js @@ -0,0 +1,203 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Views.Admin.Extensions.Index', 'View', function (Dep) { + + return Dep.extend({ + + template: "admin.extensions.index", + + packageContents: null, + + events: { + 'change input[name="package"]': function (e) { + this.$el.find('button[data-action="upload"]').addClass('disabled'); + this.$el.find('.message-container').html(''); + var files = e.currentTarget.files; + if (files.length) { + this.selectFile(files[0]); + } + }, + 'click button[data-action="upload"]': function () { + this.upload(); + }, + 'click [data-action="install"]': function (e) { + var id = $(e.currentTarget).data('id'); + + var name = this.collection.get(id).get('name'); + var version = this.collection.get(id).get('version'); + + this.run(id, name, version); + + }, + 'click [data-action="uninstall"]': function (e) { + var id = $(e.currentTarget).data('id'); + + var name = this.collection.get(id).get('name'); + + var self = this; + if (confirm(this.translate('uninstallConfirmation', 'messages', 'Admin'))) { + Espo.Ui.notify(this.translate('Uninstalling...', 'labels', 'Admin')); + + $.ajax({ + url: 'Extension/action/uninstall', + type: 'POST', + data: JSON.stringify({ + id: id + }), + error: function (xhr) { + var msg = xhr.getResponseHeader('X-Status-Reason'); + this.showErrorNotification(this.translate('Error') + ': ' + msg); + }.bind(this) + }).done(function () { + this.listenToOnce(this.collection, 'sync', function () { + Espo.Ui.success(this.translate('uninstalled', 'messages', 'Extension').replace('{name}', name)); + }, this); + this.collection.fetch(); + + }.bind(this)); + } + } + }, + + setup: function () { + this.getCollectionFactory().create('Extension', function (collection) { + this.collection = collection; + + collection.maxSize = this.getConfig().get('recordsPerPage'); + + this.wait(true); + this.listenToOnce(collection, 'sync', function () { + this.createView('list', 'Extension.Record.List', { + collection: collection, + el: this.options.el + ' > .list-container', + }); + if (collection.length == 0) { + this.once('after:render', function () { + this.$el.find('.list-container').addClass('hidden'); + }.bind(this)); + } + this.wait(false); + }); + + collection.fetch(); + + }, this); + }, + + selectFile: function (file) { + var fileReader = new FileReader(); + fileReader.onload = function (e) { + this.packageContents = e.target.result; + this.$el.find('button[data-action="upload"]').removeClass('disabled'); + }.bind(this); + fileReader.readAsDataURL(file); + }, + + showError: function (msg) { + msg = this.translate(msg, 'errors', 'Admin'); + this.$el.find('.message-container').html(msg); + }, + + showErrorNotification: function (msg) { + if (!msg) { + this.$el.find('.notify-text').addClass('hidden'); + } else { + msg = this.translate(msg, 'errors', 'Admin'); + this.$el.find('.notify-text').html(msg); + this.$el.find('.notify-text').removeClass('hidden'); + } + }, + + upload: function () { + this.$el.find('button[data-action="upload"]').addClass('disabled'); + this.notify('Uploading...'); + $.ajax({ + url: 'Extension/action/upload', + type: 'POST', + contentType: 'application/zip', + timeout: 0, + data: this.packageContents, + error: function (xhr, t, e) { + this.showError(xhr.getResponseHeader('X-Status-Reason')); + this.notify(false); + }.bind(this) + }).done(function (data) { + if (!data.id) { + this.showError(this.translate('Error occured')); + return; + } + this.notify(false); + this.createView('popup', 'Admin.Extensions.Ready', { + upgradeData: data + }, function (view) { + view.render(); + this.$el.find('button[data-action="upload"]').removeClass('disabled'); + + view.once('run', function () { + view.close(); + this.$el.find('.panel.upload').addClass('hidden'); + this.run(data.id, data.version, data.name); + }, this); + }.bind(this)); + }.bind(this)).error; + }, + + run: function (id, version, name) { + var msg = this.translate('Installing...', 'labels', 'Admin'); + this.notify('Please wait...'); + + this.showError(false); + this.showErrorNotification(false); + + $.ajax({ + url: 'Extension/action/install', + type: 'POST', + data: JSON.stringify({ + id: id + }), + error: function (xhr) { + this.$el.find('.panel.upload').removeClass('hidden'); + var msg = xhr.getResponseHeader('X-Status-Reason'); + this.showErrorNotification(this.translate('Error') + ': ' + msg); + }.bind(this) + }).done(function () { + var cache = this.getCache(); + if (cache) { + cache.clear(); + } + this.createView('popup', 'Admin.Extensions.Done', { + version: version, + name: name + }, function (view) { + this.collection.fetch(); + this.$el.find('.list-container').removeClass('hidden'); + this.$el.find('.panel.upload').removeClass('hidden'); + this.notify(false); + view.render(); + }.bind(this)); + }.bind(this)); + }, + + }); + +}); + diff --git a/frontend/client/src/views/admin/extensions/ready.js b/frontend/client/src/views/admin/extensions/ready.js new file mode 100644 index 0000000000..b951f08962 --- /dev/null +++ b/frontend/client/src/views/admin/extensions/ready.js @@ -0,0 +1,74 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Views.Admin.Extensions.Ready', 'Views.Modal', function (Dep) { + + return Dep.extend({ + + cssName: 'ready-modal', + + header: false, + + template: 'admin.extensions.ready', + + createButton: true, + + data: function () { + return { + version: this.upgradeData.version, + text: this.translate('installExtension', 'messages', 'Admin').replace('{version}', this.upgradeData.version) + .replace('{name}', this.upgradeData.name) + }; + }, + + setup: function () { + + this.buttons = [ + { + name: 'run', + text: this.translate('Install', 'labels', 'Admin'), + style: 'danger', + onClick: function (dialog) { + this.run(); + }.bind(this) + }, + { + name: 'cancel', + label: 'Cancel', + onClick: function (dialog) { + dialog.close(); + } + } + ]; + + this.upgradeData = this.options.upgradeData; + + this.header = this.getLanguage().translate('Ready for installation', 'labels', 'Admin'); + + }, + + run: function () { + this.trigger('run'); + this.remove(); + } + }); +}); + diff --git a/frontend/client/src/views/admin/integrations/oauth2.js b/frontend/client/src/views/admin/integrations/oauth2.js index 623604980e..87377f5782 100644 --- a/frontend/client/src/views/admin/integrations/oauth2.js +++ b/frontend/client/src/views/admin/integrations/oauth2.js @@ -28,6 +28,7 @@ Espo.define('Views.Admin.Integrations.OAuth2', 'Views.Admin.Integrations.Edit', data: function () { return _.extend({ + // TODO fetch from server redirectUri: this.getConfig().get('siteUrl') + '/oauthcallback' }, Dep.prototype.data.call(this)); }, diff --git a/frontend/client/src/views/admin/upgrade/index.js b/frontend/client/src/views/admin/upgrade/index.js index 5cc5d0d860..d3c2010e99 100644 --- a/frontend/client/src/views/admin/upgrade/index.js +++ b/frontend/client/src/views/admin/upgrade/index.js @@ -115,6 +115,7 @@ Espo.define('Views.Admin.Upgrade.Index', 'View', function (Dep) { id: id }), error: function (xhr) { + this.$el.find('.panel.upload').removeClass('hidden'); var msg = xhr.getResponseHeader('X-Status-Reason'); this.textNotification(this.translate('Error') + ': ' + msg); }.bind(this) diff --git a/frontend/client/src/views/admin/upgrade/ready.js b/frontend/client/src/views/admin/upgrade/ready.js index 5db7852806..bd2c2dade0 100644 --- a/frontend/client/src/views/admin/upgrade/ready.js +++ b/frontend/client/src/views/admin/upgrade/ready.js @@ -43,7 +43,7 @@ Espo.define('Views.Admin.Upgrade.Ready', 'Views.Modal', function (Dep) { this.buttons = [ { name: 'run', - label: 'Run Upgrade', + label: this.translate('Run Upgrade', 'labels', 'Admin'), style: 'danger', onClick: function (dialog) { this.run(); diff --git a/frontend/client/src/views/dashlet.js b/frontend/client/src/views/dashlet.js index 46e5c02f9b..8f2337aead 100644 --- a/frontend/client/src/views/dashlet.js +++ b/frontend/client/src/views/dashlet.js @@ -36,6 +36,7 @@ Espo.define('Views.Dashlet', 'View', function (Dep) { name: this.name, id: this.id, title: this.getOption('title'), + isDoubleHeight: this.getOption('isDoubleHeight') }; }, diff --git a/frontend/client/src/views/dashlets/abstract/record-list.js b/frontend/client/src/views/dashlets/abstract/record-list.js index 9d458a8b0d..67f4993c17 100644 --- a/frontend/client/src/views/dashlets/abstract/record-list.js +++ b/frontend/client/src/views/dashlets/abstract/record-list.js @@ -40,6 +40,9 @@ Espo.define('Views.Dashlets.Abstract.RecordList', 'Views.Dashlets.Abstract.Base' type: 'enumInt', options: [3,4,5,10,15], }, + 'isDoubleHeight': { + type: 'bool', + } }), afterRender: function () { diff --git a/frontend/client/src/views/dashlets/stream.js b/frontend/client/src/views/dashlets/stream.js index 663e4fcb8b..eaf9901428 100644 --- a/frontend/client/src/views/dashlets/stream.js +++ b/frontend/client/src/views/dashlets/stream.js @@ -28,6 +28,7 @@ Espo.define('Views.Dashlets.Stream', 'Views.Dashlets.Abstract.Base', function (D defaultOptions: { displayRecords: 5, autorefreshInterval: 0.5, + isDoubleHeight: false }, _template: '
    {{{list}}}
    ', @@ -35,7 +36,10 @@ Espo.define('Views.Dashlets.Stream', 'Views.Dashlets.Abstract.Base', function (D optionsFields: _.extend(_.clone(Dep.prototype.optionsFields), { 'displayRecords': { type: 'enumInt', - options: [3,4,5,10,15], + options: [3,4,5,10,15] + }, + 'isDoubleHeight': { + type: 'bool', }, }), diff --git a/frontend/client/src/views/extension/record/list.js b/frontend/client/src/views/extension/record/list.js new file mode 100644 index 0000000000..96965b4592 --- /dev/null +++ b/frontend/client/src/views/extension/record/list.js @@ -0,0 +1,43 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Views.Extension.Record.List', 'Views.Record.List', function (Dep) { + + return Dep.extend({ + + rowActionsView: 'Extension.Record.RowActions', + + checkboxes: false, + + removeAction: false, + + mergeAction: false, + + massUpdateAction: false, + + exportAction: false, + + allowQuickEdit: false, + + }); + +}); + diff --git a/frontend/client/src/views/extension/record/row-actions.js b/frontend/client/src/views/extension/record/row-actions.js new file mode 100644 index 0000000000..d5e5ddb730 --- /dev/null +++ b/frontend/client/src/views/extension/record/row-actions.js @@ -0,0 +1,64 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Views.Extension.Record.RowActions', 'Views.Record.RowActions.Default', function (Dep) { + + return Dep.extend({ + + getActions: function () { + if (this.options.acl.edit) { + + if (this.model.get('isInstalled')) { + return [ + { + action: 'uninstall', + label: 'Uninstall', + data: { + id: this.model.id + } + }, + + ]; + } else { + return [ + { + action: 'install', + label: 'Install', + data: { + id: this.model.id + } + }, + { + action: 'quickRemove', + label: 'Remove', + data: { + id: this.model.id + } + } + ]; + } + } + }, + + }); + +}); + diff --git a/frontend/client/src/views/external-account/oauth2.js b/frontend/client/src/views/external-account/oauth2.js index 512457ad86..6317fe9ae9 100644 --- a/frontend/client/src/views/external-account/oauth2.js +++ b/frontend/client/src/views/external-account/oauth2.js @@ -32,10 +32,13 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) { data: function () { return { integration: this.integration, - helpText: this.helpText + helpText: this.helpText, + isConnected: this.isConnected }; }, + isConnected: false, + events: { 'click button[data-action="cancel"]': function () { this.getRouter().navigate('#ExternalAccount', {trigger: true}); @@ -84,12 +87,15 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) { this.createFieldView('bool', 'enabled'); $.ajax({ - url: 'ExternalAccount/action/getOAuthCredentials?id=' + this.id, + url: 'ExternalAccount/action/getOAuth2Info?id=' + this.id, dataType: 'json' }).done(function (respose) { this.clientId = respose.clientId; - this.redirectUri = respose.redirectUri; - this.wait(false); + this.redirectUri = respose.redirectUri; + if (respose.isConnected) { + this.isConnected = true; + } + this.wait(false); }.bind(this)); }, this); @@ -163,6 +169,9 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) { this.listenToOnce(this.model, 'sync', function () { this.notify('Saved', 'success'); + if (!this.model.get('enabled')) { + this.setNotConnected(); + } }, this); this.notify('Saving...'); @@ -188,26 +197,29 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) { path += '?' + arr.join('&'); var parseUrl = function (str) { - var accessToken = false; - var expires = false; + var code = null; + var error = null; - str = str.substr(str.indexOf('#') + 1, str.length); + str = str.substr(str.indexOf('?') + 1, str.length); str.split('&').forEach(function (part) { var arr = part.split('='); var name = decodeURI(arr[0]); var value = decodeURI(arr[1] || ''); - if (name == 'access_token') { - accessToken = value; + if (name == 'code') { + code = value; } - if (name == 'expires') { - expires = value; + if (name == 'error') { + error = value; } }, this); - if (accessToken) { + if (code) { return { - accessToken: accessToken, - expires: expires + code: code, + } + } else if (error) { + return { + error: error, } } } @@ -217,27 +229,74 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) { if (popup.closed) { window.clearInterval(interval); } else { - var res = parseUrl(popup.location.href.toString()); - callback.call(self, res.accessToken, res.expires); - popup.close(); + var res = parseUrl(popup.location.href.toString()); + if (res) { + callback.call(self, res); + popup.close(); + window.clearInterval(interval); + } } }, 500); }, connect: function () { + this.notify('Please wait...'); this.popup({ - path: this.getMetadata().get('integrations.' + this.integration + '.params.url'), + path: this.getMetadata().get('integrations.' + this.integration + '.params.endpoint'), params: { client_id: this.clientId, redirect_uri: this.redirectUri, scope: this.getMetadata().get('integrations.' + this.integration + '.params.scope'), - response_type: 'token' + response_type: 'code', + access_type: 'offline', + approval_prompt: 'force' } - }, function (accessToken, expires) { - + }, function (res) { + if (res.errror) { + this.notify(false); + return; + } + if (res.code) { + this.$el.find('[data-action="connect"]').addClass('disabled'); + $.ajax({ + url: 'ExternalAccount/action/authorizationCode', + type: 'POST', + data: JSON.stringify({ + 'id': this.id, + 'code': res.code + }), + dataType: 'json', + error: function () { + this.$el.find('[data-action="connect"]').removeClass('disabled'); + }.bind(this) + }).done(function (response) { + this.notify(false); + if (response === true) { + this.setConneted(); + } else { + this.setNotConneted(); + } + this.$el.find('[data-action="connect"]').removeClass('disabled'); + }.bind(this)); + + } else { + this.notify('Error occured', 'error'); + } }); }, + setConneted: function () { + this.isConnected = true; + this.$el.find('[data-action="connect"]').addClass('hidden');; + this.$el.find('.connected-label').removeClass('hidden'); + }, + + setNotConnected: function () { + this.isConnected = false; + this.$el.find('[data-action="connect"]').removeClass('hidden');; + this.$el.find('.connected-label').addClass('hidden'); + }, + }); }); diff --git a/frontend/client/src/views/fields/currency-converted.js b/frontend/client/src/views/fields/currency-converted.js new file mode 100644 index 0000000000..20fac901f3 --- /dev/null +++ b/frontend/client/src/views/fields/currency-converted.js @@ -0,0 +1,38 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Views.Fields.CurrencyConverted', 'Views.Fields.Float', function (Dep) { + + return Dep.extend({ + + detailTemplate: 'fields.currency.detail', + + listTemplate: 'fields.currency.detail', + + data: function () { + return _.extend({ + currencyValue: this.getConfig().get('baseCurrency'), + }, Dep.prototype.data.call(this)); + }, + + }); +}); + diff --git a/frontend/client/src/views/fields/date.js b/frontend/client/src/views/fields/date.js index 8556ffdd05..c7a325debd 100644 --- a/frontend/client/src/views/fields/date.js +++ b/frontend/client/src/views/fields/date.js @@ -31,7 +31,7 @@ Espo.define('Views.Fields.Date', 'Views.Fields.Base', function (Dep) { validations: ['required', 'date', 'after', 'before'], - searchTypeOptions: ['on', 'notOn', 'after', 'before', 'between', 'today', 'past', 'future'], + searchTypeOptions: ['today', 'past', 'future', 'currentMonth', 'lastMonth', 'currentQuarter', 'lastQuarter', 'currentYear', 'lastYear', 'on', 'after', 'before', 'between'], setup: function () { Dep.prototype.setup.call(this); @@ -51,13 +51,40 @@ Espo.define('Views.Fields.Date', 'Views.Fields.Base', function (Dep) { }, getValueForDisplay: function () { - var value = this.model.get(this.name); + var value = this.model.get(this.name); if (!value) { if (this.mode == 'edit' || this.mode == 'search') { return ''; } return this.translate('None'); } + + if (this.mode == 'list' || this.mode == 'detail') { + var d = this.getDateTime().toMoment(value); + var today = moment().tz(this.getDateTime().timeZone || 'UTC').startOf('day'); + var dt = today.clone(); + + var ranges = { + 'today': [dt.unix(), dt.add('days', 1).unix()], + 'tomorrow': [dt.unix(), dt.add('days', 1).unix()], + 'yesterday': [dt.add('days', -3).unix(), dt.add('days', 1).unix()] + }; + + if (d.unix() > ranges['today'][0] && d.unix() < ranges['today'][1]) { + return this.translate('Today'); + } else if (d.unix() > ranges['tomorrow'][0] && d.unix() < ranges['tomorrow'][1]) { + return this.translate('Tomorrow'); + } else if (d.unix() > ranges['yesterday'][0] && d.unix() < ranges['yesterday'][1]) { + return this.translate('Yesterday'); + } + + if (d.format('YYYY') == today.format('YYYY')) { + return d.format('MMM DD'); + } else { + return d.format('MMM DD, YYYY'); + } + } + return this.getDateTime().toDisplayDate(value); }, @@ -130,14 +157,14 @@ Espo.define('Views.Fields.Date', 'Views.Fields.Base', function (Dep) { }, handleSearchType: function (type) { - if (~['today', 'past', 'future'].indexOf(type)) { - this.$el.find('div.primary').addClass('hidden'); + if (~['on', 'notOn', 'after', 'before'].indexOf(type)) { + this.$el.find('div.primary').removeClass('hidden'); this.$el.find('div.additional').addClass('hidden'); } else if (type == 'between') { this.$el.find('div.primary').removeClass('hidden'); this.$el.find('div.additional').removeClass('hidden'); } else { - this.$el.find('div.primary').removeClass('hidden'); + this.$el.find('div.primary').addClass('hidden'); this.$el.find('div.additional').addClass('hidden'); } }, @@ -176,11 +203,7 @@ Espo.define('Views.Fields.Date', 'Views.Fields.Base', function (Dep) { dateValue: value, dateValueTo: valueTo }; - } else if (~['today', 'past', 'future'].indexOf(type)) { - data = { - type: type - }; - } else { + } else if (~['on', 'notOn', 'after', 'before'].indexOf(type)) { if (!value) { return false; } @@ -189,6 +212,10 @@ Espo.define('Views.Fields.Date', 'Views.Fields.Base', function (Dep) { value: value, dateValue: value }; + } else { + data = { + type: type + }; } return data; }, diff --git a/frontend/client/src/views/fields/datetime.js b/frontend/client/src/views/fields/datetime.js index 64976fb1f2..b85e62420a 100644 --- a/frontend/client/src/views/fields/datetime.js +++ b/frontend/client/src/views/fields/datetime.js @@ -29,7 +29,7 @@ Espo.define('Views.Fields.Datetime', 'Views.Fields.Date', function (Dep) { validations: ['required', 'datetime', 'after', 'before'], - searchTypeOptions: ['on', 'after', 'before', 'between', 'today', 'past', 'future'], + searchTypeOptions: ['today', 'past', 'future', 'currentMonth', 'lastMonth', 'currentQuarter', 'lastQuarter', 'currentYear', 'lastYear', 'on', 'after', 'before', 'between'], timeFormatMap: { 'HH:mm': 'H:i', @@ -50,13 +50,40 @@ Espo.define('Views.Fields.Datetime', 'Views.Fields.Date', function (Dep) { }, getValueForDisplay: function () { - var value = this.model.get(this.name); + var value = this.model.get(this.name); if (!value) { if (this.mode == 'edit' || this.mode == 'search') { return ''; } return this.translate('None'); } + + if (this.mode == 'list' || this.mode == 'detail') { + var d = this.getDateTime().toMoment(value); + var now = moment().tz(this.getDateTime().timeZone || 'UTC'); + var dt = now.clone().startOf('day'); + + var ranges = { + 'today': [dt.unix(), dt.add('days', 1).unix()], + 'tomorrow': [dt.unix(), dt.add('days', 1).unix()], + 'yesterday': [dt.add('days', -3).unix(), dt.add('days', 1).unix()] + }; + + if (d.unix() > ranges['today'][0] && d.unix() < ranges['today'][1]) { + return this.translate('Today') + ' ' + d.format(this.getDateTime().timeFormat); + } else if (d.unix() > ranges['tomorrow'][0] && d.unix() < ranges['tomorrow'][1]) { + return this.translate('Tomorrow') + ' ' + d.format(this.getDateTime().timeFormat); + } else if (d.unix() > ranges['yesterday'][0] && d.unix() < ranges['yesterday'][1]) { + return this.translate('Yesterday') + ' ' + d.format(this.getDateTime().timeFormat); + } + + if (d.format('YYYY') == now.format('YYYY')) { + return d.format('MMM DD') + ' ' + d.format(this.getDateTime().timeFormat); + } else { + return d.format('MMM DD, YYYY') + ' ' + d.format(this.getDateTime().timeFormat); + } + } + return this.getDateTime().toDisplay(value); }, diff --git a/frontend/client/src/views/import/step3.js b/frontend/client/src/views/import/step3.js index 40a11ec6a8..b494b6fc58 100644 --- a/frontend/client/src/views/import/step3.js +++ b/frontend/client/src/views/import/step3.js @@ -37,6 +37,10 @@ Espo.define('Views.Import.Step3', 'View', function (Dep) { }, }, + afterRender: function () { + this.getRouter().navigate('#Import/results'); + }, + setup: function () { this.formData = this.options.formData; this.scope = this.formData.entityType; diff --git a/frontend/client/src/views/notifications/field.js b/frontend/client/src/views/notifications/field.js index 65d0661366..fe3c5e17d8 100644 --- a/frontend/client/src/views/notifications/field.js +++ b/frontend/client/src/views/notifications/field.js @@ -33,7 +33,11 @@ Espo.define('Views.Notifications.Field', 'Views.Fields.Base', function (Dep) { setup: function () { switch (this.model.get('type')) { case 'Note': - this.processNote(this.model.get('data')); + this.processNote(this.model.get('noteData')); + break; + case 'MentionInPost': + this.processMentionInPost(this.model.get('noteData')); + break; } }, @@ -49,8 +53,24 @@ Espo.define('Views.Notifications.Field', 'Views.Fields.Base', function (Dep) { onlyContent: true, }); this.wait(false); - }.bind(this)); - } + }, this); + }, + + processMentionInPost: function (data) { + this.wait(true); + this.getModelFactory().create('Note', function (model) { + model.set(data); + var viewName = 'Stream.Notes.MentionInPost'; + this.createView('notification', viewName, { + model: model, + userId: this.model.get('userId'), + isUserStream: true, + el: this.params.containerEl + ' li[data-id="' + this.model.id + '"]', + onlyContent: true, + }); + this.wait(false); + }, this); + }, }); }); diff --git a/frontend/client/src/views/stream/fields/post.js b/frontend/client/src/views/stream/fields/post.js new file mode 100644 index 0000000000..9cfe4ce7c8 --- /dev/null +++ b/frontend/client/src/views/stream/fields/post.js @@ -0,0 +1,45 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Views.Stream.Fields.Post', 'Views.Fields.Text', function (Dep) { + + return Dep.extend({ + + getValueForDisplay: function () { + var text = Dep.prototype.getValueForDisplay.call(this); + + if (this.mode == 'detail' || this.mode == 'list') { + var mentionData = (this.model.get('data') || {}).mentions || {}; + + Object.keys(mentionData).sort(function (a, b) { + return a.length < b.length + }).forEach(function (item) { + var part = '(' + mentionData[item].name + ')[#User/view/'+mentionData[item].id + ']'; + text = text.replace(new RegExp(item, 'g'), part); + }); + } + + return text; + }, + + }); + +}); diff --git a/frontend/client/src/views/stream/message.js b/frontend/client/src/views/stream/message.js index aae2faab16..791587d7cd 100644 --- a/frontend/client/src/views/stream/message.js +++ b/frontend/client/src/views/stream/message.js @@ -28,7 +28,7 @@ Espo.define('Views.Stream.Message', 'View', function (Dep) { var data = this.options.messageData; for (var key in data) { - var value = data[key]; + var value = data[key] || ''; if (value.indexOf('field:') === 0) { var field = value.substr(6); diff --git a/frontend/client/src/views/stream/note.js b/frontend/client/src/views/stream/note.js index 4a115a0b65..49004e4edc 100644 --- a/frontend/client/src/views/stream/note.js +++ b/frontend/client/src/views/stream/note.js @@ -54,7 +54,7 @@ Espo.define('Views.Stream.Note', 'View', function (Dep) { this.messageData = { 'user': 'field:createdBy', 'entity': 'field:parent', - 'entityType': this.translate(this.model.get('parentType'), 'scopeNames').toLowerCase() + 'entityType': (this.translate(this.model.get('parentType'), 'scopeNames') || '').toLowerCase() }; }, diff --git a/frontend/client/src/views/stream/notes/assign.js b/frontend/client/src/views/stream/notes/assign.js index 70e7719fa0..67758a0a8e 100644 --- a/frontend/client/src/views/stream/notes/assign.js +++ b/frontend/client/src/views/stream/notes/assign.js @@ -33,7 +33,7 @@ Espo.define('Views.Stream.Notes.Assign', 'Views.Stream.Note', function (Dep) { }, setup: function () { - var data = JSON.parse(this.model.get('data')); + var data = this.model.get('data'); this.assignedUserId = data.assignedUserId || null; this.assignedUserName = data.assignedUserName || null; diff --git a/frontend/client/src/views/stream/notes/create-related.js b/frontend/client/src/views/stream/notes/create-related.js index 781c1f5704..7498ad5006 100644 --- a/frontend/client/src/views/stream/notes/create-related.js +++ b/frontend/client/src/views/stream/notes/create-related.js @@ -39,7 +39,7 @@ Espo.define('Views.Stream.Notes.CreateRelated', 'Views.Stream.Note', function (D setup: function () { if (this.model.get('data')) { - var data = JSON.parse(this.model.get('data')); + var data = this.model.get('data'); this.entityType = data.entityType || null; this.entityId = data.entityId || null; diff --git a/frontend/client/src/views/stream/notes/create.js b/frontend/client/src/views/stream/notes/create.js index 8745ee1fb0..d181e7e30e 100644 --- a/frontend/client/src/views/stream/notes/create.js +++ b/frontend/client/src/views/stream/notes/create.js @@ -37,8 +37,8 @@ Espo.define('Views.Stream.Notes.Create', 'Views.Stream.Note', function (Dep) { }, setup: function () { - if (this.model.get('data')) { - var data = JSON.parse(this.model.get('data')); + if (this.model.get('data')) { + var data = this.model.get('data'); this.assignedUserId = data.assignedUserId || null; this.assignedUserName = data.assignedUserName || null; diff --git a/frontend/client/src/views/stream/notes/email-received.js b/frontend/client/src/views/stream/notes/email-received.js index 7be449e7bf..5939010ade 100644 --- a/frontend/client/src/views/stream/notes/email-received.js +++ b/frontend/client/src/views/stream/notes/email-received.js @@ -33,7 +33,7 @@ Espo.define('Views.Stream.Notes.EmailReceived', 'Views.Stream.Note', function (D }, setup: function () { - var data = JSON.parse(this.model.get('data')); + var data = this.model.get('data'); this.emailId = data.emailId; this.emailName = data.emailName; }, diff --git a/frontend/client/src/views/stream/notes/mention-in-post.js b/frontend/client/src/views/stream/notes/mention-in-post.js new file mode 100644 index 0000000000..33658656fa --- /dev/null +++ b/frontend/client/src/views/stream/notes/mention-in-post.js @@ -0,0 +1,52 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ + +Espo.define('Views.Stream.Notes.MentionInPost', 'Views.Stream.Note', function (Dep) { + + return Dep.extend({ + + template: 'stream.notes.post', + + messageName: 'mentionInPost', + + setup: function () { + if (this.model.get('post')) { + this.createField('post', null, null, 'Stream.Fields.Post'); + } + if ((this.model.get('attachmentsIds') || []).length) { + this.createField('attachments', 'attachmentMultiple', {}, 'Stream.Fields.AttachmentMultiple'); + } + + var data = this.model.get('data'); + + this.messageData['mentioned'] = this.options.userId; + + if (this.isUserStream) { + if (this.options.userId == this.getUser().id) { + this.messageData['mentioned'] = this.translate('you'); + } + } + + this.createMessage(); + }, + }); +}); + diff --git a/frontend/client/src/views/stream/notes/post.js b/frontend/client/src/views/stream/notes/post.js index 3464cfacde..6a55e713ab 100644 --- a/frontend/client/src/views/stream/notes/post.js +++ b/frontend/client/src/views/stream/notes/post.js @@ -29,7 +29,7 @@ Espo.define('Views.Stream.Notes.Post', 'Views.Stream.Note', function (Dep) { setup: function () { if (this.model.get('post')) { - this.createField('post'); + this.createField('post', null, null, 'Stream.Fields.Post'); } if ((this.model.get('attachmentsIds') || []).length) { this.createField('attachments', 'attachmentMultiple', {}, 'Stream.Fields.AttachmentMultiple'); diff --git a/frontend/client/src/views/stream/notes/status.js b/frontend/client/src/views/stream/notes/status.js index ad4e270b62..6aeab0660b 100644 --- a/frontend/client/src/views/stream/notes/status.js +++ b/frontend/client/src/views/stream/notes/status.js @@ -35,7 +35,7 @@ Espo.define('Views.Stream.Notes.Status', 'Views.Stream.Note', function (Dep) { }, setup: function () { - var data = JSON.parse(this.model.get('data')); + var data = this.model.get('data'); var field = data.field; var value = data.value; diff --git a/frontend/client/src/views/stream/notes/update.js b/frontend/client/src/views/stream/notes/update.js index e1a139d728..6cb66215fc 100644 --- a/frontend/client/src/views/stream/notes/update.js +++ b/frontend/client/src/views/stream/notes/update.js @@ -47,7 +47,7 @@ Espo.define('Views.Stream.Notes.Update', 'Views.Stream.Note', function (Dep) { }, setup: function () { - var data = JSON.parse(this.model.get('data')); + var data = this.model.get('data'); var fields = data.fields; diff --git a/frontend/client/src/views/stream/panel.js b/frontend/client/src/views/stream/panel.js index ddd1e330a3..eb96a4da99 100644 --- a/frontend/client/src/views/stream/panel.js +++ b/frontend/client/src/views/stream/panel.js @@ -19,7 +19,7 @@ * along with EspoCRM. If not, see http://www.gnu.org/licenses/. ************************************************************************/ -Espo.define('Views.Stream.Panel', 'Views.Record.Panels.Relationship', function (Dep) { +Espo.define('Views.Stream.Panel', ['Views.Record.Panels.Relationship', 'lib!Textcomplete'], function (Dep, Textcomplete) { return Dep.extend({ @@ -118,7 +118,7 @@ Espo.define('Views.Stream.Panel', 'Views.Record.Panels.Relationship', function ( collection.fetchNew(); }.bind(this)); - this.listenToOnce(collection, 'sync', function () { + this.listenToOnce(collection, 'sync', function () { this.createView('list', 'Stream.List', { el: this.options.el + ' > .list-container', collection: collection, @@ -128,6 +128,32 @@ Espo.define('Views.Stream.Panel', 'Views.Record.Panels.Relationship', function ( }.bind(this)); collection.fetch(); + var self = this; + + this.$textarea.textcomplete([{ + match: /(^|\s)@(\w*)$/, + index: 2, + search: function (term, callback) { + if (term.length == 0) { + callback([]); + return; + } + $.ajax({ + url: 'User?orderBy=name&limit=7&q=' + term, + + }).done(function (data) { + callback(data.list) + }); + }, + template: function (mention) { + return mention.name + ' @' + mention.userName + ''; + }, + replace: function (o) { + return '$1@' + o.userName + ''; + } + }]); + + this.createView('attachments', 'Stream.Fields.AttachmentMultiple', { model: this.seed, mode: 'edit', @@ -144,13 +170,12 @@ Espo.define('Views.Stream.Panel', 'Views.Record.Panels.Relationship', function ( this.$el.find('textarea.note').prop('rows', 1); }, - post: function () { + post: function () { + var message = this.$textarea.val(); + this.$textarea.prop('disabled', true); - - var $text = this.$el.find('textarea.note'); - this.getModelFactory().create('Note', function (model) { - var message = $text.val(); + this.getModelFactory().create('Note', function (model) { if (message == '' && this.seed.get('attachmentsIds').length == 0) { this.notify('Post cannot be empty', 'error'); this.$textarea.prop('disabled', false); @@ -184,6 +209,7 @@ Espo.define('Views.Stream.Panel', 'Views.Record.Panels.Relationship', function ( getButtons: function () { return []; }, + }); }); diff --git a/frontend/client/src/views/user/fields/user-name.js b/frontend/client/src/views/user/fields/user-name.js new file mode 100644 index 0000000000..213dfc1e76 --- /dev/null +++ b/frontend/client/src/views/user/fields/user-name.js @@ -0,0 +1,40 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + ************************************************************************/ +Espo.define('Views.User.Fields.UserName', 'Views.Fields.Varchar', function (Dep) { + + return Dep.extend({ + + afterRender: function () { + Dep.prototype.afterRender.call(this); + + if (this.mode == 'edit') { + this.$element.on('change', function () { + var value = this.$element.val(); + value = value.replace(/[^a-z0-9_\s]/gi, '').replace(/[\s]/g, '_').toLowerCase(); + this.$element.val(value); + this.trigger('change'); + }.bind(this)); + } + }, + + }); + +}); diff --git a/frontend/html/main.html b/frontend/html/main.html index 7777cf5587..1150e901cd 100644 --- a/frontend/html/main.html +++ b/frontend/html/main.html @@ -7,6 +7,10 @@ + + + + @@ -67,6 +68,9 @@ + + + diff --git a/install/core/tpl/finish.tpl b/install/core/tpl/finish.tpl index 8140e035d1..b0e25d6226 100644 --- a/install/core/tpl/finish.tpl +++ b/install/core/tpl/finish.tpl @@ -1,12 +1,35 @@ -
    -

    {$langs['labels']['Finish page title']}

    -
    {if $cronHelp} - {$cronTitle} -
    -{$cronHelp}
    -
    +
    +  {$cronTitle} +
    +	{$cronHelp}
    +	
    +
    {/if}
    diff --git a/install/core/tpl/header.tpl b/install/core/tpl/header.tpl index 6b91a7a86a..6b10fa387b 100644 --- a/install/core/tpl/header.tpl +++ b/install/core/tpl/header.tpl @@ -1,3 +1,19 @@
    - -
    \ No newline at end of file + {if $isBuilt eq true} + + {else} + + {/if} +
    +
    +
    +
    +

    + {$langs['labels']["{$action} page title"]} +

    +
    +
    + {$langs['labels']['Version']} {$version} +
    +
    +
    \ No newline at end of file diff --git a/install/core/tpl/index.tpl b/install/core/tpl/index.tpl index 7e671b05f2..4d556fb125 100644 --- a/install/core/tpl/index.tpl +++ b/install/core/tpl/index.tpl @@ -1,19 +1,21 @@ - + - EspoCRM - - + EspoCRM Installation + + {if $isBuilt eq true} + + + {else} + + + {/if} + - @@ -27,5 +29,5 @@
    - + diff --git a/install/core/tpl/main.tpl b/install/core/tpl/main.tpl index fa2ded5bb8..6927634fc1 100644 --- a/install/core/tpl/main.tpl +++ b/install/core/tpl/main.tpl @@ -1,16 +1,13 @@ -
    -

    {$langs['labels']['Main page title']}

    -
    -
    +
    -
    - EspoCRM -
    - {$langs['labels']['Main page header']} +
    + EspoCRM +
    + {$langs['labels']['Main page header']}
    @@ -34,8 +31,7 @@
    - - +
    \ No newline at end of file diff --git a/install/core/tpl/step1.tpl b/install/core/tpl/step1.tpl index f5727fce48..da99944a40 100644 --- a/install/core/tpl/step1.tpl +++ b/install/core/tpl/step1.tpl @@ -1,13 +1,10 @@ -
    -

    {$langs['labels']['Step1 page title']}

    -
    -
    - - - - - - diff --git a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/directory.html.dist b/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/directory.html.dist deleted file mode 100644 index 9b4f4b23bb..0000000000 --- a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/directory.html.dist +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Code Coverage for {{full_path}} - - - - - - - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - - - - - - - - - - - - - - -{{items}} - -
     
    Code Coverage
     
    Lines
    Functions and Methods
    Classes and Traits
    -
    -

    Legend

    -

    - Low: 0% to {{low_upper_bound}}% - Medium: {{low_upper_bound}}% to {{high_lower_bound}}% - High: {{high_lower_bound}}% to 100% -

    -

    - Generated by PHP_CodeCoverage {{version}} using PHP {{php_version}}{{generator}} at {{date}}. -

    -
    -
    - - - - diff --git a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/directory_item.html.dist b/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/directory_item.html.dist deleted file mode 100644 index 78dbb3565c..0000000000 --- a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/directory_item.html.dist +++ /dev/null @@ -1,13 +0,0 @@ - - {{icon}}{{name}} - {{lines_bar}} -
    {{lines_executed_percent}}
    -
    {{lines_number}}
    - {{methods_bar}} -
    {{methods_tested_percent}}
    -
    {{methods_number}}
    - {{classes_bar}} -
    {{classes_tested_percent}}
    -
    {{classes_number}}
    - - diff --git a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/file.html.dist b/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/file.html.dist deleted file mode 100644 index 6f439b7a27..0000000000 --- a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/file.html.dist +++ /dev/null @@ -1,65 +0,0 @@ - - - - - Code Coverage for {{full_path}} - - - - - - - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - - - - - - - - - - - - - - -{{items}} - -
     
    Code Coverage
     
    Classes and Traits
    Functions and Methods
    Lines
    - - -{{lines}} - -
    - -
    - - - - - diff --git a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/file_item.html.dist b/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/file_item.html.dist deleted file mode 100644 index 756fdd69b1..0000000000 --- a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/file_item.html.dist +++ /dev/null @@ -1,14 +0,0 @@ - - {{name}} - {{classes_bar}} -
    {{classes_tested_percent}}
    -
    {{classes_number}}
    - {{methods_bar}} -
    {{methods_tested_percent}}
    -
    {{methods_number}}
    - {{crap}} - {{lines_bar}} -
    {{lines_executed_percent}}
    -
    {{lines_number}}
    - - diff --git a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/img/glyphicons-halflings-white.png b/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/img/glyphicons-halflings-white.png deleted file mode 100644 index 3bf6484a29..0000000000 Binary files a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/img/glyphicons-halflings-white.png and /dev/null differ diff --git a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/img/glyphicons-halflings.png b/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/img/glyphicons-halflings.png deleted file mode 100644 index a996999320..0000000000 Binary files a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/img/glyphicons-halflings.png and /dev/null differ diff --git a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/js/bootstrap.min.js b/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/js/bootstrap.min.js deleted file mode 100644 index 6eeb15ce3b..0000000000 --- a/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Report/HTML/Renderer/Template/js/bootstrap.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! -* Bootstrap.js by @fat & @mdo -* Copyright 2012 Twitter, Inc. -* http://www.apache.org/licenses/LICENSE-2.0.txt -*/ -!function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[option]()})},$.fn.collapse.defaults={toggle:!0},$.fn.collapse.Constructor=Collapse,$.fn.collapse.noConflict=function(){return $.fn.collapse=old,this},$(document).on("click.collapse.data-api","[data-toggle=collapse]",function(e){var href,$this=$(this),target=$this.attr("data-target")||e.preventDefault()||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),option=$(target).data("collapse")?"toggle":$this.data();$this[$(target).hasClass("in")?"addClass":"removeClass"]("collapsed"),$(target).collapse(option)})}(window.jQuery),!function($){"use strict";function clearMenus(){$(toggle).each(function(){getParent($(this)).removeClass("open")})}function getParent($this){var $parent,selector=$this.attr("data-target");return selector||(selector=$this.attr("href"),selector=selector&&/#/.test(selector)&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),$parent.length||($parent=$this.parent()),$parent}var toggle="[data-toggle=dropdown]",Dropdown=function(element){var $el=$(element).on("click.dropdown.data-api",this.toggle);$("html").on("click.dropdown.data-api",function(){$el.parent().removeClass("open")})};Dropdown.prototype={constructor:Dropdown,toggle:function(){var $parent,isActive,$this=$(this);if(!$this.is(".disabled, :disabled"))return $parent=getParent($this),isActive=$parent.hasClass("open"),clearMenus(),isActive||$parent.toggleClass("open"),$this.focus(),!1},keydown:function(e){var $this,$items,$parent,isActive,index;if(/(38|40|27)/.test(e.keyCode)&&($this=$(this),e.preventDefault(),e.stopPropagation(),!$this.is(".disabled, :disabled"))){if($parent=getParent($this),isActive=$parent.hasClass("open"),!isActive||isActive&&27==e.keyCode)return $this.click();$items=$("[role=menu] li:not(.divider):visible a",$parent),$items.length&&(index=$items.index($items.filter(":focus")),38==e.keyCode&&index>0&&index--,40==e.keyCode&&$items.length-1>index&&index++,~index||(index=0),$items.eq(index).focus())}}};var old=$.fn.dropdown;$.fn.dropdown=function(option){return this.each(function(){var $this=$(this),data=$this.data("dropdown");data||$this.data("dropdown",data=new Dropdown(this)),"string"==typeof option&&data[option].call($this)})},$.fn.dropdown.Constructor=Dropdown,$.fn.dropdown.noConflict=function(){return $.fn.dropdown=old,this},$(document).on("click.dropdown.data-api touchstart.dropdown.data-api",clearMenus).on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("touchstart.dropdown.data-api",".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",toggle,Dropdown.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",toggle+", [role=menu]",Dropdown.prototype.keydown)}(window.jQuery),!function($){"use strict";var Modal=function(element,options){this.options=options,this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");that.$element.parent().length||that.$element.appendTo(document.body),that.$element.show(),transition&&that.$element[0].offsetWidth,that.$element.addClass("in").attr("aria-hidden",!1),that.enforceFocus(),transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")}))},hide:function(e){e&&e.preventDefault(),e=$.Event("hide"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),$(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal())},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){that.$element[0]===e.target||that.$element.has(e.target).length||that.$element.focus()})},escape:function(){var that=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(e){27==e.which&&that.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end),that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout),that.hideModal()})},hideModal:function(){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(callback){var animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$('