From 723dc3afba0f07ec4f66c3244bcad6dd3b318170 Mon Sep 17 00:00:00 2001
From: Parziphal
Topmost buffer\'s contents:
%s',
+ $status['level'], substr($this->_filename, strlen(Rails::root()) + 1), htmlentities(ob_get_clean()))
+ );
+ }
+ }
+ return $this;
+ }
+
+ protected function render(array $params)
+ {
+ $type = key($params);
+ switch ($type) {
+ case 'template':
+ $file = array_shift($params);
+ $template = new self(['file' => $file]);
+ $template->renderContent();
+ return $template->content();
+ break;
+ }
+ }
+
+ public function content($name = null)
+ {
+ if (!func_num_args())
+ return $this->_buffer;
+ else
+ return parent::content($name);
+ }
+
+ public function contents()
+ {
+ return $this->content();
+ }
+
+ /**
+ * Finally prints all the view.
+ */
+ public function get_buffer_and_clean()
+ {
+ $buffer = $this->_buffer;
+ $this->_buffer = null;
+ return $buffer;
+ }
+
+ public function t($name, array $params = [])
+ {
+ if (is_array($name)) {
+ $params = $name;
+ $name = current($params);
+ }
+
+ if (strpos($name, '.') === 0) {
+ if (is_int(strpos($this->template_filename, Rails::config()->paths->views->toString()))) {
+ $parts = array();
+ $path = substr(
+ $this->template_filename, strlen(Rails::config()->paths->views) + 1,
+ strlen(pathinfo($this->template_filename, PATHINFO_BASENAME)) * -1
+ )
+ . pathinfo($this->template_filename, PATHINFO_FILENAME);
+
+ foreach (explode('/', $path) as $part) {
+ $parts[] = ltrim($part, '_');
+ }
+ $name = implode('.', $parts) . $name;
+ }
+ }
+
+ return parent::t($name, $params);
+ }
+
+ protected function _init_render()
+ {
+ $layout_wrap = !empty($this->params['layout']);
+
+ if ($layout_wrap) {
+ $layout_file = $this->resolve_layout_file($this->params['layout']);
+
+ if (!is_file($layout_file))
+ throw new Exception\LayoutMissingException(
+ sprintf("Missing layout '%s' [ file => %s ]",
+ $this->params['layout'], $layout_file)
+ );
+ ob_start();
+ }
+
+ switch ($this->type) {
+ case 'file':
+ $this->build_template_filename();
+
+ if (!is_file($this->template_filename)) {
+ throw new Exception\TemplateMissingException(
+ sprintf("Missing template file %s", $this->template_filename)
+ );
+ }
+
+ require $this->template_filename;
+ break;
+
+ case 'contents':
+ echo $this->contents;
+ break;
+
+ case 'inline':
+ eval('?>' . $this->inline_code . 'lambda;
+ $this->lambda = null;
+ $lambda = $lambda->bindTo($this);
+ $lambda();
+ break;
+ }
+
+ if ($layout_wrap) {
+ $this->_buffer = ob_get_clean();
+ $layout = new Layout($layout_file, ['contents' => $this->_buffer], $this->locals);
+ $layout->renderContent();
+ echo $layout->content();
+ // require $layout_file;
+ }
+ }
+
+ private function _set_initial_ob_level()
+ {
+ $status = ob_get_status();
+ $this->_initial_ob_level = $this->_get_ob_level();
+ }
+
+ private function _validate_ob_level()
+ {
+ $status = ob_get_status();
+ return $this->_initial_ob_level == $this->_get_ob_level();
+ }
+
+ protected function resolve_layout_file($layout)
+ {
+ if (strpos($layout, '/') === 0 || preg_match('/^[A-Za-z]:(?:\\\|\/)/', $layout))
+ return $layout;
+ else {
+ if (is_int(strpos($layout, '.')))
+ return Rails::config()->paths->layouts . '/' . $layout;
+ else
+ return Rails::config()->paths->layouts . '/' . $layout . '.php';
+ }
+ }
+
+ private function build_template_filename()
+ {
+ $template = $this->template_file;
+ if (strpos($template, '/') === 0 || preg_match('/^[A-Za-z]:(?:\\\|\/)/', $template))
+ $this->template_filename = $template;
+ else {
+ if (is_int(strpos($template, '.'))) {
+ $this->template_filename = Rails::config()->paths->views . '/' . $template;
+ } else {
+ $this->template_filename = Rails::config()->paths->views . '/' . $template . '.php';
+ }
+ }
+ }
+
+ private function _get_ob_level()
+ {
+ $status = ob_get_status();
+ return !empty($status['level']) ? $status['level'] : 0;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActionView/Template/Exception/ExceptionInterface.php b/lib/Rails/ActionView/Template/Exception/ExceptionInterface.php
new file mode 100755
index 0000000..787a9bb
--- /dev/null
+++ b/lib/Rails/ActionView/Template/Exception/ExceptionInterface.php
@@ -0,0 +1,6 @@
+ $helper) {
+ if (method_exists($helper, $method)) {
+ self::$registry[$method] = $helperName;
+ return $helper;
+ }
+ }
+ return false;
+ }
+
+ static public function getBaseHelper()
+ {
+ return self::getHelper(self::BASE_HELPER_NAME);
+ }
+
+ static public function getHelper($name)
+ {
+ return self::$helpers[$name];
+ }
+
+ /**
+ * Adds class names to the helper queues. These classes will be instantiated
+ * in includeHelpers().
+ *
+ * @var name string helper name
+ * @see load()
+ */
+ static public function addHelper($className)
+ {
+ self::$queue[] = $className;
+ }
+
+ static public function addHelpers(array $classNames)
+ {
+ self::$queue = array_merge(self::$queue, $className);
+ }
+
+ /**
+ * For application helpers. Class names passed will be appended with "Helper".
+ */
+ static public function addAppHelpers(array $helpers)
+ {
+ self::$queue = array_merge(self::$queue, array_map(function($c) { return $c . 'Helper'; }, $helpers));
+ }
+
+ /**
+ * Actually include the helpers files.
+ *
+ * Application and current controller's helper are added here,
+ * to make sure they're top on the list.
+ */
+ static public function load()
+ {
+ if (!self::$helpersLoaded) {
+ if (($router = Rails::application()->dispatcher()->router()) && ($route = $router->route())) {
+ $controllerHelper = Rails::services()->get('inflector')->camelize($route->controller()) . 'Helper';
+ array_unshift(self::$queue, $controllerHelper);
+ }
+ $appHelper = 'ApplicationHelper';
+ array_unshift(self::$queue, $appHelper);
+
+ foreach (array_unique(self::$queue) as $name) {
+ try {
+ Rails::loader()->loadClass($name);
+ self::$helpers[$name] = new $name();
+ } catch (Rails\Loader\Exception\ExceptionInterface $e) {
+ }
+ }
+
+ # Add base helper
+ self::$helpers[self::BASE_HELPER_NAME] = new Helper\Base();
+
+ self::$helpersLoaded = true;
+ }
+ }
+}
diff --git a/lib/Rails/ActionView/Xml.php b/lib/Rails/ActionView/Xml.php
new file mode 100755
index 0000000..bde9bdc
--- /dev/null
+++ b/lib/Rails/ActionView/Xml.php
@@ -0,0 +1,69 @@
+_buffer .= ''."\n";
+ }
+
+ /**
+ * $content could be passed as second argument.
+ */
+ public function create($root, $attrs, $content = null)
+ {
+ $this->_buffer .= '<'.$root;
+
+ if (!is_array($attrs)) {
+ $content = $attrs;
+ $attrs = [];
+ }
+
+ if ($attrs) {
+ $attrs_str = [];
+ foreach ($attrs as $name => $val)
+ $attrs_str[] = $name . '="'.htmlspecialchars($val).'"';
+ $this->_buffer .= ' ' . implode(' ', $attrs_str);
+ }
+
+ if (!$content) {
+ $this->_buffer .= ' />';
+ } else {
+ $this->_buffer .= ">\n";
+
+ if (is_string($content))
+ $this->_buffer .= $content;
+ elseif ($content instanceof Closure)
+ $this->_buffer .= $content();
+ else
+ throw new Exception\InvalidArgumentError(
+ sprintf('Expecting Closure or string as third argument, %s passed.', gettype($content))
+ );
+
+ $this->_buffer .= ''.$root.'>';
+ }
+ }
+
+ public function build($el, array $params = [])
+ {
+ $this->_buffer .= (new \Rails\Xml\Xml($el, $params))->output() . "\n";
+ }
+
+ public function output()
+ {
+ !$this->_buffer && $this->create();
+ return $this->_buffer;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveModel/Collection.php b/lib/Rails/ActiveModel/Collection.php
new file mode 100755
index 0000000..17de1a6
--- /dev/null
+++ b/lib/Rails/ActiveModel/Collection.php
@@ -0,0 +1,366 @@
+members[] = $value;
+ } else {
+ $this->members[$offset] = $value;
+ }
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->members[$offset]);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->members[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return isset($this->members[$offset]) ? $this->members[$offset] : null;
+ }
+ /* } Iterator {*/
+ protected $position = 0;
+
+ public function rewind()
+ {
+ reset($this->members);
+ $this->position = key($this->members);
+ }
+
+ public function current()
+ {
+ return $this->members[$this->position];
+ }
+
+ public function key()
+ {
+ return key($this->members);
+ }
+
+ public function next()
+ {
+ next($this->members);
+ $this->position = key($this->members);
+ }
+
+ public function valid()
+ {
+ return array_key_exists($this->position, $this->members);
+ }
+ /* } */
+
+ public function __construct(array $members = array())
+ {
+ $this->members = $members;
+ }
+
+ public function merge()
+ {
+ foreach (func_get_args() as $coll) {
+ if ($coll instanceof self)
+ $coll = $coll->members();
+ $this->members = array_merge($this->members, $coll);
+ }
+ return $this;
+ }
+
+ public function members()
+ {
+ return $this->members;
+ }
+
+ # Another way to get members.
+ public function toArray()
+ {
+ return $this->members;
+ }
+
+ /**
+ * Each (experimental)
+ *
+ * If string is passed, it'll be taken as method name to be called.
+ * Eg. $posts->each('destroy'); - All posts will be destroyed.
+ * In this case, $params for the method may be passed.
+ *
+ * A Closure may also be passed.
+ */
+ public function each($function, array $params = array())
+ {
+ if (is_string($function)) {
+ foreach ($this->members() as $m) {
+ call_user_func_array(array($m, $function), $params);
+ }
+ } elseif ($function instanceof Closure) {
+ foreach ($this->members() as $idx => $m) {
+ $function($m, $idx);
+ }
+ } else {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Argument must be an either a string or a Closure, %s passed.', gettype($model))
+ );
+ }
+ }
+
+ public function reduce($var, Closure $block)
+ {
+ foreach ($this->members() as $m) {
+ $var = $block($var, $m);
+ }
+ return $var;
+ }
+
+ public function sort(Closure $criteria)
+ {
+ usort($this->members, $criteria);
+ $this->rewind();
+ return $this;
+ }
+
+ public function unshift($model)
+ {
+ if ($model instanceof Base)
+ $model = array($model);
+ elseif (!$model instanceof self) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Argument must be an instance of either ActiveRecord\Base or ActiveRecord\Collection, %s passed.', gettype($model))
+ );
+ }
+
+ foreach ($model as $m)
+ array_unshift($this->members, $m);
+
+ return $this;
+ }
+
+ /**
+ * Searches objects for a property with a value and returns object.
+ */
+ public function search($prop, $value)
+ {
+ foreach ($this->members() as $obj) {
+ if ($obj->$prop == $value)
+ return $obj;
+ }
+ return false;
+ }
+
+ # Returns a Collection with the models that matches the options.
+ # Eg: $posts->select(array('is_active' => true, 'user_id' => 4));
+ # If Closure passed as $opts, the model that returns == true on the function
+ # will be added.
+ public function select($opts)
+ {
+ $objs = array();
+
+ if (is_array($opts)) {
+ foreach ($this as $obj) {
+ foreach ($opts as $prop => $cond) {
+ if (!$obj->$prop || $obj->$prop != $cond)
+ continue;
+ $objs[] = $obj;
+ }
+ }
+ } elseif ($opts instanceof Closure) {
+ foreach ($this->members() as $obj) {
+ $opts($obj) && $objs[] = $obj;
+ }
+ }
+
+ return new self($objs);
+ }
+
+ /**
+ * Removes members according to attributes.
+ */
+ public function remove($attrs)
+ {
+ !is_array($attrs) && $attrs = array('id' => $attrs);
+
+ foreach ($this->members() as $k => $m) {
+ foreach ($attrs as $a => $v) {
+ if ($m->getAttribute($a) != $v)
+ continue 2;
+ }
+ unset($this->members[$k]);
+ }
+ $this->members = array_values($this->members);
+ return $this;
+ }
+
+ public function max($criteria)
+ {
+ if (!$this->members)
+ return false;
+
+ $current = key($this->members);
+ if (count($this->members) < 2)
+ return $this->members[$current];
+
+ $max = $this->members[$current];
+
+ if ($criteria instanceof Closure) {
+ $params = $this;
+ foreach($params as $current) {
+ if (!$next = next($params))
+ break;
+ $max = $criteria($max, $next);
+ }
+ } else {
+
+ }
+ return $max;
+ }
+
+ # Deprecated in favor of none.
+ public function blank()
+ {
+ return empty($this->members);
+ }
+
+ public function none()
+ {
+ return empty($this->members);
+ }
+
+ public function any()
+ {
+ return (bool)$this->members;
+ }
+
+ /**
+ * TODO: xml shouldn't be created here.
+ */
+ public function toXml()
+ {
+ if ($this->blank())
+ return;
+
+ $t = get_class($this->current());
+ $t = $t::t();
+
+ $xml = '';
+ $xml .= '<' . $t . '>';
+
+ foreach ($this->members() as $obj) {
+ $xml .= $obj->toXml(array('skip_instruct' => true));
+ }
+
+ $xml .= '' . $t . '>';
+
+ return $xml;
+ }
+
+ public function asJson()
+ {
+ $json = [];
+ foreach ($this->members as $member)
+ $json[] = $member->asJson();
+ return $json;
+ }
+
+ public function toJson()
+ {
+ return json_encode($this->asJson());
+ }
+
+ /**
+ * Returns an array of the attributes in the models.
+ * $attrs could be a string of a single attribute we want, and
+ * an indexed array will be returned.
+ * If $attrs is an array of many attributes, an associative array will be returned.
+ *
+ * $collection->attributes(['id', 'createdAt']);
+ * No -> $collection->attributes('id', 'name');
+ */
+ public function getAttributes($attrs)
+ {
+ $models_attrs = array();
+
+ if (is_string($attrs)) {
+ foreach ($this as $m) {
+ // if (Rails::config()->ar2) {
+ // $models_attrs[] = $m->$attrs();
+ // } else {
+ $models_attrs[] = $m->$attrs;
+ // }
+ }
+ } else {
+ foreach ($this->members() as $m) {
+ $model_attrs = [];
+ foreach ($attrs as $attr) {
+ // if (!Rails::config()->ar2) {
+ $model_attrs[$attr] = $m->$attr;
+ // } else {
+ // $model_attrs[$attr] = $m->$attr();
+ // }
+ }
+ $models_attrs[] = $model_attrs;
+ }
+ }
+
+ return $models_attrs;
+ }
+
+ public function size()
+ {
+ return count($this->members);
+ }
+
+ # Removes dupe models based on id or other attribute.
+ public function unique($attr = 'id')
+ {
+ $checked = array();
+ foreach ($this->members() as $k => $obj) {
+ if (in_array($obj->$attr, $checked))
+ unset($this->members[$k]);
+ else
+ $checked[] = $obj->$attr;
+ }
+ return $this;
+ }
+
+ # array_slices the collection.
+ public function slice($offset, $length = null)
+ {
+ $clone = clone $this;
+ $clone->members = array_slice($clone->members, $offset, $length);
+ return $clone;
+ }
+
+ public function deleteIf(Closure $conditions)
+ {
+ $deleted = false;
+ foreach ($this->members() as $k => $m) {
+ if ($conditions($m)) {
+ unset($this[$k]);
+ $deleted = true;
+ }
+ }
+ if ($deleted)
+ $this->members = array_values($this->members);
+ }
+
+ public function replace($replacement)
+ {
+ if ($replacement instanceof self)
+ $this->members = $replacement->members();
+ elseif (is_array($replacement))
+ $this->members = $replacement;
+ else
+ throw new Exception\InvalidArgumentException(sprintf("%s expects a %s or an array, %s passed", __METHOD__, __CLASS__, gettype($replacement)));
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveModel/Errors.php b/lib/Rails/ActiveModel/Errors.php
new file mode 100755
index 0000000..e7e9dd6
--- /dev/null
+++ b/lib/Rails/ActiveModel/Errors.php
@@ -0,0 +1,107 @@
+errors[$attribute]))
+ $this->errors[$attribute] = array();
+
+ $this->errors[$attribute][] = $msg;
+ }
+
+ public function addToBase($msg)
+ {
+ $this->base($msg);
+ }
+
+ public function base($msg)
+ {
+ $this->add(self::BASE_ERRORS_INDEX, $msg);
+ }
+
+ public function on($attribute)
+ {
+ if (!isset($this->errors[$attribute]))
+ return null;
+ elseif (count($this->errors[$attribute]) == 1)
+ return current($this->errors[$attribute]);
+ else
+ return $this->errors[$attribute];
+ }
+
+ public function onBase()
+ {
+ return $this->on(self::BASE_ERRORS_INDEX);
+ }
+
+ # $glue is a string that, if present, will be used to
+ # return the messages imploded.
+ public function fullMessages($glue = null)
+ {
+ $fullMessages = array();
+
+ foreach ($this->errors as $attr => $errors) {
+ foreach ($errors as $msg) {
+ if ($attr == self::BASE_ERRORS_INDEX)
+ $fullMessages[] = $msg;
+ else
+ $fullMessages[] = $this->_propper_attr($attr) . ' ' . $msg;
+ }
+ }
+
+ if ($glue !== null)
+ return implode($glue, $fullMessages);
+ else
+ return $fullMessages;
+ }
+
+ public function invalid($attribute)
+ {
+ return isset($this->errors[$attribute]);
+ }
+
+ # Deprecated in favor of none().
+ public function blank()
+ {
+ return !(bool)$this->errors;
+ }
+
+ public function none()
+ {
+ return !(bool)$this->errors;
+ }
+
+ public function any()
+ {
+ return !$this->blank();
+ }
+
+ public function all()
+ {
+ return $this->errors;
+ }
+
+ public function count()
+ {
+ $i = 0;
+ foreach ($this->errors as $errors) {
+ $i += count($errors);
+ }
+ return $i;
+ }
+
+ private function _propper_attr($attr)
+ {
+ $attr = ucfirst(strtolower($attr));
+ if (is_int(strpos($attr, '_'))) {
+ $attr = str_replace('_', ' ', $attr);
+ }
+ return $attr;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveModel/Exception/ExceptionInterface.php b/lib/Rails/ActiveModel/Exception/ExceptionInterface.php
new file mode 100755
index 0000000..9f6197c
--- /dev/null
+++ b/lib/Rails/ActiveModel/Exception/ExceptionInterface.php
@@ -0,0 +1,6 @@
+ ActiveRecord\Connection
+ */
+ $_connections = [];
+
+ /**
+ * Name of the active connection.
+ */
+ static private $activeConnectionName;
+
+ /**
+ * If an error ocurred when calling execute_sql(),
+ * it will be stored here.
+ */
+ static private $_last_error;
+
+ static public function setLastError(array $error, $connection_name = null)
+ {
+ self::$_last_error = $error;
+ }
+
+ /**
+ * Adds a connection configuration to the list of available connections.
+ */
+ static public function addConnection($config, $name)
+ {
+ self::$_connection_data[$name] = $config;
+ }
+
+ /**
+ * Sets the default active connection.
+ */
+ static public function setConnection($name)
+ {
+ self::create_connection($name);
+ self::$activeConnectionName = $name;
+ return true;
+ }
+
+ /**
+ * Stablishes a connection from the available connections list.
+ */
+ static protected function create_connection($name)
+ {
+ # If the connection is already created, return.
+ if (isset(self::$_connections[$name]))
+ return;
+ elseif (self::connectionExists($name))
+ self::$_connections[$name] = new Connection(self::$_connection_data[$name], $name);
+ else
+ throw new Exception\RuntimeException(
+ sprintf("Connection '%s' does not exist", $name)
+ );
+ }
+
+ static public function connectionExists($name)
+ {
+ return isset(self::$_connection_data[$name]);
+ }
+
+ static public function set_environment_connection($environment)
+ {
+ if (self::connectionExists($environment))
+ self::setConnection($environment);
+ elseif (self::connectionExists('default'))
+ self::setConnection('default');
+ }
+
+ /**
+ * Returns connection. By default, returns the
+ * currenct active one, or the one matching the $name.
+ *
+ * @return ActiveRecord\Connection
+ */
+ static public function connection($name = null)
+ {
+ if ($name === null) {
+ $name = self::$activeConnectionName;
+ if (!$name)
+ throw new Exception\RuntimeException("No database connection is active");
+ } else {
+ self::create_connection($name);
+ }
+
+ return self::$_connections[$name];
+ }
+
+ /**
+ * Returns active connection's name.
+ */
+ static public function activeConnectionName()
+ {
+ return self::$activeConnectionName;
+ }
+
+ /**
+ * Returns active connection's data.
+ */
+ static public function activeConnectionData()
+ {
+ if (!self::$activeConnectionName)
+ throw new Exception\RuntimeException("No database connection is active");
+ return array_merge(self::$_connection_data[self::$activeConnectionName], ['name' => self::$activeConnectionName]);
+ }
+
+ /**
+ * Used by the system when creating schema files.
+ */
+ static public function connections()
+ {
+ return self::$_connection_data;
+ }
+
+ static public function lastError()
+ {
+ return self::$_last_error;
+ }
+
+ static private function _include_additional_connection($name)
+ {
+ $config = Rails::application()->config()->active_record;
+ if (!empty($config['additional_connections']) && !empty($config['additional_connections'][$name])) {
+ self::addConnection($config['additional_connections'][$name], $name);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This could be somewhere else.
+ */
+ static public function proper_adapter_name($adapter_name)
+ {
+ $adapter_name = strtolower($adapter_name);
+
+ switch ($adapter_name) {
+ case 'mysql':
+ return 'MySql';
+ break;
+
+ case 'sqlite':
+ return 'Sqlite';
+ break;
+
+ default:
+ return $adapter_name;
+ break;
+ }
+ }
+
+ static public function restore_connection()
+ {
+ if (self::$_prev_connection) {
+ self::setConnection(self::$_prev_connection);
+ self::$_prev_connection = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Adapter/AbstractQueryBuilder.php b/lib/Rails/ActiveRecord/Adapter/AbstractQueryBuilder.php
new file mode 100755
index 0000000..42ca093
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Adapter/AbstractQueryBuilder.php
@@ -0,0 +1,62 @@
+connection = $connection;
+ }
+
+ public function executeSql()
+ {
+ $this->_stmt = $this->connection->executeSql($this->_params);
+ if ($this->will_paginate)
+ $this->calculate_found_rows();
+ }
+
+ abstract protected function calculate_found_rows();
+
+ abstract protected function _build_sql();
+
+ public function build_sql(AbstractRelation $query)
+ {
+ $complete_sql = $query->complete_sql();
+ $this->query = $query;
+
+ if ($complete_sql) {
+ list ($sql, $params) = $complete_sql;
+ array_unshift($params, $sql);
+ $this->_params = $params;
+ $this->will_paginate = $query->will_paginate();
+ } else {
+ $this->will_paginate = $query->will_paginate();
+ $this->_build_sql();
+ }
+ }
+
+ public function stmt()
+ {
+ return $this->_stmt;
+ }
+
+ public function row_count()
+ {
+ return $this->_row_count;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Adapter/AbstractTable.php b/lib/Rails/ActiveRecord/Adapter/AbstractTable.php
new file mode 100755
index 0000000..85e4ed7
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Adapter/AbstractTable.php
@@ -0,0 +1,12 @@
+connection->query("SELECT FOUND_ROWS()");
+ $rows = array_shift($rows);
+ $rows = array_shift($rows);
+ $this->_row_count = (int)$rows;
+ }
+
+ protected function _build_sql()
+ {
+ $query = $this->query;
+
+ $params = [];
+
+ $sql = 'SELECT';
+
+ if ($this->will_paginate)
+ $sql .= ' SQL_CALC_FOUND_ROWS';
+
+ if ($query->distinct)
+ $sql .= ' DISTINCT';
+
+ $sql .= ' ' . ($query->select ? implode(', ', $query->select) : '`' . $query->from . '`.*');
+ $sql .= " FROM `" . $query->from . "`";
+ $sql .= ' ' . implode(' ', $query->joins);
+
+ if ($where = $this->_build_where_clause($query)) {
+ $sql .= " WHERE " . $where[0];
+ $params = $where[1];
+ unset($where);
+ }
+
+ if ($query->group)
+ $sql .= ' GROUP BY ' . implode(', ', $query->group);
+
+ if ($query->order)
+ $sql .= ' ORDER BY ' . implode(', ', $query->order);
+
+ if ($query->having) {
+ $sql .= ' HAVING ' . implode(' ', $query->having);
+ $params = array_merge($params, $query->having_params);
+ }
+
+ if ($query->offset && $query->limit)
+ $sql .= ' LIMIT ' . $query->offset . ', ' . $query->limit;
+ elseif ($query->limit)
+ $sql .= ' LIMIT ' . $query->limit;
+
+ array_unshift($params, $sql);
+ $this->_params = $params;
+ }
+
+ private function _build_where_clause($query)
+ {
+ if (!$query->where)
+ return;
+ // if (end($query->_where) == 'name in (?)')
+ // vpe($query->where_params);
+ $where = $where_params = [];
+ $param_count = 0;
+
+ foreach ($query->where as $condition) {
+ # Case: ["foo" => $foo, "bar_baz" => $bar];
+ if (is_array($condition)) {
+ foreach ($condition as $column => $value) {
+ $where[] = $column . ' = ?';
+ $where_params[] = $value;
+ }
+ } else {
+ if ($count = substr_count($condition, '?')) {
+ foreach (range(0, $count - 1) as $i) {
+ if (!array_key_exists($param_count, $query->where_params))
+ throw new Exception\RuntimeException(sprintf("Value for question mark placeholder for WHERE clause section wasn't found: %s", $condition));
+
+ if (is_array($query->where_params[$param_count])) {
+ $condition = preg_replace('/\?/', implode(', ', array_fill(0, count($query->where_params[$param_count]), '?')), $condition, 1);
+ $where_params = array_merge($where_params, $query->where_params[$param_count]);
+ } else {
+ $where_params[] = $query->where_params[$param_count];
+ }
+ $param_count++;
+ }
+ } elseif ($count = substr_count($condition, ':')) {
+ $where_params = $query->where_params;
+ }
+
+ $where[] = $condition;
+ }
+ }
+
+ return [implode(' AND ', $where), $where_params];
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Adapter/MySql/Table.php b/lib/Rails/ActiveRecord/Adapter/MySql/Table.php
new file mode 100755
index 0000000..b94b198
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Adapter/MySql/Table.php
@@ -0,0 +1,55 @@
+executeSql("DESCRIBE `".$table_name."`");
+ if (!$rows = $stmt->fetchAll()) {
+ throw new Exception\RuntimeException(
+ sprintf("Couldn't DESCRIBE %s:\n%s", $table_name, var_export($stmt->errorInfo(), true))
+ );
+ }
+
+ $table_data = $table_indexes = $pri = $uni = [];
+
+ foreach ($rows as $row) {
+ $data = ['type' => $row['Type']];
+
+ if (strpos($row['Type'], 'enum') === 0) {
+ $enum_values = [];
+ foreach (explode(',', substr($row['Type'], 5, -1)) as $opt)
+ $enum_values[] = substr($opt, 1, -1);
+ $data['enum_values'] = $enum_values;
+ }
+
+ $table_data[$row['Field']] = $data;
+ }
+
+ $stmt = $connection->executeSql("SHOW INDEX FROM `".$table_name."`");
+ $idxs = $stmt->fetchAll();
+
+ if ($idxs) {
+ foreach ($idxs as $idx) {
+ if ($idx['Key_name'] == 'PRIMARY') {
+ $pri[] = $idx['Column_name'];
+ } elseif ($idx['Non_unique'] === '0') {
+ $uni[] = $idx['Column_name'];
+ }
+ }
+ }
+
+ if ($pri)
+ $table_indexes['pri'] = $pri;
+ elseif ($uni)
+ $table_indexes['uni'] = $uni;
+
+ return [$table_data, $table_indexes];
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Adapter/Sqlite/QueryBuilder.php b/lib/Rails/ActiveRecord/Adapter/Sqlite/QueryBuilder.php
new file mode 100755
index 0000000..f132968
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Adapter/Sqlite/QueryBuilder.php
@@ -0,0 +1,99 @@
+query->except('limit')->except('offset')->except('select')->select('COUNT(*) as count_all');
+ $this->_build_sql();
+
+ if ($stmt = $this->connection->executeSql($this->_params)) {
+ $rows = $stmt->fetchAll();
+ if (isset($rows[0]['count_all']))
+ return $rows[0]['count_all'];
+ }
+ }
+
+ protected function _build_sql()
+ {
+ $query = $this->query;
+
+ $params = [];
+
+ $sql = 'SELECT';
+
+ if ($query->distinct)
+ $sql .= ' DISTINCT';
+
+ $sql .= ' ' . ($query->select ? implode(', ', $query->select) : '`' . $query->from . '`.*');
+ $sql .= " FROM `" . $query->from . "`";
+ $sql .= ' ' . implode(' ', $query->joins);
+
+ if ($where = $this->_build_where_clause($query)) {
+ $sql .= " WHERE " . $where[0];
+ $params = $where[1];
+ unset($where);
+ }
+
+ if ($query->group)
+ $sql .= ' GROUP BY ' . implode(', ', $query->group);
+
+ if ($query->order)
+ $sql .= ' ORDER BY ' . implode(', ', $query->order);
+
+ if ($query->having) {
+ $sql .= ' HAVING ' . implode(' ', $query->having);
+ $params = array_merge($params, $query->having_params);
+ }
+
+ if ($query->offset && $query->limit)
+ $sql .= ' LIMIT ' . $query->offset . ', ' . $query->limit;
+ elseif ($query->limit)
+ $sql .= ' LIMIT ' . $query->limit;
+
+ array_unshift($params, $sql);
+ $this->_params = $params;
+ }
+
+ private function _build_where_clause($query) {
+ if (!$query->where)
+ return;
+
+ $where = $where_params = [];
+ $param_count = 0;
+
+ foreach ($query->where as $condition) {
+ # Case: ["foo" => $foo, "bar" => $bar];
+ if (is_array($condition)) {
+ foreach ($condition as $column => $value) {
+ $where[] = '`' . $column . '` = ?';
+ $where_params[] = $value;
+ }
+ } else {
+ if ($count = substr_count($condition, '?')) {
+ foreach (range(0, $count - 1) as $i) {
+ if (!isset($query->where_params[$param_count]))
+ throw new Exception\RuntimeException(sprintf("Value for question mark placeholder for WHERE clause part wasn't found (%s)", $condition));
+
+ if (is_array($query->where_params[$param_count])) {
+ $condition = preg_replace('/\?/', implode(', ', array_fill(0, count($query->where_params[$param_count]), '?')), $condition, 1);
+ }
+
+ $where_params[] = $query->where_params[$param_count];
+ $param_count++;
+ }
+ } elseif ($count = substr_count($condition, ':')) {
+ $where_params = $query->where_params;
+ }
+
+ $where[] = $condition;
+ }
+ }
+
+ return [implode(' AND ', $where), $where_params];
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Adapter/Sqlite/Table.php b/lib/Rails/ActiveRecord/Adapter/Sqlite/Table.php
new file mode 100755
index 0000000..a151d53
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Adapter/Sqlite/Table.php
@@ -0,0 +1,36 @@
+executeSql("PRAGMA table_info(`".$table_name."`);");
+
+ if (!$rows = $stmt->fetchAll(PDO::FETCH_ASSOC)) {
+ return $stmt;
+ }
+
+ $table_data = $pri = $uni = [];
+
+ $table_indexes = [
+ 'pri' => [],
+ 'uni' => []
+ ];
+
+ foreach ($rows as $row) {
+ $data = ['type' => $row['type']];
+ $table_data[$row['name']] = $data;
+
+ if ($row['pk'])
+ $table_indexes['pri'][] = $row['name'];
+ }
+
+ return [$table_data, $table_indexes];
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Base.php b/lib/Rails/ActiveRecord/Base.php
new file mode 100755
index 0000000..42a0ede
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Base.php
@@ -0,0 +1,1029 @@
+assignAttributes($attrs, $options);
+ $new_model->_create_do();
+ return $new_model;
+ }
+
+ /**
+ * Can receive parameters that will be directly passed to where();
+ */
+ static public function destroyAll()
+ {
+ $models = call_user_func_array(get_called_class() . '::where', func_get_args())->take();
+ foreach ($models as $m) {
+ $m->destroy();
+ }
+ return $models;
+ }
+
+ # TODO
+ static public function deleteAll(array $conds)
+ {
+
+ }
+
+ static public function update($id, array $attrs)
+ {
+ $attrs_str = [];
+ foreach (array_keys($attrs) as $attr)
+ $attrs_str[] = '`'.$attr.'` = ?';
+ $sql = "UPDATE `" . static::tableName() . "` SET " . implode(', ', $attrs_str) . " WHERE id = ?";
+ array_unshift($attrs, $sql);
+ $attrs[] = $id;
+ static::connection()->executeSql($attrs);
+ }
+
+ /**
+ * Searches if record exists by id.
+ *
+ * How to use:
+ * Model::exists(1);
+ * Model::exists(1, 2, 3);
+ * Model::exists([1, 2, 3]);
+ */
+ static public function exists($params)
+ {
+ $query = self::none();
+
+ if (ctype_digit((string)$params)) {
+ $query->where(['id' => $params]);
+ } else {
+ if (is_array($params))
+ $query->where('id IN (?)', $params);
+ else
+ $query->where('id IN (?)', func_get_args());
+ }
+
+ return $query->exists();
+ }
+
+ static public function countBySql()
+ {
+ $stmt = call_user_func_array([static::connection(), 'executeSql'], func_get_args());
+ if (ActiveRecord::lastError())
+ return false;
+
+ $rows = $stmt->fetchAll(PDO::FETCH_NUM);
+
+ if (isset($rows[0][0]))
+ return (int)$rows[0][0];
+ else
+ return false;
+ }
+
+ static public function maximum($attr)
+ {
+ return self::connection()->selectValue('SELECT MAX(' . $attr . ') FROM ' . static::tableName());
+ }
+
+ static public function I18n()
+ {
+ return Rails::application()->I18n();
+ }
+
+ static public function transaction($params = [], \Closure $block = null)
+ {
+ self::connection()->transaction($params, $block);
+ }
+
+ static public function connectionName()
+ {
+ return null;
+ }
+
+ /**
+ * If called class specifies a connection, such connection will be returned.
+ */
+ static public function connection()
+ {
+ $name = static::connectionName();
+ return ActiveRecord::connection($name);
+ }
+
+ /**
+ * Public properties declared in a model class can be used to avoid defining
+ * setter and getter methods; the value will be set/get directly to/from it.
+ */
+ static public function hasPublicProperty($propName)
+ {
+ $reflection = self::getReflection();
+ return $reflection->hasProperty($propName) && $reflection->getProperty($propName)->isPublic();
+ }
+
+ /**
+ * It is sometimes required an empty collection.
+ */
+ static public function emptyCollection()
+ {
+ return new Collection();
+ }
+
+ static protected function cn()
+ {
+ return get_called_class();
+ }
+
+ static protected function _registry()
+ {
+ if (!self::$registry)
+ self::$registry = new Registry();
+ return self::$registry->model(get_called_class());
+ }
+
+ /**
+ * Creates and returns a non-empty model.
+ * This function is useful to centralize the creation of non-empty models,
+ * since isNewRecord must set to null by passing non_empty.
+ */
+ static private function _create_model(array $data)
+ {
+ self::$preventInit = true;
+ $model = new static();
+ $model->attributes = $data;
+
+ # Check for primary_key and set init_attrs.
+ if (!static::table()->primaryKey()) {
+ $model->storedAttributes = $data;
+ }
+ $model->isNewRecord = false;
+ $model->_register();
+ $model->init();
+
+ return $model;
+ }
+
+ static private function _create_collection(array $data, $query = null)
+ {
+ $models = array();
+
+ foreach ($data as $d)
+ $models[] = self::_create_model($d);
+
+ $coll = new Collection($models, $query);
+ return $coll;
+ }
+
+ static private function getReflection($class = null)
+ {
+ if (!$class) {
+ $class = get_called_class();
+ }
+ if (!isset(self::$cachedReflections[$class])) {
+ self::$cachedReflections[$class] = new \ReflectionClass($class);
+ }
+ return self::$cachedReflections[$class];
+ }
+
+ public function __construct(array $attrs = [])
+ {
+ $this->assignAttributes($attrs);
+ if (!self::$preventInit) {
+ $this->init();
+ } else {
+ self::$preventInit = true;
+ }
+ }
+
+ /**
+ * Checks for the called property in this order
+ * 1. Checks if property is an attribute.
+ * 2. Checks if called property is an already loaded association.
+ * 3. Checks if called property is an association.
+ * If none if these validations is true, the property is actually called
+ * in order to generate an error.
+ */
+ public function __get($prop)
+ {
+ # See Config/default_config.php for more info.
+ // if (!Rails::config()->ar2) {
+ if (static::isAttribute($prop)) {
+ return $this->getAttribute($prop);
+ } elseif ($this->getAssociation($prop) !== null) {
+ return $this->loadedAssociations[$prop];
+ }
+
+ throw new Exception\RuntimeException(
+ sprintf("Tried to get unknown property %s::$%s", get_called_class(), $prop)
+ );
+ // }
+ // # Force error/default behaviour
+ // return $this->$prop;
+ }
+
+ /**
+ * 1. Checks for setter method and calls it if available.
+ * 2. If the property is an attribute, the value is set to it (i.e. default setter for attributes).
+ * 3. The value is set as object property. < This shouldn't happen!
+ */
+ public function __set($prop, $val)
+ {
+ // if (!Rails::config()->ar2) {
+ if (static::isAttribute($prop)) {
+ $this->setAttribute($prop, $val);
+ // } elseif ($this->setterExists($prop)) {
+ // $this->_run_setter($prop, $val);
+ } else {
+ throw new Exception\RuntimeException(
+ sprintf("Trying to set undefined property %s", $prop)
+ );
+ // # Default PHP behaviour.
+ // $this->$prop = $val;
+ }
+ // } else {
+ // if (static::isAttribute($prop)) {
+ // $this->setAttribute($prop, $val);
+ // } else {
+ // throw new Exception\RuntimeException(
+ // sprintf("Trying to set undefined property %s", $prop)
+ // );
+ // }
+ // }
+ }
+
+ public function __isset($prop)
+ {
+ return $this->issetAttribute($prop);
+ }
+
+ /**
+ * Some model's features can be overloaded:
+ * 1. Check if an attribute changed with attributeNameChanged();
+ * Calling this normally would be: attributeChanged('attribute_name');
+ * 2. Get the previous value of an attribute with attributeNameWas();
+ * Normally: attributeWas('attribute_name');
+ * 3. Overload attribute setters. Models *should* define setter
+ * methods for each attribute, but they can be called overloadingly. The correct
+ * or expected name of the setter method is camel-cased, like setAttributeName();
+ * 4. Overload scopes.
+ * 5. Overload associations.
+ * 6. Overload attribute getters. The expected method name is the camel-cased name of
+ * the attribute, like attributeName(), because the "get" prefix is omitted for getters.
+ * Models *should* define getter methods for each attribute.
+ */
+ public function __call($method, $params)
+ {
+ # Overload attributeChange()
+ if (strlen($method) > 7 && substr($method, -7) == 'Changed') {
+ $attribute = static::properAttrName(substr($method, 0, -7));
+ return $this->attributeChanged($attribute);
+ }
+
+ # Overload attributeWas()
+ if (strlen($method) > 3 && substr($method, -3) == 'Was') {
+ $attribute = static::properAttrName(substr($method, 0, -3));
+ return $this->attributeWas($attribute);
+ }
+
+ # Overload attribute setter.
+ if (substr($method, 0, 3) == 'set') {
+ $attrName = substr($method, 3);
+ $underscored = Rails::services()->get('inflector')->underscore($method);
+ if (static::isAttribute($underscored)) {
+ $this->setAttribute($underscored, array_shift($params));
+ return;
+ }
+ }
+
+ # Overload scopes.
+ if ($rel = static::scope($method, $params)) {
+ return $rel;
+ }
+
+ // # Overload associations.
+ // if ($this->getAssociation($method) !== null) {
+ // return $this->loadedAssociations[$method];
+ // }
+
+ // # Overload attributes.
+ // $underscored = Rails::services()->get('inflector')->underscore($method);
+ // if (static::isAttribute($underscored)) {
+ // return $this->getAttribute($underscored);
+ // }
+
+ throw new Exception\BadMethodCallException(
+ sprintf("Call to undefined method %s::%s", get_called_class(), $method)
+ );
+ }
+
+ public function isNewRecord()
+ {
+ return $this->isNewRecord;
+ }
+
+ public function updateAttributes(array $attrs)
+ {
+ $this->assignAttributes($attrs);
+ $this->runCallbacks('before_update');
+
+ /**
+ * iTODO: Must let know save() we're updating, so it will
+ * validate data with action "update" and not "save".
+ * Should separate save() and make this and update_attribute call
+ * something like update()?
+ */
+ if ($this->save(['action' => 'update'])) {
+ $this->runCallbacks('after_update');
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Directly passes the new value to the attributes array and then
+ * saves the record.
+ *
+ * @param string $attrName The name of the attribute.
+ * @param mixed $value The value for the attribute,
+ */
+ public function updateAttribute($attrName, $value)
+ {
+ $this->attributes[$attrName] = $value;
+ return $this->save(['skip_validation' => true, 'skip_callbacks' => true, 'action' => 'update']);
+ }
+
+ /**
+ * Save record
+ *
+ * Saves in the database the properties of the object that match
+ * the columns of the corresponding table in the database.
+ *
+ * Is the model is new, create() will be called instead.
+ *
+ * @array $values: If present, object will be updated according
+ * to this array, otherwise, according to its properties.
+ */
+ public function save(array $opts = array())
+ {
+ // if (empty($opts['skip_validation'])) {
+ // if (!$this->_validate_data(!empty($opts['action']) ? $opts['action'] : 'save'))
+ // return false;
+ // }
+
+ if ($this->isNewRecord()) {
+ if (!$this->_create_do($opts))
+ return false;
+ } else {
+ if (!$this->_validate_data('save'))
+ return false;
+ if (empty($opts['skip_callbacks'])) {
+ if (!$this->runCallbacks('before_save'))
+ return false;
+ }
+ if (!$this->_save_do($opts))
+ return false;
+ if (empty($opts['skip_callbacks'])) {
+ $this->runCallbacks('after_save');
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Delete
+ *
+ * Deletes row from database based on Primary keys.
+ */
+ static public function delete()
+ {
+ // if ($this->_delete_from_db('delete')) {
+ // foreach (array_keys(get_object_vars($this)) as $p)
+ // unset($this->$p);
+ // return true;
+ // }
+ // return false;
+ }
+
+ # Deletes current model from database but keeps model's properties.
+ public function destroy()
+ {
+ return $this->_delete_from_db('destroy');
+ }
+
+ public function errors()
+ {
+ if (!$this->errors)
+ $this->errors = new ActiveModel\Errors();
+ return $this->errors;
+ }
+
+ public function reload()
+ {
+ try {
+ $data = $this->_get_stored_data();
+ } catch (Exception\ExceptionInterface $e) {
+ return false;
+ }
+ $cn = get_called_class();
+ $refl = new \ReflectionClass($cn);
+ $reflProps = $refl->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED);
+ $defProps = $refl->getDefaultProperties();
+
+ foreach ($reflProps as $reflProp) {
+ if ($reflProp->getDeclaringClass() == $cn && !$reflProp->isStatic()) {
+ $this->{$reflProp->name} = $defProps[$reflProp->name];
+ }
+ }
+
+ $this->isNewRecord = false;
+
+ $this->attributes = $data->attributes();
+ // $this->init();
+
+ return true;
+ }
+
+ public function asJson()
+ {
+ return $this->attributes();
+ }
+
+ public function toJson()
+ {
+ return json_encode($this->asJson());
+ }
+
+ # TODO:
+ public function toXml(array $params = [])
+ {
+ if (!isset($params['attributes'])) {
+ // $this->_merge_model_attributes();
+ $attrs = $this->attributes();
+ } else {
+ $attrs = $params['attributes'];
+ }
+
+ !isset($params['root']) && $params['root'] = strtolower(str_replace('_', '-', self::cn()));
+
+ if (!isset($params['builder'])) {
+ $xml = new \Rails\Xml\Xml($attrs, $params);
+ return $xml->output();
+ } else {
+ $builder = $params['builder'];
+ unset($params['builder']);
+ $builder->build($attrs, $params);
+ }
+ }
+
+ public function getAssociation($name)
+ {
+ if (isset($this->loadedAssociations[$name])) {
+ return $this->loadedAssociations[$name];
+ } elseif ($assoc = $this->get_association_data($name)) {
+ $model = $this->_load_association($name, $assoc[0], $assoc[1]);
+ $this->loadedAssociations[$name] = $model;
+ return $this->loadedAssociations[$name];
+ }
+ }
+
+ /**
+ * ***************************
+ * Default protected methods {
+ * ***************************
+ * attrAccessible and attrProtected can be found in Base\Methods\AttributeMethods.
+ */
+
+ /**
+ * Code executed when initializing the object.
+ * Called by _create_model() and _create_do().
+ */
+ protected function init()
+ {
+ }
+
+ protected function associations()
+ {
+ return [];
+ }
+
+ /**
+ * Example:
+ *
+ * return [
+ * // Validate attributes
+ * 'attribute_name' => [
+ * 'property' => rules...
+ * ],
+ *
+ * // Passing a value or an index that isn't recognized as
+ * // attribute will be considered as custom validation method.
+ * 'methodName',
+ * 'method2Name' => [ 'on' => [ actions... ] ],
+ * ];
+ *
+ * Custom validation methods must set the error manually. They aren't expected to
+ * return anything.
+ */
+ protected function validations()
+ {
+ return [];
+ }
+
+ protected function callbacks()
+ {
+ return [];
+ }
+ /* } */
+
+ /**
+ * Returns model's current data in the database or using storedAttributes.
+ */
+ protected function _get_stored_data()
+ {
+ if (!($prim_key = static::table()->primaryKey()) && !$this->storedAttributes) {
+ throw new Exception\RuntimeException(
+ "Can't find data without primary key nor storedAttributes"
+ );
+ }
+
+ $query = static::none();
+
+ if ($prim_key) {
+ $query->where('`'.$prim_key.'` = ?', $this->$prim_key);
+ } else {
+ $model = new static();
+ $cols_names = static::table()->columnNames();
+ foreach ($this->storedAttributes as $name => $value) {
+ if (in_array($name, $cols_names)) {
+ $model->attributes[$name] = $value;
+ }
+ }
+
+ if (!$model->attributes)
+ throw new Exception\RuntimeException(
+ "Model rebuilt from storedAttributes failed"
+ );
+ else
+ return $model;
+ }
+
+ $current = $query->first();
+
+ if (!$current)
+ throw new Exception\RuntimeException(
+ "Row not found in database (searched with storedAttributes)"
+ );
+ else
+ return $current;
+ }
+
+ protected function runCallbacks($callback_name)
+ {
+ $callbacks = array();
+
+ $tmp = $this->callbacks();
+ if (isset($tmp[$callback_name])) {
+ $callbacks = $tmp[$callback_name];
+ }
+
+ $callbacks = array_unique(array_filter(array_merge($callbacks, $this->_get_parents_callbacks($callback_name))));
+
+ if ($callbacks) {
+ foreach ($callbacks as $method) {
+ if (false === $this->$method()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array|Closure $params - Additional parameters to customize the query for the association
+ */
+ private function _load_association($prop, $type, $params)
+ {
+ return $this->{'_find_' . $type}($prop, $params);
+ }
+
+ private function _get_parents_callbacks($callback_name)
+ {
+ $all_callbacks = array();
+ if (($class = get_parent_class($this)) != 'Rails\ActiveRecord\Base') {
+ $class = self::cn();
+ $obj = new $class();
+ while (($class = get_parent_class($obj)) != 'Rails\ActiveRecord\Base') {
+ $obj = new $class();
+ if ($callbacks = $obj->callbacks()) {
+ if (isset($callbacks[$callback_name]))
+ $all_callbacks = array_merge($callbacks, $callbacks[$callback_name]);
+ }
+ }
+ }
+ return $all_callbacks;
+ }
+
+ # Returns association property names.
+ private function _associations_names()
+ {
+ $associations = array();
+ foreach ($this->associations() as $assocs) {
+ foreach ($assocs as $k => $v)
+ $associations[] = is_int($k) ? $v : $k;
+ }
+ return $associations;
+ }
+
+ /**
+ * @return bool
+ * @see validations()
+ */
+ private function _validate_data($action)
+ {
+ if (!$this->runCallbacks('before_validation'))
+ return false;
+
+ $validation_success = true;
+ $modelClass = get_called_class();
+ $classProps = get_class_vars($modelClass);
+
+ foreach ($this->validations() as $attrName => $validations) {
+ /**
+ * This should only happen when passing a custom validation method with
+ * no validation options.
+ */
+ if (is_int($attrName)) {
+ $attrName = $validations;
+ $validations = [];
+ }
+
+ if (static::isAttribute($attrName) || array_key_exists($attrName, $classProps)) {
+ foreach ($validations as $type => $params) {
+ if (!is_array($params)) {
+ $params = [$params];
+ }
+
+ if ($modelClass::isAttribute($attrName)) {
+ $value = $this->getAttribute($attrName);
+ } else {
+ $value = $this->$attrName;
+ }
+
+ $validation = new Validator($type, $value, $params);
+ $validation->set_params($action, $this, $attrName);
+
+ if (!$validation->validate()->success()) {
+ $validation->set_error_message();
+ $validation_success = false;
+ }
+ }
+ } else {
+ /**
+ * The attrName passed isn't an attribute nor a property, so we assume it's a method.
+ *
+ * $attrName becomes the name of the method.
+ * $validations becomes validation options.
+ */
+ if (!empty($validations['on']) && !in_array($action, $validations['on'])) {
+ continue;
+ }
+ // $this->getAttribute($attrName);
+ $this->$attrName();
+ if ($this->errors()->any()) {
+ $validation_success = false;
+ }
+ }
+ }
+ return $validation_success;
+ }
+
+ private function _create_do()
+ {
+ if (!$this->runCallbacks('before_validation_on_create')) {
+ return false;
+ } elseif (!$this->_validate_data('create')) {
+ return false;
+ }
+
+ $this->runCallbacks('after_validation_on_create');
+
+ if (!$this->runCallbacks('before_save'))
+ return false;
+
+ if (!$this->runCallbacks('before_create'))
+ return false;
+
+ $this->_check_time_column('created_at');
+ $this->_check_time_column('updated_at');
+ $this->_check_time_column('created_on');
+ $this->_check_time_column('updated_on');
+
+ $cols_values = $cols_names = array();
+
+ // $this->_merge_model_attributes();
+
+ foreach ($this->attributes() as $attr => $val) {
+ $proper = static::properAttrName($attr);
+ if (!static::table()->columnExists($proper)) {
+ continue;
+ }
+ $cols_names[] = '`'.$attr.'`';
+ $cols_values[] = $val;
+ $init_attrs[$attr] = $val;
+ }
+
+ if (!$cols_values)
+ return false;
+
+ $binding_marks = implode(', ', array_fill(0, (count($cols_names)), '?'));
+ $cols_names = implode(', ', $cols_names);
+
+ $sql = 'INSERT INTO `'.static::tableName().'` ('.$cols_names.') VALUES ('.$binding_marks.')';
+
+ array_unshift($cols_values, $sql);
+
+ static::connection()->executeSql($cols_values);
+
+ $id = static::connection()->lastInsertId();
+
+ $primary_key = static::table()->primaryKey();
+
+ if ($primary_key && count($primary_key) == 1) {
+ if (!$id) {
+ $this->errors()->addToBase('Couldn\'t retrieve new primary key.');
+ return false;
+ }
+
+ if ($pri_key = static::table()->primaryKey()) {
+ $this->setAttribute($pri_key, $id);
+ }
+ } else {
+ $this->storedAttributes = $init_attrs;
+ }
+
+ $this->isNewRecord = false;
+ // $this->init();
+
+ $this->runCallbacks('after_create');
+ $this->runCallbacks('after_save');
+
+ return true;
+ }
+
+ private function _save_do()
+ {
+ $w = $wd = $q = $d = array();
+
+ $dt = static::table()->columnNames();
+
+ try {
+ $current = $this->_get_stored_data();
+ } catch (Exception\ExceptionInterface $e) {
+ $this->errors()->addToBase($e->getMessage());
+ return;
+ }
+
+ $has_primary_keys = false;
+
+ foreach (static::table()->indexes() as $idx) {
+ $w[] = '`'.$idx.'` = ?';
+ $wd[] = $current->$idx;
+ }
+
+ if ($w) {
+ $has_primary_keys = true;
+ }
+
+ // $this->_merge_model_attributes();
+
+ foreach ($this->attributes() as $prop => $val) {
+ # Can't update properties that don't have a column in DB, or
+ # PRImary keys, or time columns.
+ if (!in_array($prop, $dt) || $prop == 'created_at' || $prop == 'updated_at' ||
+ $prop == 'created_on' || $prop == 'updated_on'
+ ) {
+ continue;
+ } elseif (!$has_primary_keys) {
+ $w[] = '`'.$prop.'` = ?';
+ $wd[] = $current->$prop;
+ // foreach ($current->attributes() as $
+ }
+ // } elseif (!$has_primary_keys && $val === $current->$prop) {
+ // $w[] = '`'.$prop.'` = ?';
+ // $wd[] = $current->$prop;
+
+ // } else
+ if ($val != $current->$prop) {
+ $this->setChangedAttribute($prop, $current->$prop);
+ $q[] = '`'.$prop.'` = ?';
+ $d[] = $val;
+ }
+ }
+
+ # Update `updated_at|on` if exists.
+ if ($this->_check_time_column('updated_at')) {
+ $q[] = "`updated_at` = ?";
+ $d[] = $this->updated_at;
+ } elseif ($this->_check_time_column('updated_on')) {
+ $q[] = "`updated_on` = ?";
+ $d[] = $this->updated_on;
+ }
+
+ if ($q) {
+ $q = "UPDATE `" . static::tableName() . "` SET " . implode(', ', $q);
+
+ $w && $q .= ' WHERE '.implode(' AND ', $w);
+
+ $q .= ' LIMIT 1';
+
+ $d = array_merge($d, $wd);
+ array_unshift($d, $q);
+
+ static::connection()->executeSql($d);
+
+ if (ActiveRecord::lastError()) {
+ $this->errors()->addToBase(ActiveRecord::lastError());
+ return false;
+ }
+ }
+
+ $this->_update_init_attrs();
+ return true;
+ }
+
+ private function _delete_from_db($type)
+ {
+ if (!$this->runCallbacks('before_'.$type)) {
+ return false;
+ }
+
+ $w = $wd = [];
+
+ if ($keys = self::table()->indexes()) {
+ foreach ($keys as $k) {
+ $w[] = '`' . static::tableName() . '`.`'.$k.'` = ?';
+ $wd[] = $this->$k;
+ }
+ } elseif ($this->storedAttributes) {
+ foreach ($this->storedAttributes as $attr => $val) {
+ $w[] = '`'.$attr.'` = ?';
+ $wd[] = $val;
+ }
+ } else {
+ throw new Exception\LogicException(
+ "Can't delete model without attributes"
+ );
+ }
+
+ $w = implode(' AND ', $w);
+
+ $query = 'DELETE FROM `' . static::tableName() . '` WHERE '.$w;
+ array_unshift($wd, $query);
+
+ static::connection()->executeSql($wd);
+
+ $this->runCallbacks('after_'.$type);
+
+ return true;
+ }
+
+ private function get_association_data($prop)
+ {
+ if ($assocs = $this->associations()) {
+ foreach ($assocs as $type => $assoc) {
+ foreach ($assoc as $name => $params) {
+ if (is_int($name)) {
+ $name = $params;
+ $params = array();
+ }
+
+ if ($name == $prop) {
+ return array($type, $params);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private function _register()
+ {
+ self::_registry()->register($this);
+ }
+
+ /**
+ * Check time column
+ *
+ * Called by save() and create(), checks if $column
+ * exists and automatically sets a value to it.
+ */
+ private function _check_time_column($column)
+ {
+ if (!static::table()->columnExists($column))
+ return false;
+
+ $type = static::table()->columnType($column);
+
+ if ($type == 'datetime')
+ $time = date('Y-m-d H:i:s');
+ elseif ($type == 'year')
+ $time = date('Y');
+ elseif ($type == 'date')
+ $time = date('Y-m-d');
+ elseif ($type == 'time')
+ $time = date('H:i:s');
+ elseif ($type == 'timestamp')
+ $time = time();
+ else
+ return false;
+
+ $this->attributes[$column] = $time;
+
+ return true;
+ }
+
+ private function _update_init_attrs()
+ {
+ foreach (array_keys($this->storedAttributes) as $name) {
+ if (isset($this->attributes[$name]))
+ $this->storedAttributes[$name] = $this->attributes[$name];
+ }
+ }
+
+ private function _getter_for($prop)
+ {
+ $camelized = Rails::services()->get('inflector')->camelize($prop);
+ $method = 'get' . ucfirst($camelized);
+
+ if (method_exists($this, $method)) {
+ return $this->$method();
+ } elseif (method_exists($this, $camelized)) {
+ return $this->$camelized();
+ }
+ return null;
+ }
+
+ private function setterExists($attrName)
+ {
+ $inflector = Rails::services()->get('inflector');
+ $reflection = self::getReflection();
+
+ $setter = 'set' . $inflector->camelize($attrName);
+
+ if ($reflection->hasMethod($setter) && $reflection->getMethod($setter)->isPublic()) {
+ return $setter;
+ } else {
+ false;
+ }
+ }
+}
diff --git a/lib/Rails/ActiveRecord/Base/Methods/AssociationMethods.php b/lib/Rails/ActiveRecord/Base/Methods/AssociationMethods.php
new file mode 100755
index 0000000..b3d9bbc
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Base/Methods/AssociationMethods.php
@@ -0,0 +1 @@
+createdAt()
+ * The corresponding column for this attribute would be "created_at",
+ * therefore, the attribute name will be converted.
+ * For some cases, to disable the camel to lower conversion,
+ * this property can be set to false.
+ */
+ static protected $convertAttributeNames = true;
+
+ /**
+ * Expected to hold only the model's attributes.
+ */
+ protected $attributes = [];
+
+ /**
+ * Holds data grabbed from the database for models
+ * without a primary key, to be able to update them.
+ * Hoever, models should always have a primary key.
+ */
+ private $storedAttributes = array();
+
+ private $changedAttributes = array();
+
+ static public function convertAttributeNames($value = null)
+ {
+ if (null !== $value) {
+ static::$convertAttributeNames = (bool)$value;
+ } else {
+ return static::$convertAttributeNames;
+ }
+ }
+
+ static public function isAttribute($name)
+ {
+ // if (!Rails::config()->ar2) {
+ // return static::table()->columnExists(static::properAttrName($name));
+ // } else {
+ return static::table()->columnExists($name);
+ // }
+ }
+
+ /**
+ * This method allows to "overloadingly" get attributes this way:
+ * $model->parentId; instead of $model->parent_id.
+ */
+ static public function properAttrName($name)
+ {
+ if (static::convertAttributeNames()) {
+ $name = \Rails::services()->get('inflector')->underscore($name);
+ }
+ return $name;
+ }
+
+ /**
+ * @throw Exception\InvalidArgumentException
+ */
+ public function getAttribute($name)
+ {
+ if (array_key_exists($name, $this->attributes)) {
+ return $this->attributes[$name];
+ // } elseif (!Rails::config()->ar2 && static::table()->columnExists(static::properAttrName($name))) {
+ // return null;
+ } elseif (static::table()->columnExists($name)) {
+ return null;
+ }
+
+ throw new Exception\InvalidArgumentException(
+ sprintf("Trying to get non-attribute '%s' from model %s", $name, get_called_class())
+ );
+ }
+
+ public function setAttribute($name, $value)
+ {
+ if (!self::isAttribute($name)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf("Trying to set non-attribute '%s' for model %s", $name, get_called_class())
+ );
+ }
+
+ if ((string)$this->getAttribute($name) != (string)$value) {
+ $this->setChangedAttribute($name, $value);
+ }
+ $this->attributes[$name] = $value;
+ return $this;
+ }
+
+ public function issetAttribute($name)
+ {
+ if (!self::isAttribute($name)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf("'%s' isn't an attribute for model %s", $name, get_called_class())
+ );
+ }
+
+ return isset($this->attributes[$name]);
+ }
+
+ /**
+ * Add/change attributes to model
+ *
+ * Filters protected attributes of the model.
+ * Also calls the "getAttribute()" method, if exists, of the model,
+ * in case extra operation is needed when changing certain attribute.
+ * It's intended to be an equivalent to "def attribute=(val)" in rails.
+ * E.g. "is_held" for post model.
+ *
+ * @see _run_setter()
+ */
+ public function assignAttributes(array $attrs, array $options = [])
+ {
+ if (!$attrs) {
+ return;
+ }
+
+ if (empty($options['without_protection'])) {
+ $this->filterProtectedAttributes($attrs);
+ }
+
+ // if (!Rails::config()->ar2) {
+ // foreach ($attrs as $attr => $v) {
+ // if ($this->setterExists($attr)) {
+ // $this->_run_setter($attr, $v);
+ // } else {
+ // $this->$attr = $v;
+ // }
+ // }
+ // return;
+ // }
+
+ // $inflector = Rails::services()->get('inflector');
+ // $reflection = new \ReflectionClass(get_called_class());
+
+ foreach ($attrs as $attrName => $value) {
+ if (self::isAttribute($attrName)) {
+ $this->setAttribute($attrName, $value);
+ } else {
+ if ($setterName = $this->setterExists($attrName)) {
+ $this->$setterName($value);
+ // $setter = 'set' . $inflector->camelize($attrName);
+ } elseif (self::hasPublicProperty($attrName)) {
+ $this->$attrName = $value;
+ // if ($reflection->hasMethod($setter) && $reflection->getMethod($setter)->isPublic()) {
+ // $this->$setter($value);
+ } else {
+ throw new Exception\RuntimeException(
+ sprintf("Can't write unknown attribute '%s' for model %s", $attrName, get_called_class())
+ );
+ }
+ }
+ }
+ }
+
+ public function attributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * The changedAttributes array is filled upon updating a record.
+ * When updating, the stored data of the model is retrieved and checked
+ * against the data that will be saved. If an attribute changed, the old value
+ * is stored in this array.
+ *
+ * Calling a method that isn't defined, ending in Changed, for example nameChanged() or
+ * categoryIdChanged(), is the same as calling attributeChanged('name') or
+ * attributeChanged('category_id').
+ *
+ * @return bool
+ * @see attributeWas()
+ */
+ public function attributeChanged($attr)
+ {
+ return array_key_exists($attr, $this->changedAttributes);
+ }
+
+ /**
+ * This method returns the previous value of an attribute before updating a record. If
+ * it was not changed, returns null.
+ */
+ public function attributeWas($attr)
+ {
+ return $this->attributeChanged($attr) ? $this->changedAttributes[$attr] : null;
+ }
+
+ public function changedAttributes()
+ {
+ return $this->changedAttributes;
+ }
+
+ /**
+ * List of the attributes that can't be changed in the model through
+ * assignAttributes().
+ * If both attrAccessible() and attrProtected() are present in the model,
+ * only attrAccessible() will be used.
+ *
+ * Return an empty array so no attributes are protected (except the default ones).
+ */
+ protected function attrProtected()
+ {
+ return null;
+ }
+
+ /**
+ * List of the only attributes that can be changed in the model through
+ * assignAttributes().
+ * If both attrAccessible() and attrProtected() are present in the model,
+ * only attrAccessible() will be used.
+ *
+ * Return an empty array so no attributes are accessible.
+ */
+ protected function attrAccessible()
+ {
+ return null;
+ }
+
+ protected function setChangedAttribute($attr, $oldValue)
+ {
+ $this->changedAttributes[$attr] = $oldValue;
+ }
+
+ private function filterProtectedAttributes(&$attributes)
+ {
+ # Default protected attributes
+ $default_columns = ['created_at', 'updated_at', 'created_on', 'updated_on'];
+
+ if ($pk = static::table()->primaryKey()) {
+ $default_columns[] = $pk;
+ }
+
+ $default_protected = array_fill_keys(array_merge($default_columns, $this->_associations_names()), true);
+ $attributes = array_diff_key($attributes, $default_protected);
+
+ if (is_array($attrs = $this->attrAccessible())) {
+ $attributes = array_intersect_key($attributes, array_fill_keys($attrs, true));
+ } elseif (is_array($attrs = $this->attrProtected())) {
+ $attributes = array_diff_key($attributes, array_fill_keys($attrs, true));
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Base/Methods/CounterMethods.php b/lib/Rails/ActiveRecord/Base/Methods/CounterMethods.php
new file mode 100755
index 0000000..bdb35b0
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Base/Methods/CounterMethods.php
@@ -0,0 +1,26 @@
+ 1]);
+ }
+
+ static public function decrementCounter($counter_name, $id)
+ {
+ return self::updateCounters([$id], [$counter_name => -1]);
+ }
+
+ static public function updateCounters(array $ids, array $counters)
+ {
+ if (!is_array($ids))
+ $ids = [$ids];
+ $values = [];
+ foreach ($counters as $name => $value)
+ $values[] = "`" . $name . "` = `".$name."` " . ($value > 0 ? '+' : '-') . " 1";
+ $sql = "UPDATE `".self::tableName()."` SET ".implode(', ', $values)." WHERE id IN (?)";
+ return self::connection()->executeSql($sql, $ids);
+ }
+}
diff --git a/lib/Rails/ActiveRecord/Base/Methods/ModelSchemaMethods.php b/lib/Rails/ActiveRecord/Base/Methods/ModelSchemaMethods.php
new file mode 100755
index 0000000..60e7082
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Base/Methods/ModelSchemaMethods.php
@@ -0,0 +1,51 @@
+reloadSchema();
+ self::$tables[$cn] = $table;
+ }
+ return self::$tables[$cn];
+ }
+
+ static public function tableName()
+ {
+ $cn = str_replace('\\', '_', get_called_class());
+ $inf = \Rails::services()->get('inflector');
+
+ $tableName = $inf->underscore($inf->pluralize($cn));
+
+ return static::tableNamePrefix() . $tableName . static::tableNameSuffix();
+ }
+
+ static public function tableNamePrefix()
+ {
+ return '';
+ }
+
+ static public function tableNameSuffix()
+ {
+ return '';
+ }
+
+ /**
+ * @return ModelSchema
+ */
+ static protected function initTable()
+ {
+ return new ModelSchema(static::tableName(), static::connection());
+ }
+
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Base/Methods/RelationMethods.php b/lib/Rails/ActiveRecord/Base/Methods/RelationMethods.php
new file mode 100755
index 0000000..7861696
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Base/Methods/RelationMethods.php
@@ -0,0 +1,240 @@
+ $id])->first())
+ return $model;
+ throw new Rails\ActiveRecord\Exception\RecordNotFoundException(
+ sprintf("Couldn't find %s with id = %d.", self::cn(), $id)
+ );
+ }
+
+ /**
+ * "Instantiator" methods {
+ */
+ static public function from()
+ {
+ return self::createRelation('select', func_get_args());
+ }
+
+ static public function group()
+ {
+ return self::createRelation('select', func_get_args());
+ }
+
+ static public function having()
+ {
+ return self::createRelation('having', func_get_args());
+ }
+
+ static public function joins()
+ {
+ return self::createRelation('joins', func_get_args());
+ }
+
+ static public function limit()
+ {
+ return self::createRelation('limit', func_get_args());
+ }
+
+ # TODO
+ static public function unscoped()
+ {
+ return new Relation(get_called_class(), static::tableName());
+ }
+
+ /**
+ * This is the correct method to instantiate an empty relation.
+ */
+ static public function none()
+ {
+ // $cn = self::cn();
+ // return new Relation($cn, static::tableName());
+ return self::createRelation();
+ }
+
+ static public function offset()
+ {
+ return self::createRelation('offset', func_get_args());
+ }
+
+ static public function order()
+ {
+ return self::createRelation('order', func_get_args());
+ }
+
+ static public function select()
+ {
+ return self::createRelation('select', func_get_args());
+ }
+
+ static public function distinct()
+ {
+ return self::createRelation('distinct', func_get_args());
+ }
+
+ static public function where()
+ {
+ return self::createRelation('where', func_get_args());
+ }
+ /**
+ * }
+ */
+
+ /**
+ * Take all records.
+ */
+ static public function all()
+ {
+ return self::none()->take();
+ }
+
+ /**
+ * For directly pagination without conditions.
+ */
+ static public function paginate($page, $per_page)
+ {
+ $query = self::createRelation();
+ return $query->paginate($page, $per_page);
+ }
+
+ /**
+ * @return ActiveRecord\Collection
+ */
+ static public function createModelsFromQuery(AbstractRelation $query)
+ {
+ if ($query->will_paginate()) {
+ $params = [
+ 'page' => $query->get_page(),
+ 'perPage' => $query->get_per_page(),
+ 'offset' => $query->get_offset(),
+ 'totalRows' => $query->get_row_count()
+ ];
+ } else
+ $params = [];
+
+ return self::_create_collection($query->get_results()->fetchAll(PDO::FETCH_ASSOC), $params);
+ }
+
+ static protected function createRelation($init_method = null, array $init_args = [])
+ {
+ $cn = self::cn();
+ $query = new Relation($cn, static::tableName());
+ if ($init_method)
+ call_user_func_array([$query, $init_method], $init_args);
+ return $query;
+ }
+
+ /**
+ * When calling this method for pagination, how do we tell it
+ * the values for page and per_page? That's what extra_params is for...
+ * Although maybe it's not the most elegant solution.
+ * extra_params accepts 'page' and 'per_page', they will be sent to Query
+ * where they will be parsed.
+ */
+ static public function findBySql($sql, array $params = array(), array $extra_params = array())
+ {
+ $query = self::createRelation();
+
+ $query->complete_sql($sql, $params, $extra_params);
+
+ return $query->take();
+ }
+
+ private function _find_has_one($prop, array $params)
+ {
+ empty($params['class_name']) && $params['class_name'] = Rails::services()->get('inflector')->camelize($prop);
+
+ $builder = new Association($params, $this);
+ $builder->build_query();
+ return $builder->get_query()->first() ?: false;
+ }
+
+ /**
+ * @param array $params - Additional parameters to customize the query for the association
+ */
+ private function _find_has_many($prop, $params)
+ {
+ empty($params['class_name']) && $params['class_name'] = rtrim(ucfirst($prop), 's');
+
+ $builder = new Association($params, $this);
+ $builder->build_query();
+
+ return $builder->get_query()->take() ?: false;
+ }
+
+ private function _find_belongs_to($prop, array $params)
+ {
+ empty($params['class_name']) && $params['class_name'] = ucfirst($prop);
+
+ $foreign_key = !empty($params['foreign_key']) ? $params['foreign_key'] : Rails::services()->get('inflector')->underscore($prop) . '_id';
+
+ if ($this->getAttribute($foreign_key)) {
+ return $params['class_name']::where(['id' => $this->getAttribute($foreign_key)])->first() ?: false;
+ } else {
+ return false;
+ }
+ }
+
+ private function _find_has_and_belongs_to_many($prop, array $params)
+ {
+ $find_params = [];
+
+ empty($params['class_name']) && $params['class_name'] = ucfirst(Rails::services()->get('inflector')->singularize($prop));
+
+ $find_params['class_name'] = $params['class_name'];
+ $find_params['from'] = $find_params['class_name']::tableName();
+
+ empty($find_params['join_table']) && $find_params['join_table'] = $find_params['class_name']::tableName() . '_' . $find_params['from'];
+
+ empty($find_params['join_type']) && $find_params['join_type'] = 'join';
+ empty($find_params['join_table_key']) && $find_params['join_table_key'] = 'id';
+ empty($find_params['association_foreign_key']) && $find_params['association_foreign_key'] = substr($find_params['from'], 0, -1) . '_id';
+
+ $joins = strtoupper($find_params['join_type']) . ' `' . $find_params['join_table'] . '` ON `' . $find_params['from'] . '`.`' . $find_params['join_table_key'] . '` = `' . $find_params['join_table'] . '`.`' . $find_params['association_foreign_key'] . '`';
+
+ ## Having Post model with has_and_belongs_to_many => tags (Tag model):
+ // 'join_table' => posts_tags [not this => (or tags_posts)]
+ // 'join_table_key' => id
+ // 'foreign_key' => post_id
+ // 'association_foreign_key' => tag_id
+
+ # Needed SQL params
+ // 'select' => posts.*
+ // 'joins' => JOIN posts_tags ON posts.id = posts_tags.post_id',
+ // 'conditions' => array('posts_tags.post_id = ?', $this->id)
+
+ /**
+
+ 'has_and_belongs_to_many' => [
+ 'tags' => [
+ 'join_type' => 'join',
+ 'join_table_key' => 'id',
+ 'foreign_key' => 'post_id',
+ 'association_foreign_key' => 'tag_id',
+ 'select' => 'posts.*'
+ ]
+ ]
+
+ */
+ $relation = new Association($find_params, $this);
+ $relation->build_query();
+ $relation->get_query()->joins($joins);
+ return $relation->get_query()->take() ?: false;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Base/Methods/ScopingMethods.php b/lib/Rails/ActiveRecord/Base/Methods/ScopingMethods.php
new file mode 100755
index 0000000..4fae004
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Base/Methods/ScopingMethods.php
@@ -0,0 +1,63 @@
+scopes();
+
+ if (isset($scopes[$name])) {
+ $lambda = $scopes[$name];
+ $relation = new Relation($cn, static::tableName());
+
+ $lambda = $lambda->bindTo($relation);
+ call_user_func_array($lambda, $params);
+ return $relation;
+ } else
+ return false;
+ }
+
+ /**
+ * Defines scopes.
+ *
+ * Eg:
+ *
+ class Book extends ActiveRecord\Base
+ {
+ static protected function scopes()
+ {
+ return [
+ 'fantasy' => function() {
+ $this->where(['genre' => 'fantasy']);
+ },
+ 'available' => function() {
+ $this->where(['status' => 'available']);
+ },
+ 'author' => function($author_name, $limit = 0) {
+ $this->where('author_name = ?', $author_name);
+ if ($limit)
+ $this->limit($limit);
+ }
+ ];
+ }
+ }
+ * The anonymous function is binded to an ActiveRecord\Relation object.
+ *
+ * Not supporting default scope for now.
+ * Not supporting static methods as scopes, as they couldn't
+ * be called from a model instance.
+ */
+ protected function scopes()
+ {
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Collection.php b/lib/Rails/ActiveRecord/Collection.php
new file mode 100755
index 0000000..7c51301
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Collection.php
@@ -0,0 +1,73 @@
+members = $members;
+ $this->set_extra_params($data);
+ }
+
+ public function currentPage()
+ {
+ return $this->page;
+ }
+
+ public function perPage()
+ {
+ return $this->perPage;
+ }
+
+ public function offset()
+ {
+ return $this->offset;
+ }
+
+ public function totalPages()
+ {
+ return $this->totalPages;
+ }
+
+ public function totalRows()
+ {
+ return $this->totalRows;
+ }
+
+ public function previousPage()
+ {
+ return $this->page - 1 ?: false;
+ }
+
+ public function nextPage()
+ {
+ return $this->page + 1 > $this->totalPages ? false : $this->page + 1;
+ }
+
+ private function set_extra_params($params)
+ {
+ if ($params) {
+ $params = array_intersect_key($params, array_fill_keys(array('page', 'perPage', 'offset', 'totalRows'), null));
+ foreach($params as $k => $v) {
+ $this->$k = (int)$v;
+ }
+ }
+ if ($this->totalRows && $this->perPage) {
+ $this->totalPages = (int)ceil($this->totalRows / $this->perPage);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Connection.php b/lib/Rails/ActiveRecord/Connection.php
new file mode 100755
index 0000000..b89bd26
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Connection.php
@@ -0,0 +1,265 @@
+default_connection_config(), $config);
+
+ if (empty($config['dsn'])) {
+ if ($dsn = $this->build_dsn($config))
+ $config['dsn'] = $dsn;
+ else
+ throw new Exception\ErrorException(sprintf("Not enough info to create DSN string for connection '%s'", $name));
+ }
+
+ $this->pdo = $this->create_connection($config);
+ } elseif ($config instanceof PDO) {
+ $this->pdo = $config;
+ } else {
+ throw new Exception\InvalidArgumentException('Connection must be either array or instance of PDO.');
+ }
+
+ $this->adapter_name = ActiveRecord::proper_adapter_name($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
+
+ $this->name = $name;
+ }
+
+ protected function build_dsn($config)
+ {
+ if (!isset($config['adapter']))
+ return;
+
+ switch ($config['adapter']) {
+ case 'mysql':
+ $str = $config['adapter'];
+
+ $params = [];
+ if (isset($config['host']))
+ $params['host'] = $config['host'];
+ if (isset($config['database']))
+ $params['dbname'] = $config['database'];
+ if (isset($config['port']))
+ $params['port'] = $config['port'];
+ if (isset($config['charset']))
+ $params['charset'] = $config['charset'];
+
+ if (!isset($config['dsn_params'])) {
+ $config['dsn_params'] = [];
+ }
+
+ $params = array_merge($params, $config['dsn_params']);
+
+ if ($params) {
+ $str .= ':';
+ foreach ($params as $key => $value)
+ $str .= $key . '=' . $value . ';';
+ }
+
+ return $str;
+
+ case 'sqlite':
+ $str = $config['adapter'];
+ if (isset($config['database'])) {
+ $str .= ':' . Rails::root() . '/' . $config['database'] . '';
+ }
+ return $str;
+ }
+ }
+
+ protected function default_connection_config()
+ {
+ return [
+ 'username' => null,
+ 'password' => null,
+ 'driver_options' => [],
+ 'pdo_attributes' => []
+ ];
+ }
+
+ /**
+ * Executes the sql and returns the statement.
+ */
+ public function executeSql()
+ {
+ $params = func_get_args();
+ $sql = array_shift($params);
+
+ if (!$sql)
+ throw new Exception\LogicException("Can't execute SQL without SQL");
+ elseif (is_array($sql)) {
+ $params = $sql;
+ $sql = array_shift($params);
+ }
+ $this->_parse_query_multimark($sql, $params);
+
+ if (!$stmt = $this->pdo->prepare($sql)) {
+ list($code, $drvrcode, $msg) = $this->pdo->errorInfo();
+ $e = new Exception\QueryException(
+ sprintf("[PDOStatement error] [SQLSTATE %s] (%s) %s", $code, $drvrcode, $msg)
+ );
+ throw $e;
+ } elseif (!$stmt->execute($params) && Rails::env() == 'development') {
+ list($code, $drvrcode, $msg) = $stmt->errorInfo();
+ $e = new Exception\QueryException(
+ sprintf("[PDOStatement error] [SQLSTATE %s] (%s) %s", $code, $drvrcode, $msg)
+ );
+ $e->setStatement($stmt, $params);
+ throw $e;
+ }
+
+ $err = $stmt->errorInfo();
+
+ if ($err[2]) {
+ ActiveRecord::setLastError($err, $this->name);
+ $e = new Exception\QueryException(
+ sprintf("[SQLSTATE %s] (%s) %s", $err[0], (string)$err[1], $err[2])
+ );
+ $e->setStatement($stmt, $params);
+
+ $msg = "Error on database query execution\n";
+ $msg .= $e->getMessage();
+ Rails::log()->message($msg);
+ }
+ return $stmt;
+ }
+
+ /**
+ * Executes the sql and returns the results.
+ */
+ public function query()
+ {
+ $stmt = call_user_func_array([$this, 'executeSql'], func_get_args());
+ if (ActiveRecord::lastError())
+ return false;
+ return $stmt->fetchAll();
+ }
+
+ public function select()
+ {
+ $stmt = call_user_func_array([$this, 'executeSql'], func_get_args());
+ if (ActiveRecord::lastError())
+ return false;
+ return $stmt->fetchAll(PDO::FETCH_ASSOC);
+ }
+
+ public function selectRow()
+ {
+ $stmt = call_user_func_array([$this, 'executeSql'], func_get_args());
+ if (ActiveRecord::lastError())
+ return false;
+ return $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+
+ # Use only when retrieving ONE column from multiple rows.
+ public function selectValues()
+ {
+ $stmt = call_user_func_array([$this, 'executeSql'], func_get_args());
+ if (ActiveRecord::lastError())
+ return false;
+
+ $cols = array();
+ if ($data = $stmt->fetchAll(PDO::FETCH_ASSOC)) {
+ foreach ($data as $d)
+ $cols[] = current($d);
+ }
+ return $cols;
+ }
+
+ public function selectValue()
+ {
+ $stmt = call_user_func_array([$this, 'executeSql'], func_get_args());
+ if (ActiveRecord::lastError())
+ return false;
+
+ if ($data = $stmt->fetch()) {
+ $data = array_shift($data);
+ }
+ return $data;
+ }
+
+ public function lastInsertId()
+ {
+ return $this->pdo->lastInsertId();
+ }
+
+ public function transaction($params = [], \Closure $block = null)
+ {
+ if (!$block) {
+ $block = $params;
+ }
+ $this->resource()->beginTransaction();
+ $block();
+ $this->resource()->commit();
+ }
+
+ public function resource()
+ {
+ return $this->pdo;
+ }
+
+ public function pdo()
+ {
+ return $this->pdo;
+ }
+
+ public function adapterName()
+ {
+ return $this->adapter_name;
+ }
+
+ public function name()
+ {
+ return $this->name;
+ }
+
+ protected function _parse_query_multimark(&$query, array &$params)
+ {
+ # If the number of tokens isn't equal to parameters, ignore
+ # it and return. PDOStatement will trigger a Warning.
+ if (is_bool(strpos($query, '?')) || substr_count($query, '?') != count($params))
+ return;
+
+ $parts = explode('?', $query);
+ $parsed_params = array();
+
+ foreach ($params as $k => $v) {
+ if (is_array($v)) {
+ $k++;
+ $count = count($v);
+ $parts[$k] = ($count > 1 ? ', ' . implode(', ', array_fill(0, $count - 1, '?')) : '') . $parts[$k];
+ $parsed_params = array_merge($parsed_params, $v);
+ } else {
+ $parsed_params[] = $v;
+ }
+ }
+
+ $params = $parsed_params;
+ $query = implode('?', $parts);
+ }
+
+ protected function create_connection($data)
+ {
+ $pdo = new PDO($data['dsn'], $data['username'], $data['password'], $data['driver_options']);
+
+ if ($data['pdo_attributes']) {
+ foreach ($data['pdo_attributes'] as $attr => $val)
+ $pdo->setAttribute($attr, $val);
+ }
+ return $pdo;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Exception/BadMethodCallException.php b/lib/Rails/ActiveRecord/Exception/BadMethodCallException.php
new file mode 100755
index 0000000..b380ad9
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Exception/BadMethodCallException.php
@@ -0,0 +1,6 @@
+stmt = $stmt;
+ $this->values = $values;
+ return $this;
+ }
+
+ public function postMessage()
+ {
+ $msg = '';
+ if ($this->stmt) {
+ $msg .= "\nQuery: " . $this->stmt->queryString;
+ if ($this->values)
+ $msg .= "\nValues: " . var_export($this->values, true);
+ }
+ return $msg;
+ }
+}
diff --git a/lib/Rails/ActiveRecord/Exception/QueryException.php b/lib/Rails/ActiveRecord/Exception/QueryException.php
new file mode 100755
index 0000000..9aeba71
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Exception/QueryException.php
@@ -0,0 +1,7 @@
+name = $name;
+ $this->connection = $connection;
+ }
+
+ public function name()
+ {
+ return $this->name;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ $this->reloadSchema();
+ }
+
+ public function schemaFile()
+ {
+ $path = Rails::root() . '/db/table_schema/' . $this->connection_name();
+ $file = $path . '/' . $this->name . '.php';
+ return $file;
+ }
+
+ public function reloadSchema()
+ {
+ $config = Rails::application()->config();
+
+ if ($config->active_record->use_cached_schema) {
+ $this->loadCachedSchema();
+ } elseif (!$this->getSchema()) {
+ throw new Exception\RuntimeException(
+ sprintf("Couldn't find schema for %s: %s", $this->name, $this->error_stmt)
+ );
+ }
+ }
+
+ protected function loadCachedSchema()
+ {
+ $file = $this->schemaFile();
+ if (!is_file($file)) {
+ throw new Exception\RuntimeException(
+ sprintf(
+ "Couldn't find schema file for table %s: %s",
+ $this->name,
+ $file
+ )
+ );
+ }
+ list($this->columns, $this->indexes) = require $file;
+ }
+
+ protected function getSchema()
+ {
+ $adapter = self::adapterClass();
+ $data = $adapter::fetchSchema($this->connection, $this->name);
+
+ list($this->columns, $this->indexes) = $data;
+
+ return true;
+ }
+
+ protected function adapterClass()
+ {
+ $name = $this->connection->adapterName();
+ return "Rails\ActiveRecord\Adapter\\" . $name . "\Table";
+ }
+
+ /**
+ * This method can be overwritten to return an array
+ * with the names of the columns.
+ */
+ public function columnNames()
+ {
+ return array_keys($this->columns);
+ }
+
+ public function columnType($column_name)
+ {
+ return $this->columns[$column_name]['type'];
+ }
+
+ public function columnExists($column_name)
+ {
+ return !empty($this->columns[$column_name]);
+ }
+
+ public function enumValues($column_name)
+ {
+ if (!isset($this->columns[$column_name])) {
+ throw new Exception\RuntimeException(
+ sprintf("Column name %s not found for table %s", $column_name, $this->name)
+ );
+ }
+ if (strpos($this->columns[$column_name]['type'], 'enum') !== 0) {
+ throw new Exception\RuntimeException(
+ sprintf("Column %s.%s is not of type enum", $this->name, $column_name)
+ );
+ }
+ return $this->columns[$column_name]['enum_values'];
+ }
+
+ public function setPrimaryKey($key)
+ {
+ $this->primaryKey = $key;
+ }
+
+ public function primaryKey()
+ {
+ if ($this->primaryKey) {
+ return $this->primaryKey;
+ } else {
+ $keys = $this->indexes('pri');
+ if ($keys) {
+ return $keys[0];
+ }
+ }
+ return false;
+ }
+
+ public function indexes($type = null)
+ {
+ if (!$type) {
+ return $this->indexes ? current($this->indexes) : array();
+ } else {
+ if (isset($this->indexes[$type]))
+ return $this->indexes[$type];
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Registry.php b/lib/Rails/ActiveRecord/Registry.php
new file mode 100755
index 0000000..e044f42
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Registry.php
@@ -0,0 +1,146 @@
+ [
+ 'Post' => array(
+ 'table_name' => 'posts',
+ 'table' => ActiveRecord_ModelTable instance,
+ 'instances' => array(
+ $model_1_id => $model_1,
+ $model_2_id => $model_2
+ )
+ );
+ ]
+ *
+ * PriKey will be used for quick find.
+ * If we need to find the model by an attribute
+ * other than its PriKey... Maybe we could do
+ * the query, but then search in the registry
+ * for the model, now that we have the PK. If
+ * it's there, retrieve the model! else, add it.
+ */
+ private $_reg = array();
+
+ /**
+ * Sets model to which methods will respond to.
+ */
+ private $_current_model;
+
+ /**
+ * @var string $model_name Model's class name
+ */
+ public function model($model_name)
+ {
+ $connection = $this->connection();
+
+ if (!isset($this->_reg[$connection]))
+ $this->_reg[$connection] = [];
+
+ if (!isset($this->_reg[$connection][$model_name])) {
+ $table_name = $model_name::tableName();
+ $this->_reg[$connection][$model_name] = array(
+ 'table_name' => $table_name,
+ 'table' => null,
+ 'instances' => array()
+ );
+ }
+ $this->_current_model = $model_name;
+ return $this;
+ }
+
+ public function tableName()
+ {
+ $cn = $this->_current_model;
+ return $cn::tableName();
+ }
+
+ public function table()
+ {
+ if (empty($this->_reg[$this->connection()][$this->_current_model]['table'])) {
+ $cn = $this->get_table_class();
+ $this->_reg[$this->connection()][$this->_current_model]['table'] = new $cn($this->tableName(), $this->_current_model);
+ }
+ return $this->_reg[$this->connection()][$this->_current_model]['table'];
+ }
+
+ public function search($id)
+ {
+ $id = (string)$id;
+ if (isset($this->_reg[$this->connection()][$this->_current_model]) && isset($this->_reg[$this->connection()][$this->_current_model][$id]))
+ return $this->_reg[$this->connection()][$this->_current_model][$id];
+ }
+
+ public function register($model)
+ {
+ $cn = get_class($model);
+
+ if (!$cn::table()->primaryKey()) {
+ return;
+ } elseif (!$model->getAttribute('id')) {
+ return;
+ }
+
+ if (!isset($this->_reg[$this->connection()][$this->_current_model]))
+ $this->_reg[$this->connection()][$this->_current_model] = array();
+
+ $id = (string)$model->getAttribute('id');
+
+ $this->_reg[$this->connection()][$this->_current_model][$id] = $model;
+ return true;
+ }
+
+ public function find_joining_table($table1, $table2)
+ {
+ if (isset(self::$_joining_tables[$table1]))
+ return self::$_joining_tables[$table1];
+
+ $tables = [
+ $table1 . '_' . $table2,
+ $table2 . '_' . $table1
+ ];
+
+ $sql = "SHOW TABLES LIKE ?";
+ $stmt = ActiveRecord::connection()->prepare($sql);
+ foreach ($tables as $table) {
+ $stmt->execute([$table]);
+ if ($stmt->fetch(PDO::FETCH_NUM)) {
+ self::$_joining_tables[$table1] = $table;
+ self::$_joining_tables[$table2] = $table;
+ return $table;
+ }
+ }
+ return false;
+ }
+
+ public function connection()
+ {
+ return ActiveRecord::activeConnectionName();
+ }
+
+ protected function get_table_class()
+ {
+ $model = $this->_current_model;
+ $adapter = ActiveRecord::proper_adapter_name($model::connection()->adapterName());
+ $cn = 'Rails\ActiveRecord\Adapter\\' . $adapter . '\Table';
+ return $cn;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Relation.php b/lib/Rails/ActiveRecord/Relation.php
new file mode 100755
index 0000000..0789dcd
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Relation.php
@@ -0,0 +1,6 @@
+model_name = $model_name;
+ $this->from($table_name);
+ }
+ }
+
+ public function __call($method, $params)
+ {
+ /**
+ * Check if method is a scope method in the model class.
+ */
+ $cn = $this->model_name;
+
+ if ($this->merge_with_scope($method, $params))
+ return $this;
+
+ throw new Exception\BadMethodCallException(
+ sprintf("Call to undefined method %s::%s()", __CLASS__, $method)
+ );
+ }
+
+ /**
+ * Excecutes the query.
+ * Returns all records.
+ *
+ * @return ActiveRecord\Collection
+ */
+ public function take($limit = null)
+ {
+ if ($limit !== null) {
+ $this->limit($limit);
+ }
+ $this->executeSql();
+ $cn = $this->model_name;
+ return $cn::createModelsFromQuery($this);
+ }
+
+ /**
+ * Executes a pagination query, that will calculate found rows.
+ * Both parameters (page and per_page) must be set, else an Exception will be thrown.
+ * Parameters can be set on the fly or using page() and per_page().
+ *
+ * @param int $page Current page number
+ * @param int $per_page Results per page
+ * @throw Exception
+ * @see page()
+ * @see per_page()
+ */
+ public function paginate($page = null, $per_page = null)
+ {
+ if ($page !== null)
+ $this->page($page);
+
+ if ($per_page !== null)
+ $this->perPage($per_page);
+
+ if ($this->page === null)
+ throw new Exception\BadMethodCallException("Missing page parameter for pagination");
+ elseif ($this->limit === null || $this->offset === null)
+ throw new Exception\BadMethodCallException("Missing per_page parameter for pagination");
+
+ $this->will_paginate = true;
+
+ return $this->take();
+ }
+
+ /**
+ * For this to work, all :where parameters must have been passed like this:
+ * Model::where(["foo" => $foo, "bar" => $bar])->where(["baz" => $baz]);
+ */
+ public function firstOrInitialize()
+ {
+ $model = $this->first();
+
+ if ($model)
+ return $model;
+ else {
+ $cn = $this->model_name;
+ $model = new $cn();
+
+ foreach ($this->where as $params) {
+ if (!is_array($params))
+ throw new Exception\InvalidArgumentException("Invalid 'where' parameters passed for firstOrInitialize");
+
+ foreach ($params as $column => $value)
+ $model->$column = $value;
+ }
+ return $model;
+ }
+ }
+
+ /**
+ * For this to work, all :where parameters must have been passed like this:
+ * Model::where(["foo" => $foo, "bar" => $bar])->where(["baz" => $baz]);
+ *
+ * A closure object may be passed to perform any additional actions (like adding
+ * additional properties to the model) before it is created.
+ */
+ public function firstOrCreate(Closure $block = null)
+ {
+ $model = $this->first();
+
+ if ($model) {
+ return $model;
+ } else {
+ $cn = $this->model_name;
+ $model = new $cn();
+
+ foreach ($this->where as $params) {
+ if (!is_array($params)) {
+ throw new Exception\InvalidArgumentException("Invalid 'where' parameters passed for firstOrCreate");
+ }
+
+ foreach ($params as $column => $value)
+ $model->$column = $value;
+ }
+
+ if ($block)
+ $block($model);
+
+ $model->save();
+ return $model;
+ }
+ }
+
+ /**
+ * @return null|ActiveRecord_Base The records found or null
+ */
+ public function first($limit = 1)
+ {
+ $this->limit = $limit;
+
+ $collection = $this->take();
+
+ if ($limit == 1) {
+ if ($collection->any())
+ return $collection[0];
+ } else
+ return $collection;
+ }
+
+ /**
+ * Accepts more than 1 argument.
+ */
+ public function pluck($column)
+ {
+ $columns = func_get_args();
+ $argv_count = func_num_args();
+
+ $collection = $this->take();
+ $values = [];
+
+ if ($collection->any()) {
+ if ($argv_count == 1) {
+ foreach ($collection as $model)
+ $values[] = $model->$column;
+ } else {
+ foreach ($collection as $model) {
+ $row_values = [];
+ foreach ($columns as $column) {
+ $row_values[] = $model->$column;
+ }
+ $values[] = $row_values;
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ public function count()
+ {
+ $this->select('COUNT(*) as count_all');
+ $this->executeSql();
+ if (($rows = $this->builder->stmt()->fetchAll()) && isset($rows[0]['count_all']))
+ return (int)$rows[0]['count_all'];
+ }
+
+ public function exists()
+ {
+ return (bool)$this->count();
+ }
+
+ public function last()
+ {
+ $this->limit = 1;
+
+ $collection = $this->take();
+ if ($collection->any())
+ return last($collection->members());
+ }
+
+ /**
+ * "Unsets" a clause.
+ * If the clause is invalid, it's just ignored.
+ * This method has a complementary method called "only",
+ * not yet implemented.
+ */
+ public function except($clause)
+ {
+ // $prop = '_' . $clause;
+
+ switch ($clause) {
+ case 'distinct':
+ case 'from':
+ case 'offset':
+ case 'limit':
+ case 'page':
+ case 'per_page':
+ $this->$clause = null;
+ break;
+
+ case 'select':
+ case 'joins':
+ case 'where':
+ case 'where_params':
+ case 'order':
+ case 'group':
+ case 'having':
+ case 'having_params':
+ $this->$clause = [];
+ break;
+ }
+ return $this;
+ }
+
+ /**
+ * Methods that modify the query {
+ */
+ public function from($from)
+ {
+ $this->from = $from;
+ return $this;
+ }
+
+ public function group()
+ {
+ $this->group = array_merge($this->group, func_get_args());
+ return $this;
+ }
+
+ /**
+ * How to use:
+ * $query->having("foo > ? AND bar < ?", $foo, $bar);
+ * $query->having("foo > :foo AND bar < :bar", ['foo' => $foo, 'bar' => $bar]);
+ * $query->having("foo > 15");
+ */
+ public function having()
+ {
+ $args = func_get_args();
+ $count = count($args);
+
+ if ($count == 1) {
+ $this->having[] = $args[0];
+ } else {
+ $this->having[] = array_shift($args);
+
+ if ($count == 2 && is_array($args[0]))
+ $args = $args[0];
+
+ if ($args)
+ $this->having_params = array_merge($this->having_params, $args);
+ }
+ return $this;
+ }
+
+ public function joins($params)
+ {
+ if (!is_array($params))
+ $params = [$params];
+ $this->joins = array_merge($this->joins, $params);
+
+ return $this;
+ }
+
+ public function limit($limit)
+ {
+ $this->limit = $limit;
+ return $this;
+ }
+
+ public function offset($offset)
+ {
+ $this->offset = $offset;
+ return $this;
+ }
+
+ public function order()
+ {
+ $this->order = array_merge($this->order, func_get_args());
+ return $this;
+ }
+
+ public function select($params)
+ {
+ if (!is_array($params))
+ $params = [$params];
+ $this->select = array_merge($this->select, $params);
+
+ return $this;
+ }
+
+ public function distinct($value = true)
+ {
+ $this->distinct = $value;
+ return $this;
+ }
+
+ /**
+ * $query->where("foo = ? AND bar = ?", $foo, $bar);
+ * $query->where("foo = ? AND bar = ?", $foo, [$bar, $baz]);
+ * $query->where("foo = :foo AND bar = :bar", ['foo' => $foo, 'bar' => $bar]);
+ * $query->where("foo = true");
+ *
+ * Following 2 methods are basically the same, first is recommended over the second:
+ * $query->where(["foo" => $foo, "bar" => $bar]);
+ * $query->where("foo", true, "bar", $bar);
+ *
+ * Can't do:
+ * $query->where("foo = ? AND bar = ?", [$foo, $bar]);
+ * $query->where(["foo = ? AND bar = ?", $foo, $bar]);
+ * $query->where(["foo", $foo]);
+ */
+ public function where()
+ {
+ $args = func_get_args();
+ $count = count($args);
+
+ if ($count == 1) {
+ if (is_array($args[0])) {
+ # This is expected to be a column => value associated array.
+ # In this case, the array is stored as is.
+ $this->where[] = $args[0];
+ } else {
+ # This is expected to be a string.
+ $this->where[] = $args[0];
+ }
+ } elseif ($count) {
+ # Case: $query->where("foo", true, "bar_baz", $bar);
+ if ($count >= 2 && is_int($count / 2) && (!is_array($args[1]) || Toolbox\ArrayTools::isIndexed($args[1])) && is_bool(strpos($args[0], ' '))) {
+ $where = [];
+ foreach ($args as $key => $value) {
+ $key++;
+ if ($key && !($key % 2)) {
+ $where[$next_key] = $value;
+ } else {
+ $next_key = $value;
+ }
+ }
+ $this->where[] = $where;
+ } else {
+ $this->where[] = array_shift($args);
+
+ # Case: $query->where('foo => :foo', ['foo' => $foo]);
+ if ($count == 2 && is_array($args[0]) && !Toolbox\ArrayTools::isIndexed($args[0]))
+ $args = $args[0];
+
+ if ($args)
+ $this->where_params = array_merge($this->where_params, $args);
+ }
+ }
+ return $this;
+ }
+ /**
+ * }
+ */
+
+ public function page($page)
+ {
+ $this->page = $page;
+ return $this;
+ }
+
+ /**
+ * Note that for this to work correctly, $page must be set.
+ */
+ public function perPage($per_page)
+ {
+ $this->limit = $per_page;
+ $this->offset = ($this->page - 1) * $per_page;
+ return $this;
+ }
+
+ /**
+ * The following public methods are supposed to be used only by Rails.
+ */
+ public function will_paginate()
+ {
+ return $this->will_paginate;
+ }
+
+ /**
+ * TODO: this method should be changed.
+ * Expected to be used only by the system, when calling find_by_sql().
+ */
+ public function complete_sql($sql = null, array $params = [], array $extra_params = [])
+ {
+ if (func_num_args()) {
+ if (isset($extra_params['page']))
+ $this->page = $extra_params['page'];
+ if (isset($extra_params['perPage']))
+ $this->limit = $extra_params['perPage'];
+ if (isset($extra_params['offset']))
+ $this->offset = $extra_params['offset'];
+ if ($this->page && $this->limit)
+ $this->will_paginate = true;
+
+ $this->complete_sql = [$sql, $params];
+ return $this;
+ } else
+ return $this->complete_sql;
+ }
+
+ public function get_page()
+ {
+ return $this->page;
+ }
+
+ public function get_per_page()
+ {
+ return $this->limit;
+ }
+
+ public function get_offset()
+ {
+ return $this->offset;
+ }
+
+ public function get_row_count()
+ {
+ return $this->builder->row_count();
+ }
+
+ public function get_results()
+ {
+ return $this->builder->stmt();
+ }
+
+ public function merge(self $other)
+ {
+ $scalars = [
+ 'distinct',
+ 'from',
+ 'offset',
+ 'limit',
+ 'page',
+ 'per_page',
+ ];
+
+ $arrays = [
+ 'select',
+ 'joins',
+ 'where',
+ 'where_params',
+ 'order',
+ 'group',
+ 'having',
+ 'having_params',
+ ];
+
+ foreach ($scalars as $scalar) {
+ if ($other->$scalar !== null) {
+ $this->$scalar = $other->$scalar;
+ }
+ }
+
+ foreach ($arrays as $array) {
+ $this->$array = array_merge($this->$array, $other->$array);
+ }
+ return $this;
+ }
+
+ # This method is expected to be used only by Rails.
+ public function default_scoped($value)
+ {
+ $this->default_scoped = (bool)$value;
+ return $this;
+ }
+
+ private function _set_model_connection()
+ {
+ if (defined($this->model_name . '::connection')) {
+ $this->previous_connection_name = ActiveRecord::activeConnectionName();
+ $model_name = $this->model_name;
+ ActiveRecord::setConnection($model_name::connection);
+ return true;
+ }
+ }
+
+ private function _restore_previous_connection()
+ {
+ if ($this->previous_connection_name) {
+ ActiveRecord::setConnection($this->previous_connection_name);
+ $this->previous_connection_name = null;
+ }
+ }
+
+ protected function executeSql()
+ {
+ $this->_set_model_connection();
+ $builder_class = $this->_get_builder_class();
+
+ $model_name = $this->model_name;
+ $model_connection = $model_name::connection();
+ $this->builder = new $builder_class($model_connection);
+
+ $this->builder->build_sql($this);
+ $this->builder->executeSql();
+
+ $this->_restore_previous_connection();
+ }
+
+ private function merge_with_scope($name, array $params)
+ {
+ $cn = $this->model_name;
+
+ if ($relation = $cn::scope($name, $params)) {
+ $this->merge($relation);
+ return true;
+ }
+
+ return false;
+ }
+
+ private function _get_builder_class()
+ {
+ $cn = $this->model_name;
+ $class = '\Rails\ActiveRecord\Adapter\\';
+ $class .= ActiveRecord::proper_adapter_name($cn::connection()->adapterName());
+ $class .= '\QueryBuilder';
+ return $class;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Relation/Association.php b/lib/Rails/ActiveRecord/Relation/Association.php
new file mode 100755
index 0000000..535ab33
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Relation/Association.php
@@ -0,0 +1,48 @@
+params = $params;
+ $this->parent_model = $parent_model;
+ }
+
+ public function get_query()
+ {
+ return $this->query;
+ }
+
+ public function build_query()
+ {
+ $params = $this->params;
+
+ if (empty($params['foreign_key'])) {
+ $cn = get_class($this->parent_model);
+ $params['foreign_key'] = substr($cn::tableName(), 0, -1).'_id';
+ }
+
+ $query = new Relation($params['class_name'], $params['class_name']::tableName());
+
+ $query->where('`' . $params['foreign_key'] . "` = ?", $this->parent_model->id);
+
+ # params[0], if present, it's an anonymous function to customize the relation.
+ # The function is binded to the relation object.
+ if (isset($this->params[0])) {
+ $lambda = array_shift($this->params);
+ $lambda->bindTo($this);
+ $lambda();
+ }
+
+ $this->query = $query;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveRecord/Validation/Exception/ExceptionInterface.php b/lib/Rails/ActiveRecord/Validation/Exception/ExceptionInterface.php
new file mode 100755
index 0000000..c8e5e3a
--- /dev/null
+++ b/lib/Rails/ActiveRecord/Validation/Exception/ExceptionInterface.php
@@ -0,0 +1,6 @@
+ array(
+ * 'length' => '5..',
+ * 'format' => function($name) { ... return true; },
+ * )
+ */
+class Validator extends RailsValidation
+{
+ private
+ $_model,
+
+ $_action,
+
+ $_property,
+
+ /**
+ * Helps for validations that could have
+ * different messages (e.g. length (minimum, maximum, is))
+ */
+ $_error_message_type = 'default',
+
+ $_error_message,
+
+ $_continue_validation;
+
+ public function set_params($action, Rails\ActiveRecord\Base $model, $property)
+ {
+ $this->_model = $model;
+ $this->_action = $action;
+ $this->_property = $property;
+ }
+
+ public function validate()
+ {
+ // if (current($this->_params) instanceof \Closure)
+ // $this->_run_closure();
+ // else
+ $this->_check_conditions();
+
+ if ($this->_continue_validation)
+ parent::validate();
+
+ return $this;
+ }
+
+ public function set_error_message()
+ {
+ if (isset($this->_params['base_message']))
+ $this->_model->errors()->addToBase($this->_params['base_message']);
+ else {
+ $this->_set_error_message();
+ $this->_model->errors()->add($this->_property, $this->_error_message);
+ }
+ }
+
+ protected function _validate_number()
+ {
+ if (isset($this->_params['allow_null']) && $this->_model->getAttribute($this->_property) === null)
+ return true;
+ else
+ return parent::_validate_number();
+ }
+
+ protected function _validate_length()
+ {
+ if (isset($this->_params['allow_null']) && $this->_model->getAttribute($this->_property) === null)
+ return true;
+ elseif (isset($this->_params['allow_blank']) && $this->_model->getAttribute($this->_property) === '')
+ return true;
+ else
+ return parent::_validate_length();
+ }
+
+ protected function _validate_uniqueness()
+ {
+ $cn = get_class($this->_model);
+ if ($this->_model->isNewRecord()) {
+ $query = $cn::where('`'.$this->_property.'` = ?', $this->_model->getAttribute($this->_property));
+ } else {
+ $query = $cn::where('`'.$this->_property.'` = ? AND id != ?', $this->_model->getAttribute($this->_property), $this->_model->getAttribute('id'));
+ }
+ return !((bool)$query->first());
+ }
+
+ protected function _validate_presence()
+ {
+ $property = trim($this->_model->{$this->_property});
+ return !empty($property);
+ }
+
+ protected function _validate_confirmation()
+ {
+ $property = Rails::services()->get('inflector')->camelize($this->_property, false) . 'Confirmation';
+
+ if ($this->_model->$property === null)
+ return true;
+
+ return (string)$this->_model->getAttribute($this->_property) == (string)$this->_model->$property;
+ }
+
+ protected function _validate_acceptance()
+ {
+ return !empty($this->_model->{$this->_property});
+ }
+
+ private function _run_closure()
+ {
+ $closure = current($this->_params);
+ if ($closure($this->_model->{$this->_property}) === true) {
+ $this->_success = true;
+ }
+ }
+
+ private function _check_conditions()
+ {
+ if (!isset($this->_params['on']))
+ $this->_params['on'] = 'save';
+ $this->_run_on();
+
+ if (isset($this->_params['if']))
+ $this->_run_if();
+ }
+
+ private function _run_on()
+ {
+ if ($this->_params['on'] == 'save' || $this->_params['on'] == $this->_action)
+ $this->_continue_validation = true;
+ else
+ $this->_success = true;
+ }
+
+ private function _run_if()
+ {
+ if (is_array($this->_params['if'])) {
+ foreach ($this->_params['if'] as $cond => $params) {
+ if ($params instanceof \Closure) {
+ if ($params() !== true) {
+ $this->_success = true;
+ $this->_continue_validation = false;
+ return;
+ }
+ } else {
+ switch ($cond) {
+ case 'property_exists':
+ if ($this->_model->$params === null) {
+ $this->_success = true;
+ $this->_continue_validation = false;
+ return;
+ }
+ break;
+ }
+ }
+ }
+ } else {
+ throw new Exception\RuntimeException(
+ sprintf("Validation condition must be an array, %s passed", gettype($this->_params['if']))
+ );
+ }
+
+ $this->_continue_validation = true;
+ }
+
+ private function _set_error_message()
+ {
+ $message = '';
+ $this->_define_error_message_type();
+
+ if ($this->_error_message_type != 'default') {
+ if (isset($this->_params[$this->_error_message_type]))
+ $message = $this->_params[$this->_error_message_type];
+ }
+ if (!$message)
+ $message = $this->_error_message();
+ $this->_error_message = $message;
+ }
+
+ private function _define_error_message_type()
+ {
+ switch ($this->_type) {
+ case 'length':
+ if ($this->_result == -1)
+ $msg_type = 'too_short';
+ elseif ($this->_result == 1)
+ $msg_type = 'too_long';
+ else
+ $msg_type = 'wrong_length';
+ break;
+ default:
+ $msg_type = 'default';
+ break;
+ }
+ $this->_error_message_type = $msg_type;
+ }
+
+ private function _error_message()
+ {
+ switch ($this->_type) {
+ case 'number':
+ case 'length':
+ if ($this->_result == 2 && (!empty($this->_params['even']) || !empty($this->_params['odd']))) {
+ $type = !empty($this->_params['even']) ? "even" : "odd";
+ $params = ["odd"];
+ } else {
+ if (!empty($this->_params['in'])) {
+ if ($this->_result == -1) {
+ $params = ['greater_than_or_equal_to', ['count' => $this->_params['in'][0]]];
+ } else {
+ $params = ['less_than_or_equal_to', ['count' => $this->_params['in'][1]]];
+ }
+ } elseif (!empty($this->_params['is'])) {
+ $params = ['equal_to', ['count' => $this->_params['is']]];
+ } elseif (!empty($this->_params['minimum'])) {
+ $params = ['greater_than_or_equal_to', ['count' => $this->_params['minimum']]];
+ } elseif (!empty($this->_params['maximum'])) {
+ $params = ['less_than_or_equal_to', ['count' => $this->_params['maximum']]];
+ }
+ }
+ break;
+
+ case 'blank':
+ $params = ['blank'];
+ break;
+
+ case 'uniqueness':
+ $params = ['uniqueness'];
+ break;
+
+ case 'confirmation':
+ $params = ["confirmation"];
+ break;
+
+ default:
+ $params = ['invalid'];
+ break;
+ }
+ $params[0] = 'errors.messages.' . $params[0];
+ return call_user_func_array([\Rails::application()->I18n(), 't'], $params);
+ }
+}
diff --git a/lib/Rails/ActiveSupport/Inflector/DefaultEnglishInflections.php b/lib/Rails/ActiveSupport/Inflector/DefaultEnglishInflections.php
new file mode 100755
index 0000000..2e3387f
--- /dev/null
+++ b/lib/Rails/ActiveSupport/Inflector/DefaultEnglishInflections.php
@@ -0,0 +1,82 @@
+ '\1zes',
+ '/^(oxen)$/i' => '\1',
+ '/^(ox)$/i' => '\1en',
+ '/^(m|l)ice$/i' => '\1ice',
+ '/^(m|l)ouse$/i' => '\1ice',
+ '/(matr|vert|ind)(?:ix|ex)$/i' => '\1ices',
+ '/(x|ch|ss|sh)$/i' => '\1es',
+ '/([^aeiouy]|qu)y$/i' => '\1ies',
+ '/(hive)$/i' => '\1s',
+ '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
+ '/sis$/i' => 'ses',
+ '/([ti])a$/i' => '\1a',
+ '/([ti])um$/i' => '\1a',
+ '/(buffal|tomat)o$/i' => '\1oes',
+ '/(alias|status)$/i' => '\1es',
+ '/(bu)s$/i' => '\1ses',
+ '/(octop|vir)i$/i' => '\1i',
+ '/(octop|vir)us$/i' => '\1i',
+ '/^(ax|test)is$/i' => '\1es',
+ '/s$/i' => 's',
+ '/$/' => 's',
+ ];
+
+ protected $singulars = [
+ '/(database)s$/i' => '\1',
+ '/(quiz)zes$/i' => '\1',
+ '/(matr)ices$/i' => '\1ix',
+ '/(vert|ind)ices$/i' => '\1ex',
+ '/^(ox)en/i' => '\1',
+ '/(alias|status)(es)?$/i' => '\1',
+ '/(octop|vir)(us|i)$/i' => '\1us',
+ '/^(a)x[ie]s$/i' => '\1xis',
+ '/(cris|test)(is|es)$/i' => '\1is',
+ '/(shoe)s$/i' => '\1',
+ '/(o)es$/i' => '\1',
+ '/(bus)(es)?$/i' => '\1',
+ '/^(m|l)ice$/i' => '\1ouse',
+ '/(x|ch|ss|sh)es$/i' => '\1',
+ '/(m)ovies$/i' => '\1ovie',
+ '/(s)eries$/i' => '\1eries',
+ '/([^aeiouy]|qu)ies$/i' => '\1y',
+ '/([lr])ves$/i' => '\1f',
+ '/(tive)s$/i' => '\1',
+ '/(hive)s$/i' => '\1',
+ '/([^f])ves$/i' => '\1fe',
+ '/(^analy)(sis|ses)$/i' => '\1sis',
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i' => '\1sis',
+ '/([ti])a$/i' => '\1um',
+ '/(n)ews$/i' => '\1ews',
+ '/(ss)$/i' => '\1',
+ '/s$/i' => '',
+ ];
+
+ protected $irregulars = [
+ 'person' => 'people',
+ 'man' => 'men',
+ 'child' => 'children',
+ 'sex' => 'sexes',
+ 'move' => 'moves',
+ 'cow' => 'kine',
+ 'zombie' => 'zombies',
+ ];
+
+ protected $uncountables = [
+ 'equipment',
+ 'information',
+ 'rice',
+ 'money',
+ 'species',
+ 'series',
+ 'fish',
+ 'sheep',
+ 'jeans',
+ 'police',
+ ];
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveSupport/Inflector/Inflections.php b/lib/Rails/ActiveSupport/Inflector/Inflections.php
new file mode 100755
index 0000000..d77540c
--- /dev/null
+++ b/lib/Rails/ActiveSupport/Inflector/Inflections.php
@@ -0,0 +1,132 @@
+acronyms[strtolower($word)] = $word;
+ $this->acronymRegex = '/' . implode('|', $this->acronymRegex) . '/';
+ }
+
+ public function plural($rule, $replacement)
+ {
+ unset($this->uncountables[$rule]);
+ unset($this->uncountables[$replacement]);
+ $this->plurals[$rule] = $replacement;
+ }
+
+ public function singular($rule, $replacement)
+ {
+ unset($this->uncountables[$rule]);
+ unset($this->uncountables[$replacement]);
+ $this->singulars[$rule] = $replacement;
+ }
+
+ public function irregular($singular, $plural)
+ {
+ unset($this->uncountables[$singular]);
+ unset($this->uncountables[$plural]);
+
+ $s0 = $singular[0];
+ $srest = substr($singular, 1, -1);
+
+ $p0 = $plural[0];
+ $prest = substr($plural, 1, -1);
+
+ if (strtoupper($s0) == strtoupper($p0)) {
+ $this->plural("/($s0)$srest$/i", '\1' . $prest);
+ $this->plural("/($p0)$prest$/i", '\1' . $prest);
+
+ $this->singular("/($s0)$srest$/i", '\1' . $srest);
+ $this->singular("/($p0)$prest$/i", '\1' . $srest);
+ } else {
+ $this->plural("/".strtoupper($s0)."(?i)$srest$/", strtoupper($p0) . $prest);
+ $this->plural("/".strtolower($s0)."(?i)$srest$/", strtolower($p0) . $prest);
+ $this->plural("/".strtoupper($p0)."(?i)$prest$/", strtoupper($p0) . $prest);
+ $this->plural("/".strtolower($p0)."(?i)$prest$/", strtolower($p0) . $prest);
+
+ $this->singular("/".strtoupper($s0)."(?i)$srest$/", strtoupper($s0) . $srest);
+ $this->singular("/".strtolower($s0)."(?i)$srest$/", strtolower($s0) . $srest);
+ $this->singular("/".strtoupper($p0)."(?i)$prest$/", strtoupper($s0) . $srest);
+ $this->singular("/".strtolower($p0)."(?i)$prest$/", strtolower($s0) . $srest);
+ }
+ }
+
+ public function uncountable()
+ {
+ $this->uncountables = array_merge($this->uncountables, func_get_args());
+ }
+
+ public function human($rule, $replacement)
+ {
+ $this->humans[$rule] = $replacement;
+ }
+
+ public function clear($scope = 'all')
+ {
+ switch ($scope) {
+ case 'all':
+ $this->plurals = $this->singulars = $this->uncountables = $this->humans = [];
+ break;
+
+ default:
+ if (!isset($this->$scope)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf("Unknown scope to clear: %s", $scope)
+ );
+ }
+ $this->$scope = [];
+ break;
+ }
+ }
+
+ public function acronyms()
+ {
+ return $this->acronyms;
+ }
+
+ public function plurals()
+ {
+ return $this->plurals;
+ }
+
+ public function singulars()
+ {
+ return $this->singulars;
+ }
+
+ public function irregulars()
+ {
+ return $this->irregulars;
+ }
+
+ public function uncountables()
+ {
+ return $this->uncountables;
+ }
+
+ public function humans()
+ {
+ return $this->humans;
+ }
+
+ public function acronymRegex()
+ {
+ return $this->acronymRegex;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveSupport/Inflector/Inflector.php b/lib/Rails/ActiveSupport/Inflector/Inflector.php
new file mode 100755
index 0000000..18737be
--- /dev/null
+++ b/lib/Rails/ActiveSupport/Inflector/Inflector.php
@@ -0,0 +1,201 @@
+inflections['en'] = new DefaultEnglishInflections();
+ }
+
+ public function inflections($locale = 'en', Closure $block = null)
+ {
+ if ($locale instanceof Closure) {
+ $block = $locale;
+ $locale = 'en';
+ }
+
+ $inflections = $this->getInflections($locale);
+
+ if ($block) {
+ $block($inflections);
+ } else {
+ return $inflections;
+ }
+ }
+
+ public function pluralize($word, $locale = 'en')
+ {
+ $irregulars = $this->inflections()->irregulars();
+ if (isset($irregulars[$word])) {
+ return $this->inflections()->irregulars()[$word];
+ }
+ return $this->applyInflections($word, $this->inflections($locale)->plurals());
+ }
+
+ public function singularize($word, $locale = 'en')
+ {
+ if (is_string($key = array_search($word, $this->inflections()->irregulars()))) {
+ return $key;
+ }
+ return $this->applyInflections($word, $this->inflections($locale)->singulars());
+ }
+
+ public function camelize($term, $uppercaseFirstLetter = true)
+ {
+ $string = (string)$term;
+ $acronyms = $this->inflections()->acronyms();
+
+ if ($uppercaseFirstLetter) {
+ $string = preg_replace_callback('/^[a-z\d]*/', function($m) use ($acronyms) {
+ if (isset($acronyms[$m[0]])) {
+ return $acronyms[$m[0]];
+ } else {
+ return ucfirst($m[0]);
+ }
+ }, $string);
+ } else {
+ $acronymRegex = $this->inflections()->acronymRegex();
+ $string = preg_replace_callback('/^(?:'.$acronymRegex.'(?=\b|[A-Z_])|\w)/', function($m) use($term) {
+ return strtolower($m[0]);
+ }, $string);
+ }
+
+ return preg_replace_callback('/(?:_|(\/))([a-z\d]*)/i', function($m) use ($acronyms) {
+ if (isset($acronyms[$m[2]])) {
+ return $m[1] . $acronyms[$m[2]];
+ } else {
+ return ucfirst($m[2]);
+ }
+ }, $string);
+ }
+
+ public function underscore($camelCasedWord)
+ {
+ $word = (string)$camelCasedWord;
+ $word = preg_replace_callback('/(?:([A-Za-z\d])|^)(?=\b|[^a-z])/', function($m) use ($camelCasedWord) {
+ $ret = '';
+ if (isset($m[1])) {
+ $ret = $m[1];
+ }
+ if (isset($m[2])) {
+ $ret .= $m[2];
+ }
+
+ return $ret;
+ }, $word);
+
+ $word = preg_replace([
+ '/([A-Z\d]+)([A-Z][a-z])/',
+ '/([a-z\d])([A-Z])/'
+ ], [
+ '\1_\2',
+ '\1_\2'
+ ], $word);
+
+ $word = strtr($word, '-\\', '_/');
+ $word = strtolower($word);
+ return $word;
+ }
+
+ public function humanize($lowerCaseAndUnderscoredWord)
+ {
+ $result = (string)$lowerCaseAndUnderscoredWord;
+ foreach ($this->inflections()->humans() as $rule => $replacement) {
+ $ret = preg_replace($rule, $replacement, $result, -1, $count);
+ if ($count) {
+ $result = $ret;
+ break;
+ }
+ }
+
+ if (strpos($result, '_id') === strlen($result) - 3) {
+ $result = substr($result, 0, -3);
+ }
+ $result = strtr($result, '_', ' ');
+
+ $acronyms = $this->inflections()->acronyms();
+
+ $result = preg_replace_callback('/([a-z\d]*)/i', function($m) use ($acronyms) {
+ if (isset($acronyms[$m[1]])) {
+ return $acronyms[$m[1]];
+ } else {
+ return strtolower($m[1]);
+ }
+ }, $result);
+
+ $result = preg_replace_callback('/^\w/', function($m) {
+ return strtoupper($m[0]);
+ }, $result);
+ return $result;
+ }
+
+ public function titleize($word)
+ {
+ return ucwords($this->humanize($this->underscore($word)));
+ }
+
+ public function tableize($className)
+ {
+ return $this->pluralize($this->underscore($className));
+ }
+
+ public function classify($tableName)
+ {
+ return $this->camelize($this->singuralize(preg_replace('/.*\./', '', $tableName)));
+ }
+
+ public function ordinal($number)
+ {
+ $absNumber = (int)$number;
+ if (in_array($absNumber % 100, range(11, 13))) {
+ return $absNumber . 'th';
+ } else {
+ switch ($absNumber % 100) {
+ case 1:
+ return $absNumber . 'st';
+ case 2:
+ return $absNumber . 'nd';
+ case 3:
+ return $absNumber . 'rd';
+ default:
+ return $absNumber . 'th';
+ }
+ }
+ }
+
+ protected function getInflections($locale)
+ {
+ if (!isset($this->inflections[$locale])) {
+ $this->inflections[$locale] = new Inflections();
+ }
+ return $this->inflections[$locale];
+ }
+
+ protected function applyInflections($word, $rules)
+ {
+ if (
+ !$word ||
+ (
+ preg_match('/\b\w+\Z/', strtolower($word), $m) &&
+ in_array($m[0], $this->inflections()->uncountables())
+ )
+ ) {
+ return $word;
+ } else {
+ foreach ($rules as $rule => $replacement) {
+ $ret = preg_replace($rule, $replacement, $word, -1, $count);
+ if ($count) {
+ $word = $ret;
+ break;
+ }
+ }
+ return $word;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ActiveSupport/Inflector/Word.php b/lib/Rails/ActiveSupport/Inflector/Word.php
new file mode 100755
index 0000000..1aec2d3
--- /dev/null
+++ b/lib/Rails/ActiveSupport/Inflector/Word.php
@@ -0,0 +1,82 @@
+word = $word;
+ $this->inflector = $inflector;
+ $this->locale = $locale;
+ }
+
+ public function __toString()
+ {
+ return $this->word;
+ }
+
+ public function setLocale($locale)
+ {
+ $this->locale = $locale;
+ }
+
+ public function pluralize()
+ {
+ $this->word = $this->inflector->pluralize($this->word, $this->locale);
+ return $this;
+ }
+
+ public function singularize()
+ {
+ $this->word = $this->inflector->singularize($this->word, $this->locale);
+ return $this;
+ }
+
+ public function camelize($uppercaseFirstLetter = true)
+ {
+ $this->word = $this->inflector->camelize($this->word, $uppercaseFirstLetter);
+ return $this;
+ }
+
+ public function underscore()
+ {
+ $this->word = $this->inflector->underscore($this->word);
+ return $this;
+ }
+
+ public function humanize()
+ {
+ $this->word = $this->inflector->humanize($this->word);
+ return $this;
+ }
+
+ public function titleize()
+ {
+ $this->word = $this->inflector->titleize($this->word);
+ return $this;
+ }
+
+ public function tableize()
+ {
+ $this->word = $this->inflector->tableize($this->word);
+ return $this;
+ }
+
+ public function classify()
+ {
+ $this->word = $this->inflector->tableize($this->word);
+ return $this;
+ }
+
+ public function ordinal()
+ {
+ $this->word = $this->inflector->ordinal($this->word);
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Application/Base.php b/lib/Rails/Application/Base.php
new file mode 100755
index 0000000..b714c48
--- /dev/null
+++ b/lib/Rails/Application/Base.php
@@ -0,0 +1,356 @@
+_run();
+ }
+
+ # This is the initializer.
+ static public function initialize()
+ {
+ if (!self::$_instance) {
+ $cn = get_called_class();
+ self::$_instance = new $cn();
+ self::$_instance->name = substr($cn, 0, strpos($cn, '\\'));
+ }
+ self::$_instance->boot();
+
+ if (Rails::cli())
+ Rails::console()->run();
+ }
+
+ static public function instance()
+ {
+ return self::$_instance;
+ }
+
+ static public function configure(Closure $config)
+ {
+ $config(self::$_instance->config);
+ }
+
+ static public function routes()
+ {
+ return self::$_instance->_dispatcher->router()->routes();
+ }
+
+ # Used only by Rails
+ static public function setPanelConfig()
+ {
+ $basePath = Rails::path() . '/Panel';
+ $paths = Rails::config()->paths;
+
+ $paths->application->setPath($basePath)->setBasePaths([]);
+ $trait = $basePath . '/traits/AdminController.php';
+ require_once $trait;
+
+ $trait = $basePath . '/traits/ApplicationController.php';
+ require_once $trait;
+
+ Rails::loader()->addPaths([
+ $paths->helpers->toString(),
+ $paths->controllers->toString()
+ ]);
+
+ Rails::config()->action_view->layout = 'application';
+ }
+
+ public function __construct()
+ {
+ }
+
+ public function boot()
+ {
+ $this->resetConfig(Rails::env());
+ $this->setPhpConfig();
+ $this->_load_files();
+ $this->_load_active_record();
+ $this->initPlugins();
+ $this->setDispatcher();
+ $this->init();
+ $this->runInitializers();
+ }
+
+ public function resetConfig($environment)
+ {
+ $this->setDefaultConfig();
+ $this->initConfig($this->config);
+ $this->setEnvironmentConfig($environment);
+ }
+
+ public function config()
+ {
+ return $this->config;
+ }
+
+ public function dispatcher()
+ {
+ return $this->_dispatcher;
+ }
+
+ public function controller()
+ {
+ return $this->_controller;
+ }
+
+ /**
+ * Created so it can be called by Rails when creating the ExceptionHandler
+ */
+ public function set_controller(\Rails\ActionController\Base $controller)
+ {
+ $this->_controller = $controller;
+ }
+
+ public function I18n()
+ {
+ return Rails::services()->get('i18n');
+ // if (!$this->_I18n) {
+ // $this->_I18n = new I18n();
+ // }
+ // return $this->_I18n;
+ }
+
+ public function name()
+ {
+ return $this->name;
+ }
+
+ public function validateSafeIps()
+ {
+ return $this->config()->safe_ips->includes($this->dispatcher()->request()->remoteIp());
+ }
+
+ public function router()
+ {
+ return $this->_dispatcher->router();
+ }
+
+ /**
+ * For custom init.
+ */
+ protected function init()
+ {
+ }
+
+ protected function _run()
+ {
+ ActionView::clean_buffers();
+ ob_start();
+
+ $this->_dispatcher->find_route();
+ if ($this->dispatcher()->router()->route()->assets_route()) {
+ Rails::assets()->server()->dispatch_request();
+ } else {
+ if ($this->dispatcher()->router()->route()->rails_admin())
+ $this->setPanelConfig();
+ $this->_load_controller();
+ $this->controller()->run_request_action();
+ }
+
+ $this->_dispatcher->respond();
+ }
+
+ public function setDispatcher()
+ {
+ $this->_dispatcher = new ActionDispatch();
+ $this->_dispatcher->init();
+ }
+
+ /**
+ * Used in /config/application.php to initialize custom configuration.
+ *
+ * @see _default_config()
+ * @return array.
+ */
+ protected function initConfig($config)
+ {
+ }
+
+ private function initPlugins()
+ {
+ if ($this->config()->plugins) {
+ foreach ($this->config()->plugins->toArray() as $pluginName) {
+ $initClassName = $pluginName . '\Initializer';
+ $initializer = new $initClassName();
+ $initializer->initialize();
+ }
+ }
+ }
+
+ private function _load_controller()
+ {
+ $route = $this->dispatcher()->router()->route();
+ $controller_name = $route->controller();
+
+ if ($route->namespaces()) {
+ $namespaces = [];
+ foreach ($route->namespaces() as $ns)
+ $namespaces[] = Rails::services()->get('inflector')->camelize($ns);
+ $class_name = implode('\\', $namespaces) . '\\' . Rails::services()->get('inflector')->camelize($controller_name) . 'Controller';
+ } else {
+ $class_name = Rails::services()->get('inflector')->camelize($controller_name) . 'Controller';
+ }
+
+ $controller = new $class_name();
+
+ if (!$controller instanceof \Rails\ActionController\Base) {
+ throw new Exception\RuntimeException(
+ sprintf('Controller %s must be extension of Rails\ActionController\Base', $class_name)
+ );
+ }
+ $this->_controller = $controller;
+ }
+
+ /**
+ * Load default files.
+ */
+ private function _load_files()
+ {
+ foreach ($this->config()->load_files as $file) {
+ require $file;
+ }
+ }
+
+ private function _load_active_record()
+ {
+ $basename = Rails::root() . '/config/database';
+ $database_file = $basename . '.yml';
+ $connections = [];
+
+ /**
+ * It's possible to not to have a database file if no database will be used.
+ */
+ if (is_file($database_file)) {
+ $connections = Rails\Yaml\Parser::readFile($database_file);
+ } else {
+ $database_file = Rails::root() . '/config/database.php';
+ if (is_file($database_file))
+ $connections = require $database_file;
+ }
+
+ if ($connections) {
+ foreach ($connections as $name => $config)
+ ActiveRecord::addConnection($config, $name);
+ # Set environment connection.
+ ActiveRecord::set_environment_connection(Rails::env());
+ }
+ }
+
+ private function setDefaultConfig()
+ {
+ $config = Rails::config();
+ $config->session = [
+ 'name' => '_' . Rails::services()->get('inflector')->underscore($this->name) . '_session_id',
+ 'start' => true
+ ];
+ $this->config = $config;
+ }
+
+ private function setEnvironmentConfig($environment)
+ {
+ $config = Rails::config();
+
+ $file = $config->paths->config->concat('environments') . '/' . $environment . '.php';
+ if (!is_file($file)) {
+ throw new Exception\RuntimeException(
+ sprintf("Environment config file not found [ environment => %s, file => %s ]",
+ $environment, $file)
+ );
+ }
+ include $file;
+ }
+
+ /**
+ * Sets some php-related config. This happens once (i.e. if the
+ * config is reset, this method isn't called again).
+ */
+ private function setPhpConfig()
+ {
+ if (function_exists('ini_set')) {
+ if ($this->config->error->log) {
+ ini_set('log_errors', true);
+ ini_set('error_log', Rails::config()->log_file);
+ }
+ }
+
+ if ($this->config['date']['timezone']) {
+ date_default_timezone_set($this->config['date']['timezone']);
+ } elseif (function_exists('ini_get')) {
+ if (!ini_get('date.timezone') && !date_default_timezone_get()) {
+ throw new Exception\RuntimeException(
+ "date.timezone directive doesn't seem to have a value. " .
+ "You are requested to either set a value in your php.ini or in your application's configuration (date->timezone)"
+ );
+ }
+ }
+
+ if ($this->config()->session->start && !Rails::cli()) {
+ if ($session_name = $this->config()->session->name) {
+ session_name($session_name);
+ }
+ session_start();
+ }
+
+ if ($paths = $this->config()->eager_load_paths->toArray()) {
+ Rails::loader()->addPaths($paths);
+ set_include_path(get_include_path() . PATH_SEPARATOR . implode(PATH_SEPARATOR, $paths));
+ }
+
+ set_error_handler('Rails::errorHandler', $this->config()->error->report_types);
+ }
+
+ private function runInitializers()
+ {
+ $dirName = 'initializers';
+ $path = $this->config()->paths->config->concat($dirName);
+ if (is_dir($path)) {
+ $files = [];
+
+ $patt = $path . '/*.php';
+ $files = array_merge($files, glob($patt));
+
+ foreach (Toolbox\FileTools::listDirs($path) as $dir) {
+ $patt = $dir . '/*.php';
+ $files = array_merge($files, glob($patt));
+ }
+
+ foreach ($files as $file) {
+ require $file;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Application/Exception/ExceptionInterface.php b/lib/Rails/Application/Exception/ExceptionInterface.php
new file mode 100755
index 0000000..9cf9ff0
--- /dev/null
+++ b/lib/Rails/Application/Exception/ExceptionInterface.php
@@ -0,0 +1,6 @@
+_get_array()[] = $value;
+ else
+ $this->_get_array()[$offset] = $value;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->_get_array()[$offset]);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->_get_array()[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ if ($this->offsetExists($offset))
+ return $this->_get_array()[$offset];
+ return null;
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->_get_array());
+ }
+
+ public function merge()
+ {
+ foreach (func_get_args() as $arr) {
+ if (!is_array($arr))
+ throw new Exception\InvalidArgumentException(
+ sprintf("All arguments passed to merge() must be array, %s passed", gettype($arr))
+ );
+ foreach ($arr as $k => $v)
+ $this->offsetSet($k, $v);
+ }
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/ArrayHelper/Exception/ExceptionInterface.php b/lib/Rails/ArrayHelper/Exception/ExceptionInterface.php
new file mode 100755
index 0000000..ded69c4
--- /dev/null
+++ b/lib/Rails/ArrayHelper/Exception/ExceptionInterface.php
@@ -0,0 +1,6 @@
+_var_name = $var_name;
+ $this->_key_name = $key_name;
+ $this->merge($values);
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ global ${$this->_var_name};
+ if ($offset === null)
+ ${$this->_var_name}[$this->_key_name][] = $value;
+ else
+ ${$this->_var_name}[$this->_key_name][$offset] = $value;
+ }
+
+ protected function _get_array()
+ {
+ global ${$this->_var_name};
+ return ${$this->_var_name};
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Assets/Assets.php b/lib/Rails/Assets/Assets.php
new file mode 100755
index 0000000..a77d297
--- /dev/null
+++ b/lib/Rails/Assets/Assets.php
@@ -0,0 +1,522 @@
+console) {
+ $this->console->write($message);
+ }
+ }
+
+ public function setConsole(Rails\Console\Console $console)
+ {
+ $this->console = $console;
+ }
+
+ public function addPaths($paths)
+ {
+ if (!is_array($paths)) {
+ $paths = [$paths];
+ }
+
+ $this->paths = array_merge($this->paths, $paths);
+ }
+
+ /**
+ * URL for non-static assets.
+ */
+ public function getFileUrl($filename)
+ {
+ if ($file = $this->findFile($filename)) {
+ return $file->url();
+ }
+ return false;
+ }
+
+ /**
+ * Returns not only the URL for the file, but also the URLs
+ * of the files it requires (if it's a manifest file).
+ */
+ public function getFileUrls($filename)
+ {
+ if ($file = $this->findFile($filename)) {
+ $parser = new Rails\Assets\Parser\Base($file);
+ $parser->parse(Rails\Assets\Parser\Base::PARSE_TYPE_GET_PATHS);
+ $urls = [];
+ foreach ($parser->urlPaths() as $url) {
+ $urls[] = $url . '?body=1';
+ }
+ return $urls;
+ }
+ return false;
+ }
+
+ public function findFile($filename)
+ {
+ $pinfo = pathinfo($filename);
+ $extension = $pinfo['extension'];
+ $dir = $pinfo['dirname'] == '.' ? '' : $pinfo['dirname'] . '/';
+ $file_relative_path_root = $dir . $pinfo['filename'];
+
+ $found = false;
+ $pattern = $file_relative_path_root . '.' . $extension . '*';
+
+ foreach ($this->paths as $assets_root) {
+ $filePatt = $assets_root . '/' . $pattern;
+ $files = glob($filePatt);
+
+ if ($files) {
+ $file = new File($extension, $files[0]);
+ $found = true;
+ break;
+ }
+ }
+
+ if ($found) {
+ return $file;
+ } else {
+ return false;
+ }
+ }
+
+ public function findCompiledFile($file)
+ {
+ if (!$this->config()->digest) {
+ return $this->base_path() . ltrim($this->prefix()) . '/' . $file;
+ } else {
+ $index = $this->getManifestIndex();
+
+ if (isset($index[$file])) {
+ return $this->base_path() . ltrim($this->prefix()) . '/' . $index[$file];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds all files named $filename within all assets
+ * dirs and subdirs.
+ */
+ public function findAllFiles($filename)
+ {
+ $pinfo = pathinfo($filename);
+ $extension = $pinfo['extension'];
+ $dir = $pinfo['dirname'] == '.' ? '' : $pinfo['dirname'] . '/';
+ $file_relative_path_root = $dir . $pinfo['filename'];
+
+ $foundFiles = [];
+
+ $pattern = $file_relative_path_root . '.' . $extension . '*';
+
+ foreach ($this->paths as $assetsRoot) {
+ $foundFiles = array_merge($foundFiles, Rails\Toolbox\FileTools::searchFile($assetsRoot, $pattern));
+ }
+
+ return $foundFiles;
+ }
+
+ /**
+ * Deletes all compiled files and compiles them again.
+ */
+ public function compileAll()
+ {
+ $this->emptyCompileDir();
+
+ $this->compileOtherFiles();
+
+ $manifestNames = $this->config()->precompile;
+
+ foreach ($manifestNames as $manifest) {
+ $ext = pathinfo($manifest, PATHINFO_EXTENSION);
+ $paths = $this->findAllFiles($manifest);
+ foreach ($paths as $path) {
+ $this->compileFile(new File($ext, $path));
+ }
+ }
+ }
+
+ public function addFilePatterns($exts)
+ {
+ $this->filePatterns = array_merge($this->filePatterns, (array)$exts);
+ }
+
+ protected function compileOtherFiles()
+ {
+ $exts = $this->filePatterns;
+ $pattern = '*.{' . implode(',', $exts) .'}';
+ $foundFiles = [];
+
+ foreach ($this->paths as $assetsRoot) {
+ $foundFiles = array_merge($foundFiles, Rails\Toolbox\FileTools::searchFile($assetsRoot, $pattern, GLOB_BRACE));
+ }
+
+ $files = [];
+ $assetsPath = $this->compilePath() . $this->prefix();
+ foreach ($foundFiles as $foundFile) {
+ $file = new File($foundFile);
+
+ $contents = file_get_contents($foundFile);
+ $this->createCompiledFile($assetsPath . '/' . $file->relative_path(), $contents, false);
+
+ $md5 = md5_file($foundFile);
+ $md5File = $file->relative_file_root_path() . '-' . $md5 . '.' . $file->type();
+ $this->createCompiledFile($assetsPath . '/' . $md5File, $contents, false);
+ }
+ }
+
+ /**
+ * Deletes everything inside the compile folder.
+ */
+ public function emptyCompileDir()
+ {
+ $this->console("Deleting compiled assets");
+ $dir = $this->compilePath() . $this->prefix();
+ if (is_dir($dir)) {
+ Rails\Toolbox\FileTools::emptyDir($dir);
+ }
+ }
+
+ /**
+ * Accepts:
+ * string - a filename (e.g. application.css), or
+ * File object
+ *
+ * Compiles, minifies and gz-compresses files. Also updates the
+ * manifest index file.
+ * Note that files with same name will be deleted.
+ */
+ public function compileFile($filename)
+ {
+ if (!$this->compilePath()) {
+ throw new Exception\RuntimeException(
+ "Missing asset configuration 'compile_path'"
+ );
+ } elseif (!$this->manifestFileName) {
+ throw new Exception\RuntimeException(
+ sprintf("Property %s::\$manifestFileName must not be empty", __CLASS__)
+ );
+ }
+
+ if (is_string($filename)) {
+ $file = $this->findFile($filename);
+ if (!$file) {
+ throw new Exception\RuntimeException(
+ sprintf("Asset file not found: %s", $filename)
+ );
+ }
+ } elseif ($filename instanceof File) {
+ $file = $filename;
+ } else {
+ throw new Exception\InvalidArgumentException(
+ sprintf(
+ "Argument must be string or Rails\Assets\File, %s passed",
+ gettype($filename)
+ )
+ );
+ }
+
+ $this->console("Compiling file " . $file->full_path());
+
+ $basePath = $this->compilePath() . $this->prefix();
+ $ext = $file->type();
+ $relativeDir = $file->relative_dir();
+ if ($relativeDir) {
+ $relativeDir .= '/';
+ }
+ $fileroot = $basePath . '/' . $relativeDir . $file->file_root();
+
+ $compiledFileDir = dirname($fileroot);
+
+ if (!is_dir($compiledFileDir)) {
+ try {
+ mkdir($compiledFileDir, 0755, true);
+ } catch (\Exception $e) {
+ throw new Exception\RuntimeException(
+ sprintf("Couldn't create dir %s: %s", $compiledFileDir, $e->getMessage())
+ );
+ }
+ }
+
+ set_time_limit(360);
+
+ $parser = new Parser\Base($file);
+ $parser->parse(Parser\Base::PARSE_TYPE_FULL);
+ $fileContents = $parser->parsed_file();
+
+ if ($this->config()->compress) {
+ $fileContents = $this->compressFile($fileContents, $ext);
+ }
+
+ $compileFiles = [ $fileroot . '.' . $ext ];
+
+ if ($this->config()->digest) {
+ $md5 = md5($fileContents);
+ $compileFiles[] = $fileroot . '-' . $md5 . '.' . $ext;
+ }
+
+ foreach ($compileFiles as $compileFile) {
+ $this->createCompiledFile($compileFile, $fileContents);
+ }
+
+ $relativePath = $relativeDir . $file->file_root();
+
+ if ($this->config()->digest) {
+ # Delete previous md5 files
+ $pattern = $fileroot . '-*.' . $ext . '*';
+ if ($mfiles = glob($pattern)) {
+ $regexp = '/-' . $md5 . '\.' . $ext . '(\.gz)?$/';
+ foreach ($mfiles as $mfile) {
+ if (!preg_match($regexp, $mfile)) {
+ unlink($mfile);
+ }
+ }
+ }
+ $this->updateManifestIndex($relativePath . '.' . $ext, $relativePath . '-' . $md5 . '.' . $ext);
+ }
+ return true;
+ }
+
+ protected function createCompiledFile($compiledFilename, $fileContents, $compress = null)
+ {
+ $dir = dirname($compiledFilename);
+ if (!is_dir($dir)) {
+ mkdir($dir, 0777, true);
+ }
+
+ if (file_put_contents($compiledFilename, $fileContents)) {
+ $this->console("Created " . $compiledFilename);
+ } else {
+ throw new Exception\RuntimeException(
+ sprintf("Couldn't write file %s", $compiledFilename)
+ );
+ }
+
+ if (null === $compress) {
+ $compress = $this->config()->gz_compression;
+ }
+
+ # Compress files.
+ if ($compress) {
+ $gzFile = $compiledFilename . '.gz';
+ $gzdata = gzencode($fileContents, $this->config()->gz_compression_level);
+
+ if (file_put_contents($gzFile, $gzdata)) {
+ $this->console("Created " . $gzFile);
+ } else {
+ throw new Exception\RuntimeException(
+ sprintf("Couldn't write file %s", $compiledFilename)
+ );
+ }
+ }
+ }
+
+ /**
+ * Path where the folder named after $prefix will
+ * be created to store compiled assets.
+ */
+ public function compilePath()
+ {
+ return $this->config()->compile_path;
+ }
+
+ /**
+ * Gets the contents of the manifest index file.
+ *
+ * @return array
+ */
+ public function getManifestIndex()
+ {
+ $file = $this->manifestIndexFile();
+ if (is_file($file)) {
+ $index = Rails\Yaml\Parser::readFile($file);
+ # Force array.
+ if (!is_array($index)) {
+ $index = [];
+ }
+ } else {
+ $index = [];
+ }
+ return $index;
+ }
+
+ /**
+ * Path to the manifest file.
+ *
+ * @return string
+ */
+ public function manifestIndexFile()
+ {
+ $basePath = $this->manifestFilePath ?: $this->config()->compile_path . $this->prefix();
+ $indexFile = $basePath . '/' . $this->manifestFileName . '.yml';
+ return $indexFile;
+ }
+
+ /**
+ * @param string $file
+ * @param string $md5File
+ */
+ protected function updateManifestIndex($file, $md5File)
+ {
+ $index = $this->getManifestIndex();
+ $index[$file] = $md5File;
+ Rails\Yaml\Parser::writeFile($this->manifestIndexFile(), $index);
+ }
+
+ public function prefix()
+ {
+ if (!$this->prefix) {
+ $this->prefix = str_replace('\\', '/', Rails::application()->config()->assets->prefix);
+ }
+ return $this->prefix;
+ }
+
+ public function public_path()
+ {
+ return Rails::publicPath() . $this->prefix();
+ }
+
+ public function trim_paths(array $files)
+ {
+ $trimd = [];
+ foreach ($files as $file) {
+ foreach ($this->paths as $path) {
+ if (is_int(strpos($file, $path))) {
+ $trimd[] = str_replace($path, '', $file);
+ continue 2;
+ }
+ }
+ $trimd[] = $file;
+ }
+ return $trimd;
+ }
+
+ public function compressFile($contents, $ext)
+ {
+ $key = $ext . '_compressor';
+ $conf = $this->config()->$key;
+
+ if (!$conf) {
+ throw new Exception\RuntimeException(
+ "No compressor defined for extension $ext"
+ );
+ }
+
+ if ($conf instanceof Closure) {
+ $compressed = $conf($contents);
+ } else {
+ $class = $conf['class_name'];
+
+ $method = $conf['method'];
+ $static = empty($conf['static']);
+
+ if (!empty($conf['file'])) {
+ require_once $conf['file'];
+ }
+
+ if ($static) {
+ $compressed = $class::$method($contents);
+ } else {
+ $compressor = new $class();
+ $compressed = $compressor->$method($contents);
+ }
+ }
+
+ return $compressed;
+ }
+
+ public function config()
+ {
+ return Rails::application()->config()->assets;
+ }
+
+ public function cache_read($key)
+ {
+ return Rails::cache()->read($key, ['path' => 'rails']);
+ }
+
+ public function cache_write($key, $contents)
+ {
+ return Rails::cache()->write($key, $contents, ['path' => 'rails']);
+ }
+
+ public function base_path()
+ {
+ return Rails::application()->router()->basePath();
+ }
+
+ public function paths()
+ {
+ return $this->paths;
+ }
+
+ public function server()
+ {
+ if (!$this->server) {
+ $this->server = new Server;
+ }
+ return $this->server;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Assets/Compiler.php b/lib/Rails/Assets/Compiler.php
new file mode 100755
index 0000000..eff79bd
--- /dev/null
+++ b/lib/Rails/Assets/Compiler.php
@@ -0,0 +1,9 @@
+type = $this->extension = $type;
+
+ if (preg_match('/^\w:/', $path) || strpos($path, '/') === 0) {
+ $path = str_replace('\\', '/', $path);
+ foreach ($this->assets_paths() as $asset_path) {
+ if (strpos($path, $asset_path) === 0) {
+ $this->baseDir = $asset_path;
+ $path = substr($path, strlen($asset_path) + 1);
+ break;
+ }
+ }
+ }
+
+ $subDirs = explode('/', str_replace('\\', '/', dirname($path)));
+
+ $parseExt = $path;
+
+ while ($ext != $this->type) {
+ $this->extensions[] = $ext;
+ $parseExt = substr($parseExt, 0, (strlen($ext) + 1) * -1);
+ $ext = pathinfo($parseExt, PATHINFO_EXTENSION);
+ if (!$ext) {
+ throw new Exception\InvalidArgumentException(
+ sprintf(
+ "Asset of type %s doesn't have the corresponding extension: %s",
+ $this->type,
+ $path
+ )
+ );
+ }
+ }
+
+ $fileRoot = pathinfo($parseExt, PATHINFO_FILENAME);
+
+ $this->subDirs = $subDirs;
+ $this->fileRoot = $fileRoot;
+
+ $this->validate_subdirs();
+ }
+
+ public function base_dir($baseDir = null)
+ {
+ if ($baseDir)
+ $this->baseDir = str_replace('\\', '/', $baseDir);
+ else
+ return $this->baseDir;
+ }
+
+ /**
+ * Relative dir (relative to assets paths).
+ */
+ public function relative_dir()
+ {
+ return implode('/', $this->subDirs);
+ }
+
+ public function full_dir()
+ {
+ $relativeDir = $this->relative_dir();
+ if ($relativeDir) {
+ $relativeDir = '/' . $relativeDir;
+ }
+ return $this->baseDir . $relativeDir;
+ }
+
+ public function relative_path()
+ {
+ $full_path = implode('/', $this->subDirs) . '/' . $this->fileRoot . '.' . $this->extension;
+ if ($this->extensions) {
+ $full_path .= '.' . implode('.', array_reverse($this->extensions));
+ }
+ return ltrim($full_path, '/');
+ }
+
+ public function full_path()
+ {
+ return $this->baseDir . '/' . $this->relative_path();
+ }
+
+ /**
+ * Full path except file extension
+ */
+ public function full_file_root_path()
+ {
+ $subDirs = implode('/', $this->subDirs);
+ if ($subDirs) {
+ $subDirs .= '/';
+ }
+ return $this->baseDir . '/' . $subDirs . $this->fileRoot;
+ }
+
+ /**
+ * Sub dirs + file root
+ */
+ public function relative_file_root_path()
+ {
+ $subDirs = implode('/', $this->subDirs);
+ if ($subDirs) {
+ $subDirs .= '/';
+ }
+ return $subDirs . $this->fileRoot;
+ }
+
+ public function sub_dirs()
+ {
+ return $this->subDirs;
+ }
+
+ public function file_root()
+ {
+ return $this->fileRoot;
+ }
+
+ public function extension()
+ {
+ return $this->extension;
+ }
+
+ public function extensions()
+ {
+ return $this->extensions;
+ }
+
+ public function type()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Creates a file "familiar": another asset file of the same type,
+ * whose baseDir is unknown.
+ */
+ public function familiar($file_path)
+ {
+ $dirs = explode('/', $file_path);
+ $new_file_root = array_pop($dirs);
+ array_unshift($dirs, reset($this->subDirs));
+
+ $file = new static($this->type);
+ $file->subDirs = $dirs;
+ $file->fileRoot = $new_file_root;
+ $file->extension = $this->extension;
+ return $file;
+ }
+
+ public function url($basePath = null, $prefix = null)
+ {
+ if (null === $basePath) {
+ $basePath = Rails::assets()->base_path();
+ }
+ if (null === $prefix) {
+ $prefix = Rails::assets()->prefix();
+ }
+
+ return $basePath . $prefix . '/' . $this->relative_file_root_path() . '.' . $this->type;
+ }
+
+ protected function assets_paths()
+ {
+ return Rails::assets()->paths();
+ }
+
+ protected function validate_subdirs()
+ {
+ $subDirs = [];
+
+ foreach ($this->subDirs as $sd) {
+ if (preg_match('/^[\w-]+$/', $sd))
+ $subDirs[] = $sd;
+ }
+
+ $this->subDirs = $subDirs;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Assets/Initializer.php b/lib/Rails/Assets/Initializer.php
new file mode 100755
index 0000000..be00dca
--- /dev/null
+++ b/lib/Rails/Assets/Initializer.php
@@ -0,0 +1,15 @@
+parseType = $parseType;
+
+ $this->getFileContents();
+
+ if ($this->parseType == self::PARSE_TYPE_NONE) {
+ $this->parsedFile = $this->fileContents;
+ $this->fileContents = null;
+ } else {
+ if ($this->parseType == self::PARSE_TYPE_GET_PATHS) {
+ $this->urlPaths[] = $this->file->url();
+ }
+ $this->extractDirectives();
+ $this->executeDirectives();
+ }
+
+ return $this;
+ }
+
+ /**
+ * $path_parts = [0]=>assets_path; [1]=>filename; [2]=>extension;
+ */
+ public function __construct($file, Base $parentParser = null)
+ {
+ if (!$file instanceof File) {
+ $extension = pathinfo($file, PATHINFO_EXTENSION);
+ $file = new File($extension, $file);
+ }
+
+ $this->file = $file;
+
+ if (!isset(self::$_first_parents[$file->extension()])) {
+ self::$_first_parents[$file->extension()] = $this;
+ }
+
+ $this->parentParser = $parentParser;
+
+ $this->firstParent()->add_required_file($file);
+ }
+
+ protected function extractDirectives()
+ {
+ $lines = explode("\n", $this->fileContents);
+
+ $commentBlock = false;
+
+ $directivesRegexp = implode('|', [
+ 'require_self',
+ 'require_directory [.-\w\s\/]+',
+ 'require_tree [.-\w\s\/]+',
+ 'require [.-\w\s\/]+',
+ 'include [.-\w\s\/]+',
+ ]);
+
+ foreach ($lines as $k => $line) {
+ $line = trim($line);
+
+ if (!$line) {
+ continue;
+ }
+
+ $ab = substr($line, 0, 2);
+ $c = substr($line, 2, 1);
+
+ if ($ab == '/*') {
+ $commentBlock = true;
+ } elseif (!$commentBlock && !preg_match('~^\s*//|/\*~', $line)) {
+ # Not a comment and not in a comment block.
+ # Directives block ended.
+ break;
+ }
+
+ if (preg_match('~^\W+=\s+?(' . $directivesRegexp . ')~', $line, $m)) {
+ $this->directives[] = array_filter(explode(' ', $m[1]));
+ unset($lines[$k]);
+ }
+
+ if ($commentBlock && is_int(strpos($line, '*/'))) {
+ $commentBlock = false;
+ }
+ }
+
+ $this->fileContents = implode("\n", $lines);
+ }
+
+ protected function executeDirectives()
+ {
+ if ($this->directives) {
+ foreach ($this->directives as $directive) {
+ $command = $directive[0];
+
+ if (isset($directive[1])) {
+ $params = $directive[1];
+ }
+
+ switch ($command) {
+ case 'require_directory':
+ $this->requireDir($params);
+ break;
+
+ case 'require_tree':
+ $this->requireTree($params);
+ break;
+
+ case 'require_self':
+ $this->requireSelf();
+ break;
+
+ case 'require':
+ $this->requireFile($params . '.' . $this->file->type());
+ break;
+
+ case 'include':
+ $this->includeFile($params . '.' . $this->file->type());
+ break;
+
+ default:
+ throw new Exception\RuntimeException(
+ sprintf("Invalid directive in file %s:\n%s", $this->file->full_path(), var_export($directive, true))
+ );
+ }
+ }
+ }
+
+ $this->requireSelf();
+ $this->parsedFile = implode("\n", $this->parsedFile);
+ }
+
+ public function urlPaths()
+ {
+ return $this->urlPaths;
+ }
+
+ protected function requireSelf()
+ {
+ if (!$this->requiredSelf) {
+ $this->requiredSelf = true;
+ $this->parsedFile[] = $this->fileContents;
+ $this->fileContents = null;
+ }
+ }
+
+ protected function includeFile($filename)
+ {
+ if (!$file = $this->filenameToFile($filename)) {
+ throw new Exception\FileNotFoundException(
+ sprintf(
+ "Included file '%s' not found. Require trace:\n%s",
+ $filename,
+ var_export($this->_parents_trace(), true)
+ )
+ );
+ }
+
+ $parser = new static($file, $this);
+ $parser->parse($this->parseType);
+
+ if ($this->parseType == Base::PARSE_TYPE_GET_PATHS) {
+ $this->urlPaths = array_merge($this->urlPaths, $parser->urlPaths());
+ }
+
+ $this->firstParent()->add_required_file($file);
+ $this->parsedFile[] = $parser->parsed_file();
+ }
+
+ protected function requireFile($filename)
+ {
+ if (!$file = $this->filenameToFile($filename)) {
+ throw new Exception\FileNotFoundException(
+ sprintf(
+ "Required file '%s' not found. Require trace:\n%s",
+ $filename,
+ var_export($this->_parents_trace(), true)
+ )
+ );
+ }
+
+ if ($this->firstParent()->file_already_required($file)) {
+ return false;
+ }
+
+ $this->includeFile($file);
+ }
+
+ private function filenameToFile($filename)
+ {
+ if (!$filename instanceof File) {
+ $file = Rails::assets()->findFile($filename);
+ if (!$file) {
+ return false;
+ }
+ } else {
+ $file = $filename;
+ }
+
+ return $file;
+ }
+
+ protected function requireDir($dir)
+ {
+ $path = $this->file->full_dir() . '/' . $dir;
+ $realPath = realpath($path);
+
+ if (!$realPath) {
+ throw new Exception\RuntimeException(
+ sprintf(
+ "Directory not found: %s",
+ $path
+ )
+ );
+ }
+
+ $files = glob($realPath . '/*.' . $this->file->type() . '*');
+
+ foreach ($files as $file) {
+ $file = new File($this->file->type(), $file);
+ $this->requireFile($file);
+ }
+ }
+
+ protected function requireTree($tree)
+ {
+ $rootDir = $this->file->full_dir();
+
+ $path = $rootDir . '/' . $tree;
+ $realPath = realpath($path);
+
+ if (!$realPath) {
+ throw new Exception\RuntimeException(
+ sprintf(
+ "Path to tree not found: %s",
+ $path
+ )
+ );
+ }
+
+ # Require files in root directory.
+ $this->requireDir($tree);
+
+ # Require files in sub directories.
+ $allDirs = Toolbox\FileTools::listDirs($realPath);
+
+ foreach ($allDirs as $dir) {
+ $relativeDir = trim(substr($dir, strlen($rootDir)), '/');
+ $this->requireDir($relativeDir);
+ }
+ }
+
+ public function parsedFile($implode = true)
+ {
+ return $implode && is_array($this->parsedFile) ? implode(self::EOL, $this->parsedFile) : $this->parsedFile;
+ }
+
+ public function parsed_file($implode = true)
+ {
+ return $this->parsedFile($implode);
+ }
+
+ public function parentParser()
+ {
+ return $this->parentParser;
+ }
+
+ public function add_required_file($file)
+ {
+ $this->_required_files[] = $file->full_file_root_path();
+ }
+
+ public function file_already_required($filename)
+ {
+ return in_array($filename->full_file_root_path(), $this->_required_files);
+ }
+
+ public function required_files()
+ {
+ return $this->_required_files;
+ }
+
+ public function config()
+ {
+ return Assets::instance()->config();
+ }
+
+ protected function getFileContents()
+ {
+ // if ($cached = $this->_cached_other_extension_file())
+ // return $cached
+
+ $contents = file_get_contents($this->file->full_path());
+
+ if ($this->file->extensions()) {
+ foreach ($this->file->extensions() as $ext) {
+ if ($ext == 'php') {
+ $contents = PHParser::parseContents($contents);
+ // $contents = $parser->parse();
+ } else {
+ $key = $this->file->type() . '_extensions';
+ // if (!$this->config()->$key)
+ // vpe($key);
+ // vpe($this->config()->$key);
+ $conf = $this->config()->$key->$ext;
+ // $conf = $this->config()['parsers'][self::EXTENSION][$ext];
+
+ if (!$conf) {
+ throw new Exception\RuntimeException(
+ sprintf(
+ "Unknown parser [ file=>%s, extension=>%s ]",
+ $this->file->full_path(),
+ $ext
+ )
+ );
+ }
+
+ if ($conf instanceof Closure) {
+ $conf($contents);
+ } else {
+ $class = $conf['class_name'];
+ $method = $conf['method'];
+ $static = !empty($conf['static']);
+
+ if (!empty($conf['file'])) {
+ // vpe(get_include_path());
+ require_once $conf['file'];
+ }
+
+ if ($static) {
+ $func = $class . '::' . $method;
+ $contents = $func($contents);
+ } else {
+ $parser = new $class();
+ $contents = $parser->$method($contents);
+ }
+ }
+ }
+ }
+ }
+
+ $this->fileContents = $contents;
+ }
+
+ protected function firstParent()
+ {
+ return self::$_first_parents[$this->file->extension()];
+ }
+
+ protected function _parents_trace()
+ {
+ $parent = $this->parentParser;
+ if (!$parent)
+ return [$this->file->full_path()];
+
+ $files = [$parent->file->full_path()];
+
+ while ($parent) {
+ $parent = $parent->parentParser;
+ $parent && $files[] = $parent->file->full_path();
+ }
+ return $files;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Assets/Parser/Javascript/ClosureApi/ClosureApi.php b/lib/Rails/Assets/Parser/Javascript/ClosureApi/ClosureApi.php
new file mode 100755
index 0000000..4c3d293
--- /dev/null
+++ b/lib/Rails/Assets/Parser/Javascript/ClosureApi/ClosureApi.php
@@ -0,0 +1,87 @@
+makeRequest($code, ['output_info' => 'errors']);
+
+ if (trim($obj->resp())) {
+ if (self::$save_file_on_error)
+ file_put_contents(self::errorFile(), $code);
+ throw new Exception\ErrorsOnCodeException($obj->resp());
+ }
+
+ $obj->makeRequest($code);
+ $resp = $obj->resp();
+ if (!trim($resp))
+ throw new Exception\BlankResponseException("Closure returned an empty string (file too large? size => ".strlen($code).")");
+ return $resp;
+ }
+
+ static public function errorFile()
+ {
+ if (!self::$save_path)
+ self::$save_path = Rails::root() . '/tmp';
+
+ return self::$save_path . '/' . self::$errorFile_name;
+ }
+
+ public function __construct($params)
+ {
+ $this->_params = array_merge($this->defaultParams(), $params);
+ }
+
+ public function makeRequest($code, array $extra_params = [])
+ {
+ $params = array_merge($this->_params, $extra_params);
+ $params['js_code'] = $code;
+
+ $ch = curl_init(self::API_URL);
+ curl_setopt_array($ch, [
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => http_build_query($params),
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_HTTPHEADER => [
+ "Content-type: application/x-www-form-urlencoded"
+ ]
+ ]);
+ $this->_resp = curl_exec($ch);
+ $this->_lastInfo = curl_getinfo($ch);
+ curl_close($ch);
+ }
+
+ public function resp()
+ {
+ return $this->_resp;
+ }
+
+ public function lastInfo()
+ {
+ return $this->_lastInfo;
+ }
+
+ protected function defaultParams()
+ {
+ return [
+ 'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
+ 'output_info' => 'compiled_code',
+ 'output_format' => 'text',
+ 'language' => 'ECMASCRIPT5'
+ ];
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Assets/Parser/Javascript/ClosureApi/Exception/BlankResponseException.php b/lib/Rails/Assets/Parser/Javascript/ClosureApi/Exception/BlankResponseException.php
new file mode 100755
index 0000000..c2b8460
--- /dev/null
+++ b/lib/Rails/Assets/Parser/Javascript/ClosureApi/Exception/BlankResponseException.php
@@ -0,0 +1,6 @@
+parse($contents);
+ }
+
+ public function __construct($file)
+ {
+ $this->file = $file;
+ }
+
+ public function parse($contents)
+ {
+ ob_start();
+ eval('?>' . $contents);
+ return ob_get_clean();
+ }
+
+ /**
+ * Returns relative paths.
+ */
+ protected function assetPath($file, array $options = [])
+ {
+ $root = \Rails::application()->router()->rootPath();
+ if ($root == '/') {
+ $root = '';
+ }
+ $path = Rails::assets()->prefix() . '/' . $file;
+ return $this->getRelativePath($this->file->url(), $path);
+ }
+
+ # SO: /a/12236654/638668
+ protected function getRelativePath($from, $to)
+ {
+ $from = str_replace('\\', '/', $from);
+ $to = str_replace('\\', '/', $to);
+
+ $from = explode('/', $from);
+ $to = explode('/', $to);
+ $relPath = $to;
+
+ foreach($from as $depth => $dir) {
+ if($dir === $to[$depth]) {
+ array_shift($relPath);
+ } else {
+ $remaining = count($from) - $depth;
+ if($remaining > 1) {
+ $padLength = (count($relPath) + $remaining - 1) * -1;
+ $relPath = array_pad($relPath, $padLength, '..');
+ break;
+ } else {
+ $relPath[0] = './' . $relPath[0];
+ }
+ }
+ }
+ return implode('/', $relPath);
+ }
+}
diff --git a/lib/Rails/Assets/Parser/PHParser.php b/lib/Rails/Assets/Parser/PHParser.php
new file mode 100755
index 0000000..cbbf301
--- /dev/null
+++ b/lib/Rails/Assets/Parser/PHParser.php
@@ -0,0 +1,26 @@
+parse($contents);
+ }
+
+ public function parse($contents)
+ {
+ ob_start();
+ eval('?>' . $contents);
+ return ob_get_clean();
+ }
+}
diff --git a/lib/Rails/Assets/Server.php b/lib/Rails/Assets/Server.php
new file mode 100755
index 0000000..599b9ca
--- /dev/null
+++ b/lib/Rails/Assets/Server.php
@@ -0,0 +1,153 @@
+request()->path();
+
+ if ($base_path = $this->base_path())
+ $path = str_replace($base_path, '', $path);
+
+ $request = str_replace(Rails::assets()->prefix() . '/', '', $path);
+ $this->serve_file($request);
+ }
+
+ public function serve_file($file_path)
+ {
+ $file = Rails::assets()->findFile($file_path);
+
+ if (!$file) {
+ $this->set_404_headers();
+ return;
+ }
+
+ $ext = $file->type();
+ $parser = null;
+
+ switch ($ext) {
+ case 'js':
+ $parser = new Parser\Base($file);
+ $this->set_javascript_headers();
+ break;
+
+ case 'css':
+ $parser = new Parser\Base($file);
+ $this->set_stylesheet_headers();
+ break;
+
+ case 'jpeg':
+ $this->headers()->contentType('image/jpeg');
+ break;
+
+ case 'jpg':
+ $this->headers()->contentType('image/jpeg');
+ break;
+
+ case 'png':
+ $this->headers()->contentType('image/png');
+ break;
+
+ case 'gif':
+ $this->headers()->contentType('image/gif');
+ break;
+
+ case 'svg':
+ $this->headers()->contentType('image/svg+xml');
+ break;
+
+ case 'ttf':
+ $this->headers()->contentType('application/x-font-ttf');
+ break;
+
+ case 'woff':
+ $this->headers()->contentType('application/font-woff');
+ break;
+
+ default:
+ $this->headers()->contentType('application/octet-stream');
+ return;
+ }
+
+ if ($parser) {
+ if ($this->params()->body) {
+ $parseType = Parser\Base::PARSE_TYPE_NONE;
+ } else {
+ $parseType = Parser\Base::PARSE_TYPE_FULL;
+ }
+
+ $parser->parse($parseType);
+ $file_contents = $parser->parsed_file();
+ } else {
+ $file_contents = file_get_contents($file->full_path());
+ }
+
+ $etag = md5($file_contents);
+ $date = date('D, d M Y H:i:s e');
+ $last_modified = Rails::cache()->fetch('assets.last_mod.' . $file->full_path(), function() use ($date){
+ return $date;
+ });
+
+ if ($this->file_modified($etag, $last_modified)) {
+ $this->headers()->add("Last-Modified", $last_modified);
+ $this->headers()->add("Cache-Control", 'public, max-age=31536000');
+ $this->headers()->add("ETag", $etag);
+ Rails::application()->dispatcher()->response()->body($file_contents);
+ } else {
+ $this->headers()->status('HTTP/1.1 304 Not Modified');
+ Rails::application()->dispatcher()->response()->body('');
+ }
+ }
+
+ public function base_path()
+ {
+ return Rails::application()->router()->basePath();
+ }
+
+ public function request()
+ {
+ return Rails::application()->dispatcher()->request();
+ }
+
+ public function params()
+ {
+ return Rails::application()->dispatcher()->parameters();
+ }
+
+ public function headers()
+ {
+ return Rails::application()->dispatcher()->headers();
+ }
+
+ private function file_modified($etag, $last_modified)
+ {
+ $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
+ $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : false;
+
+ if ((($if_none_match && $if_none_match == $etag) || !$if_none_match) &&
+ ($if_modified_since && $if_modified_since == $last_modified))
+ {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private function set_404_headers()
+ {
+ $this->headers()->status(404);
+ }
+
+ private function set_javascript_headers()
+ {
+ $this->headers()->contentType('application/javascript');
+ }
+
+ private function set_stylesheet_headers()
+ {
+ $this->headers()->contentType('text/css');
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Assets/Traits/AssetPathTrait.php b/lib/Rails/Assets/Traits/AssetPathTrait.php
new file mode 100755
index 0000000..a17cc7b
--- /dev/null
+++ b/lib/Rails/Assets/Traits/AssetPathTrait.php
@@ -0,0 +1,31 @@
+assetPath('jquery-ui/loading.gif');
+ */
+ protected function assetPath($file, array $options = [])
+ {
+ if (!isset($options['digest'])) {
+ $options['digest'] = true;
+ }
+
+ if ($options['digest']) {
+ if ($path = \Rails::assets()->findCompiledFile($file)) {
+ return $path;
+ }
+ }
+ $root = \Rails::application()->router()->rootPath();
+ if ($root == '/') {
+ $root = '';
+ }
+ return $root . \Rails::assets()->prefix() . '/' . $file;
+ }
+}
diff --git a/lib/Rails/Cache/Cache.php b/lib/Rails/Cache/Cache.php
new file mode 100755
index 0000000..cf6cda1
--- /dev/null
+++ b/lib/Rails/Cache/Cache.php
@@ -0,0 +1,92 @@
+toArray();
+
+ switch ($config[0]) {
+ case 'file_store':
+ $class = '\Rails\Cache\Store\FileStore';
+ break;
+
+ case 'mem_cached_store':
+ $class = '\Rails\Cache\Store\MemCachedStore';
+ break;
+
+ default:
+ $class = $config[0];
+ break;
+ }
+
+ array_shift($config);
+
+ $this->store = new $class($config);
+ }
+
+ public function read($key, array $params = [])
+ {
+ return $this->store->read($key, $params);
+ }
+
+ public function write($key, $value, array $options = [])
+ {
+ return $this->store->write($key, $value, $options);
+ }
+
+ public function delete($key, array $params = [])
+ {
+ return $this->store->delete($key, $params);
+ }
+
+ public function exists($key)
+ {
+ return $this->store->exists($key);
+ }
+
+ public function fetch($key, $options = null, Closure $block = null)
+ {
+ if ($options instanceof Closure) {
+ $block = $options;
+ $options = [];
+ }
+ $value = $this->read($key, $options);
+
+ if ($value === null) {
+ $value = $block();
+ $this->write($key, $value, $options);
+ }
+ return $value;
+ }
+
+ public function readMulti()
+ {
+ $names = func_get_args();
+ if (is_array(end($names)))
+ $options = array_pop($names);
+ else
+ $options = [];
+
+ $results = [];
+ foreach ($names as $name) {
+ if (null !== ($value = $this->read($name)))
+ $results[$name] = $value;
+ }
+ return $results;
+ }
+
+ public function store()
+ {
+ return $this->store;
+ }
+}
diff --git a/lib/Rails/Cache/Entry.php b/lib/Rails/Cache/Entry.php
new file mode 100755
index 0000000..a1a1b6d
--- /dev/null
+++ b/lib/Rails/Cache/Entry.php
@@ -0,0 +1,171 @@
+_key = $key;
+ $this->_hash = $this->_hash_key($key);
+ }
+
+ public function read(array $params = [])
+ {
+ if (isset($params['path'])) {
+ $this->_dir = $params['path'];
+ unset($params['path']);
+ }
+
+ if ($this->file_exists())
+ $this->_read_file();
+ return $this->_value;
+ }
+
+ public function write($val, array $params)
+ {
+ $this->_value = serialize($val);
+ if (isset($params['expires_in'])) {
+ if (!ctype_digit((string)$params['expires_in']))
+ $params['expires_in'] = strtotime($params['expires_in']);
+ }
+ if (isset($params['path'])) {
+ $this->_dir = $params['path'];
+ unset($params['path']);
+ }
+ $this->_params = $params;
+
+ $header = [];
+ foreach ($params as $k => $v)
+ $header[] = $k . self::KEY_VALUE_SEPARATOR . $v;
+ $header = implode(self::DATA_SEPARATOR, $header);
+
+ if (!is_dir($this->_path()))
+ mkdir($this->_path(), 0777, true);
+
+ file_put_contents($this->_file_name(), $header . "\n" . $this->_value);
+ }
+
+ public function delete()
+ {
+ $this->_value = null;
+ $this->_delete_file();
+ }
+
+ public function value()
+ {
+ return $this->_value;
+ }
+
+ public function file_exists()
+ {
+ if ($this->_file_exists === null) {
+ $this->_file_exists = is_file($this->_file_name());
+ }
+ return $this->_file_exists;
+ }
+
+ public function unserialize_e_handler()
+ {
+ $this->_value = false;
+ }
+
+ private function _read_file()
+ {
+ $this->_file_contents = file_get_contents($this->_file_name());
+ $this->_parse_contents();
+ if ($this->_expired()) {
+ $this->delete();
+ } else {
+
+ }
+ }
+
+ private function _parse_contents()
+ {
+ $regex = '/^(\V+)/';
+ preg_match($regex, $this->_file_contents, $m);
+ if (!empty($m[1])) {
+ foreach(explode(self::DATA_SEPARATOR, $m[1]) as $data) {
+ list($key, $val) = explode(self::KEY_VALUE_SEPARATOR, $data);
+ $this->_params[$key] = $val;
+ }
+ } else
+ $m[1] = '';
+
+ # For some reason, try/catch Exception didn't work.
+ $err_handler = set_error_handler([$this, "unserialize_e_handler"]);
+
+ $this->_value = unserialize(str_replace($m[1] . "\n", '', $this->_file_contents));
+ $this->_file_contents = null;
+
+ set_error_handler($err_handler);
+ }
+
+ private function _expired()
+ {
+ if (!isset($this->_params['expires_in']) || $this->_params['expires_in'] > time())
+ return false;
+ return true;
+ }
+
+ private function _delete_file()
+ {
+ if (is_file($this->_file_name()))
+ unlink($this->_file_name());
+ }
+
+ private function _file_name()
+ {
+ return $this->_path() . '/' . $this->_hash;
+ }
+
+ private function _generate_path($key)
+ {
+ $md5 = $this->_hash_key($key);
+ $ab = substr($md5, 0, 2);
+ $cd = substr($md5, 2, 2);
+ return $this->_base_path() . '/' . $ab . '/' . $cd;
+ }
+
+ private function _base_path()
+ {
+ $subdir = $this->_dir ? '/' . str_replace('.', '', $this->_dir) : '';
+ return Rails::cache()->path() . $subdir;
+ }
+
+ private function _hash_key($key)
+ {
+ return md5($key);
+ }
+
+ private function _path()
+ {
+ if (!$this->_path) {
+ $this->_path = $this->_generate_path($this->_key);
+ }
+ return $this->_path;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Cache/Exception/ExceptionInterface.php b/lib/Rails/Cache/Exception/ExceptionInterface.php
new file mode 100755
index 0000000..2c5c49d
--- /dev/null
+++ b/lib/Rails/Cache/Exception/ExceptionInterface.php
@@ -0,0 +1,6 @@
+basePath = $config[0];
+ }
+
+ public function read($key, array $params = [])
+ {
+ return $this->getEntry($key, $params)->read();
+ }
+
+ public function write($key, $value, array $params)
+ {
+ return $this->getEntry($key, $params)->write($value);
+ }
+
+ public function delete($key, array $params)
+ {
+ return $this->getEntry($key, $params)->delete();
+ }
+
+ public function exists($key, array $params)
+ {
+ return $this->getEntry($key, $params)->fileExists();
+ }
+
+ /**
+ * Removes cache files from directory.
+ */
+ public function deleteDirectory($dirname)
+ {
+ $dirpath = $this->path() . '/' . $dirname;
+
+ if (is_dir($dirpath)) {
+ Toolbox::emptyDir($dirpath);
+ }
+ }
+
+ public function basePath()
+ {
+ return $this->basePath;
+ }
+
+ protected function getEntry($key, array $params)
+ {
+ return new Entry($key, $params, $this);
+ }
+}
diff --git a/lib/Rails/Cache/Store/FileStore/Entry.php b/lib/Rails/Cache/Store/FileStore/Entry.php
new file mode 100755
index 0000000..2a9c0b5
--- /dev/null
+++ b/lib/Rails/Cache/Store/FileStore/Entry.php
@@ -0,0 +1,181 @@
+store = $store;
+
+ $this->_key = $key;
+ $this->_hash = $this->_hash_key($key);
+
+ if (isset($params['path'])) {
+ $this->_dir = $params['path'];
+ unset($params['path']);
+ }
+
+ $this->params = $params;
+ }
+
+ public function read()
+ {
+ if ($this->fileExists())
+ $this->_read_file();
+
+ return $this->_value;
+ }
+
+ public function write($val)
+ {
+ $this->_value = serialize($val);
+
+ if (isset($this->params['expires_in'])) {
+ if (!ctype_digit((string)$this->params['expires_in']))
+ $this->params['expires_in'] = strtotime('+' . $this->params['expires_in']);
+ }
+ if (isset($this->params['path'])) {
+ $this->_dir = $this->params['path'];
+ unset($this->params['path']);
+ }
+ $this->params = $this->params;
+
+ $header = [];
+ foreach ($this->params as $k => $v)
+ $header[] = $k . self::KEY_VALUE_SEPARATOR . $v;
+ $header = implode(self::DATA_SEPARATOR, $header);
+
+ if (!is_dir($this->_path()))
+ mkdir($this->_path(), 0777, true);
+
+ return (bool)file_put_contents($this->_file_name(), $header . "\n" . $this->_value);
+ }
+
+ public function delete()
+ {
+ $this->_value = null;
+ return $this->_delete_file();
+ }
+
+ public function value()
+ {
+ return $this->_value;
+ }
+
+ public function fileExists()
+ {
+ if ($this->_file_exists === null) {
+ $this->_file_exists = is_file($this->_file_name());
+ }
+ return $this->_file_exists;
+ }
+
+ public function unserialize_e_handler()
+ {
+ $this->_value = false;
+ }
+
+ private function _read_file()
+ {
+ $this->_file_contents = file_get_contents($this->_file_name());
+ $this->_parse_contents();
+ if ($this->_expired()) {
+ $this->delete();
+ } else {
+
+ }
+ }
+
+ private function _parse_contents()
+ {
+ $regex = '/^(\V+)/';
+ preg_match($regex, $this->_file_contents, $m);
+ if (!empty($m[1])) {
+ foreach(explode(self::DATA_SEPARATOR, $m[1]) as $data) {
+ list($key, $val) = explode(self::KEY_VALUE_SEPARATOR, $data);
+ $this->params[$key] = $val;
+ }
+ } else
+ $m[1] = '';
+
+ # For some reason, try/catch Exception didn't work.
+ $err_handler = set_error_handler([$this, "unserialize_e_handler"]);
+
+ $this->_value = unserialize(str_replace($m[1] . "\n", '', $this->_file_contents));
+ $this->_file_contents = null;
+
+ set_error_handler($err_handler);
+ }
+
+ private function _expired()
+ {
+ if (!isset($this->params['expires_in']) || $this->params['expires_in'] > time())
+ return false;
+ return true;
+ }
+
+ private function _delete_file()
+ {
+ if (is_file($this->_file_name()))
+ return unlink($this->_file_name());
+ return true;
+ }
+
+ private function _file_name()
+ {
+ return $this->_path() . '/' . $this->_hash;
+ }
+
+ private function _hash_key($key)
+ {
+ return md5($key);
+ }
+
+ private function _path()
+ {
+ if (!$this->_path) {
+ $this->_path = $this->_generate_path($this->_key);
+ }
+ return $this->_path;
+ }
+
+ private function _generate_path($key)
+ {
+ $md5 = $this->_hash_key($key);
+ $ab = substr($md5, 0, 2);
+ $cd = substr($md5, 2, 2);
+ return $this->_base_path() . '/' . $ab . '/' . $cd;
+ }
+
+ private function _base_path()
+ {
+ $subdir = $this->_dir ? '/' . str_replace('.', '', $this->_dir) : '';
+ return $this->store->basePath() . $subdir;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Cache/Store/MemCachedStore.php b/lib/Rails/Cache/Store/MemCachedStore.php
new file mode 100755
index 0000000..a66e99e
--- /dev/null
+++ b/lib/Rails/Cache/Store/MemCachedStore.php
@@ -0,0 +1,71 @@
+connection = new Memcached('Rails.Application.' . Rails::application()->name());
+ $this->connection->addServers($servers);
+ }
+
+ public function connection()
+ {
+ return $this->connection;
+ }
+
+ public function read($key, array $params = [])
+ {
+ $value = $this->connection->get($key);
+
+ if (!$value) {
+ if ($this->connection->getResultCode() == Memcached::RES_NOTFOUND) {
+ return null;
+ } else {
+ # There was some kind of error.
+ }
+ } else {
+ return unserialize($value);
+ }
+ }
+
+ public function write($key, $val, array $params)
+ {
+ $val = serialize($val);
+
+ if (isset($params['expires_in'])) {
+ if (!ctype_digit((string)$params['expires_in']))
+ $expires_in = strtotime('+' . $params['expires_in']);
+ } else
+ $expires_in = 0;
+
+ $resp = $this->connection->add($key, $val, $expires_in);
+ }
+
+ public function delete($key, array $params)
+ {
+ $time = !empty($params['time']) ? $params['time'] : 0;
+ return $this->connection->delete($key, $time);
+ }
+
+ public function exists($key, array $params)
+ {
+ if ($this->connection->get($key))
+ return true;
+ else {
+ # An error could have occured.
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Config/Config.php b/lib/Rails/Config/Config.php
new file mode 100755
index 0000000..9a36314
--- /dev/null
+++ b/lib/Rails/Config/Config.php
@@ -0,0 +1,88 @@
+container);
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if (null === $offset) {
+ $this->container[] = $value;
+ } else {
+ if (is_array($value)) {
+ $value = new self($value);
+ }
+ $this->container[$offset] = $value;
+ }
+ }
+ public function offsetExists($offset)
+ {
+ return isset($this->container[$offset]);
+ }
+ public function offsetUnset($offset)
+ {
+ unset($this->container[$offset]);
+ }
+ public function offsetGet($offset)
+ {
+ return isset($this->container[$offset]) ? $this->container[$offset] : null;
+ }
+
+ public function __construct(array $config = [])
+ {
+ $this->add($config);
+ }
+
+ public function __set($prop, $value)
+ {
+ if (is_array($value))
+ $this->offsetSet($prop, new self($value));
+ else
+ $this->offsetSet($prop, $value);
+ }
+
+ public function __get($prop)
+ {
+ if ($this->offsetExists($prop))
+ return $this->offsetGet($prop);
+ }
+
+ public function __isset($prop)
+ {
+ return (bool)$this->__get($prop);
+ }
+
+ public function add(array $config)
+ {
+ foreach ($config as $name => $value) {
+ $this->__set($name, $value);
+ }
+ }
+
+ public function merge(array $config)
+ {
+ $this->container = array_merge($this->container, $config);
+ return $this;
+ }
+
+ public function toArray()
+ {
+ return $this->container;
+ }
+
+ public function includes($value)
+ {
+ return in_array($value, $this->container);
+ }
+
+ public function keys()
+ {
+ return array_keys($this->container);
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Config/default_config.php b/lib/Rails/Config/default_config.php
new file mode 100755
index 0000000..06981e4
--- /dev/null
+++ b/lib/Rails/Config/default_config.php
@@ -0,0 +1,295 @@
+ $appPath,
+ 'views' => $viewsPath,
+ 'config' => new Rails\Paths\Path('config', [self::$root]),
+ 'controllers' => new Rails\Paths\Path('controllers', [$appPath]),
+ 'helpers' => new Rails\Paths\Path('helpers', [$appPath]),
+ 'models' => new Rails\Paths\Path('models', [$appPath]),
+ 'layouts' => new Rails\Paths\Path('layouts', [$viewsPath]),
+ 'mailers' => new Rails\Paths\Path('mailers', [$appPath]),
+ 'log' => new Rails\Paths\Path('log', [self::$root]),
+]);
+
+$config = new Rails\Config\Config();
+
+$config->paths = $paths;
+
+$config->base_path = ''; // must NOT include leading nor trailing slash.
+
+/**
+ * If names begin with a letter, the path of the file
+ * will be relative to Rails::root().
+ */
+$config->load_files = [];
+
+$config->rails_panel_path = false; // NO leading nor trailing slashes, e.g. "sysadmin", "foo/bar/sysadmin"
+
+$config->safe_ips = [
+ '127.0.0.1',
+ '::1'
+];
+
+$config->error = [
+ 'report_types' => E_ALL,
+ 'log' => true,
+ 'log_max_size' => 1024
+];
+
+$config->consider_all_requests_local = !empty($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] === '127.0.0.1';
+
+$config->date = [
+ 'timezone' => null
+];
+
+$config->i18n = [
+ 'path' => Rails::root() . '/config/locales',
+ 'default_locale' => 'en'
+];
+
+$config->action_controller = [
+ 'base' => [
+ 'default_charset' => 'utf-8'
+ ],
+ 'exception_handler' => false
+];
+
+# Temporary flag
+$config->ar2 = false;
+
+$config->active_record = [
+ 'use_cached_schema' => false,
+ // 'base' => [
+ // /**
+ // * If true, overloading attributes or associations via __get() won't work
+ // * on models. This is temporary, as now overloading should be don't through __call().
+ // */
+ // 'call_only_attr_overload' => false
+ // ]
+];
+
+$config->action_view = [
+ 'layout' => 'application',
+ 'suffix' => 'phtml',
+ 'helpers' => [
+ // 'will_paginate' => [
+ // /**
+ // * Sets a default custom renderer class.
+ // * Built-in renderers are Bootstrap and Legacy.
+ // * A custom renderer class can be created, extending
+ // * Rails\ActionView\Helper\WillPaginate\AbstractRenderer.
+ // */
+ // 'renderer' => 'bootstrap',
+ // 'always_show' => false # Show pagination even if there's only 1 page
+ // ]
+ ]
+];
+
+$config->action_mailer = [
+ /**
+ * Allows "sendmail", "smtp" or "file".
+ * Also allows a Closure that must return a transport
+ * implementing Zend\Mail\Transport\TransportInterface
+ */
+ 'delivery_method' => 'sendmail',
+
+ 'smtp_settings' => [
+ 'address' => '',
+ 'port' => 25,
+ 'domain' => '',
+ 'user_name' => '',
+ 'password' => '',
+ 'authentication' => 'login',
+ 'enable_starttls_auto' => true // Requires openssl PHP extension.
+ ],
+
+ 'file_settings' => [
+ /**
+ * Directory where mails will be stored.
+ *
+ * Default directory is created automatically, otherwise
+ * it must already exist.
+ *
+ * Defaults to Rails::root() . /tmp/mail.
+ */
+ 'location' => null,
+
+ /**
+ * Any callable argument.
+ *
+ * Defaults to Rails\ActionMailer\ActionMailer::filenameGenerator()
+ */
+ 'name_generator' => null
+ ],
+
+ 'default_options' => [],
+];
+
+/**
+ * If assets are enabled, this option will make Assets serve static assets from the public folder
+ * or non-static from application's folders.
+ */
+$config->serve_static_assets = false;
+
+$config->assets = [
+ 'enabled' => true,
+
+ /**
+ * In development, files included by manifests are served individually to help debug javascript.
+ * However, including many files can increase the page load times a lot.
+ * With this option, manifest files and their children are concatenated, resulting in 1 file.
+ */
+ 'concat' => false,
+
+ /**
+ * Additional [absolute] paths to search assets in.
+ */
+ 'paths' => [],
+
+ /**
+ * Defines how an extension is handled.
+ * Pass either an array or a Closure.
+ *
+ * The Closure must expect 1 parameter which are the contents
+ * of the file to be processed.
+ *
+ * Arrays accept 4 parameters:
+ * file: if present, the file will be require()'d
+ * class_name: the name of the class.
+ * method: the name of the method to which the contents of the file
+ * will be passed.
+ * static: if true, the method will be called statically.
+ */
+ 'css_extensions' => [
+ 'scss' => [
+ 'file' => 'scss.inc.php',
+ 'class_name' => 'scssc',
+ 'method' => 'compile',
+ 'static' => false
+ ]
+ ],
+
+ 'js_extensions' => [
+ // CoffeeScript
+ ],
+
+ /**
+ * This is Not zip compression.
+ * This tells Assets to minify files upon compile using the compressors
+ * defined below.
+ */
+ 'compress' => true,
+
+ /**
+ * Compressors.
+ * Accepts an array like the one for extensions.
+ */
+ 'css_compressor' => [
+ 'class_name' => 'Minify_CSS_Compressor',
+ 'method' => 'process'
+ ],
+ 'js_compressor' => [
+ 'class_name' => 'Rails\Assets\Parser\Javascript\ClosureApi\ClosureApi',
+ 'method' => 'minify'
+ ],
+
+ /**
+ * Create a gzipped version of compiled files.
+ * Uses gzencode()
+ */
+ 'gz_compression' => true,
+
+ 'gz_compression_level' => 9,
+
+ /**
+ * Names of the manifest files that will be compiled upon
+ * Assets::compileAll(). Only names, no paths.
+ */
+ 'precompile' => [
+ 'application.js',
+ 'application.css'
+ ],
+
+ /**
+ * Non js or css files that will be copied to the public assets folder
+ * when compiling. These values will be passed to glob() with GLOB_BRACE.
+ */
+ 'patterns' => [
+ '.gif',
+ '.png',
+ '.jpg',
+ '.jpeg',
+ ],
+
+ /**
+ * This is used as the URL path to server non-static assets for
+ * development, and is also the name of the folder where assets are
+ * stored.
+ */
+ 'prefix' => '/assets',
+
+ /**
+ * Path where the prefix folder will be created, and inside it, compiled
+ * assets will finally be stored.
+ */
+ 'compile_path' => self::$publicPath,
+
+ # Generate digests for assets URLs
+ 'digest' => false,
+
+ # Has no use.
+ 'version' => '1.0'
+];
+
+$config->cookies = [
+ 'expires' => 0,
+ 'path' => null, # Defaults to base_path() . '/', set in Cookies
+ 'domain' => null,
+ 'secure' => false,
+ 'httponly' => false,
+ 'raw' => false
+];
+
+$config->eager_load_paths = [];
+
+/**
+ * Set's the cache storing class.
+ *
+ * For FileStore
+ * cache_store ( string 'file_store', string $cache_path )
+ *
+ * For Memcached
+ * cache_store ( string 'memcached' [, mixed $server1 [, mixed $server2 ... ] ] )
+ * Defaults to host "localhost" and port 11211.
+ * Examples:
+ * Add 1 server with default options.
+ * $config->cache_store = [ 'memcached' ];
+ * Add 1 server with foobarhost as host and default port.
+ * $config->cache_store = [ 'memcached', 'foobarhost' ];
+ * Add 2 servers: one with localhost as host with default port, and the other one
+ * with other.server as host and 4456 as port.
+ * $config->cache_store = [ 'memcached', 'localhost', ['other.server', 4456] ];
+ */
+$config->cache_store = ['file_store', Rails::root() . '/tmp/cache'];
+
+/**
+ * This is supposed to be used to set a custom logger.
+ * But rather, customize the default logger in Application\Base::init().
+ */
+$config->logger = null;
+
+$config->log_file = Rails::root() . '/log/' . Rails::env() . '.log';
+
+/**
+ * Accepts int from 0 to 8.
+ */
+$config->log_level = false;
+
+return $config;
\ No newline at end of file
diff --git a/lib/Rails/Console/ApplicationConsole.php b/lib/Rails/Console/ApplicationConsole.php
new file mode 100755
index 0000000..6250cf5
--- /dev/null
+++ b/lib/Rails/Console/ApplicationConsole.php
@@ -0,0 +1,145 @@
+argv = !empty($_SERVER['argv']) ? $_SERVER['argv'] : [];
+ array_shift($this->argv);
+ $this->mainArgv = array_shift($this->argv);
+ }
+
+ public function params()
+ {
+ return $this->params;
+ }
+
+ public function run()
+ {
+ switch ($this->mainArgv) {
+ case 'generate':
+ $gen = new Generators\Generator($this);
+ $gen->parseCmd();
+ break;
+
+ case 'assets':
+ $rules = [
+ 'assets' => '',
+ 'action' => ''
+ ];
+
+ $opts = new Zend\Console\Getopt($rules);
+
+ $argv = $opts->getArguments();
+ if (empty($argv[1])) {
+ $this->terminate("Missing argument 2");
+ }
+
+ \Rails::resetConfig('production');
+
+ switch ($argv[1]) {
+ case 'compile:all':
+ \Rails::assets()->setConsole($this);
+ \Rails::assets()->compileAll();
+ break;
+
+ case (strpos($argv[1], 'compile:') === 0):
+ $parts = explode(':', $argv[1]);
+ if (empty($parts[1])) {
+ $this->terminate("Missing asset name to compile");
+ }
+ \Rails::assets()->setConsole($this);
+ \Rails::assets()->compileFile($parts[1]);
+ break;
+
+ default:
+ $this->terminate("Unknown action for assets");
+ break;
+ }
+
+ break;
+
+ case 'routes':
+ $routes = $this->createRoutes();
+
+ $rules = [
+ 'routes' => '',
+ 'f-s' => ''
+ ];
+
+ $opts = new Zend\Console\Getopt($rules);
+
+ if ($filename = $opts->getOption('f')) {
+ if (true === $filename) {
+ $logFile = \Rails::config()->paths->log->concat('routes.log');
+ } else {
+ $logFile = \Rails::root() . '/' . $filename;
+ }
+ file_put_contents($logFile, $routes);
+ }
+
+ $this->write($routes);
+ break;
+ }
+ }
+
+ protected function createRoutes()
+ {
+ $router = \Rails::application()->dispatcher()->router();
+
+ $routes = [
+ ['root', '', '/', $router->rootRoute()->to()]
+ ];
+
+ foreach ($router->routes() as $route) {
+ $routes[] = [
+ $route->alias() ?: '',
+ strtoupper(implode(', ', $route->via())),
+ '/' . $route->url(),
+ $route->to()
+ ];
+ }
+
+ $aliasMaxLen = 0;
+ $viaMaxLen = 0;
+ $pathMaxLen = 0;
+ $toMaxLen = 0;
+
+ foreach ($routes as $route) {
+ $aliasLen = strlen($route[0]);
+ $viaLen = strlen($route[1]);
+ $pathLen = strlen($route[2]);
+
+ if ($aliasLen > $aliasMaxLen)
+ $aliasMaxLen = $aliasLen;
+ if ($viaLen > $viaMaxLen)
+ $viaMaxLen = $viaLen;
+ if ($pathLen > $pathMaxLen)
+ $pathMaxLen = $pathLen;
+ }
+
+ $lines = [];
+
+ foreach ($routes as $route) {
+ $route[0] = str_pad($route[0], $aliasMaxLen, ' ', STR_PAD_LEFT);
+ $route[1] = str_pad($route[1], $viaMaxLen, ' ');
+ $route[2] = str_pad($route[2], $pathMaxLen, ' ');
+ $lines[] = implode(' ', $route);
+ }
+
+ return implode("\n", $lines);
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Console/Console.php b/lib/Rails/Console/Console.php
new file mode 100755
index 0000000..1c2b4e0
--- /dev/null
+++ b/lib/Rails/Console/Console.php
@@ -0,0 +1,16 @@
+write($message);
+ exit;
+ }
+}
diff --git a/lib/Rails/Console/Generators/Generator.php b/lib/Rails/Console/Generators/Generator.php
new file mode 100755
index 0000000..7a5cae3
--- /dev/null
+++ b/lib/Rails/Console/Generators/Generator.php
@@ -0,0 +1,99 @@
+console = $console;
+
+ $rules = [
+ 'generator' => '',
+ 'model' => '',
+ 'f|force' => '',
+ ];
+
+ $this->opts = new Zend\Console\Getopt($rules);
+ }
+
+ public function parseCmd()
+ {
+ $argv = $this->opts->getArguments();
+
+ if (!$argv[1]) {
+ $console->terminate('Missing generator');
+ }
+
+ try {
+ switch ($argv[1]) {
+ case 'model':
+ $this->generateModel();
+ break;
+
+ case 'controller':
+ $this->generateController();
+ break;
+
+ case 'db-schema':
+ Toolbox\DbTools::generateSchemaFiles();
+ break;
+
+ default:
+ $this->terminate(
+ sprintf("Unknown generator for %s", $argv[1])
+ );
+ }
+ } catch (FileGenerator\Exception\ExceptionInterface $e) {
+ $this->terminate(
+ "Error: " . $e->getMessage()
+ );
+ }
+ }
+
+ protected function generateModel()
+ {
+ $rules = [
+ 'name' => '',
+ ];
+
+ $opts = $this->opts->addRules($rules);
+ $argv = $opts->getArguments();
+
+ if (empty($argv[2])) {
+ $this->console->terminate("Missing name for model");
+ }
+
+ $name = $argv[2];
+ $options = $opts->getOptions();
+
+ FileGenerators\ModelGenerator::generate($name, $options, $this->console);
+ }
+
+ protected function generateController()
+ {
+ $rules = [
+ 'name' => '',
+ ];
+
+ $opts = $this->opts->addRules($rules);
+ $argv = $opts->getArguments();
+
+ if (empty($argv[2])) {
+ $this->console->terminate("Missing name for controller");
+ }
+
+ $name = $argv[2];
+ $options = $opts->getOptions();
+
+ FileGenerators\ControllerGenerator::generate($name, $options, $this->console);
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Console/Generators/Model.php b/lib/Rails/Console/Generators/Model.php
new file mode 100755
index 0000000..6448dd0
--- /dev/null
+++ b/lib/Rails/Console/Generators/Model.php
@@ -0,0 +1,7 @@
+title ?: $this->_title;
+
+ // if (!empty($params['scope'])) {
+ // $repl = '%scope%';
+ // $title = str_replace($repl, $params['scope'], $title);
+ // }
+
+ return $title;
+ }
+
+ public function shift_trace()
+ {
+ if (!$this->_reduced_trace)
+ $this->_reduced_trace = $this->getTrace();
+ array_shift($this->_reduced_trace);
+ }
+
+ public function get_trace()
+ {
+ return $this->_reduced_trace;
+ }
+
+ public function skip_info()
+ {
+ return $this->skip_info;
+ }
+
+ public function status()
+ {
+ return $this->status;
+ }
+
+ public function postMessage()
+ {
+ return '';
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Exception/InvalidArgumentException.php b/lib/Rails/Exception/InvalidArgumentException.php
new file mode 100755
index 0000000..1d6ef62
--- /dev/null
+++ b/lib/Rails/Exception/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+error_data;
+ else
+ $this->error_data = $edata;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Exception/PHPError/Catchable.php b/lib/Rails/Exception/PHPError/Catchable.php
new file mode 100755
index 0000000..a93d933
--- /dev/null
+++ b/lib/Rails/Exception/PHPError/Catchable.php
@@ -0,0 +1,7 @@
+title()) {
+ if ($e instanceof PHPError) {
+ if ($errorScope) {
+ $error_name = $e->title() . ' in ' . $errorScope;
+ } else {
+ $error_name = $e->title();
+ }
+ } else {
+ $error_name = $e->title();
+ }
+ } else {
+ if ($errorScope) {
+ $error_name = get_class($e) . ' in ' . $errorScope;
+ } else {
+ $error_name = get_class($e) . ' thrown';
+ }
+ }
+
+ if (Rails::cli()) {
+ $error_name = '*** ' . $error_name . ' ***';
+ }
+
+ $html = '';
+ $html .= ''.$error_name.'
'."\n";
+
+ $html .= ''.$e->getMessage();
+ if ($e instanceof RailsException && ($postMessage = $e->postMessage())) {
+ $html .= "\n" . $postMessage;
+ }
+ if (!empty($options['extraMessages'])) {
+ $html .= "\n" . $options['extraMessages'];
+ }
+
+ if ($e instanceof RailsException && $e->skip_info()) {
+ $html .= "
\n";
+ } else {
+ $base_dir = Rails::root() . DIRECTORY_SEPARATOR;
+ $rails_base_dir = Rails::path() . '/';
+
+ $file = $line = $context_args = null;
+
+ $trace = $e->getTrace();
+
+ if ($e instanceof PHPError) {
+ if (!empty($e->error_data()['errargs']))
+ $context_args = $e->error_data()['errargs'];
+ } else {
+ if (isset($trace[0]['args']))
+ $context_args = $trace[0]['args'];
+ }
+
+ $trace = $e->getTraceAsString();
+
+ $trace = str_replace([Rails::path(), Rails::root() . '/'], ['Rails', ''], $trace);
+
+ $file = self::pretty_path($e->getFile());
+
+ $line = $e->getLine();
+
+ $html .= '';
+ $html .= "\n";
+ $html .= self::rootAndTrace();
+ $html .= '' . $trace . '
';
+
+ if ($context_args) {
+ ob_start();
+ var_dump($context_args);
+ $context = ob_get_clean();
+ } else {
+ $context = '';
+ }
+ $html .= self::showContextLink();
+
+ if (!Rails::cli()) {
+ $html .= ' ';
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Cleans up the HTML report created by create_html_report() for logging purposes.
+ */
+ static public function cleanup_report($log)
+ {
+ return strip_tags(str_replace(self::removableLines(), ['', "\nError context:\n"], $log));
+ }
+
+ static public function pretty_path($path)
+ {
+ if (strpos($path, Rails::path()) === 0)
+ return 'Rails' . substr($path, strlen(Rails::path()));
+ elseif (strpos($path, Rails::root()) === 0)
+ return substr($path, strlen(Rails::root()));
+ else
+ return $path;
+ }
+
+ /**
+ * Experimental (like everything else):
+ * These functions will be used to skip lines in the trace,
+ * just to give the title of the exception a more accurate
+ * scope (the function/method where the error occured).
+ * If the validation returns true, a line will be skipped.
+ * This is particulary useful when the exception was thrown by
+ * the error handler, or the like.
+ */
+ static protected function traceSkippers()
+ {
+ return [
+ function ($trace) {
+ if (
+ isset($trace[0]['class']) &&
+ isset($trace[0]['function']) &&
+ $trace[0]['class'] == 'Rails' &&
+ $trace[0]['function'] == 'errorHandler'
+ ) {
+ return true;
+ }
+ },
+
+ function ($trace) {
+ if (
+ isset($trace[0]['class']) &&
+ isset($trace[0]['function']) &&
+ $trace[0]['class'] == 'Rails\ActiveRecord\Base' &&
+ $trace[0]['function'] == '__get'
+ ) {
+ return true;
+ }
+ }
+ ];
+ }
+
+ static private function rootAndTrace()
+ {
+ $lines = 'Rails::root(): ' . Rails::root() . "
";
+ $lines .= 'Trace
'."\n";
+ return $lines;
+ }
+
+ static private function showContextLink()
+ {
+ $lines = 'Show error context';
+ return $lines;
+ }
+
+ static private function removableLines()
+ {
+ return [self::rootAndTrace(), self::showContextLink()];
+ }
+
+ static protected function findErrorScope($e)
+ {
+ $tr = $e->getTrace();
+
+ foreach (self::traceSkippers() as $skipper) {
+ if (true === $skipper($tr)) {
+ array_shift($tr);
+ }
+ }
+
+ if (isset($tr[0]['class']) && isset($tr[0]['function']) && isset($tr[0]['type'])) {
+ if ($tr[0]['type'] == '->') {
+ // # looks better than ->
+ $type = '#';
+ // $type = '->';
+ } else {
+ $type = $tr[0]['type'];
+ }
+ $errorScope = $tr[0]['class'] . $type . $tr[0]['function'] . '';
+ } elseif (!$e instanceof PHPError && isset($tr[0]['function'])) {
+ $errorScope = $tr[0]['function'] . '';
+ } else {
+ $errorScope = '';
+ }
+
+ return $errorScope;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Exception/RuntimeException.php b/lib/Rails/Exception/RuntimeException.php
new file mode 100755
index 0000000..eb657d6
--- /dev/null
+++ b/lib/Rails/Exception/RuntimeException.php
@@ -0,0 +1,7 @@
+setLocale($locale);
+ else {
+ $this->_locale = $this->config()->default_locale;
+ }
+ $this->_load_rails_locale();
+ $this->loadLocale();
+ }
+
+ public function config()
+ {
+ return Rails::application()->config()->i18n;
+ }
+
+ public function locale($val = null)
+ {
+ if ($val !== null) {
+ throw new \Exception("Deprecated - use setLocale()");
+ } else
+ return $this->_locale;
+ }
+
+ public function setLocale($value)
+ {
+ if (!is_string($value)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Locale value must be a string, %s passed', gettype($value))
+ );
+ } elseif ($this->_locale == $value) {
+ return;
+ }
+ $this->_locale = $value;
+ $this->_load_rails_locale($value);
+ $this->loadLocale($value);
+ }
+
+ public function t($name, array $params = [])
+ {
+ if (is_array($name)) {
+ $params = $name;
+ $name = array_shift($params);
+ } elseif (!is_string($name)) {
+ throw new Exception\InvalidArgumentException(
+ sprintf('Argument must be either an array or string, %s passed', gettype($params))
+ );
+ }
+
+ if (is_null($tr = $this->_get_translation($this->_locale, $name))
+ && ($this->_locale != $this->config()->default_locale ? is_null($tr = $this->_get_translation($this->config()->default_locale, $name)) : true))
+ {
+ return false;
+ }
+
+ if (is_int(strpos($tr, '%{'))) {
+ foreach ($params as $k => $param) {
+ $tr = str_replace('%{'.$k.'}', $param, $tr);
+ unset($params[$k]);
+ }
+ }
+ if ($params) {
+ call_user_func_array('sprintf', array_merge(array($tr), $params));
+ }
+
+ return $tr;
+ }
+
+ public function available_locales()
+ {
+ if (!is_array($this->_available_locales)) {
+ $this->_get_available_locales();
+ }
+ return $this->_available_locales;
+ }
+
+ public function defaultLocale()
+ {
+ return $this->config()->default_locale;
+ }
+
+ private function _get_translation($lang, $name)
+ {
+ $tr = null;
+
+ if (isset($this->_tr[$lang])) {
+ if (is_int(strpos($name, '.'))) {
+ $tr = $this->_tr[$lang];
+ foreach (explode('.', $name) as $idx) {
+ if (isset($tr[$idx])) {
+ $tr = $tr[$idx];
+ } else {
+ break;
+ }
+ }
+
+ if (!is_string($tr))
+ $tr = null;
+ } else {
+ if (isset($this->_tr[$lang][$name]))
+ $tr = $this->_tr[$lang][$name];
+ }
+ }
+
+ return $tr;
+ }
+
+ private function _get_available_locales()
+ {
+ $dh = opendir($this->config()->path);
+
+ $this->_available_locales = array();
+
+ while (!is_bool($file = readdir($dh))) {
+ if ($file == '.' || $file == '..')
+ continue;
+ $locale = pathinfo($file, PATHINFO_FILENAME);
+ $this->_available_locales[] = $locale;
+ }
+ closedir($dh);
+ }
+
+ /**
+ * Loads locale file.
+ * If not a full path (i.e. doesn't start with / or x:), it'd be
+ * taken as relative path to locales path (i.e. config/locales). In this
+ * case, the extension of the file must be omitted.
+ *
+ * $i18n->loadLocale("/home/www/foo/bar/es.yml");
+ * $i18n->loadLocale("es"); <- loads config/locales/es.php|yml
+ * $i18n->loadLocale("subdir/es"); <- loads config/locales/subdir/es.php|yml
+ * $i18n->loadLocale("/some/path/locales/%locale%.yml"); %locale% will be replaced with current locale
+ */
+ public function loadLocale($locale = null)
+ {
+ !$locale && $locale = $this->_locale;
+
+ if (in_array($locale, $this->loaded_locales)) {
+ return;
+ }
+
+ if (is_int(($pos = strpos($locale, '%locale%')))) {
+ $locale = substr_replace($locale, $this->locale(), $pos, 8);
+ }
+
+ if (substr($locale, 0, 1) == '/' || substr($locale, 1, 1) == ':') {
+ $file = $locale;
+ } else {
+ $patt = $this->config()->path . '/' . $locale . '.{php,yml}';
+ $files = glob($patt, GLOB_BRACE);
+ if ($files) {
+ $file = $files[0];
+ } else {
+ return false;
+ }
+ }
+
+ $ext = pathinfo($file, PATHINFO_EXTENSION);
+ if ($ext == 'yml') {
+ $locale_data = Rails\Yaml\Parser::readFile($file);
+ } else {
+ $locale_data = require $file;
+ }
+ $this->_tr = array_merge_recursive($this->_tr, $locale_data);
+
+ $this->loaded_locales[] = $locale;
+ return true;
+ }
+
+ private function _load_rails_locale($locale = null)
+ {
+ !func_num_args() && $locale = $this->_locale;
+
+
+ $locale = ucfirst(strtolower($locale));
+ $class_name = 'Rails\I18n\Locales\\' . $locale;
+
+ if (class_exists($class_name, false))
+ return;
+
+ $file = __DIR__ . '/Locales/' . $locale . '.';
+ $exts = ['php'];
+
+ foreach ($exts as $ext) {
+ $f = $file.$ext;
+ if (is_file($f)) {
+ require $f;
+ $obj = new $class_name();
+ $this->_tr = array_merge_recursive($this->_tr, $obj->tr());
+ break;
+ }
+ }
+
+ /**
+ * If there's nothing in the array, it means we've just tried to load the default
+ * application locale for Rails that isn't supported. Load english locale instead.
+ */
+ if (!$this->_tr)
+ $this->_load_rails_locale('en');
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/I18n/Locales/AbstractLocales.php b/lib/Rails/I18n/Locales/AbstractLocales.php
new file mode 100755
index 0000000..15765c2
--- /dev/null
+++ b/lib/Rails/I18n/Locales/AbstractLocales.php
@@ -0,0 +1,12 @@
+translations;
+ }
+}
diff --git a/lib/Rails/I18n/Locales/En.php b/lib/Rails/I18n/Locales/En.php
new file mode 100755
index 0000000..60fd23b
--- /dev/null
+++ b/lib/Rails/I18n/Locales/En.php
@@ -0,0 +1,54 @@
+ [
+ 'actionview' => [
+ 'helper' => [
+ 'date' => [
+ 'less_than_a_minute'=> 'Less than a minute',
+ 'one_minute' => '1 minute',
+ 'x_minutes' => '%{t} minutes',
+ 'about_one_hour' => 'about one hour',
+ 'about_x_hours' => 'about %{t} hours',
+ 'one_day' => '1 day',
+ 'x_days' => '%{t} days',
+ 'about_one_month' => 'about 1 month',
+ 'x_months' => '%{t} months',
+ 'about_one_year' => 'about 1 year',
+ 'over_a_year' => 'over a year',
+ 'almost_two_years' => 'almost 2 years',
+ 'about_x_years' => 'about %{t} years'
+ ]
+ ]
+ ],
+
+ 'errors' => [
+ 'messages' => [
+ "inclusion" => "is not included in the list",
+ "exclusion" => "is reserved",
+ "invalid" => "is invalid",
+ "confirmation" => "doesn't match confirmation",
+ "accepted" => "must be accepted",
+ "empty" => "can't be empty",
+ "blank" => "can't be blank",
+ "too_long" => "is too long (maximum is %{count} characters)",
+ "too_short" => "is too short (minimum is %{count} characters)",
+ "wrong_length" => "is the wrong length (should be %{count} characters)",
+ "not_a_number" => "is not a number",
+ "not_an_integer" => "must be an integer",
+ "greater_than" => "must be greater than %{count}",
+ "greater_than_or_equal_to" => "must be greater than or equal to %{count}",
+ "equal_to" => "must be equal to %{count}",
+ "less_than" => "must be less than %{count}",
+ "less_than_or_equal_to" => "must be less than or equal to %{count}",
+ "odd" => "must be odd",
+ "even" => "must be even",
+
+ # These belong to activerecord
+ "uniqueness" => "must be unique"
+ ]
+ ]
+ ]];
+}
\ No newline at end of file
diff --git a/lib/Rails/I18n/Locales/Es.php b/lib/Rails/I18n/Locales/Es.php
new file mode 100755
index 0000000..3cbbd37
--- /dev/null
+++ b/lib/Rails/I18n/Locales/Es.php
@@ -0,0 +1,54 @@
+ [
+ 'actionview' => [
+ 'helper' => [
+ 'date' => [
+ 'less_than_a_minute'=> 'menos de un minuto',
+ 'one_minute' => '1 minuto',
+ 'x_minutes' => '%{t} minutos',
+ 'about_one_hour' => 'alrededor de una hora',
+ 'about_x_hours' => 'alrededor de %{t} horas',
+ 'one_day' => '1 dÃa',
+ 'x_days' => '%{t} dÃas',
+ 'about_one_month' => 'alrededor de 1 mes',
+ 'x_months' => '%{t} meses',
+ 'about_one_year' => 'alrededor 1 año',
+ 'over_a_year' => 'más de un año',
+ 'almost_two_years' => 'casi 2 años',
+ 'about_x_years' => 'alrededor de %{t} años'
+ ]
+ ]
+ ],
+
+ 'errors' => [
+ 'messages' => [
+ "inclusion" => "is not included in the list",
+ "exclusion" => "is reserved",
+ "invalid" => "is invalid",
+ "confirmation" => "doesn't match confirmation",
+ "accepted" => "must be accepted",
+ "empty" => "can't be empty",
+ "blank" => "can't be blank",
+ "too_long" => "is too long (maximum is %{count} characters)",
+ "too_short" => "is too short (minimum is %{count} characters)",
+ "wrong_length" => "is the wrong length (should be %{count} characters)",
+ "not_a_number" => "is not a number",
+ "not_an_integer" => "must be an integer",
+ "greater_than" => "must be greater than %{count}",
+ "greater_than_or_equal_to" => "must be greater than or equal to %{count}",
+ "equal_to" => "must be equal to %{count}",
+ "less_than" => "must be less than %{count}",
+ "less_than_or_equal_to" => "must be less than or equal to %{count}",
+ "odd" => "must be odd",
+ "even" => "must be even",
+
+ # These belong to activerecord
+ "uniqueness" => "must be unique"
+ ]
+ ]
+ ]];
+}
\ No newline at end of file
diff --git a/lib/Rails/Loader/Exception/ClassNotFoundException.php b/lib/Rails/Loader/Exception/ClassNotFoundException.php
new file mode 100755
index 0000000..af1dae2
--- /dev/null
+++ b/lib/Rails/Loader/Exception/ClassNotFoundException.php
@@ -0,0 +1,6 @@
+addPaths($paths);
+ }
+
+ $this->loadRequiredClasses();
+ }
+
+ public function setComposerAutoload(\Composer\Autoload\ClassLoader $loader)
+ {
+ $this->composerAutoloader = $loader;
+ }
+
+ public function addPath($path)
+ {
+ $this->addPaths((array)$path);
+ }
+
+ public function addPaths(array $paths)
+ {
+ $this->paths = array_merge($this->paths, $paths);
+ }
+
+ public function loadClass($class_name)
+ {
+ if ($this->runAutoloaders($class_name)) {
+ return;
+ }
+
+ if ($this->composerAutoloader->loadClass($class_name)) {
+ return;
+ }
+
+ if (is_int(strpos($class_name, '\\'))) {
+ $parts = explode('\\', $class_name);
+ $file = array_pop($parts);
+ $class_file_path = implode('/', $parts) . '/' . $file . '.php';
+ } else
+ $class_file_path = str_replace('_', '/', $class_name) . '.php';
+
+ $paths = $this->paths;
+ $found = false;
+
+ foreach ($paths as $path) {
+ if ($found = is_file(($class_file = $path . '/' . $class_file_path))) {
+ break;
+ }
+ }
+
+ if (!$found) {
+ require_once __DIR__ . '/Exception/FileNotFoundException.php';
+ throw new Exception\FileNotFoundException(sprintf("Couldn't find file for class %s, searched for %s in:\n%s", $class_name, $class_file_path, implode("\n", $paths)));
+ }
+
+ require $class_file;
+
+ if (!class_exists($class_name, false) && !interface_exists($class_name, false) && !trait_exists($class_name, false)) {
+ require_once __DIR__ . '/Exception/ClassNotFoundException.php';
+ throw new Exception\ClassNotFoundException(sprintf("File %s doesn't contain class/interface/trait %s.", $class_file, $class_name));
+ }
+ }
+
+ /**
+ * Autoloaders must return true is the class was
+ * successfuly loaded.
+ */
+ public function registerAutoloader(callable $function)
+ {
+ $this->class_autoloaders[] = $function;
+ }
+
+ public function runAutoloaders($class_name)
+ {
+ foreach ($this->class_autoloaders as $autoload) {
+ if (is_array($autoload)) {
+ $object = $autoload[0];
+ $method = $autoload[1];
+
+ if (true === $object->$method($class_name)) {
+ return true;
+ }
+ } elseif (is_string($autoload)) {
+ if (true === call_user_func($autoload, $class_name))
+ return true;
+ } elseif ($autoload instanceof Closure) {
+ if (true === $autoload($class_name))
+ return true;
+ } else {
+ require_once __DIR__ . '/Exception/RuntimeException.php';
+ throw new Exception\RuntimeException(sprintf("Invalid autoloader type (%s)", gettype($autoload)));
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Loader uses ActiveRecord and ActionMailer. These classes must be available.
+ */
+ protected function loadRequiredClasses()
+ {
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Log/Formatter/Simple.php b/lib/Rails/Log/Formatter/Simple.php
new file mode 100755
index 0000000..b9206d8
--- /dev/null
+++ b/lib/Rails/Log/Formatter/Simple.php
@@ -0,0 +1,43 @@
+originalFormat = $this->format;
+ $this->format = trim(str_replace('%priorityName%', '', $this->format), ': ');
+ }
+
+ /**
+ * Hack: write log with date.
+ */
+ if (!empty($event['extra']['date'])) {
+ if (!$this->originalFormat) {
+ $this->originalFormat = $this->format;
+ }
+ $this->format = '[%timestamp%] ' . $this->format;
+ unset($event['extra']['date']);
+ }
+
+ $ret = parent::format($event);
+
+ if ($this->originalFormat) {
+ $this->format = $this->originalFormat;
+ $this->originalFormat = null;
+ }
+
+ return $ret;
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Log/Logger.php b/lib/Rails/Log/Logger.php
new file mode 100755
index 0000000..3baaadc
--- /dev/null
+++ b/lib/Rails/Log/Logger.php
@@ -0,0 +1,116 @@
+ 'EMERGENCY',
+ self::ALERT => 'ALERT',
+ self::CRIT => 'CRITICAL',
+ self::ERR => 'ERROR',
+ self::WARN => 'WARNING',
+ self::NOTICE => 'NOTICE',
+ self::INFO => 'INFO',
+ self::DEBUG => 'DEBUG',
+ self::NONE => 'NONE',
+ );
+
+ protected $name = '';
+
+ public function __construct(array $options = [])
+ {
+ if (isset($options['name'])) {
+ $this->name = $options['name'];
+ unset($optiona['name']);
+ }
+ parent::__construct();
+ }
+
+ public function emergency($message, $extra = [])
+ {
+ return $this->emerg($message, $extra);
+ }
+
+ public function critical($message, $extra = [])
+ {
+ return $this->crit($message, $extra);
+ }
+
+ public function error($message, $extra = [])
+ {
+ return $this->err($message, $extra);
+ }
+
+ public function warning($message, $extra = [])
+ {
+ return $this->warn($message, $extra);
+ }
+
+ public function none($message)
+ {
+ return $this->log(self::NONE, $message);
+ }
+
+ public function vars(array $vars)
+ {
+ ob_start();
+ foreach ($vars as $var) {
+ var_dump($var);
+ }
+ $message = ob_get_clean();
+ $message .= "\n";
+ return $this->none($message);
+ }
+
+ /**
+ * Additional text can be passed through $options['extraMessages'].
+ */
+ public function exception(\Exception $e, array $options = [])
+ {
+ $message = \Rails\Exception\Reporting\Reporter::create_report($e, $options);
+ $message = \Rails\Exception\Reporting\Reporter::cleanup_report($message);
+
+ return $this->message($message);
+ }
+
+ /**
+ * Adds date-time and request data, if any.
+ */
+ public function message($err)
+ {
+ return $this->none($this->buildErrorMessage($err));
+ }
+
+ private function buildErrorMessage($err, $requestInfo = true)
+ {
+ if ($requestInfo) {
+ $request = ' ' . $this->buildRequestInfo();
+ } else {
+ $request = '';
+ }
+
+ $message = date('[d-M-Y H:i:s T]') . $request . "\n";
+ $message .= $err;
+ $message = trim($message);
+ $message .= "\n";
+ return $message;
+ }
+
+ private function buildRequestInfo()
+ {
+ if (\Rails::application()->dispatcher() && \Rails::application()->dispatcher()->request()) {
+ $method = \Rails::application()->dispatcher()->request()->method();
+ $route = \Rails::application()->dispatcher()->request()->fullPath();
+ $request = '[' . $method . '] ' . $route;
+ } elseif (\Rails::cli()) {
+ $request = '[cli]';
+ } else {
+ $request = '';
+ }
+ return $request;
+ }
+}
diff --git a/lib/Rails/Panel/assets/panel.css b/lib/Rails/Panel/assets/panel.css
new file mode 100755
index 0000000..8350359
--- /dev/null
+++ b/lib/Rails/Panel/assets/panel.css
@@ -0,0 +1,6 @@
+/**
+ *= require bootstrap
+ */
+body {
+ padding-top: 60px;
+}
\ No newline at end of file
diff --git a/lib/Rails/Panel/controllers/AdminController.php b/lib/Rails/Panel/controllers/AdminController.php
new file mode 100755
index 0000000..5f1283d
--- /dev/null
+++ b/lib/Rails/Panel/controllers/AdminController.php
@@ -0,0 +1,140 @@
+parse();
+
+ $this->response()->headers()->setContentType('text/css');
+ $this->render(['text' => $parser->parsedFile()]);
+ }
+
+ final public function genTableData()
+ {
+ Toolbox\DbTools::generateSchemaFiles();
+ $this->toIndex('Database schema files updated');
+ }
+
+ public function createFiles()
+ {
+ if ($this->request()->isPost()) {
+ Rails::resetConfig('development');
+
+ $base_name = trim($this->params()->file_name);
+ if ($base_name) {
+ if ($this->params()->type['controller'])
+ $this->createControllerFile($base_name);
+ if ($this->params()->type['model'])
+ $this->createModelFile($base_name);
+ if ($this->params()->type['helper'])
+ $this->createHelperFile($base_name);
+ }
+
+ Rails::application()->setPanelConfig();
+ }
+ }
+
+ public function showRoutes()
+ {
+ $router = Rails::application()->dispatcher()->router();
+ $this->routes = [
+ ['root', null, '/', $router->rootRoute()->to()],
+ ['rails_panel', null, '/' . $router->panelRoute()->url(), ''],
+ ];
+
+ foreach ($router->routes() as $route) {
+ if ($route instanceof Route\HiddenRoute) {
+ continue;
+ }
+ $this->routes[] = [
+ $route->alias(), strtoupper(implode(', ', $route->via())), '/' . $route->url(), $route->to()
+ ];
+ }
+ }
+
+ public function compileAssets()
+ {
+ $this->error = '';
+ if ($this->request()->isPost()) {
+ Rails::resetConfig('production');
+
+ try {
+ if ($this->params()->all) {
+ Rails::assets()->compileAll();
+ } elseif ($this->params()->file) {
+ $file = $this->params()->file;
+ Rails::assets()->compileFile($file);
+ }
+ } catch (Rails\Assets\Parser\Javascript\ClosureApi\Exception\ErrorsOnCodeException $e) {
+ if ($e instanceof Rails\Assets\Parser\Javascript\ClosureApi\Exception\ErrorsOnCodeException) {
+ Rails::log()->error(
+ sprintf(
+ "[%s] Asset compilation error for file %s\n%s",
+ date('Y-m-d H:i:s'),
+ $file_path . '.' . $ext,
+ $e->getMessage()
+ )
+ );
+ $message = sprintf("ClosureAPI reported an error - JS file was saved to %s for verfications, error was logged.%s
",
+ Rails\Assets\Parser\Javascript\ClosureApi\ClosureApi::errorFile(), $e->getMessage());
+ } else {
+ // throw $e;
+ // $message = sprintf('%s raised: %s', get_class($e), $e->getMessage());
+ }
+
+ $this->error = $message;
+ }
+
+ Rails::resetConfig('development');
+ Rails::application()->setPanelConfig();
+ }
+ }
+
+ final protected function toIndex($notice)
+ {
+ $url = '/' . Rails::application()->config()->rails_panel_path;
+ parent::redirectTo(array($url, 'notice' => $notice));
+ }
+
+ public function redirectTo($redirect_params, array $params = array())
+ {
+ $redirect_params[0] = '/' . Rails::application()->config()->rails_panel_path . '/' . $redirect_params[0];
+ parent::redirectTo($redirect_params, $params);
+ }
+
+ private function createControllerFile($name, $options = [])
+ {
+ Toolbox\FileGenerators\ControllerGenerator::generate($name, $options);
+ }
+
+ private function createModelFile($name, $options = [])
+ {
+ Toolbox\FileGenerators\ModelGenerator::generate($name, $options);
+ }
+
+ private function createHelperFile($base_name)
+ {
+ $name = $base_name . '_helper.php';
+ $path = Rails::config()->paths->helpers;
+
+ if (is_file($path . '/' . $name))
+ return;
+
+ $class_name = Rails::services()->get('inflector')->camelize($base_name) . 'Helper';
+ $contents = " [
+ 'isUserAllowed'
+ ]
+ ];
+ }
+
+ final protected function isUserAllowed()
+ {
+ if (!Rails::application()->validateSafeIps()) {
+ $this->render(['action' => 'forbidden'], ['status' => 403]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Panel/helpers/ApplicationHelper.php b/lib/Rails/Panel/helpers/ApplicationHelper.php
new file mode 100755
index 0000000..6245f41
--- /dev/null
+++ b/lib/Rails/Panel/helpers/ApplicationHelper.php
@@ -0,0 +1,14 @@
+base()->linkTo($link, $url_params, $attrs);
+ } else {
+ $base_path = Rails::application()->router()->basePath();
+ $attrs['href'] = $base_path . '/' . Rails::application()->config()->rails_panel_path . '/' . substr($url_params, 1);
+ return $this->contentTag('a', $link, $attrs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/Rails/Panel/traits/AdminController.php b/lib/Rails/Panel/traits/AdminController.php
new file mode 100755
index 0000000..89dfa8c
--- /dev/null
+++ b/lib/Rails/Panel/traits/AdminController.php
@@ -0,0 +1,6 @@
+
+Compile assets
+
+
+
+
+
+
+
+
+
+ Assets folder
+ File name
+
+
+ = Rails::application()->config()->assets->prefix . '/' ?>
+ = $this->textFieldTag('file', $this->params()->file, ['size' => 40, 'autofocus', 'placeholder' => 'E.g. application.css']) ?>
+
+
+ = $this->submitTag('Compile file', ['class' => 'btn btn-primary']) ?> Create files
+= $this->formTag(null, ['class' => 'form-horizontal well'], function() { ?>
+ FORBIDDEN FOR CLIENT request()->remoteIp() ?>
Use the menu above.
diff --git a/lib/Rails/Panel/views/admin/show_routes.php b/lib/Rails/Panel/views/admin/show_routes.php new file mode 100755 index 0000000..9debb90 --- /dev/null +++ b/lib/Rails/Panel/views/admin/show_routes.php @@ -0,0 +1,20 @@ +Prefix | +Verb | +URI Pattern | +Controller#Action | + + + routes as $route) : ?> +
---|---|---|---|
= $route[0] ?> | += $route[1] ?> | += $route[2] ?> | += $route[3] ?> | +
'; + call_user_func_array('vd', func_get_args()); + echo ''; + exit; +} \ No newline at end of file diff --git a/lib/Rails/Traits/ArrayObjectAccess.php b/lib/Rails/Traits/ArrayObjectAccess.php new file mode 100755 index 0000000..0eb0e7c --- /dev/null +++ b/lib/Rails/Traits/ArrayObjectAccess.php @@ -0,0 +1,69 @@ +headers('foo', 'bar') : Sets $headers['foo'] = 'bar'. + * $object->headers('foo') : Returns the value of $headers['foo'], null if not set. + * $object->headers('foo', null) : Unsets $headers['foo']. + * $object->headers() : Returns $headers. + * $object->headers(['foo' => 'bar', 'baz' => true]) : Sets many values at once. + * + * The method to access the $headers property should look like this: + * + * public function headers() + * { + * return $this->_aoaccess('headers', func_get_args()); + * } + */ +trait ArrayObjectAccess +{ + private function _aoaccess($prop_name, $args) + { + $num_args = count($args); + + if ($num_args == 1) { + $key = array_shift($args); + if (is_array($key)) { + foreach ($key as $prop => $val) + $this->_aoaccess_set($prop_name, $prop, $val); + return $this; + } elseif (isset($this->$prop_name[$key])) + return $this->$prop_name[$key]; + else + return null; + } elseif ($num_args) { + list($key, $value) = $args; + $this->_aoaccess_set($prop_name, $key, $value); + return $this; + } else { + return $this->$prop_name; + } + } + + /** + * Helps automatize the creation of ArrayObjects. + * Expected to be called in the constructor. + * + * @param array $props : Names of the properties that are ArrayObjects. + */ + private function _aoaccess_init(array $props) + { + foreach ($props as $prop) + $this->$prop = new \ArrayObject(); + } + + private function _aoaccess_set($prop_name, $key, $value) + { + if ($value === null) + unset($this->$prop_name[$key]); + else + $this->$prop_name[$key] = $value; + } +} \ No newline at end of file diff --git a/lib/Rails/Validation/Exception/ExceptionInterface.php b/lib/Rails/Validation/Exception/ExceptionInterface.php new file mode 100755 index 0000000..c8e5e3a --- /dev/null +++ b/lib/Rails/Validation/Exception/ExceptionInterface.php @@ -0,0 +1,6 @@ +_type = $type; + $this->_data = $data; + $this->_params = $params; + } + + public function validate() + { + $validate_method = '_validate_' . $this->_type; + if (!method_exists($this, $validate_method)) { + throw new Exception\RuntimeException( + sprintf("Validation for '%s' isn't supported", $this->_type) + ); + } + $this->_success = $this->$validate_method(); + return $this; + } + + public function success() + { + return $this->_success; + } + + protected function _validate_length() + { + $this->_data = strlen($this->_data); + $this->_params['only_integer'] = true; + return $this->_validate_number(); + } + + protected function _validate_format() + { + return (bool)preg_match($this->_params['with'], $this->_data); + } + + protected function _validate_number() + { + $result = 2; + $this->_convert_number($this->_data); + + if (!empty($this->_params['in'])) { + list($min, $max) = $this->_params['in']; + + if ($this->_data < $min) + $result = -1; + elseif ($this->_data > $max) + $result = 1; + } elseif (!empty($this->_params['is'])) { + if ((int)$this->_data != (int)$this->_params['is']) { + if ($this->_data > $this->_params['is']) { + $result = 1; + } else { + $result = -1; + } + } + } elseif (!empty($this->_params['minimum'])) { + if ((int)$this->_data < (int)$this->_params['minimum']) { + $result = -1; + } + } elseif (!empty($this->_params['maximum'])) { + if ((int)$this->_data > (int)$this->_params['maximum']) { + $result = 1; + } + } else { + throw new Exception\RuntimeException( + "No supported number validation passed" + ); + } + + # Check params. { + if (!empty($this->_params['even'])) { + if (($this->_data % 2)) + $success = false; + } elseif (!empty($this->_params['odd'])) { + if (($this->_data % 1)) + $success = false; + } + # } + + if (isset($success) && $success === false) { + $this->_result = $result; + } elseif ($result !== 2) { + $success = false; + $this->_result = $result; + } else + $success = true; + + return $success; + } + + protected function _validate_inclusion() + { + if (is_array($this->_rule)) { + if (!in_array($this->_data, $this->_rule)) + return false; + } elseif (is_string($this->_rule) && is_int(strpos($this->_rule, '..'))) { + return $this->_validate_number(); + } else { + throw new Exception\InvalidArgumentException( + sprintf("Invalid exclusion validation rule, must be either an array or a numeric rule, %s passed", gettype($this->_rule)) + ); + } + } + + protected function _validate_exclusion() + { + if (is_array($this->_rule)) { + if (in_array($this->_data, $this->_rule)) + return false; + } elseif (is_string($this->_rule) && is_int(strpos($this->_rule, '..'))) { + return !($this->_validate_number()); + } else { + throw new Exception\InvalidArgumentException( + sprintf("Invalid exclusion validation rule, must be either an array or a numeric rule, %s passed", gettype($this->_rule)) + ); + } + } + + /** + * Helper function for _validate_number() + */ + private function _convert_number(&$num) + { + if (!empty($this->_params['only_integer'])) + $num = (int)$num; + else + $num = (float)$num; + } +} \ No newline at end of file diff --git a/lib/Rails/Xml/Exception/ExceptionInterface.php b/lib/Rails/Xml/Exception/ExceptionInterface.php new file mode 100755 index 0000000..e958589 --- /dev/null +++ b/lib/Rails/Xml/Exception/ExceptionInterface.php @@ -0,0 +1,6 @@ +toXml(); + } elseif (is_array($el)) { + $attrs = $el; + } else { + throw new Exception\InvalidArgumentException( + sprintf("%s accepts either a child of ActiveRecord\Base or an array, %s passed", + __METHOD__, gettype($attrs)) + ); + } + + if (!isset($params['root'])) + throw new Exception\InvalidArgumentException( + sprintf('InvalidArgumentException', "Missing 'root' parameter for %s", __METHOD__) + ); + + $this->_attrs = $attrs; + $this->_root = $params['root']; + unset($params['root']); + + $this->_params = $params; + } + + public function instruct() + { + $this->_buffer .= ''."\n"; + } + + public function create() + { + if (empty($this->_params['skip_instruct'])) + $this->instruct(); + + $this->_buffer .= '<'.$this->_root; + + $attrs_str = []; + + # TODO: fix $val, it should accept any value. + foreach ($this->_attrs as $name => $val) { + if (is_bool($val)) + $val = $val ? 'true' : 'false'; + elseif (!is_scalar($val)) + $val = ''; + + $attrs_str[] = $name . '="'.htmlspecialchars((string)$val).'"'; + } + + $this->_buffer .= ' ' . implode(' ', $attrs_str); + + $this->_buffer .= ' />'; + } + + public function output() + { + if (!$this->_buffer) + $this->create(); + return $this->_buffer; + } +} \ No newline at end of file diff --git a/lib/Rails/Yaml/Parser.php b/lib/Rails/Yaml/Parser.php new file mode 100755 index 0000000..ffd1eea --- /dev/null +++ b/lib/Rails/Yaml/Parser.php @@ -0,0 +1,59 @@ +read(); + } + + static public function writeFile($filepath, $contents) + { + return (new self($filepath))->write($contents); + } + + public function __construct($filepath) + { + $this->filepath = $filepath; + } + + public function read() + { + try { + if (function_exists('yaml_parse')) { + return yaml_parse_file($this->filepath); + } else { + return SfYaml::parse($this->filepath); + } + } catch (\Exception $e) { + $msg = sprintf("Error while reading file %s:\n", $this->filepath); + $msg .= $e->getMessage(); + $cn = get_class($e); + throw new $cn($msg); + } + } + + public function write($contents, array $params = []) + { + try { + if (function_exists('yaml_emit')) { + $params = array_merge([$contents], $params); + $yaml = call_user_func_array('yaml_emit', $params); + return file_put_contents($this->filepath, $yaml); + } else { + $yaml = call_user_func_array('Symfony\Component\Yaml\Yaml::dump', array_merge([$contents], $params)); + return file_put_contents($this->filepath, $yaml); + } + } catch (\Exception $e) { + $msg = sprintf("Error while writing file %s:\n", $this->filepath); + $msg .= $e->getMessage(); + $cn = get_class($e); + throw new $cn($msg); + } + } +} \ No newline at end of file diff --git a/vendor/README b/vendor/README new file mode 100755 index 0000000..7c5019f --- /dev/null +++ b/vendor/README @@ -0,0 +1 @@ +This assets folder is used by the Rails Panel. \ No newline at end of file diff --git a/vendor/assets/stylesheets/bootstrap.css b/vendor/assets/stylesheets/bootstrap.css new file mode 100755 index 0000000..4049691 --- /dev/null +++ b/vendor/assets/stylesheets/bootstrap.css @@ -0,0 +1,6805 @@ +/*! + * Bootstrap v3.0.0 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */ + +/*! normalize.css v2.1.0 | MIT License | git.io/normalize */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} + +audio, +canvas, +video { + display: inline-block; +} + +audio:not([controls]) { + display: none; + height: 0; +} + +[hidden] { + display: none; +} + +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + margin: 0; +} + +a:focus { + outline: thin dotted; +} + +a:active, +a:hover { + outline: 0; +} + +h1 { + margin: 0.67em 0; + font-size: 2em; +} + +abbr[title] { + border-bottom: 1px dotted; +} + +b, +strong { + font-weight: bold; +} + +dfn { + font-style: italic; +} + +hr { + height: 0; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +mark { + color: #000; + background: #ff0; +} + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 1em; +} + +pre { + white-space: pre-wrap; +} + +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +img { + border: 0; +} + +svg:not(:root) { + overflow: hidden; +} + +figure { + margin: 0; +} + +fieldset { + padding: 0.35em 0.625em 0.75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} + +legend { + padding: 0; + border: 0; +} + +button, +input, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: 100%; +} + +button, +input { + line-height: normal; +} + +button, +select { + text-transform: none; +} + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; +} + +button[disabled], +html input[disabled] { + cursor: default; +} + +input[type="checkbox"], +input[type="radio"] { + padding: 0; + box-sizing: border-box; +} + +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +textarea { + overflow: auto; + vertical-align: top; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +@media print { + * { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + @page { + margin: 2cm .5cm; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .table td, + .table th { + background-color: #fff !important; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} + +*, +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 62.5%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.428571429; + color: #333333; + background-color: #ffffff; +} + +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input, +select[multiple], +textarea { + background-image: none; +} + +a { + color: #428bca; + text-decoration: none; +} + +a:hover, +a:focus { + color: #2a6496; + text-decoration: underline; +} + +a:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +img { + vertical-align: middle; +} + +.img-responsive { + display: block; + height: auto; + max-width: 100%; +} + +.img-rounded { + border-radius: 6px; +} + +.img-thumbnail { + display: inline-block; + height: auto; + max-width: 100%; + padding: 4px; + line-height: 1.428571429; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +.img-circle { + border-radius: 50%; +} + +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + border: 0; +} + +p { + margin: 0 0 10px; +} + +.lead { + margin-bottom: 20px; + font-size: 16.099999999999998px; + font-weight: 200; + line-height: 1.4; +} + +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} + +small { + font-size: 85%; +} + +cite { + font-style: normal; +} + +.text-muted { + color: #999999; +} + +.text-primary { + color: #428bca; +} + +.text-warning { + color: #c09853; +} + +.text-danger { + color: #b94a48; +} + +.text-success { + color: #468847; +} + +.text-info { + color: #3a87ad; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 500; + line-height: 1.1; +} + +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small { + font-weight: normal; + line-height: 1; + color: #999999; +} + +h1, +h2, +h3 { + margin-top: 20px; + margin-bottom: 10px; +} + +h4, +h5, +h6 { + margin-top: 10px; + margin-bottom: 10px; +} + +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; +} + +h1 small, +.h1 small { + font-size: 24px; +} + +h2 small, +.h2 small { + font-size: 18px; +} + +h3 small, +.h3 small, +h4 small, +.h4 small { + font-size: 14px; +} + +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eeeeee; +} + +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} + +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} + +dl { + margin-bottom: 20px; +} + +dt, +dd { + line-height: 1.428571429; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} + +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } + .dl-horizontal dd:before, + .dl-horizontal dd:after { + display: table; + content: " "; + } + .dl-horizontal dd:after { + clear: both; + } + .dl-horizontal dd:before, + .dl-horizontal dd:after { + display: table; + content: " "; + } + .dl-horizontal dd:after { + clear: both; + } +} + +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999999; +} + +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + border-left: 5px solid #eeeeee; +} + +blockquote p { + font-size: 17.5px; + font-weight: 300; + line-height: 1.25; +} + +blockquote p:last-child { + margin-bottom: 0; +} + +blockquote small { + display: block; + line-height: 1.428571429; + color: #999999; +} + +blockquote small:before { + content: '\2014 \00A0'; +} + +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #eeeeee; + border-left: 0; +} + +blockquote.pull-right p, +blockquote.pull-right small { + text-align: right; +} + +blockquote.pull-right small:before { + content: ''; +} + +blockquote.pull-right small:after { + content: '\00A0 \2014'; +} + +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +address { + display: block; + margin-bottom: 20px; + font-style: normal; + line-height: 1.428571429; +} + +code, +pre { + font-family: Monaco, Menlo, Consolas, "Courier New", monospace; +} + +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + white-space: nowrap; + background-color: #f9f2f4; + border-radius: 4px; +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.428571429; + color: #333333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #cccccc; + border-radius: 4px; +} + +pre.prettyprint { + margin-bottom: 20px; +} + +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border: 0; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.container:before, +.container:after { + display: table; + content: " "; +} + +.container:after { + clear: both; +} + +.container:before, +.container:after { + display: table; + content: " "; +} + +.container:after { + clear: both; +} + +.row { + margin-right: -15px; + margin-left: -15px; +} + +.row:before, +.row:after { + display: table; + content: " "; +} + +.row:after { + clear: both; +} + +.row:before, +.row:after { + display: table; + content: " "; +} + +.row:after { + clear: both; +} + +.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, +.col-sm-1, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-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, +.col-lg-1, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-10, +.col-lg-11, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col-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 { + float: left; +} + +.col-xs-1 { + width: 8.333333333333332%; +} + +.col-xs-2 { + width: 16.666666666666664%; +} + +.col-xs-3 { + width: 25%; +} + +.col-xs-4 { + width: 33.33333333333333%; +} + +.col-xs-5 { + width: 41.66666666666667%; +} + +.col-xs-6 { + width: 50%; +} + +.col-xs-7 { + width: 58.333333333333336%; +} + +.col-xs-8 { + width: 66.66666666666666%; +} + +.col-xs-9 { + width: 75%; +} + +.col-xs-10 { + width: 83.33333333333334%; +} + +.col-xs-11 { + width: 91.66666666666666%; +} + +.col-xs-12 { + width: 100%; +} + +@media (min-width: 768px) { + .container { + max-width: 750px; + } + .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 { + float: left; + } + .col-sm-1 { + width: 8.333333333333332%; + } + .col-sm-2 { + width: 16.666666666666664%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-4 { + width: 33.33333333333333%; + } + .col-sm-5 { + width: 41.66666666666667%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-7 { + width: 58.333333333333336%; + } + .col-sm-8 { + width: 66.66666666666666%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-10 { + width: 83.33333333333334%; + } + .col-sm-11 { + width: 91.66666666666666%; + } + .col-sm-12 { + width: 100%; + } + .col-sm-push-1 { + left: 8.333333333333332%; + } + .col-sm-push-2 { + left: 16.666666666666664%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-4 { + left: 33.33333333333333%; + } + .col-sm-push-5 { + left: 41.66666666666667%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-7 { + left: 58.333333333333336%; + } + .col-sm-push-8 { + left: 66.66666666666666%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-10 { + left: 83.33333333333334%; + } + .col-sm-push-11 { + left: 91.66666666666666%; + } + .col-sm-pull-1 { + right: 8.333333333333332%; + } + .col-sm-pull-2 { + right: 16.666666666666664%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-4 { + right: 33.33333333333333%; + } + .col-sm-pull-5 { + right: 41.66666666666667%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-7 { + right: 58.333333333333336%; + } + .col-sm-pull-8 { + right: 66.66666666666666%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-10 { + right: 83.33333333333334%; + } + .col-sm-pull-11 { + right: 91.66666666666666%; + } + .col-sm-offset-1 { + margin-left: 8.333333333333332%; + } + .col-sm-offset-2 { + margin-left: 16.666666666666664%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-4 { + margin-left: 33.33333333333333%; + } + .col-sm-offset-5 { + margin-left: 41.66666666666667%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-7 { + margin-left: 58.333333333333336%; + } + .col-sm-offset-8 { + margin-left: 66.66666666666666%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-10 { + margin-left: 83.33333333333334%; + } + .col-sm-offset-11 { + margin-left: 91.66666666666666%; + } +} + +@media (min-width: 992px) { + .container { + max-width: 970px; + } + .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 { + float: left; + } + .col-md-1 { + width: 8.333333333333332%; + } + .col-md-2 { + width: 16.666666666666664%; + } + .col-md-3 { + width: 25%; + } + .col-md-4 { + width: 33.33333333333333%; + } + .col-md-5 { + width: 41.66666666666667%; + } + .col-md-6 { + width: 50%; + } + .col-md-7 { + width: 58.333333333333336%; + } + .col-md-8 { + width: 66.66666666666666%; + } + .col-md-9 { + width: 75%; + } + .col-md-10 { + width: 83.33333333333334%; + } + .col-md-11 { + width: 91.66666666666666%; + } + .col-md-12 { + width: 100%; + } + .col-md-push-0 { + left: auto; + } + .col-md-push-1 { + left: 8.333333333333332%; + } + .col-md-push-2 { + left: 16.666666666666664%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-4 { + left: 33.33333333333333%; + } + .col-md-push-5 { + left: 41.66666666666667%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-7 { + left: 58.333333333333336%; + } + .col-md-push-8 { + left: 66.66666666666666%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-10 { + left: 83.33333333333334%; + } + .col-md-push-11 { + left: 91.66666666666666%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-pull-1 { + right: 8.333333333333332%; + } + .col-md-pull-2 { + right: 16.666666666666664%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-4 { + right: 33.33333333333333%; + } + .col-md-pull-5 { + right: 41.66666666666667%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-7 { + right: 58.333333333333336%; + } + .col-md-pull-8 { + right: 66.66666666666666%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-10 { + right: 83.33333333333334%; + } + .col-md-pull-11 { + right: 91.66666666666666%; + } + .col-md-offset-0 { + margin-left: 0; + } + .col-md-offset-1 { + margin-left: 8.333333333333332%; + } + .col-md-offset-2 { + margin-left: 16.666666666666664%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-4 { + margin-left: 33.33333333333333%; + } + .col-md-offset-5 { + margin-left: 41.66666666666667%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-7 { + margin-left: 58.333333333333336%; + } + .col-md-offset-8 { + margin-left: 66.66666666666666%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-10 { + margin-left: 83.33333333333334%; + } + .col-md-offset-11 { + margin-left: 91.66666666666666%; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1170px; + } + .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 { + float: left; + } + .col-lg-1 { + width: 8.333333333333332%; + } + .col-lg-2 { + width: 16.666666666666664%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-4 { + width: 33.33333333333333%; + } + .col-lg-5 { + width: 41.66666666666667%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-7 { + width: 58.333333333333336%; + } + .col-lg-8 { + width: 66.66666666666666%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-10 { + width: 83.33333333333334%; + } + .col-lg-11 { + width: 91.66666666666666%; + } + .col-lg-12 { + width: 100%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-push-1 { + left: 8.333333333333332%; + } + .col-lg-push-2 { + left: 16.666666666666664%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-4 { + left: 33.33333333333333%; + } + .col-lg-push-5 { + left: 41.66666666666667%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-7 { + left: 58.333333333333336%; + } + .col-lg-push-8 { + left: 66.66666666666666%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-10 { + left: 83.33333333333334%; + } + .col-lg-push-11 { + left: 91.66666666666666%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-pull-1 { + right: 8.333333333333332%; + } + .col-lg-pull-2 { + right: 16.666666666666664%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-4 { + right: 33.33333333333333%; + } + .col-lg-pull-5 { + right: 41.66666666666667%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-7 { + right: 58.333333333333336%; + } + .col-lg-pull-8 { + right: 66.66666666666666%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-10 { + right: 83.33333333333334%; + } + .col-lg-pull-11 { + right: 91.66666666666666%; + } + .col-lg-offset-0 { + margin-left: 0; + } + .col-lg-offset-1 { + margin-left: 8.333333333333332%; + } + .col-lg-offset-2 { + margin-left: 16.666666666666664%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-4 { + margin-left: 33.33333333333333%; + } + .col-lg-offset-5 { + margin-left: 41.66666666666667%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-7 { + margin-left: 58.333333333333336%; + } + .col-lg-offset-8 { + margin-left: 66.66666666666666%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-10 { + margin-left: 83.33333333333334%; + } + .col-lg-offset-11 { + margin-left: 91.66666666666666%; + } +} + +table { + max-width: 100%; + background-color: transparent; +} + +th { + text-align: left; +} + +.table { + width: 100%; + margin-bottom: 20px; +} + +.table thead > tr > th, +.table tbody > tr > th, +.table tfoot > tr > th, +.table thead > tr > td, +.table tbody > tr > td, +.table tfoot > tr > td { + padding: 8px; + line-height: 1.428571429; + vertical-align: top; + border-top: 1px solid #dddddd; +} + +.table thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #dddddd; +} + +.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 #dddddd; +} + +.table .table { + background-color: #ffffff; +} + +.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 #dddddd; +} + +.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 #dddddd; +} + +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} + +.table-striped > tbody > tr:nth-child(odd) > td, +.table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} + +.table-hover > tbody > tr:hover > td, +.table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; +} + +table col[class*="col-"] { + display: table-column; + float: none; +} + +table td[class*="col-"], +table th[class*="col-"] { + display: table-cell; + float: none; +} + +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} + +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td { + background-color: #d0e9c6; + border-color: #c9e2b3; +} + +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; + border-color: #eed3d7; +} + +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td { + background-color: #ebcccc; + border-color: #e6c1c7; +} + +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td { + background-color: #faf2cc; + border-color: #f8e5be; +} + +@media (max-width: 768px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-x: scroll; + overflow-y: hidden; + border: 1px solid #dddddd; + } + .table-responsive > .table { + margin-bottom: 0; + background-color: #fff; + } + .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 > thead > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > thead > tr:last-child > td, + .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; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} + +label { + display: inline-block; + margin-bottom: 5px; + font-weight: bold; +} + +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + /* IE8-9 */ + + line-height: normal; +} + +input[type="file"] { + display: block; +} + +select[multiple], +select[size] { + height: auto; +} + +select optgroup { + font-family: inherit; + font-size: inherit; + font-style: inherit; +} + +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + height: auto; +} + +.form-control:-moz-placeholder { + color: #999999; +} + +.form-control::-moz-placeholder { + color: #999999; +} + +.form-control:-ms-input-placeholder { + color: #999999; +} + +.form-control::-webkit-input-placeholder { + color: #999999; +} + +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.428571429; + color: #555555; + vertical-align: middle; + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eeeeee; +} + +textarea.form-control { + height: auto; +} + +.form-group { + margin-bottom: 15px; +} + +.radio, +.checkbox { + display: block; + min-height: 20px; + padding-left: 20px; + margin-top: 10px; + margin-bottom: 10px; + vertical-align: middle; +} + +.radio label, +.checkbox label { + display: inline; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} + +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; +} + +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} + +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} + +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} + +input[type="radio"][disabled], +input[type="checkbox"][disabled], +.radio[disabled], +.radio-inline[disabled], +.checkbox[disabled], +.checkbox-inline[disabled], +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"], +fieldset[disabled] .radio, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} + +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +select.input-sm { + height: 30px; + line-height: 30px; +} + +textarea.input-sm { + height: auto; +} + +.input-lg { + height: 45px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +select.input-lg { + height: 45px; + line-height: 45px; +} + +textarea.input-lg { + height: auto; +} + +.has-warning .help-block, +.has-warning .control-label { + color: #c09853; +} + +.has-warning .form-control { + border-color: #c09853; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-warning .form-control:focus { + border-color: #a47e3c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; +} + +.has-warning .input-group-addon { + color: #c09853; + background-color: #fcf8e3; + border-color: #c09853; +} + +.has-error .help-block, +.has-error .control-label { + color: #b94a48; +} + +.has-error .form-control { + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-error .form-control:focus { + border-color: #953b39; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; +} + +.has-error .input-group-addon { + color: #b94a48; + background-color: #f2dede; + border-color: #b94a48; +} + +.has-success .help-block, +.has-success .control-label { + color: #468847; +} + +.has-success .form-control { + border-color: #468847; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-success .form-control:focus { + border-color: #356635; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; +} + +.has-success .input-group-addon { + color: #468847; + background-color: #dff0d8; + border-color: #468847; +} + +.form-control-static { + padding-top: 7px; + margin-bottom: 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; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } +} + +.form-horizontal .control-label, +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} + +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} + +.form-horizontal .form-group:before, +.form-horizontal .form-group:after { + display: table; + content: " "; +} + +.form-horizontal .form-group:after { + clear: both; +} + +.form-horizontal .form-group:before, +.form-horizontal .form-group:after { + display: table; + content: " "; +} + +.form-horizontal .form-group:after { + clear: both; +} + +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + } +} + +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.428571429; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + border: 1px solid transparent; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.btn:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.btn:hover, +.btn:focus { + color: #333333; + text-decoration: none; +} + +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} + +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-default { + color: #333333; + background-color: #ffffff; + border-color: #cccccc; +} + +.btn-default:hover, +.btn-default:focus, +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + color: #333333; + background-color: #ebebeb; + border-color: #adadad; +} + +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + background-image: none; +} + +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #ffffff; + border-color: #cccccc; +} + +.btn-primary { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + color: #ffffff; + background-color: #3276b1; + border-color: #285e8e; +} + +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + background-image: none; +} + +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #428bca; + border-color: #357ebd; +} + +.btn-warning { + color: #ffffff; + background-color: #f0ad4e; + border-color: #eea236; +} + +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + color: #ffffff; + background-color: #ed9c28; + border-color: #d58512; +} + +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + background-image: none; +} + +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} + +.btn-danger { + color: #ffffff; + background-color: #d9534f; + border-color: #d43f3a; +} + +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + color: #ffffff; + background-color: #d2322d; + border-color: #ac2925; +} + +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + background-image: none; +} + +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} + +.btn-success { + color: #ffffff; + background-color: #5cb85c; + border-color: #4cae4c; +} + +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + color: #ffffff; + background-color: #47a447; + border-color: #398439; +} + +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + background-image: none; +} + +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} + +.btn-info { + color: #ffffff; + background-color: #5bc0de; + border-color: #46b8da; +} + +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + color: #ffffff; + background-color: #39b3d7; + border-color: #269abc; +} + +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + background-image: none; +} + +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} + +.btn-link { + font-weight: normal; + color: #428bca; + cursor: pointer; + border-radius: 0; +} + +.btn-link, +.btn-link:active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} + +.btn-link:hover, +.btn-link:focus { + color: #2a6496; + text-decoration: underline; + background-color: transparent; +} + +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #999999; + text-decoration: none; +} + +.btn-lg { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +.btn-sm, +.btn-xs { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-xs { + padding: 1px 5px; +} + +.btn-block { + display: block; + width: 100%; + padding-right: 0; + padding-left: 0; +} + +.btn-block + .btn-block { + margin-top: 5px; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.in { + opacity: 1; +} + +.collapse { + display: none; +} + +.collapse.in { + display: block; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('glyphicons-halflings-regular.eot'); + src: url('glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('glyphicons-halflings-regular.woff') format('woff'), url('glyphicons-halflings-regular.ttf') format('truetype'), url('glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg'); +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + -webkit-font-smoothing: antialiased; + font-style: normal; + font-weight: normal; + line-height: 1; +} + +.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-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-print:before { + content: "\e045"; +} + +.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-eye-open:before { + content: "\e105"; +} + +.glyphicon-eye-close:before { + content: "\e106"; +} + +.glyphicon-warning-sign:before { + content: "\e107"; +} + +.glyphicon-plane:before { + content: "\e108"; +} + +.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-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-tasks:before { + content: "\e137"; +} + +.glyphicon-filter:before { + content: "\e138"; +} + +.glyphicon-fullscreen:before { + content: "\e140"; +} + +.glyphicon-dashboard:before { + content: "\e141"; +} + +.glyphicon-heart-empty:before { + content: "\e143"; +} + +.glyphicon-link:before { + content: "\e144"; +} + +.glyphicon-phone:before { + content: "\e145"; +} + +.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"; +} + +.glyphicon-briefcase:before { + content: "\1f4bc"; +} + +.glyphicon-calendar:before { + content: "\1f4c5"; +} + +.glyphicon-pushpin:before { + content: "\1f4cc"; +} + +.glyphicon-paperclip:before { + content: "\1f4ce"; +} + +.glyphicon-camera:before { + content: "\1f4f7"; +} + +.glyphicon-lock:before { + content: "\1f512"; +} + +.glyphicon-bell:before { + content: "\1f514"; +} + +.glyphicon-bookmark:before { + content: "\1f516"; +} + +.glyphicon-fire:before { + content: "\1f525"; +} + +.glyphicon-wrench:before { + content: "\1f527"; +} + +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid #000000; + border-right: 4px solid transparent; + border-bottom: 0 dotted; + border-left: 4px solid transparent; + content: ""; +} + +.dropdown { + position: relative; +} + +.dropdown-toggle:focus { + outline: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + list-style: none; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} + +.dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + color: #333333; + white-space: nowrap; +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #428bca; +} + +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #428bca; + outline: 0; +} + +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999999; +} + +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.open > .dropdown-menu { + display: block; +} + +.open > a { + outline: 0; +} + +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.428571429; + color: #999999; +} + +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} + +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0 dotted; + border-bottom: 4px solid #000000; + 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 { + right: 0; + left: auto; + } +} + +.btn-default .caret { + border-top-color: #333333; +} + +.btn-primary .caret, +.btn-success .caret, +.btn-warning .caret, +.btn-danger .caret, +.btn-info .caret { + border-top-color: #fff; +} + +.dropup .btn-default .caret { + border-bottom-color: #333333; +} + +.dropup .btn-primary .caret, +.dropup .btn-success .caret, +.dropup .btn-warning .caret, +.dropup .btn-danger .caret, +.dropup .btn-info .caret { + border-bottom-color: #fff; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} + +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} + +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn: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: none; +} + +.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:before, +.btn-toolbar:after { + display: table; + content: " "; +} + +.btn-toolbar:after { + clear: both; +} + +.btn-toolbar:before, +.btn-toolbar:after { + display: table; + content: " "; +} + +.btn-toolbar:after { + clear: both; +} + +.btn-toolbar .btn-group { + float: left; +} + +.btn-toolbar > .btn + .btn, +.btn-toolbar > .btn-group + .btn, +.btn-toolbar > .btn + .btn-group, +.btn-toolbar > .btn-group + .btn-group { + margin-left: 5px; +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +.btn-group > .btn:first-child { + margin-left: 0; +} + +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.btn-group > .btn-group { + float: left; +} + +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group > .btn-group:first-child > .btn:last-child, +.btn-group > .btn-group:first-child > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn-group:last-child > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + +.btn-group-xs > .btn { + padding: 5px 10px; + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} + +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} + +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} + +.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 { + display: block; + float: none; + width: 100%; + max-width: 100%; +} + +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after { + display: table; + content: " "; +} + +.btn-group-vertical > .btn-group:after { + clear: both; +} + +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after { + display: table; + content: " "; +} + +.btn-group-vertical > .btn-group:after { + clear: both; +} + +.btn-group-vertical > .btn-group > .btn { + float: none; +} + +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-right-radius: 0; + border-bottom-left-radius: 4px; + 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 > .btn:last-child, +.btn-group-vertical > .btn-group:first-child > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn-group:last-child > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.btn-group-justified { + display: table; + width: 100%; + border-collapse: separate; + table-layout: fixed; +} + +.btn-group-justified .btn { + display: table-cell; + float: none; + width: 1%; +} + +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + display: none; +} + +.input-group { + position: relative; + display: table; + border-collapse: separate; +} + +.input-group.col { + float: none; + padding-right: 0; + padding-left: 0; +} + +.input-group .form-control { + width: 100%; + margin-bottom: 0; +} + +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 45px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 45px; + line-height: 45px; +} + +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn { + height: auto; +} + +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} + +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn { + height: auto; +} + +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} + +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} + +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + text-align: center; + background-color: #eeeeee; + border: 1px solid #cccccc; + border-radius: 4px; +} + +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} + +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} + +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} + +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group-addon:first-child { + border-right: 0; +} + +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.input-group-addon:last-child { + border-left: 0; +} + +.input-group-btn { + position: relative; + white-space: nowrap; +} + +.input-group-btn > .btn { + position: relative; +} + +.input-group-btn > .btn + .btn { + margin-left: -4px; +} + +.input-group-btn > .btn:hover, +.input-group-btn > .btn:active { + z-index: 2; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav:before, +.nav:after { + display: table; + content: " "; +} + +.nav:after { + clear: both; +} + +.nav:before, +.nav:after { + display: table; + content: " "; +} + +.nav:after { + clear: both; +} + +.nav > li { + position: relative; + display: block; +} + +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} + +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} + +.nav > li.disabled > a { + color: #999999; +} + +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #999999; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} + +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eeeeee; + border-color: #428bca; +} + +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.nav > li > a > img { + max-width: none; +} + +.nav-tabs { + border-bottom: 1px solid #dddddd; +} + +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} + +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.428571429; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} + +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #dddddd; +} + +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555555; + cursor: default; + background-color: #ffffff; + border: 1px solid #dddddd; + border-bottom-color: transparent; +} + +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} + +.nav-tabs.nav-justified > li { + float: none; +} + +.nav-tabs.nav-justified > li > a { + text-align: center; +} + +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } +} + +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-bottom: 1px solid #dddddd; +} + +.nav-tabs.nav-justified > .active > a { + border-bottom-color: #ffffff; +} + +.nav-pills > li { + float: left; +} + +.nav-pills > li > a { + border-radius: 5px; +} + +.nav-pills > li + li { + margin-left: 2px; +} + +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #ffffff; + background-color: #428bca; +} + +.nav-stacked > li { + float: none; +} + +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} + +.nav-justified { + width: 100%; +} + +.nav-justified > li { + float: none; +} + +.nav-justified > li > a { + text-align: center; +} + +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } +} + +.nav-tabs-justified { + border-bottom: 0; +} + +.nav-tabs-justified > li > a { + margin-right: 0; + border-bottom: 1px solid #dddddd; +} + +.nav-tabs-justified > .active > a { + border-bottom-color: #ffffff; +} + +.tabbable:before, +.tabbable:after { + display: table; + content: " "; +} + +.tabbable:after { + clear: both; +} + +.tabbable:before, +.tabbable:after { + display: table; + content: " "; +} + +.tabbable:after { + clear: both; +} + +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} + +.tab-content > .active, +.pill-content > .active { + display: block; +} + +.nav .caret { + border-top-color: #428bca; + border-bottom-color: #428bca; +} + +.nav a:hover .caret { + border-top-color: #2a6496; + border-bottom-color: #2a6496; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.navbar { + position: relative; + z-index: 1000; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} + +.navbar:before, +.navbar:after { + display: table; + content: " "; +} + +.navbar:after { + clear: both; +} + +.navbar:before, +.navbar:after { + display: table; + content: " "; +} + +.navbar:after { + clear: both; +} + +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} + +.navbar-header:before, +.navbar-header:after { + display: table; + content: " "; +} + +.navbar-header:after { + clear: both; +} + +.navbar-header:before, +.navbar-header:after { + display: table; + content: " "; +} + +.navbar-header:after { + clear: both; +} + +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} + +.navbar-collapse { + max-height: 340px; + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} + +.navbar-collapse:before, +.navbar-collapse:after { + display: table; + content: " "; +} + +.navbar-collapse:after { + clear: both; +} + +.navbar-collapse:before, +.navbar-collapse:after { + display: table; + content: " "; +} + +.navbar-collapse:after { + clear: both; +} + +.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-collapse .navbar-nav.navbar-left:first-child { + margin-left: -15px; + } + .navbar-collapse .navbar-nav.navbar-right:last-child { + margin-right: -15px; + } + .navbar-collapse .navbar-text:last-child { + margin-right: 0; + } +} + +.container > .navbar-header, +.container > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} + +@media (min-width: 768px) { + .container > .navbar-header, + .container > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} + +.navbar-static-top { + 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; + border-width: 0 0 1px; +} + +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} + +.navbar-fixed-top { + top: 0; + z-index: 1030; +} + +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; +} + +.navbar-brand { + float: left; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} + +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} + +@media (min-width: 768px) { + .navbar > .container .navbar-brand { + margin-left: -15px; + } +} + +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + border: 1px solid transparent; + border-radius: 4px; +} + +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} + +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} + +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} + +.navbar-nav { + margin: 7.5px -15px; +} + +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} + +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} + +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} + +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + } +} + +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 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); +} + +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } +} + +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } +} + +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} + +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.navbar-nav.pull-right > li > .dropdown-menu, +.navbar-nav > li > .dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} + +.navbar-text { + float: left; + margin-top: 15px; + margin-bottom: 15px; +} + +@media (min-width: 768px) { + .navbar-text { + margin-right: 15px; + margin-left: 15px; + } +} + +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} + +.navbar-default .navbar-brand { + color: #777777; +} + +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} + +.navbar-default .navbar-text { + color: #777777; +} + +.navbar-default .navbar-nav > li > a { + color: #777777; +} + +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333333; + background-color: transparent; +} + +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555555; + background-color: #e7e7e7; +} + +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} + +.navbar-default .navbar-toggle { + border-color: #dddddd; +} + +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #dddddd; +} + +.navbar-default .navbar-toggle .icon-bar { + background-color: #cccccc; +} + +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e6e6e6; +} + +.navbar-default .navbar-nav > .dropdown > a:hover .caret, +.navbar-default .navbar-nav > .dropdown > a:focus .caret { + border-top-color: #333333; + border-bottom-color: #333333; +} + +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555555; + background-color: #e7e7e7; +} + +.navbar-default .navbar-nav > .open > a .caret, +.navbar-default .navbar-nav > .open > a:hover .caret, +.navbar-default .navbar-nav > .open > a:focus .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} + +.navbar-default .navbar-nav > .dropdown > a .caret { + border-top-color: #777777; + border-bottom-color: #777777; +} + +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333333; + 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: #555555; + 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: #cccccc; + background-color: transparent; + } +} + +.navbar-default .navbar-link { + color: #777777; +} + +.navbar-default .navbar-link:hover { + color: #333333; +} + +.navbar-inverse { + background-color: #222222; + border-color: #080808; +} + +.navbar-inverse .navbar-brand { + color: #999999; +} + +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .navbar-text { + color: #999999; +} + +.navbar-inverse .navbar-nav > li > a { + color: #999999; +} + +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #080808; +} + +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444444; + background-color: transparent; +} + +.navbar-inverse .navbar-toggle { + border-color: #333333; +} + +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333333; +} + +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ffffff; +} + +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} + +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #ffffff; + background-color: #080808; +} + +.navbar-inverse .navbar-nav > .dropdown > a:hover .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.navbar-inverse .navbar-nav > .dropdown > a .caret { + border-top-color: #999999; + border-bottom-color: #999999; +} + +.navbar-inverse .navbar-nav > .open > a .caret, +.navbar-inverse .navbar-nav > .open > a:hover .caret, +.navbar-inverse .navbar-nav > .open > a:focus .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #999999; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + 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: #ffffff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444444; + background-color: transparent; + } +} + +.navbar-inverse .navbar-link { + color: #999999; +} + +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} + +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} + +.breadcrumb > li { + display: inline-block; +} + +.breadcrumb > li + li:before { + padding: 0 5px; + color: #cccccc; + content: "/\00a0"; +} + +.breadcrumb > .active { + color: #999999; +} + +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} + +.pagination > li { + display: inline; +} + +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.428571429; + text-decoration: none; + background-color: #ffffff; + border: 1px solid #dddddd; +} + +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} + +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + background-color: #eeeeee; +} + +.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: #ffffff; + cursor: default; + background-color: #428bca; + border-color: #428bca; +} + +.pagination > .disabled > span, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #999999; + cursor: not-allowed; + background-color: #ffffff; + border-color: #dddddd; +} + +.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: 6px; + border-top-left-radius: 6px; +} + +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} + +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; +} + +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} + +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} + +.pager:before, +.pager:after { + display: table; + content: " "; +} + +.pager:after { + clear: both; +} + +.pager:before, +.pager:after { + display: table; + content: " "; +} + +.pager:after { + clear: both; +} + +.pager li { + display: inline; +} + +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 15px; +} + +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} + +.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: #999999; + cursor: not-allowed; + background-color: #ffffff; +} + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} + +.label[href]:hover, +.label[href]:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +.label:empty { + display: none; +} + +.label-default { + background-color: #999999; +} + +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #808080; +} + +.label-primary { + background-color: #428bca; +} + +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #3071a9; +} + +.label-success { + background-color: #5cb85c; +} + +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} + +.label-info { + background-color: #5bc0de; +} + +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} + +.label-warning { + background-color: #f0ad4e; +} + +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} + +.label-danger { + background-color: #d9534f; +} + +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} + +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #999999; + border-radius: 10px; +} + +.badge:empty { + display: none; +} + +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +.btn .badge { + position: relative; + top: -1px; +} + +a.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #428bca; + background-color: #ffffff; +} + +.nav-pills > li > a > .badge { + margin-left: 3px; +} + +.jumbotron { + padding: 30px; + margin-bottom: 30px; + font-size: 21px; + font-weight: 200; + line-height: 2.1428571435; + color: inherit; + background-color: #eeeeee; +} + +.jumbotron h1 { + line-height: 1; + color: inherit; +} + +.jumbotron p { + line-height: 1.4; +} + +.container .jumbotron { + border-radius: 6px; +} + +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1 { + font-size: 63px; + } +} + +.thumbnail { + display: inline-block; + display: block; + height: auto; + max-width: 100%; + padding: 4px; + line-height: 1.428571429; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +.thumbnail > img { + display: block; + height: auto; + max-width: 100%; +} + +a.thumbnail:hover, +a.thumbnail:focus { + border-color: #428bca; +} + +.thumbnail > img { + margin-right: auto; + margin-left: auto; +} + +.thumbnail .caption { + padding: 9px; + color: #333333; +} + +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} + +.alert h4 { + margin-top: 0; + color: inherit; +} + +.alert .alert-link { + font-weight: bold; +} + +.alert > p, +.alert > ul { + margin-bottom: 0; +} + +.alert > p + p { + margin-top: 5px; +} + +.alert-dismissable { + padding-right: 35px; +} + +.alert-dismissable .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} + +.alert-success { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.alert-success hr { + border-top-color: #c9e2b3; +} + +.alert-success .alert-link { + color: #356635; +} + +.alert-info { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.alert-info hr { + border-top-color: #a6e1ec; +} + +.alert-info .alert-link { + color: #2d6987; +} + +.alert-warning { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.alert-warning hr { + border-top-color: #f8e5be; +} + +.alert-warning .alert-link { + color: #a47e3c; +} + +.alert-danger { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.alert-danger hr { + border-top-color: #e6c1c7; +} + +.alert-danger .alert-link { + color: #953b39; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-moz-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-o-keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + color: #ffffff; + text-align: center; + background-color: #428bca; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + transition: width 0.6s ease; +} + +.progress-striped .progress-bar { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} + +.progress.active .progress-bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + +.progress-bar-success { + background-color: #5cb85c; +} + +.progress-striped .progress-bar-success { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-info { + background-color: #5bc0de; +} + +.progress-striped .progress-bar-info { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-warning { + background-color: #f0ad4e; +} + +.progress-striped .progress-bar-warning { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-danger { + background-color: #d9534f; +} + +.progress-striped .progress-bar-danger { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.media, +.media-body { + overflow: hidden; + zoom: 1; +} + +.media, +.media .media { + margin-top: 15px; +} + +.media:first-child { + margin-top: 0; +} + +.media-object { + display: block; +} + +.media-heading { + margin: 0 0 5px; +} + +.media > .pull-left { + margin-right: 10px; +} + +.media > .pull-right { + margin-left: 10px; +} + +.media-list { + padding-left: 0; + list-style: none; +} + +.list-group { + padding-left: 0; + margin-bottom: 20px; +} + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #ffffff; + border: 1px solid #dddddd; +} + +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} + +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} + +.list-group-item > .badge { + float: right; +} + +.list-group-item > .badge + .badge { + margin-right: 5px; +} + +a.list-group-item { + color: #555555; +} + +a.list-group-item .list-group-item-heading { + color: #333333; +} + +a.list-group-item:hover, +a.list-group-item:focus { + text-decoration: none; + background-color: #f5f5f5; +} + +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #428bca; + border-color: #428bca; +} + +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading { + color: inherit; +} + +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #e1edf7; +} + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} + +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} + +.panel { + margin-bottom: 20px; + background-color: #ffffff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} + +.panel-body { + padding: 15px; +} + +.panel-body:before, +.panel-body:after { + display: table; + content: " "; +} + +.panel-body:after { + clear: both; +} + +.panel-body:before, +.panel-body:after { + display: table; + content: " "; +} + +.panel-body:after { + clear: both; +} + +.panel > .list-group { + margin-bottom: 0; +} + +.panel > .list-group .list-group-item { + border-width: 1px 0; +} + +.panel > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.panel > .list-group .list-group-item:last-child { + border-bottom: 0; +} + +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} + +.panel > .table { + margin-bottom: 0; +} + +.panel > .panel-body + .table { + border-top: 1px solid #dddddd; +} + +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} + +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; +} + +.panel-title > a { + color: inherit; +} + +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #dddddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} + +.panel-group .panel { + margin-bottom: 0; + overflow: hidden; + border-radius: 4px; +} + +.panel-group .panel + .panel { + margin-top: 5px; +} + +.panel-group .panel-heading { + border-bottom: 0; +} + +.panel-group .panel-heading + .panel-collapse .panel-body { + border-top: 1px solid #dddddd; +} + +.panel-group .panel-footer { + border-top: 0; +} + +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #dddddd; +} + +.panel-default { + border-color: #dddddd; +} + +.panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #dddddd; +} + +.panel-default > .panel-heading + .panel-collapse .panel-body { + border-top-color: #dddddd; +} + +.panel-default > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #dddddd; +} + +.panel-primary { + border-color: #428bca; +} + +.panel-primary > .panel-heading { + color: #ffffff; + background-color: #428bca; + border-color: #428bca; +} + +.panel-primary > .panel-heading + .panel-collapse .panel-body { + border-top-color: #428bca; +} + +.panel-primary > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #428bca; +} + +.panel-success { + border-color: #d6e9c6; +} + +.panel-success > .panel-heading { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.panel-success > .panel-heading + .panel-collapse .panel-body { + border-top-color: #d6e9c6; +} + +.panel-success > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #d6e9c6; +} + +.panel-warning { + border-color: #fbeed5; +} + +.panel-warning > .panel-heading { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.panel-warning > .panel-heading + .panel-collapse .panel-body { + border-top-color: #fbeed5; +} + +.panel-warning > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #fbeed5; +} + +.panel-danger { + border-color: #eed3d7; +} + +.panel-danger > .panel-heading { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.panel-danger > .panel-heading + .panel-collapse .panel-body { + border-top-color: #eed3d7; +} + +.panel-danger > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #eed3d7; +} + +.panel-info { + border-color: #bce8f1; +} + +.panel-info > .panel-heading { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.panel-info > .panel-heading + .panel-collapse .panel-body { + border-top-color: #bce8f1; +} + +.panel-info > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #bce8f1; +} + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} + +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} + +.well-lg { + padding: 24px; + border-radius: 6px; +} + +.well-sm { + padding: 9px; + border-radius: 3px; +} + +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} + +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} + +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} + +.modal-open { + overflow: hidden; +} + +body.modal-open, +.modal-open .navbar-fixed-top, +.modal-open .navbar-fixed-bottom { + margin-right: 15px; +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + display: none; + overflow: auto; + overflow-y: scroll; +} + +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -moz-transition: -moz-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} + +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); +} + +.modal-dialog { + z-index: 1050; + width: auto; + padding: 10px; + margin-right: auto; + margin-left: auto; +} + +.modal-content { + position: relative; + background-color: #ffffff; + border: 1px solid #999999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + outline: none; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + background-clip: padding-box; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; + background-color: #000000; +} + +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} + +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} + +.modal-header { + min-height: 16.428571429px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} + +.modal-header .close { + margin-top: -2px; +} + +.modal-title { + margin: 0; + line-height: 1.428571429; +} + +.modal-body { + position: relative; + padding: 20px; +} + +.modal-footer { + padding: 19px 20px 20px; + margin-top: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} + +.modal-footer:after { + clear: both; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} + +.modal-footer:after { + clear: both; +} + +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} + +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} + +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} + +@media screen and (min-width: 768px) { + .modal-dialog { + right: auto; + left: 50%; + width: 600px; + padding-top: 30px; + padding-bottom: 30px; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } +} + +.tooltip { + position: absolute; + z-index: 1030; + display: block; + font-size: 12px; + line-height: 1.4; + opacity: 0; + filter: alpha(opacity=0); + visibility: visible; +} + +.tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); +} + +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} + +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} + +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} + +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + text-decoration: none; + background-color: #000000; + border-radius: 4px; +} + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.top-right .tooltip-arrow { + right: 5px; + bottom: 0; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-right-color: #000000; + border-width: 5px 5px 5px 0; +} + +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-left-color: #000000; + border-width: 5px 0 5px 5px; +} + +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + background-clip: padding-box; +} + +.popover.top { + margin-top: -10px; +} + +.popover.right { + margin-left: 10px; +} + +.popover.bottom { + margin-top: 10px; +} + +.popover.left { + margin-left: -10px; +} + +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} + +.popover-content { + padding: 9px 14px; +} + +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.popover .arrow { + border-width: 11px; +} + +.popover .arrow:after { + border-width: 10px; + content: ""; +} + +.popover.top .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + border-bottom-width: 0; +} + +.popover.top .arrow:after { + bottom: 1px; + margin-left: -10px; + border-top-color: #ffffff; + border-bottom-width: 0; + content: " "; +} + +.popover.right .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); + border-left-width: 0; +} + +.popover.right .arrow:after { + bottom: -10px; + left: 1px; + border-right-color: #ffffff; + border-left-width: 0; + content: " "; +} + +.popover.bottom .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); + border-top-width: 0; +} + +.popover.bottom .arrow:after { + top: 1px; + margin-left: -10px; + border-bottom-color: #ffffff; + border-top-width: 0; + content: " "; +} + +.popover.left .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); + border-right-width: 0; +} + +.popover.left .arrow:after { + right: 1px; + bottom: -10px; + border-left-color: #ffffff; + border-right-width: 0; + content: " "; +} + +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} + +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + height: auto; + max-width: 100%; + line-height: 1; +} + +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} + +.carousel-inner > .active { + left: 0; +} + +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} + +.carousel-inner > .next { + left: 100%; +} + +.carousel-inner > .prev { + left: -100%; +} + +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} + +.carousel-inner > .active.left { + left: -100%; +} + +.carousel-inner > .active.right { + left: 100%; +} + +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + opacity: 0.5; + filter: alpha(opacity=50); +} + +.carousel-control.left { + background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} + +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} + +.carousel-control:hover, +.carousel-control:focus { + color: #ffffff; + text-decoration: none; + opacity: 0.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%; + left: 50%; + z-index: 5; + display: inline-block; +} + +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + font-family: serif; +} + +.carousel-control .icon-prev:before { + content: '\2039'; +} + +.carousel-control .icon-next:before { + content: '\203a'; +} + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} + +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + border: 1px solid #ffffff; + border-radius: 10px; +} + +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #ffffff; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #ffffff; + 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 .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + margin-left: -15px; + font-size: 30px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} + +.clearfix:before, +.clearfix:after { + display: table; + content: " "; +} + +.clearfix:after { + clear: both; +} + +.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; +} + +.affix { + position: fixed; +} + +@-ms-viewport { + width: device-width; +} + +@media screen and (max-width: 400px) { + @-ms-viewport { + width: 320px; + } +} + +.hidden { + display: none !important; + visibility: hidden !important; +} + +.visible-xs { + display: none !important; +} + +tr.visible-xs { + display: none !important; +} + +th.visible-xs, +td.visible-xs { + display: none !important; +} + +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-xs.visible-sm { + display: block !important; + } + tr.visible-xs.visible-sm { + display: table-row !important; + } + th.visible-xs.visible-sm, + td.visible-xs.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-xs.visible-md { + display: block !important; + } + tr.visible-xs.visible-md { + display: table-row !important; + } + th.visible-xs.visible-md, + td.visible-xs.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-xs.visible-lg { + display: block !important; + } + tr.visible-xs.visible-lg { + display: table-row !important; + } + th.visible-xs.visible-lg, + td.visible-xs.visible-lg { + display: table-cell !important; + } +} + +.visible-sm { + display: none !important; +} + +tr.visible-sm { + display: none !important; +} + +th.visible-sm, +td.visible-sm { + display: none !important; +} + +@media (max-width: 767px) { + .visible-sm.visible-xs { + display: block !important; + } + tr.visible-sm.visible-xs { + display: table-row !important; + } + th.visible-sm.visible-xs, + td.visible-sm.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-sm.visible-md { + display: block !important; + } + tr.visible-sm.visible-md { + display: table-row !important; + } + th.visible-sm.visible-md, + td.visible-sm.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-sm.visible-lg { + display: block !important; + } + tr.visible-sm.visible-lg { + display: table-row !important; + } + th.visible-sm.visible-lg, + td.visible-sm.visible-lg { + display: table-cell !important; + } +} + +.visible-md { + display: none !important; +} + +tr.visible-md { + display: none !important; +} + +th.visible-md, +td.visible-md { + display: none !important; +} + +@media (max-width: 767px) { + .visible-md.visible-xs { + display: block !important; + } + tr.visible-md.visible-xs { + display: table-row !important; + } + th.visible-md.visible-xs, + td.visible-md.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-md.visible-sm { + display: block !important; + } + tr.visible-md.visible-sm { + display: table-row !important; + } + th.visible-md.visible-sm, + td.visible-md.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-md.visible-lg { + display: block !important; + } + tr.visible-md.visible-lg { + display: table-row !important; + } + th.visible-md.visible-lg, + td.visible-md.visible-lg { + display: table-cell !important; + } +} + +.visible-lg { + display: none !important; +} + +tr.visible-lg { + display: none !important; +} + +th.visible-lg, +td.visible-lg { + display: none !important; +} + +@media (max-width: 767px) { + .visible-lg.visible-xs { + display: block !important; + } + tr.visible-lg.visible-xs { + display: table-row !important; + } + th.visible-lg.visible-xs, + td.visible-lg.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-lg.visible-sm { + display: block !important; + } + tr.visible-lg.visible-sm { + display: table-row !important; + } + th.visible-lg.visible-sm, + td.visible-lg.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-lg.visible-md { + display: block !important; + } + tr.visible-lg.visible-md { + display: table-row !important; + } + th.visible-lg.visible-md, + td.visible-lg.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} + +.hidden-xs { + display: block !important; +} + +tr.hidden-xs { + display: table-row !important; +} + +th.hidden-xs, +td.hidden-xs { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } + tr.hidden-xs { + display: none !important; + } + th.hidden-xs, + td.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-xs.hidden-sm { + display: none !important; + } + tr.hidden-xs.hidden-sm { + display: none !important; + } + th.hidden-xs.hidden-sm, + td.hidden-xs.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-xs.hidden-md { + display: none !important; + } + tr.hidden-xs.hidden-md { + display: none !important; + } + th.hidden-xs.hidden-md, + td.hidden-xs.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-xs.hidden-lg { + display: none !important; + } + tr.hidden-xs.hidden-lg { + display: none !important; + } + th.hidden-xs.hidden-lg, + td.hidden-xs.hidden-lg { + display: none !important; + } +} + +.hidden-sm { + display: block !important; +} + +tr.hidden-sm { + display: table-row !important; +} + +th.hidden-sm, +td.hidden-sm { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-sm.hidden-xs { + display: none !important; + } + tr.hidden-sm.hidden-xs { + display: none !important; + } + th.hidden-sm.hidden-xs, + td.hidden-sm.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } + tr.hidden-sm { + display: none !important; + } + th.hidden-sm, + td.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-sm.hidden-md { + display: none !important; + } + tr.hidden-sm.hidden-md { + display: none !important; + } + th.hidden-sm.hidden-md, + td.hidden-sm.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-sm.hidden-lg { + display: none !important; + } + tr.hidden-sm.hidden-lg { + display: none !important; + } + th.hidden-sm.hidden-lg, + td.hidden-sm.hidden-lg { + display: none !important; + } +} + +.hidden-md { + display: block !important; +} + +tr.hidden-md { + display: table-row !important; +} + +th.hidden-md, +td.hidden-md { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-md.hidden-xs { + display: none !important; + } + tr.hidden-md.hidden-xs { + display: none !important; + } + th.hidden-md.hidden-xs, + td.hidden-md.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-md.hidden-sm { + display: none !important; + } + tr.hidden-md.hidden-sm { + display: none !important; + } + th.hidden-md.hidden-sm, + td.hidden-md.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } + tr.hidden-md { + display: none !important; + } + th.hidden-md, + td.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-md.hidden-lg { + display: none !important; + } + tr.hidden-md.hidden-lg { + display: none !important; + } + th.hidden-md.hidden-lg, + td.hidden-md.hidden-lg { + display: none !important; + } +} + +.hidden-lg { + display: block !important; +} + +tr.hidden-lg { + display: table-row !important; +} + +th.hidden-lg, +td.hidden-lg { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-lg.hidden-xs { + display: none !important; + } + tr.hidden-lg.hidden-xs { + display: none !important; + } + th.hidden-lg.hidden-xs, + td.hidden-lg.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-lg.hidden-sm { + display: none !important; + } + tr.hidden-lg.hidden-sm { + display: none !important; + } + th.hidden-lg.hidden-sm, + td.hidden-lg.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-lg.hidden-md { + display: none !important; + } + tr.hidden-lg.hidden-md { + display: none !important; + } + th.hidden-lg.hidden-md, + td.hidden-lg.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } + tr.hidden-lg { + display: none !important; + } + th.hidden-lg, + td.hidden-lg { + display: none !important; + } +} + +.visible-print { + display: none !important; +} + +tr.visible-print { + display: none !important; +} + +th.visible-print, +td.visible-print { + display: none !important; +} + +@media print { + .visible-print { + display: block !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } + .hidden-print { + display: none !important; + } + tr.hidden-print { + display: none !important; + } + th.hidden-print, + td.hidden-print { + display: none !important; + } +} \ No newline at end of file