diff --git a/app/Config.example.php b/app/Config.example.php
index 07de120ff0..5abf9aeae8 100644
--- a/app/Config.example.php
+++ b/app/Config.example.php
@@ -96,6 +96,21 @@
*/
// define('SITEEMAIL', 'email@domain.com');
+/**
+ * Setup the Database configuration.
+ */
+Config::set('database', array(
+ 'default' => array(
+ 'driver' => DB_TYPE,
+ 'hostname' => DB_HOST,
+ 'database' => DB_NAME,
+ 'username' => DB_USER,
+ 'password' => DB_PASS,
+ 'charset' => 'utf8',
+ 'collation' => 'utf8_general_ci',
+ ),
+));
+
/**
* Setup the (class) Aliases configuration.
*/
@@ -126,6 +141,6 @@
'SimpleCurl' => '\Helpers\SimpleCurl',
'TableBuilder' => '\Helpers\TableBuilder',
'Tags' => '\Helpers\Tags',
- 'Url' => '\Helpers\Url'
+ 'Url' => '\Helpers\Url',
+ 'DB' => '\Database\Facade',
));
-
diff --git a/app/Templates/Default/default.php b/app/Templates/Default/default.php
index 79f6c93876..315d8f1daf 100644
--- a/app/Templates/Default/default.php
+++ b/app/Templates/Default/default.php
@@ -29,6 +29,7 @@
'>German |
'>French |
'>Italian |
+ '>Japanese |
'>Dutch |
'>Persian |
'>Polish |
diff --git a/system/Database/Connection.php b/system/Database/Connection.php
new file mode 100644
index 0000000000..54d36f1ac6
--- /dev/null
+++ b/system/Database/Connection.php
@@ -0,0 +1,242 @@
+tablePrefix = $config['prefix'];
+
+ // Create the PDO instance from the given configuration.
+ extract($config);
+
+ $dsn = "$driver:host={$hostname};dbname={$database}";
+
+ $options = array(
+ PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES {$charset} COLLATE {$collation}"
+ );
+
+ $this->pdo = new PDO($dsn, $username, $password, $options);
+ }
+
+ /**
+ * Get an instance of the Database Connection.
+ *
+ * @param $name string Name of the connection provided in the configuration
+ * @return Connection|\PDO|null
+ * @throws \Exception
+ */
+ public static function getInstance($name = 'default')
+ {
+ if (isset(static::$instances[$name])) {
+ // When already have an Connection instance, return it.
+ return static::$instances[$name];
+ }
+
+ // Get the requested Connection options.
+ $config = Config::get('database');
+
+ if (isset($config[$name])) {
+ // Create the Connection instance.
+ static::$instances[$name] = new static($config[$name]);
+
+ // Return the Connection instance.
+ return static::$instances[$name];
+ }
+
+ throw new \Exception("Connection name '$name' is not defined in your configuration");
+ }
+
+ /**
+ * Begin a fluent query against a database table.
+ *
+ * @param string $table
+ * @return \Database\Query
+ */
+ public function table($table)
+ {
+ $query = new Query($this);
+
+ return $query->from($table);
+ }
+
+ /**
+ * Run a select statement against the database.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @return array
+ */
+ public function select($query, $bindings = array())
+ {
+ $statement = $this->getPdo()->prepare($query);
+
+ $statement->execute($bindings);
+
+ return $statement->fetchAll($this->getFetchMode());
+ }
+
+ /**
+ * Run an insert statement against the database.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @return bool
+ */
+ public function insert($query, $bindings = array())
+ {
+ return $this->statement($query, $bindings);
+ }
+
+ /**
+ * Run an update statement against the database.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @return int
+ */
+ public function update($query, $bindings = array())
+ {
+ return $this->affectingStatement($query, $bindings);
+ }
+
+ /**
+ * Run a delete statement against the database.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @return int
+ */
+ public function delete($query, $bindings = array())
+ {
+ return $this->affectingStatement($query, $bindings);
+ }
+
+ /**
+ * Execute an SQL statement and return the boolean result.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @return bool
+ */
+ public function statement($query, $bindings = array())
+ {
+ return $this->getPdo()->prepare($query)->execute($bindings);
+ }
+
+ /**
+ * Run an SQL statement and get the number of rows affected.
+ *
+ * @param string $query
+ * @param array $bindings
+ * @return int
+ */
+ public function affectingStatement($query, $bindings = array())
+ {
+ $statement = $this->getPdo()->prepare($query);
+
+ $statement->execute($bindings);
+
+ return $statement->rowCount();
+ }
+
+ /**
+ * Get the table prefix for the connection.
+ *
+ * @return string
+ */
+ public function getTablePrefix()
+ {
+ return $this->tablePrefix;
+ }
+
+ /**
+ * Set the table prefix in use by the connection.
+ *
+ * @param string $prefix
+ * @return void
+ */
+ public function setTablePrefix($prefix)
+ {
+ $this->tablePrefix = $prefix;
+ }
+
+ /**
+ * Get the PDO instance.
+ *
+ * @return PDO
+ */
+ public function getPdo()
+ {
+ return $this->pdo;
+ }
+
+ /**
+ * Get the default fetch mode for the connection.
+ *
+ * @return int
+ */
+ public function getFetchMode()
+ {
+ return $this->fetchMode;
+ }
+
+ /**
+ * Set the default fetch mode for the connection.
+ *
+ * @param int $fetchMode
+ * @return int
+ */
+ public function setFetchMode($fetchMode)
+ {
+ $this->fetchMode = $fetchMode;
+ }
+}
diff --git a/system/Database/Facade.php b/system/Database/Facade.php
new file mode 100644
index 0000000000..fb8a13d85a
--- /dev/null
+++ b/system/Database/Facade.php
@@ -0,0 +1,29 @@
+type = $type;
+ $this->query = $query;
+ $this->table = $table;
+ }
+
+ /**
+ * Add an "ON" clause to the join.
+ *
+ * @param string $first
+ * @param string $operator
+ * @param string $second
+ * @param string $boolean
+ * @param bool $where
+ * @return \Database\JoinClause
+ */
+ public function on($first, $operator, $second, $boolean = 'and', $where = false)
+ {
+ $this->clauses[] = compact('first', 'operator', 'second', 'boolean', 'where');
+
+ if ($where) $this->query->addBinding($second);
+
+ return $this;
+ }
+
+ /**
+ * Add an "OR ON" clause to the join.
+ *
+ * @param string $first
+ * @param string $operator
+ * @param string $second
+ * @return \Database\JoinClause
+ */
+ public function orOn($first, $operator, $second)
+ {
+ return $this->on($first, $operator, $second, 'or');
+ }
+
+ /**
+ * Add an "ON WHERE" clause to the join.
+ *
+ * @param string $first
+ * @param string $operator
+ * @param string $second
+ * @param string $boolean
+ * @return \Database\JoinClause
+ */
+ public function where($first, $operator, $second, $boolean = 'and')
+ {
+ return $this->on($first, $operator, $second, $boolean, true);
+ }
+
+ /**
+ * Add an "OR ON WHERE" clause to the join.
+ *
+ * @param string $first
+ * @param string $operator
+ * @param string $second
+ * @param string $boolean
+ * @return \Database\JoinClause
+ */
+ public function orWhere($first, $operator, $second)
+ {
+ return $this->on($first, $operator, $second, 'or', true);
+ }
+}
diff --git a/system/Database/Model.php b/system/Database/Model.php
new file mode 100644
index 0000000000..c4745595a4
--- /dev/null
+++ b/system/Database/Model.php
@@ -0,0 +1,101 @@
+table) {
+ // Not Table name specified? Try to auto-calculate it.
+ $className = get_class($this);
+
+ $this->table = Inflector::tableize(class_basename($className));
+ }
+
+ $this->db = Connection::getInstance();
+ }
+
+ /**
+ * Get the Table for the Model.
+ *
+ * @return string
+ */
+ public function getTable()
+ {
+ return $this->table;
+ }
+
+ /**
+ * Get the Primary Key for the Model.
+ *
+ * @return string
+ */
+ public function getKeyName()
+ {
+ return $this->primaryKey;
+ }
+
+ /**
+ * Get a new Query for the Model's table.
+ *
+ * @return \Database\Query
+ */
+ public function newQuery()
+ {
+ return $this->db
+ ->table($this->table)
+ ->setModel($this);
+ }
+
+ /**
+ * Handle dynamic method calls into the method.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ $query = $this->newQuery();
+
+ return call_user_func_array(array($query, $method), $parameters);
+ }
+}
diff --git a/system/Database/Query.php b/system/Database/Query.php
new file mode 100644
index 0000000000..0188b2fbbb
--- /dev/null
+++ b/system/Database/Query.php
@@ -0,0 +1,1546 @@
+', '<=', '>=', '<>', '!=', 'like', 'not like', 'between', 'ilike', '&', '|', '^', '<<', '>>',
+ );
+
+ /**
+ * Create a new query instance.
+ *
+ * @return void
+ */
+ public function __construct(Connection $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * Set the columns to be selected.
+ *
+ * @param array $columns
+ * @return \Database\Query
+ */
+ public function select($columns = array('*'))
+ {
+ $this->columns = is_array($columns) ? $columns : func_get_args();
+
+ return $this;
+ }
+
+ /**
+ * Force the query to only return distinct results.
+ *
+ * @return \Database\Query
+ */
+ public function distinct()
+ {
+ $this->distinct = true;
+
+ return $this;
+ }
+
+ /**
+ * Set the table which the query is targeting.
+ *
+ * @param string $table
+ * @return \Database\Query
+ */
+ public function from($table)
+ {
+ $this->from = $table;
+
+ return $this;
+ }
+
+ /**
+ * Add a join clause to the query.
+ *
+ * @param string $table
+ * @param string $first
+ * @param string $operator
+ * @param string $two
+ * @param string $type
+ * @param bool $where
+ * @return \Database\Query
+ */
+ public function join($table, $one, $operator = null, $two = null, $type = 'inner', $where = false)
+ {
+ if ($one instanceof Closure) {
+ $this->joins[] = new JoinClause($this, $type, $table);
+
+ call_user_func($one, end($this->joins));
+ } else {
+ $join = new JoinClause($this, $type, $table);
+
+ $this->joins[] = $join->on(
+ $one, $operator, $two, 'and', $where
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a "JOIN WHERE" clause to the query.
+ *
+ * @param string $table
+ * @param string $first
+ * @param string $operator
+ * @param string $two
+ * @param string $type
+ * @return \Database\Query
+ */
+ public function joinWhere($table, $one, $operator, $two, $type = 'inner')
+ {
+ return $this->join($table, $one, $operator, $two, $type, true);
+ }
+
+ /**
+ * Add a left join to the query.
+ *
+ * @param string $table
+ * @param string $first
+ * @param string $operator
+ * @param string $second
+ * @return \Database\Query
+ */
+ public function leftJoin($table, $first, $operator = null, $second = null)
+ {
+ return $this->join($table, $first, $operator, $second, 'left');
+ }
+
+ /**
+ * Add a "JOIN WHERE" clause to the query.
+ *
+ * @param string $table
+ * @param string $first
+ * @param string $operator
+ * @param string $two
+ * @return \Database\Query
+ */
+ public function leftJoinWhere($table, $one, $operator, $two)
+ {
+ return $this->joinWhere($table, $one, $operator, $two, 'left');
+ }
+
+ /**
+ * Add a basic where clause to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param mixed $value
+ * @param string $boolean
+ * @return \Database\Query
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function where($column, $operator = null, $value = null, $boolean = 'and')
+ {
+ if (func_num_args() == 2) {
+ list($value, $operator) = array($operator, '=');
+ } else if ($this->invalidOperatorAndValue($operator, $value)) {
+ throw new \InvalidArgumentException("Value must be provided.");
+ }
+
+ if ($column instanceof Closure) {
+ return $this->whereNested($column, $boolean);
+ }
+
+ if (! in_array(strtolower($operator), $this->operators, true)) {
+ list($value, $operator) = array($operator, '=');
+ }
+
+ if ($value instanceof Closure) {
+ return $this->whereSub($column, $operator, $value, $boolean);
+ }
+
+ if (is_null($value)) {
+ return $this->whereNull($column, $boolean, $operator != '=');
+ }
+
+ $type = 'Basic';
+
+ $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
+
+ $this->bindings[] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Add an "OR WHERE" clause to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param mixed $value
+ * @return \Database\Query
+ */
+ public function orWhere($column, $operator = null, $value = null)
+ {
+ return $this->where($column, $operator, $value, 'or');
+ }
+
+ /**
+ * Determine if the given operator and value combination is legal.
+ *
+ * @param string $operator
+ * @param mixed $value
+ * @return bool
+ */
+ protected function invalidOperatorAndValue($operator, $value)
+ {
+ $isOperator = in_array($operator, $this->operators);
+
+ return ($isOperator && ($operator != '=') && is_null($value));
+ }
+
+ /**
+ * Add a where between statement to the query.
+ *
+ * @param string $column
+ * @param array $values
+ * @param string $boolean
+ * @param bool $not
+ * @return \Database\Query
+ */
+ public function whereBetween($column, array $values, $boolean = 'and', $not = false)
+ {
+ $type = 'between';
+
+ $this->wheres[] = compact('column', 'type', 'boolean', 'not');
+
+ $this->bindings = array_merge($this->bindings, $values);
+
+ return $this;
+ }
+
+ /**
+ * Add an or where between statement to the query.
+ *
+ * @param string $column
+ * @param array $values
+ * @return \Database\Query
+ */
+ public function orWhereBetween($column, array $values)
+ {
+ return $this->whereBetween($column, $values, 'or');
+ }
+
+ /**
+ * Add a where not between statement to the query.
+ *
+ * @param string $column
+ * @param array $values
+ * @param string $boolean
+ * @return \Database\Query
+ */
+ public function whereNotBetween($column, array $values, $boolean = 'and')
+ {
+ return $this->whereBetween($column, $values, $boolean, true);
+ }
+
+ /**
+ * Add an or where not between statement to the query.
+ *
+ * @param string $column
+ * @param array $values
+ * @return \Database\Query
+ */
+ public function orWhereNotBetween($column, array $values)
+ {
+ return $this->whereNotBetween($column, $values, 'or');
+ }
+
+ /**
+ * Add a nested where statement to the query.
+ *
+ * @param \Closure $callback
+ * @param string $boolean
+ * @return \Database\Query
+ */
+ public function whereNested(Closure $callback, $boolean = 'and')
+ {
+ $query = $this->newQuery();
+
+ $query->from($this->from);
+
+ call_user_func($callback, $query);
+
+ return $this->addNestedWhereQuery($query, $boolean);
+ }
+
+ /**
+ * Add another query builder as a nested where to the query builder.
+ *
+ * @param \Database\Query $query
+ * @param string $boolean
+ * @return \Database\Query
+ */
+ public function addNestedWhereQuery($query, $boolean = 'and')
+ {
+ if (count($query->wheres)) {
+ $type = 'Nested';
+
+ $this->wheres[] = compact('type', 'query', 'boolean');
+
+ $this->mergeBindings($query);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a full sub-select to the query.
+ *
+ * @param string $column
+ * @param string $operator
+ * @param \Closure $callback
+ * @param string $boolean
+ * @return \Database\Query
+ */
+ protected function whereSub($column, $operator, Closure $callback, $boolean)
+ {
+ $type = 'Sub';
+
+ $query = $this->newQuery();
+
+ call_user_func($callback, $query);
+
+ $this->wheres[] = compact('type', 'column', 'operator', 'query', 'boolean');
+
+ $this->mergeBindings($query);
+
+ return $this;
+ }
+
+ /**
+ * Add a "WHERE IN" clause to the query.
+ *
+ * @param string $column
+ * @param mixed $values
+ * @param string $boolean
+ * @param bool $not
+ * @return \Database\Query
+ */
+ public function whereIn($column, $values, $boolean = 'and', $not = false)
+ {
+ $type = $not ? 'NotIn' : 'In';
+
+ if ($values instanceof Closure) {
+ return $this->whereInSub($column, $values, $boolean, $not);
+ }
+
+ $this->wheres[] = compact('type', 'column', 'values', 'boolean');
+
+ $this->bindings = array_merge($this->bindings, $values);
+
+ return $this;
+ }
+
+ /**
+ * Add an "OR WHERE IN" clause to the query.
+ *
+ * @param string $column
+ * @param mixed $values
+ * @return \Database\Query
+ */
+ public function orWhereIn($column, $values)
+ {
+ return $this->whereIn($column, $values, 'or');
+ }
+
+ /**
+ * Add a "WHERE NOT IN" clause to the query.
+ *
+ * @param string $column
+ * @param mixed $values
+ * @param string $boolean
+ * @return \Database\Query
+ */
+ public function whereNotIn($column, $values, $boolean = 'and')
+ {
+ return $this->whereIn($column, $values, $boolean, true);
+ }
+
+ /**
+ * Add an "OR WHERE NOT IN" clause to the query.
+ *
+ * @param string $column
+ * @param mixed $values
+ * @return \Database\Query
+ */
+ public function orWhereNotIn($column, $values)
+ {
+ return $this->whereNotIn($column, $values, 'or');
+ }
+
+ /**
+ * Add a where in with a sub-select to the query.
+ *
+ * @param string $column
+ * @param \Closure $callback
+ * @param string $boolean
+ * @param bool $not
+ * @return \Database\Query
+ */
+ protected function whereInSub($column, Closure $callback, $boolean, $not)
+ {
+ $type = $not ? 'NotInSub' : 'InSub';
+
+ call_user_func($callback, $query = $this->newQuery());
+
+ $this->wheres[] = compact('type', 'column', 'query', 'boolean');
+
+ $this->mergeBindings($query);
+
+ return $this;
+ }
+
+ /**
+ * Add a "WHERE NULL" clause to the query.
+ *
+ * @param string $column
+ * @param string $boolean
+ * @param bool $not
+ * @return \Database\Query
+ */
+ public function whereNull($column, $boolean = 'and', $not = false)
+ {
+ $type = $not ? 'NotNull' : 'Null';
+
+ $this->wheres[] = compact('type', 'column', 'boolean');
+
+ return $this;
+ }
+
+ /**
+ * Add an "OR WHERE NULL" clause to the query.
+ *
+ * @param string $column
+ * @return \Database\Query
+ */
+ public function orWhereNull($column)
+ {
+ return $this->whereNull($column, 'or');
+ }
+
+ /**
+ * Add a "WHERE NOT NULL" clause to the query.
+ *
+ * @param string $column
+ * @param string $boolean
+ * @return \Database\Query
+ */
+ public function whereNotNull($column, $boolean = 'and')
+ {
+ return $this->whereNull($column, $boolean, true);
+ }
+
+ /**
+ * Add an "OR WHERE NOT NULL" clause to the query.
+ *
+ * @param string $column
+ * @return \Database\Query
+ */
+ public function orWhereNotNull($column)
+ {
+ return $this->whereNotNull($column, 'or');
+ }
+
+ /**
+ * Add an "ORDER BY" clause to the query.
+ *
+ * @param string $column
+ * @param string $direction
+ * @return \Database\Query
+ */
+ public function orderBy($column, $direction = 'asc')
+ {
+ $direction = strtolower($direction) == 'asc' ? 'asc' : 'desc';
+
+ $this->orders[] = compact('column', 'direction');
+
+ return $this;
+ }
+
+ /**
+ * Set the "OFFSET" value of the query.
+ *
+ * @param int $value
+ * @return \Database\Query
+ */
+ public function offset($value)
+ {
+ $this->offset = max(0, $value);
+
+ return $this;
+ }
+
+ /**
+ * Alias to set the "OFFSET" value of the query.
+ *
+ * @param int $value
+ * @return \Database\Query
+ */
+ public function skip($value)
+ {
+ return $this->offset($value);
+ }
+
+ /**
+ * Set the "LIMIT" value of the query.
+ *
+ * @param int $value
+ * @return \Database\Query
+ */
+ public function limit($value)
+ {
+ if ($value > 0) {
+ $this->limit = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Alias to set the "LIMIT" value of the query.
+ *
+ * @param int $value
+ * @return \Database\Query
+ */
+ public function take($value)
+ {
+ return $this->limit($value);
+ }
+
+ /**
+ * Execute a query for a single record by ID.
+ *
+ * @param int $id
+ * @param array $columns
+ * @return mixed
+ */
+ public function find($id, $columns = array('*'))
+ {
+ $uniqueIdentifier = isset($this->model) ? $this->model->getKeyName() : 'id';
+
+ return $this->where($uniqueIdentifier, '=', $id)->first($columns);
+ }
+
+ /**
+ * Pluck a single column's value from the first result of a query.
+ *
+ * @param string $column
+ * @return mixed
+ */
+ public function pluck($column)
+ {
+ $result = $this->first(array($column));
+
+ if (! is_null($result)) {
+ $result = (array) $result;
+ }
+
+ return (count($result) > 0) ? reset($result) : null;
+ }
+
+ /**
+ * Execute the query and get the first result.
+ *
+ * @param array $columns
+ * @return mixed
+ */
+ public function first($columns = array('*'))
+ {
+ $results = $this->take(1)->get($columns);
+
+ return (count($results) > 0) ? reset($results) : null;
+ }
+
+ /**
+ * Execute the query as a "SELECT" statement.
+ *
+ * @param array $columns
+ * @return array
+ */
+ public function get($columns = array('*'))
+ {
+ if (is_null($this->columns)) {
+ $this->columns = $columns;
+ }
+
+ $results = $this->runSelect();
+
+ return $results;
+ }
+
+ /**
+ * Run the query as a "SELECT" statement against the connection.
+ *
+ * @return array
+ */
+ protected function runSelect()
+ {
+ return $this->db->select($this->toSql(), $this->bindings);
+ }
+
+ /**
+ * Get the SQL representation of the query.
+ *
+ * @return string
+ */
+ public function toSql()
+ {
+ return $this->compileSelect($this);
+ }
+
+ /**
+ * Determine if any rows exist for the current query.
+ *
+ * @return bool
+ */
+ public function exists()
+ {
+ return $this->count() > 0;
+ }
+
+ /**
+ * Retrieve the "COUNT" result of the query.
+ *
+ * @param string $column
+ * @return int
+ */
+ public function count($column = '*')
+ {
+ return (int) $this->aggregate(__FUNCTION__, array($column));
+ }
+
+ /**
+ * Retrieve the minimum value of a given column.
+ *
+ * @param string $column
+ * @return mixed
+ */
+ public function min($column)
+ {
+ return $this->aggregate(__FUNCTION__, array($column));
+ }
+
+ /**
+ * Retrieve the maximum value of a given column.
+ *
+ * @param string $column
+ * @return mixed
+ */
+ public function max($column)
+ {
+ return $this->aggregate(__FUNCTION__, array($column));
+ }
+
+ /**
+ * Retrieve the sum of the values of a given column.
+ *
+ * @param string $column
+ * @return mixed
+ */
+ public function sum($column)
+ {
+ return $this->aggregate(__FUNCTION__, array($column));
+ }
+
+ /**
+ * Retrieve the average of the values of a given column.
+ *
+ * @param string $column
+ * @return mixed
+ */
+ public function avg($column)
+ {
+ return $this->aggregate(__FUNCTION__, array($column));
+ }
+
+ /**
+ * Execute an aggregate function on the database.
+ *
+ * @param string $function
+ * @param array $columns
+ * @return mixed
+ */
+ public function aggregate($function, $columns = array('*'))
+ {
+ $this->aggregate = compact('function', 'columns');
+
+ $results = $this->get($columns);
+
+ $this->columns = null;
+
+ $this->aggregate = null;
+
+ if (isset($results[0])) {
+ $result = $results[0];
+
+ $result = (array) $result;
+
+ return $result['aggregate'];
+ }
+ }
+
+ /**
+ * Insert a new record into the database.
+ *
+ * @param array $values
+ * @return bool
+ */
+ public function insert(array $values)
+ {
+ if (!is_array(reset($values))) {
+ $values = array($values);
+ } else {
+ foreach ($values as $key => $value) {
+ ksort($value);
+
+ $values[$key] = $value;
+ }
+ }
+
+ $bindings = array();
+
+ foreach ($values as $record) {
+ $bindings = array_merge($bindings, array_values($record));
+ }
+
+ $sql = $this->compileInsert($values);
+
+ $bindings = $this->cleanBindings($bindings);
+
+ return $this->db->insert($sql, $bindings);
+ }
+
+ /**
+ * Replace a new record into the database.
+ *
+ * @param array $values
+ * @return bool
+ */
+ public function replace(array $values)
+ {
+ if (!is_array(reset($values))) {
+ $values = array($values);
+ } else {
+ foreach ($values as $key => $value) {
+ ksort($value);
+
+ $values[$key] = $value;
+ }
+ }
+
+ $bindings = array();
+
+ foreach ($values as $record) {
+ $bindings = array_merge($bindings, array_values($record));
+ }
+
+ $sql = $this->compileReplace($values);
+
+ $bindings = $this->cleanBindings($bindings);
+
+ return $this->db->insert($sql, $bindings);
+ }
+
+ /**
+ * Insert a new record and get the value of the primary key.
+ *
+ * @param array $values
+ * @return int
+ */
+ public function insertGetId(array $values)
+ {
+ $sql = $this->compileInsert($values);
+
+ $values = $this->cleanBindings($values);
+
+ $this->db->insert($sql, $values);
+
+ $id = $this->db->getPdo()->lastInsertId();
+
+ return is_numeric($id) ? (int) $id : $id;
+ }
+
+ /**
+ * Update a record in the database.
+ *
+ * @param array $values
+ * @return int
+ */
+ public function update(array $values)
+ {
+ $bindings = array_values(array_merge($values, $this->bindings));
+
+ $sql = $this->compileUpdate($values);
+
+ return $this->db->update($sql, $this->cleanBindings($bindings));
+ }
+
+ /**
+ * Delete a record from the database.
+ *
+ * @param mixed $id
+ * @return int
+ */
+ public function delete($id = null)
+ {
+ $uniqueIdentifier = isset($this->model) ? $this->model->getKeyName() : 'id';
+
+ if (! is_null($id)) {
+ $this->where($uniqueIdentifier, '=', $id);
+ }
+
+ $sql = $this->compileDelete();
+
+ return $this->db->delete($sql, $this->bindings);
+ }
+
+ /**
+ * Run a truncate statement on the table.
+ *
+ * @return void
+ */
+ public function truncate()
+ {
+ foreach ($this->compileTruncate() as $sql => $bindings) {
+ $this->db->statement($sql, $bindings);
+ }
+ }
+
+ /**
+ * Get a new instance of the query builder.
+ *
+ * @return \Database\Query
+ */
+ public function newQuery()
+ {
+ return new Query($this->db);
+ }
+
+ /**
+ * Merge an array of bindings into our bindings.
+ *
+ * @param \Database\Query $query
+ * @return \Database\Query
+ */
+ public function mergeBindings(Query $query)
+ {
+ $this->bindings = array_values(array_merge($this->bindings, $query->bindings));
+
+ return $this;
+ }
+
+ /**
+ * Remove all of the expressions from a list of bindings.
+ *
+ * @param array $bindings
+ * @return array
+ */
+ protected function cleanBindings(array $bindings)
+ {
+ return array_values(array_filter($bindings, function($binding) {
+ return true;
+ }));
+ }
+
+ /**
+ * Set a model instance for the model being queried.
+ *
+ * @param \Database\Model|null $model
+ * @return \Database\Query
+ */
+ public function setModel($model)
+ {
+ $this->model = $model;
+
+ return $this;
+ }
+
+ /**
+ * Compile a select query into SQL.
+ *
+ * @param \Database\Query $query
+ * @return string
+ */
+ public function compileSelect(Query $query)
+ {
+ if (is_null($query->columns)) {
+ $query->columns = array('*');
+ }
+
+ return trim($this->concatenate($this->compileComponents($query)));
+ }
+
+ /**
+ * Compile the components necessary for a select clause.
+ *
+ * @param \Database\Query $query
+ * @return array
+ */
+ protected function compileComponents(Query $query)
+ {
+ $sql = array();
+
+ $selectComponents = array(
+ 'aggregate',
+ 'columns',
+ 'from',
+ 'joins',
+ 'wheres',
+ 'orders',
+ 'limit',
+ 'offset'
+ );
+
+ foreach ($selectComponents as $component) {
+ if (!is_null($query->$component)) {
+ $method = 'compile' .ucfirst($component);
+
+ $sql[$component] = $this->$method($query, $query->$component);
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Compile an aggregated select clause.
+ *
+ * @param \Database\Query $query
+ * @param array $aggregate
+ * @return string
+ */
+ protected function compileAggregate(Query $query, $aggregate)
+ {
+ $column = $this->columnize($aggregate['columns']);
+
+ if ($query->distinct && $column !== '*') {
+ $column = 'DISTINCT '.$column;
+ }
+
+ return 'SELECT ' .$aggregate['function'] .'(' .$column .') AS AGGREGATE';
+ }
+
+ /**
+ * Compile the "SELECT *" portion of the query.
+ *
+ * @param \Database\Query $query
+ * @param array $columns
+ * @return string
+ */
+ protected function compileColumns(Query $query, $columns)
+ {
+ if (is_null($query->aggregate)) {
+ $select = $query->distinct ? 'SELECT DISTINCT ' : 'SELECT ';
+
+ return $select .$this->columnize($columns);
+ }
+
+ return '';
+ }
+
+ /**
+ * Compile the "FROM" portion of the query.
+ *
+ * @param \Database\Query $query
+ * @param string $table
+ * @return string
+ */
+ protected function compileFrom(Query $query, $table)
+ {
+ return 'FROM '.$this->wrapTable($table);
+ }
+
+ /**
+ * Compile the "JOIN" portions of the query.
+ *
+ * @param \Database\Query $query
+ * @param array $joins
+ * @return string
+ */
+ protected function compileJoins(Query $query, $joins)
+ {
+ $sql = array();
+
+ foreach ($joins as $join) {
+ $table = $this->wrapTable($join->table);
+
+ $clauses = array();
+
+ foreach ($join->clauses as $clause) {
+ $clauses[] = $this->compileJoinConstraint($clause);
+ }
+
+ $clauses[0] = $this->removeLeadingBoolean($clauses[0]);
+
+ $clauses = implode(' ', $clauses);
+
+ $type = $join->type;
+
+ $sql[] = "$type JOIN $table ON $clauses";
+ }
+
+ return implode(' ', $sql);
+ }
+
+ /**
+ * Create a join clause constraint segment.
+ *
+ * @param array $clause
+ * @return string
+ */
+ protected function compileJoinConstraint(array $clause)
+ {
+ $first = $this->wrap($clause['first']);
+
+ $second = $clause['where'] ? '?' : $this->wrap($clause['second']);
+
+ return "{$clause['boolean']} $first {$clause['operator']} $second";
+ }
+
+ /**
+ * Compile the "WHERE" portions of the query.
+ *
+ * @param \Database\Query $query
+ * @return string
+ */
+ protected function compileWheres(Query $query)
+ {
+ $sql = array();
+
+ if (is_null($query->wheres)) {
+ return '';
+ }
+
+ foreach ($query->wheres as $where) {
+ $method = "compileWhere{$where['type']}";
+
+ $sql[] = $where['boolean'].' '.$this->$method($where);
+ }
+
+ if (count($sql) > 0) {
+ $sql = implode(' ', $sql);
+
+ return 'WHERE ' .preg_replace('/AND |OR /', '', $sql, 1);
+ }
+
+ return '';
+ }
+
+ /**
+ * Compile a nested where clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereNested($where)
+ {
+ $nested = $where['query'];
+
+ return '(' .substr($this->compileWheres($nested), 6) .')';
+ }
+
+ /**
+ * Compile a where condition with a sub-select.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereSub($where)
+ {
+ $select = $this->compileSelect($where['query']);
+
+ return $this->wrap($where['column']) .' ' .$where['operator'] ." ($select)";
+ }
+
+ /**
+ * Compile a basic where clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereBasic($where)
+ {
+ $value = $this->parameter($where['value']);
+
+ return $this->wrap($where['column']) .' ' .$where['operator'] .' ' .$value;
+ }
+
+ /**
+ * Compile a "BETWEEN" where clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereBetween($where)
+ {
+ $between = $where['not'] ? 'NOT BETWEEN' : 'BETWEEN';
+
+ return $this->wrap($where['column']) .' ' .$between .' ? AND ?';
+ }
+
+ /**
+ * Compile a "WHERE IN" clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereIn($where)
+ {
+ $values = $this->parameterize($where['values']);
+
+ return $this->wrap($where['column']) .' IN (' .$values .')';
+ }
+
+ /**
+ * Compile a "WHERE NOT IN" clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereNotIn($where)
+ {
+ $values = $this->parameterize($where['values']);
+
+ return $this->wrap($where['column']) .' NOT IN (' .$values .')';
+ }
+
+ /**
+ * Compile a where in sub-select clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereInSub($where)
+ {
+ $select = $this->compileSelect($where['query']);
+
+ return $this->wrap($where['column']) .' IN (' .$select .')';
+ }
+
+ /**
+ * Compile a where not in sub-select clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereNotInSub($where)
+ {
+ $select = $this->compileSelect($where['query']);
+
+ return $this->wrap($where['column']) .' NOT IN (' .$select .')';
+ }
+
+ /**
+ * Compile a "WHERE NULL" clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereNull($where)
+ {
+ return $this->wrap($where['column']) .' IS NULL';
+ }
+
+ /**
+ * Compile a "WHERE NOT NULL" clause.
+ *
+ * @param array $where
+ * @return string
+ */
+ protected function compileWhereNotNull($where)
+ {
+ return $this->wrap($where['column']) .' IS NOT NULL';
+ }
+
+ /**
+ * Compile the "ORDER BY" portions of the query.
+ *
+ * @param \Database\Query $query
+ * @param array $orders
+ * @return string
+ */
+ protected function compileOrders(Query $query, $orders)
+ {
+ $me = $this;
+
+ return 'ORDER BY ' .implode(', ', array_map(function($order) use ($me) {
+ if (isset($order['sql'])) {
+ return $order['sql'];
+ }
+
+ return $me->wrap($order['column']).' '.$order['direction'];
+ }, $orders));
+ }
+
+ /**
+ * Compile the "LIMIT" portions of the query.
+ *
+ * @param \Database\Query $query
+ * @param int $limit
+ * @return string
+ */
+ protected function compileLimit(Query $query, $limit)
+ {
+ return 'LIMIT ' .(int) $limit;
+ }
+
+ /**
+ * Compile the "OFFSET" portions of the query.
+ *
+ * @param \Database\Query $query
+ * @param int $offset
+ * @return string
+ */
+ protected function compileOffset(Query $query, $offset)
+ {
+ return 'OFFSET ' .(int) $offset;
+ }
+
+ /**
+ * Compile an insert statement into SQL.
+ *
+ * @param array $values
+ * @return string
+ */
+ public function compileInsert(array $values)
+ {
+ $table = $this->wrapTable($this->from);
+
+ if (! is_array(reset($values))) {
+ $values = array($values);
+ }
+
+ $columns = $this->columnize(array_keys(reset($values)));
+
+ $parameters = $this->parameterize(reset($values));
+
+ $value = array_fill(0, count($values), "($parameters)");
+
+ $parameters = implode(', ', $value);
+
+ return "INSERT INTO $table ($columns) VALUES $parameters";
+ }
+
+ /**
+ * Compile an replace statement into SQL.
+ *
+ * @param array $values
+ * @return string
+ */
+ public function compileReplace(array $values)
+ {
+ $table = $this->wrapTable($this->from);
+
+ if (! is_array(reset($values))) {
+ $values = array($values);
+ }
+
+ $columns = $this->columnize(array_keys(reset($values)));
+
+ $parameters = $this->parameterize(reset($values));
+
+ $value = array_fill(0, count($values), "($parameters)");
+
+ $parameters = implode(', ', $value);
+
+ return "REPLACE INTO $table ($columns) VALUES $parameters";
+ }
+
+ /**
+ * Compile an update statement into SQL.
+ *
+ * @param array $values
+ * @return string
+ */
+ public function compileUpdate($values)
+ {
+ $table = $this->wrapTable($this->from);
+
+ $columns = array();
+
+ foreach ($values as $key => $value) {
+ $columns[] = $this->wrap($key) .' = ' .$this->parameter($value);
+ }
+
+ $columns = implode(', ', $columns);
+
+ if (isset($this->joins)) {
+ $joins = ' ' .$this->compileJoins($this, $this->joins);
+ } else {
+ $joins = '';
+ }
+
+ $where = $this->compileWheres($this);
+
+ $sql = trim("UPDATE {$table}{$joins} SET $columns $where");
+
+ if (isset($this->orders)) {
+ $sql .= ' ' .$this->compileOrders($this, $this->orders);
+ }
+
+ if (isset($this->limit)) {
+ $sql .= ' ' .$this->compileLimit($this, $this->limit);
+ }
+
+ return rtrim($sql);
+ }
+
+ /**
+ * Compile a delete statement into SQL.
+ *
+ * @return string
+ */
+ public function compileDelete()
+ {
+ $table = $this->wrapTable($this->from);
+
+ $where = is_array($this->wheres) ? $this->compileWheres($this) : '';
+
+ $sql = trim("DELETE FROM $table ".$where);
+
+ if (isset($this->limit)) {
+ $sql .= ' '.$this->compileLimit($this, $this->limit);
+ }
+
+ return rtrim($sql);
+ }
+
+ /**
+ * Compile a truncate table statement into SQL.
+ *
+ * @param \Database\Query $query
+ * @return array
+ */
+ public function compileTruncate()
+ {
+ return array('TRUNCATE ' .$this->wrapTable($this->from) => array());
+ }
+
+ /**
+ * Wrap a table in keyword identifiers.
+ *
+ * @param string $table
+ * @return string
+ */
+ public function wrapTable($table)
+ {
+ return $this->wrap($this->db->getTablePrefix() .$table);
+ }
+
+ /**
+ * Wrap a value in keyword identifiers.
+ *
+ * @param string $value
+ * @return string
+ */
+ public function wrap($value)
+ {
+ if (strpos(strtolower($value), ' as ') !== false) {
+ $segments = explode(' ', $value);
+
+ return $this->wrap($segments[0]) .' AS ' .$this->wrap($segments[2]);
+ }
+
+ $wrapped = array();
+
+ $segments = explode('.', $value);
+
+ foreach ($segments as $key => $segment) {
+ if ($key == 0 && count($segments) > 1) {
+ $wrapped[] = $this->wrapTable($segment);
+ } else {
+ $wrapped[] = $this->wrapValue($segment);
+ }
+ }
+
+ return implode('.', $wrapped);
+ }
+
+ /**
+ * Wrap a single string in keyword identifiers.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function wrapValue($value)
+ {
+ return ($value !== '*') ? sprintf($this->wrapper, $value) : $value;
+ }
+
+ /**
+ * Concatenate an array of segments, removing empties.
+ *
+ * @param array $segments
+ * @return string
+ */
+ protected function concatenate($segments)
+ {
+ return implode(' ', array_filter($segments, function($value) {
+ return (string) ($value !== '');
+ }));
+ }
+
+ /**
+ * Remove the leading boolean from a statement.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function removeLeadingBoolean($value)
+ {
+ return preg_replace('/AND |OR /', '', $value, 1);
+ }
+
+ /**
+ * Create query parameter place-holders for an array.
+ *
+ * @param array $values
+ * @return string
+ */
+ public function parameterize(array $values)
+ {
+ return implode(', ', array_map(array($this, 'parameter'), $values));
+ }
+
+ /**
+ * Get the appropriate query parameter place-holder for a value.
+ *
+ * @param mixed $value
+ * @return string
+ */
+ public function parameter($value)
+ {
+ return '?';
+ }
+
+ /**
+ * Convert an array of column names into a delimited string.
+ *
+ * @param array $columns
+ * @return string
+ */
+ public function columnize(array $columns)
+ {
+ return implode(', ', array_map(array($this, 'wrap'), $columns));
+ }
+}
diff --git a/system/functions.php b/system/functions.php
index 2fa0487bea..81dc045c7b 100644
--- a/system/functions.php
+++ b/system/functions.php
@@ -67,6 +67,16 @@ function str_ends_with($haystack, $needle)
return (($needle === '') || (substr($haystack, - strlen($needle)) === $needle));
}
+/**
+ * Class name helper
+ * @param string $className
+ * @return string
+ */
+function class_basename($className)
+{
+ return basename(str_replace('\\', '/', $className));
+}
+
/**
* Determine if the given object has a toString method.
*