+ * @param string $query User-provided query to execute.
+ * @return string Contains the returned rows from the query.
+ */
+ public function rawAddPrefix($query){
+ $query = str_replace(PHP_EOL, null, $query);
+ $query = preg_replace('/\s+/', ' ', $query);
+ preg_match_all("/(from|into|update|join) [\\'\\´]?([a-zA-Z0-9_-]+)[\\'\\´]?/i", $query, $matches);
+ list($from_table, $from, $table) = $matches;
+
+ return str_replace($table[0], self::$prefix.$table[0], $query);
+ }
+
+ /**
+ * Execute raw SQL query.
+ *
+ * @param string $query User-provided query to execute.
+ * @param array $bindParams Variables array to bind to the SQL statement.
+ *
+ * @return array Contains the returned rows from the query.
+ * @throws Exception
+ */
+ public function rawQuery($query, $bindParams = null)
+ {
+ $query = $this->rawAddPrefix($query);
+ $params = array(''); // Create the empty 0 index
+ $this->_query = $query;
+ $stmt = $this->_prepareQuery();
+
+ if (is_array($bindParams) === true) {
+ foreach ($bindParams as $prop => $val) {
+ $params[0] .= $this->_determineType($val);
+ array_push($params, $bindParams[$prop]);
+ }
+
+ call_user_func_array(array($stmt, 'bind_param'), $this->refValues($params));
+ }
+
+ $stmt->execute();
+ $this->count = $stmt->affected_rows;
+ $this->_stmtError = $stmt->error;
+ $this->_stmtErrno = $stmt->errno;
+ $this->_lastQuery = $this->replacePlaceHolders($this->_query, $params);
+ $res = $this->_dynamicBindResults($stmt);
+ $this->reset();
+
+ return $res;
+ }
+
+ /**
+ * Helper function to execute raw SQL query and return only 1 row of results.
+ * Note that function do not add 'limit 1' to the query by itself
+ * Same idea as getOne()
+ *
+ * @param string $query User-provided query to execute.
+ * @param array $bindParams Variables array to bind to the SQL statement.
+ *
+ * @return array|null Contains the returned row from the query.
+ * @throws Exception
+ */
+ public function rawQueryOne($query, $bindParams = null)
+ {
+ $res = $this->rawQuery($query, $bindParams);
+ if (is_array($res) && isset($res[0])) {
+ return $res[0];
+ }
+
+ return null;
+ }
+
+ /**
+ * Helper function to execute raw SQL query and return only 1 column of results.
+ * If 'limit 1' will be found, then string will be returned instead of array
+ * Same idea as getValue()
+ *
+ * @param string $query User-provided query to execute.
+ * @param array $bindParams Variables array to bind to the SQL statement.
+ *
+ * @return mixed Contains the returned rows from the query.
+ * @throws Exception
+ */
+ public function rawQueryValue($query, $bindParams = null)
+ {
+ $res = $this->rawQuery($query, $bindParams);
+ if (!$res) {
+ return null;
+ }
+
+ $limit = preg_match('/limit\s+1;?$/i', $query);
+ $key = key($res[0]);
+ if (isset($res[0][$key]) && $limit == true) {
+ return $res[0][$key];
+ }
+
+ $newRes = Array();
+ for ($i = 0; $i < $this->count; $i++) {
+ $newRes[] = $res[$i][$key];
+ }
+ return $newRes;
+ }
+
+ /**
+ * A method to perform select query
+ *
+ * @param string $query Contains a user-provided select query.
+ * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
+ *
+ * @return array Contains the returned rows from the query.
+ * @throws Exception
+ */
+ public function query($query, $numRows = null)
+ {
+ $this->_query = $query;
+ $stmt = $this->_buildQuery($numRows);
+ $stmt->execute();
+ $this->_stmtError = $stmt->error;
+ $this->_stmtErrno = $stmt->errno;
+ $res = $this->_dynamicBindResults($stmt);
+ $this->reset();
+
+ return $res;
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) options for SQL queries.
+ *
+ * @uses $MySqliDb->setQueryOption('name');
+ *
+ * @param string|array $options The options name of the query.
+ *
+ * @throws Exception
+ * @return MysqliDb
+ */
+ public function setQueryOption($options)
+ {
+ $allowedOptions = Array('ALL', 'DISTINCT', 'DISTINCTROW', 'HIGH_PRIORITY', 'STRAIGHT_JOIN', 'SQL_SMALL_RESULT',
+ 'SQL_BIG_RESULT', 'SQL_BUFFER_RESULT', 'SQL_CACHE', 'SQL_NO_CACHE', 'SQL_CALC_FOUND_ROWS',
+ 'LOW_PRIORITY', 'IGNORE', 'QUICK', 'MYSQLI_NESTJOIN', 'FOR UPDATE', 'LOCK IN SHARE MODE');
+
+ if (!is_array($options)) {
+ $options = Array($options);
+ }
+
+ foreach ($options as $option) {
+ $option = strtoupper($option);
+ if (!in_array($option, $allowedOptions)) {
+ throw new Exception('Wrong query option: ' . $option);
+ }
+
+ if ($option == 'MYSQLI_NESTJOIN') {
+ $this->_nestJoin = true;
+ } elseif ($option == 'FOR UPDATE') {
+ $this->_forUpdate = true;
+ } elseif ($option == 'LOCK IN SHARE MODE') {
+ $this->_lockInShareMode = true;
+ } else {
+ $this->_queryOptions[] = $option;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Function to enable SQL_CALC_FOUND_ROWS in the get queries
+ *
+ * @return MysqliDb
+ * @throws Exception
+ */
+ public function withTotalCount()
+ {
+ $this->setQueryOption('SQL_CALC_FOUND_ROWS');
+ return $this;
+ }
+
+ /**
+ * A convenient SELECT * function.
+ *
+ * @param string $tableName The name of the database table to work with.
+ * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
+ * or only $count
+ * @param string $columns Desired columns
+ *
+ * @return array|MysqliDb Contains the returned rows from the select query.
+ * @throws Exception
+ */
+ public function get($tableName, $numRows = null, $columns = '*')
+ {
+ if (empty($columns)) {
+ $columns = '*';
+ }
+
+ $column = is_array($columns) ? implode(', ', $columns) : $columns;
+
+ if (strpos($tableName, '.') === false) {
+ $this->_tableName = self::$prefix . $tableName;
+ } else {
+ $this->_tableName = $tableName;
+ }
+
+ $this->_query = 'SELECT ' . implode(' ', $this->_queryOptions) . ' ' .
+ $column . " FROM " . $this->_tableName;
+ $stmt = $this->_buildQuery($numRows);
+
+ if ($this->isSubQuery) {
+ return $this;
+ }
+
+ $stmt->execute();
+ $this->_stmtError = $stmt->error;
+ $this->_stmtErrno = $stmt->errno;
+ $res = $this->_dynamicBindResults($stmt);
+ $this->reset();
+
+ return $res;
+ }
+
+ /**
+ * A convenient SELECT * function to get one record.
+ *
+ * @param string $tableName The name of the database table to work with.
+ * @param string $columns Desired columns
+ *
+ * @return array Contains the returned rows from the select query.
+ * @throws Exception
+ */
+ public function getOne($tableName, $columns = '*')
+ {
+ $res = $this->get($tableName, 1, $columns);
+
+ if ($res instanceof MysqliDb) {
+ return $res;
+ } elseif (is_array($res) && isset($res[0])) {
+ return $res[0];
+ } elseif ($res) {
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * A convenient SELECT COLUMN function to get a single column value from one row
+ *
+ * @param string $tableName The name of the database table to work with.
+ * @param string $column The desired column
+ * @param int $limit Limit of rows to select. Use null for unlimited..1 by default
+ *
+ * @return mixed Contains the value of a returned column / array of values
+ * @throws Exception
+ */
+ public function getValue($tableName, $column, $limit = 1)
+ {
+ $res = $this->ArrayBuilder()->get($tableName, $limit, "{$column} AS retval");
+
+ if (!$res) {
+ return null;
+ }
+
+ if ($limit == 1) {
+ if (isset($res[0]["retval"])) {
+ return $res[0]["retval"];
+ }
+ return null;
+ }
+
+ $newRes = Array();
+ for ($i = 0; $i < $this->count; $i++) {
+ $newRes[] = $res[$i]['retval'];
+ }
+ return $newRes;
+ }
+
+ /**
+ * Insert method to add new row
+ *
+ * @param string $tableName The name of the table.
+ * @param array $insertData Data containing information for inserting into the DB.
+ *
+ * @return bool Boolean indicating whether the insert query was completed successfully.
+ * @throws Exception
+ */
+ public function insert($tableName, $insertData)
+ {
+ return $this->_buildInsert($tableName, $insertData, 'INSERT');
+ }
+
+ /**
+ * Insert method to add several rows at once
+ *
+ * @param string $tableName The name of the table.
+ * @param array $multiInsertData Two-dimensional Data-array containing information for inserting into the DB.
+ * @param array $dataKeys Optional Table Key names, if not set in insertDataSet.
+ *
+ * @return bool|array Boolean indicating the insertion failed (false), else return id-array ([int])
+ * @throws Exception
+ */
+ public function insertMulti($tableName, array $multiInsertData, array $dataKeys = null)
+ {
+ // only auto-commit our inserts, if no transaction is currently running
+ $autoCommit = (isset($this->_transaction_in_progress) ? !$this->_transaction_in_progress : true);
+ $ids = array();
+
+ if($autoCommit) {
+ $this->startTransaction();
+ }
+
+ foreach ($multiInsertData as $insertData) {
+ if($dataKeys !== null) {
+ // apply column-names if given, else assume they're already given in the data
+ $insertData = array_combine($dataKeys, $insertData);
+ }
+
+ $id = $this->insert($tableName, $insertData);
+ if(!$id) {
+ if($autoCommit) {
+ $this->rollback();
+ }
+ return false;
+ }
+ $ids[] = $id;
+ }
+
+ if($autoCommit) {
+ $this->commit();
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Replace method to add new row
+ *
+ * @param string $tableName The name of the table.
+ * @param array $insertData Data containing information for inserting into the DB.
+ *
+ * @return bool Boolean indicating whether the insert query was completed successfully.
+ * @throws Exception
+ */
+ public function replace($tableName, $insertData)
+ {
+ return $this->_buildInsert($tableName, $insertData, 'REPLACE');
+ }
+
+ /**
+ * A convenient function that returns TRUE if exists at least an element that
+ * satisfy the where condition specified calling the "where" method before this one.
+ *
+ * @param string $tableName The name of the database table to work with.
+ *
+ * @return bool
+ * @throws Exception
+ */
+ public function has($tableName)
+ {
+ $this->getOne($tableName, '1');
+ return $this->count >= 1;
+ }
+
+ /**
+ * Update query. Be sure to first call the "where" method.
+ *
+ * @param string $tableName The name of the database table to work with.
+ * @param array $tableData Array of data to update the desired row.
+ * @param int $numRows Limit on the number of rows that can be updated.
+ *
+ * @return bool
+ * @throws Exception
+ */
+ public function update($tableName, $tableData, $numRows = null)
+ {
+ if ($this->isSubQuery) {
+ return;
+ }
+
+ $this->_query = "UPDATE " . self::$prefix . $tableName;
+
+ $stmt = $this->_buildQuery($numRows, $tableData);
+ $status = $stmt->execute();
+ $this->reset();
+ $this->_stmtError = $stmt->error;
+ $this->_stmtErrno = $stmt->errno;
+ $this->count = $stmt->affected_rows;
+
+ return $status;
+ }
+
+ /**
+ * Delete query. Call the "where" method first.
+ *
+ * @param string $tableName The name of the database table to work with.
+ * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
+ * or only $count
+ *
+ * @return bool Indicates success. 0 or 1.
+ * @throws Exception
+ */
+ public function delete($tableName, $numRows = null)
+ {
+ if ($this->isSubQuery) {
+ return;
+ }
+
+ $table = self::$prefix . $tableName;
+
+ if (count($this->_join)) {
+ $this->_query = "DELETE " . preg_replace('/.* (.*)/', '$1', $table) . " FROM " . $table;
+ } else {
+ $this->_query = "DELETE FROM " . $table;
+ }
+
+ $stmt = $this->_buildQuery($numRows);
+ $stmt->execute();
+ $this->_stmtError = $stmt->error;
+ $this->_stmtErrno = $stmt->errno;
+ $this->count = $stmt->affected_rows;
+ $this->reset();
+
+ return ($stmt->affected_rows > -1); // -1 indicates that the query returned an error
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) AND WHERE statements for SQL queries.
+ *
+ * @uses $MySqliDb->where('id', 7)->where('title', 'MyTitle');
+ *
+ * @param string $whereProp The name of the database field.
+ * @param mixed $whereValue The value of the database field.
+ * @param string $operator Comparison operator. Default is =
+ * @param string $cond Condition of where statement (OR, AND)
+ *
+ * @return MysqliDb
+ */
+ public function where($whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
+ {
+ if (count($this->_where) == 0) {
+ $cond = '';
+ }
+
+ $this->_where[] = array($cond, $whereProp, $operator, $whereValue);
+ return $this;
+ }
+
+ /**
+ * This function store update column's name and column name of the
+ * autoincrement column
+ *
+ * @param array $updateColumns Variable with values
+ * @param string $lastInsertId Variable value
+ *
+ * @return MysqliDb
+ */
+ public function onDuplicate($updateColumns, $lastInsertId = null)
+ {
+ $this->_lastInsertId = $lastInsertId;
+ $this->_updateColumns = $updateColumns;
+ return $this;
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) OR WHERE statements for SQL queries.
+ *
+ * @uses $MySqliDb->orWhere('id', 7)->orWhere('title', 'MyTitle');
+ *
+ * @param string $whereProp The name of the database field.
+ * @param mixed $whereValue The value of the database field.
+ * @param string $operator Comparison operator. Default is =
+ *
+ * @return MysqliDb
+ */
+ public function orWhere($whereProp, $whereValue = 'DBNULL', $operator = '=')
+ {
+ return $this->where($whereProp, $whereValue, $operator, 'OR');
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) AND HAVING statements for SQL queries.
+ *
+ * @uses $MySqliDb->having('SUM(tags) > 10')
+ *
+ * @param string $havingProp The name of the database field.
+ * @param mixed $havingValue The value of the database field.
+ * @param string $operator Comparison operator. Default is =
+ *
+ * @param string $cond
+ *
+ * @return MysqliDb
+ */
+
+ public function having($havingProp, $havingValue = 'DBNULL', $operator = '=', $cond = 'AND')
+ {
+ // forkaround for an old operation api
+ if (is_array($havingValue) && ($key = key($havingValue)) != "0") {
+ $operator = $key;
+ $havingValue = $havingValue[$key];
+ }
+
+ if (count($this->_having) == 0) {
+ $cond = '';
+ }
+
+ $this->_having[] = array($cond, $havingProp, $operator, $havingValue);
+ return $this;
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) OR HAVING statements for SQL queries.
+ *
+ * @uses $MySqliDb->orHaving('SUM(tags) > 10')
+ *
+ * @param string $havingProp The name of the database field.
+ * @param mixed $havingValue The value of the database field.
+ * @param string $operator Comparison operator. Default is =
+ *
+ * @return MysqliDb
+ */
+ public function orHaving($havingProp, $havingValue = null, $operator = null)
+ {
+ return $this->having($havingProp, $havingValue, $operator, 'OR');
+ }
+
+ /**
+ * This method allows you to concatenate joins for the final SQL statement.
+ *
+ * @uses $MySqliDb->join('table1', 'field1 <> field2', 'LEFT')
+ *
+ * @param string $joinTable The name of the table.
+ * @param string $joinCondition the condition.
+ * @param string $joinType 'LEFT', 'INNER' etc.
+ *
+ * @throws Exception
+ * @return MysqliDb
+ */
+ public function join($joinTable, $joinCondition, $joinType = '')
+ {
+ $allowedTypes = array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER', 'NATURAL');
+ $joinType = strtoupper(trim($joinType));
+
+ if ($joinType && !in_array($joinType, $allowedTypes)) {
+ throw new Exception('Wrong JOIN type: ' . $joinType);
+ }
+
+ if (!is_object($joinTable)) {
+ $joinTable = self::$prefix . $joinTable;
+ }
+
+ $this->_join[] = Array($joinType, $joinTable, $joinCondition);
+
+ return $this;
+ }
+
+
+ /**
+ * This is a basic method which allows you to import raw .CSV data into a table
+ * Please check out http://dev.mysql.com/doc/refman/5.7/en/load-data.html for a valid .csv file.
+ *
+ * @author Jonas Barascu (Noneatme)
+ *
+ * @param string $importTable The database table where the data will be imported into.
+ * @param string $importFile The file to be imported. Please use double backslashes \\ and make sure you
+ * @param string $importSettings An Array defining the import settings as described in the README.md
+ *
+ * @return boolean
+ * @throws Exception
+ */
+ public function loadData($importTable, $importFile, $importSettings = null)
+ {
+ // We have to check if the file exists
+ if (!file_exists($importFile)) {
+ // Throw an exception
+ throw new Exception("importCSV -> importFile " . $importFile . " does not exists!");
+ }
+
+ // Define the default values
+ // We will merge it later
+ $settings = Array("fieldChar" => ';', "lineChar" => PHP_EOL, "linesToIgnore" => 1);
+
+ // Check the import settings
+ if (gettype($importSettings) == "array") {
+ // Merge the default array with the custom one
+ $settings = array_merge($settings, $importSettings);
+ }
+
+ // Add the prefix to the import table
+ $table = self::$prefix . $importTable;
+
+ // Add 1 more slash to every slash so maria will interpret it as a path
+ $importFile = str_replace("\\", "\\\\", $importFile);
+
+ // Switch between LOAD DATA and LOAD DATA LOCAL
+ $loadDataLocal = isset($settings["loadDataLocal"]) ? 'LOCAL' : '';
+
+ // Build SQL Syntax
+ $sqlSyntax = sprintf('LOAD DATA %s INFILE \'%s\' INTO TABLE %s',
+ $loadDataLocal, $importFile, $table);
+
+ // FIELDS
+ $sqlSyntax .= sprintf(' FIELDS TERMINATED BY \'%s\'', $settings["fieldChar"]);
+ if (isset($settings["fieldEnclosure"])) {
+ $sqlSyntax .= sprintf(' ENCLOSED BY \'%s\'', $settings["fieldEnclosure"]);
+ }
+
+ // LINES
+ $sqlSyntax .= sprintf(' LINES TERMINATED BY \'%s\'', $settings["lineChar"]);
+ if (isset($settings["lineStarting"])) {
+ $sqlSyntax .= sprintf(' STARTING BY \'%s\'', $settings["lineStarting"]);
+ }
+
+ // IGNORE LINES
+ $sqlSyntax .= sprintf(' IGNORE %d LINES', $settings["linesToIgnore"]);
+
+ // Execute the query unprepared because LOAD DATA only works with unprepared statements.
+ $result = $this->queryUnprepared($sqlSyntax);
+
+ // Are there rows modified?
+ // Let the user know if the import failed / succeeded
+ return (bool) $result;
+ }
+
+ /**
+ * This method is useful for importing XML files into a specific table.
+ * Check out the LOAD XML syntax for your MySQL server.
+ *
+ * @author Jonas Barascu
+ *
+ * @param string $importTable The table in which the data will be imported to.
+ * @param string $importFile The file which contains the .XML data.
+ * @param string $importSettings An Array defining the import settings as described in the README.md
+ *
+ * @return boolean Returns true if the import succeeded, false if it failed.
+ * @throws Exception
+ */
+ public function loadXml($importTable, $importFile, $importSettings = null)
+ {
+ // We have to check if the file exists
+ if(!file_exists($importFile)) {
+ // Does not exists
+ throw new Exception("loadXml: Import file does not exists");
+ return;
+ }
+
+ // Create default values
+ $settings = Array("linesToIgnore" => 0);
+
+ // Check the import settings
+ if(gettype($importSettings) == "array") {
+ $settings = array_merge($settings, $importSettings);
+ }
+
+ // Add the prefix to the import table
+ $table = self::$prefix . $importTable;
+
+ // Add 1 more slash to every slash so maria will interpret it as a path
+ $importFile = str_replace("\\", "\\\\", $importFile);
+
+ // Build SQL Syntax
+ $sqlSyntax = sprintf('LOAD XML INFILE \'%s\' INTO TABLE %s',
+ $importFile, $table);
+
+ // FIELDS
+ if(isset($settings["rowTag"])) {
+ $sqlSyntax .= sprintf(' ROWS IDENTIFIED BY \'%s\'', $settings["rowTag"]);
+ }
+
+ // IGNORE LINES
+ $sqlSyntax .= sprintf(' IGNORE %d LINES', $settings["linesToIgnore"]);
+
+ // Exceute the query unprepared because LOAD XML only works with unprepared statements.
+ $result = $this->queryUnprepared($sqlSyntax);
+
+ // Are there rows modified?
+ // Let the user know if the import failed / succeeded
+ return (bool) $result;
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) ORDER BY statements for SQL queries.
+ *
+ * @uses $MySqliDb->orderBy('id', 'desc')->orderBy('name', 'desc', '^[a-z]')->orderBy('name', 'desc');
+ *
+ * @param string $orderByField The name of the database field.
+ * @param string $orderbyDirection
+ * @param mixed $customFieldsOrRegExp Array with fieldset for ORDER BY FIELD() ordering or string with regular expression for ORDER BY REGEXP ordering
+ *
+ * @return MysqliDb
+ * @throws Exception
+ */
+ public function orderBy($orderByField, $orderbyDirection = "DESC", $customFieldsOrRegExp = null)
+ {
+ $allowedDirection = Array("ASC", "DESC");
+ $orderbyDirection = strtoupper(trim($orderbyDirection));
+ $orderByField = preg_replace("/[^ -a-z0-9\.\(\),_`\*\'\"]+/i", '', $orderByField);
+
+ // Add table prefix to orderByField if needed.
+ //FIXME: We are adding prefix only if table is enclosed into `` to distinguish aliases
+ // from table names
+ $orderByField = preg_replace('/(\`)([`a-zA-Z0-9_]*\.)/', '\1' . self::$prefix . '\2', $orderByField);
+
+
+ if (empty($orderbyDirection) || !in_array($orderbyDirection, $allowedDirection)) {
+ throw new Exception('Wrong order direction: ' . $orderbyDirection);
+ }
+
+ if (is_array($customFieldsOrRegExp)) {
+ foreach ($customFieldsOrRegExp as $key => $value) {
+ $customFieldsOrRegExp[$key] = preg_replace("/[^\x80-\xff-a-z0-9\.\(\),_` ]+/i", '', $value);
+ }
+ $orderByField = 'FIELD (' . $orderByField . ', "' . implode('","', $customFieldsOrRegExp) . '")';
+ }elseif(is_string($customFieldsOrRegExp)){
+ $orderByField = $orderByField . " REGEXP '" . $customFieldsOrRegExp . "'";
+ }elseif($customFieldsOrRegExp !== null){
+ throw new Exception('Wrong custom field or Regular Expression: ' . $customFieldsOrRegExp);
+ }
+
+ $this->_orderBy[$orderByField] = $orderbyDirection;
+ return $this;
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) GROUP BY statements for SQL queries.
+ *
+ * @uses $MySqliDb->groupBy('name');
+ *
+ * @param string $groupByField The name of the database field.
+ *
+ * @return MysqliDb
+ */
+ public function groupBy($groupByField)
+ {
+ $groupByField = preg_replace("/[^-a-z0-9\.\(\),_\* <>=!]+/i", '', $groupByField);
+
+ $this->_groupBy[] = $groupByField;
+ return $this;
+ }
+
+
+ /**
+ * This method sets the current table lock method.
+ *
+ * @author Jonas Barascu
+ *
+ * @param string $method The table lock method. Can be READ or WRITE.
+ *
+ * @throws Exception
+ * @return MysqliDb
+ */
+ public function setLockMethod($method)
+ {
+ // Switch the uppercase string
+ switch(strtoupper($method)) {
+ // Is it READ or WRITE?
+ case "READ" || "WRITE":
+ // Succeed
+ $this->_tableLockMethod = $method;
+ break;
+ default:
+ // Else throw an exception
+ throw new Exception("Bad lock type: Can be either READ or WRITE");
+ break;
+ }
+ return $this;
+ }
+
+ /**
+ * Locks a table for R/W action.
+ *
+ * @author Jonas Barascu
+ *
+ * @param string|array $table The table to be locked. Can be a table or a view.
+ *
+ * @return bool if succeeded;
+ * @throws Exception
+ */
+ public function lock($table)
+ {
+ // Main Query
+ $this->_query = "LOCK TABLES";
+
+ // Is the table an array?
+ if(gettype($table) == "array") {
+ // Loop trough it and attach it to the query
+ foreach($table as $key => $value) {
+ if(gettype($value) == "string") {
+ if($key > 0) {
+ $this->_query .= ",";
+ }
+ $this->_query .= " ".self::$prefix.$value." ".$this->_tableLockMethod;
+ }
+ }
+ }
+ else{
+ // Build the table prefix
+ $table = self::$prefix . $table;
+
+ // Build the query
+ $this->_query = "LOCK TABLES ".$table." ".$this->_tableLockMethod;
+ }
+
+ // Execute the query unprepared because LOCK only works with unprepared statements.
+ $result = $this->queryUnprepared($this->_query);
+ $errno = $this->mysqli()->errno;
+
+ // Reset the query
+ $this->reset();
+
+ // Are there rows modified?
+ if($result) {
+ // Return true
+ // We can't return ourself because if one table gets locked, all other ones get unlocked!
+ return true;
+ }
+ // Something went wrong
+ else {
+ throw new Exception("Locking of table ".$table." failed", $errno);
+ }
+
+ // Return the success value
+ return false;
+ }
+
+ /**
+ * Unlocks all tables in a database.
+ * Also commits transactions.
+ *
+ * @author Jonas Barascu
+ * @return MysqliDb
+ * @throws Exception
+ */
+ public function unlock()
+ {
+ // Build the query
+ $this->_query = "UNLOCK TABLES";
+
+ // Execute the query unprepared because UNLOCK and LOCK only works with unprepared statements.
+ $result = $this->queryUnprepared($this->_query);
+ $errno = $this->mysqli()->errno;
+
+ // Reset the query
+ $this->reset();
+
+ // Are there rows modified?
+ if($result) {
+ // return self
+ return $this;
+ }
+ // Something went wrong
+ else {
+ throw new Exception("Unlocking of tables failed", $errno);
+ }
+
+
+ // Return self
+ return $this;
+ }
+
+
+ /**
+ * This methods returns the ID of the last inserted item
+ *
+ * @return int The last inserted item ID.
+ * @throws Exception
+ */
+ public function getInsertId()
+ {
+ return $this->mysqli()->insert_id;
+ }
+
+ /**
+ * Escape harmful characters which might affect a query.
+ *
+ * @param string $str The string to escape.
+ *
+ * @return string The escaped string.
+ * @throws Exception
+ */
+ public function escape($str)
+ {
+ return $this->mysqli()->real_escape_string($str);
+ }
+
+ /**
+ * Method to call mysqli->ping() to keep unused connections open on
+ * long-running scripts, or to reconnect timed out connections (if php.ini has
+ * global mysqli.reconnect set to true). Can't do this directly using object
+ * since _mysqli is protected.
+ *
+ * @return bool True if connection is up
+ * @throws Exception
+ */
+ public function ping()
+ {
+ return $this->mysqli()->ping();
+ }
+
+ /**
+ * This method is needed for prepared statements. They require
+ * the data type of the field to be bound with "i" s", etc.
+ * This function takes the input, determines what type it is,
+ * and then updates the param_type.
+ *
+ * @param mixed $item Input to determine the type.
+ *
+ * @return string The joined parameter types.
+ */
+ protected function _determineType($item)
+ {
+ switch (gettype($item)) {
+ case 'NULL':
+ case 'string':
+ return 's';
+ break;
+
+ case 'boolean':
+ case 'integer':
+ return 'i';
+ break;
+
+ case 'blob':
+ return 'b';
+ break;
+
+ case 'double':
+ return 'd';
+ break;
+ }
+ return '';
+ }
+
+ /**
+ * Helper function to add variables into bind parameters array
+ *
+ * @param string Variable value
+ */
+ protected function _bindParam($value)
+ {
+ $this->_bindParams[0] .= $this->_determineType($value);
+ array_push($this->_bindParams, $value);
+ }
+
+ /**
+ * Helper function to add variables into bind parameters array in bulk
+ *
+ * @param array $values Variable with values
+ */
+ protected function _bindParams($values)
+ {
+ foreach ($values as $value) {
+ $this->_bindParam($value);
+ }
+ }
+
+ /**
+ * Helper function to add variables into bind parameters array and will return
+ * its SQL part of the query according to operator in ' $operator ?' or
+ * ' $operator ($subquery) ' formats
+ *
+ * @param string $operator
+ * @param mixed $value Variable with values
+ *
+ * @return string
+ */
+ protected function _buildPair($operator, $value)
+ {
+ if (!is_object($value)) {
+ $this->_bindParam($value);
+ return ' ' . $operator . ' ? ';
+ }
+
+ $subQuery = $value->getSubQuery();
+ $this->_bindParams($subQuery['params']);
+
+ return " " . $operator . " (" . $subQuery['query'] . ") " . $subQuery['alias'];
+ }
+
+ /**
+ * Internal function to build and execute INSERT/REPLACE calls
+ *
+ * @param string $tableName The name of the table.
+ * @param array $insertData Data containing information for inserting into the DB.
+ * @param string $operation Type of operation (INSERT, REPLACE)
+ *
+ * @return bool Boolean indicating whether the insert query was completed successfully.
+ * @throws Exception
+ */
+ private function _buildInsert($tableName, $insertData, $operation)
+ {
+ if ($this->isSubQuery) {
+ return;
+ }
+
+ $this->_query = $operation . " " . implode(' ', $this->_queryOptions) . " INTO " . self::$prefix . $tableName;
+ $stmt = $this->_buildQuery(null, $insertData);
+ $status = $stmt->execute();
+ $this->_stmtError = $stmt->error;
+ $this->_stmtErrno = $stmt->errno;
+ $haveOnDuplicate = !empty ($this->_updateColumns);
+ $this->reset();
+ $this->count = $stmt->affected_rows;
+
+ if ($stmt->affected_rows < 1) {
+ // in case of onDuplicate() usage, if no rows were inserted
+ if ($status && $haveOnDuplicate) {
+ return true;
+ }
+ return false;
+ }
+
+ if ($stmt->insert_id > 0) {
+ return $stmt->insert_id;
+ }
+
+ return true;
+ }
+
+ /**
+ * Abstraction method that will compile the WHERE statement,
+ * any passed update data, and the desired rows.
+ * It then builds the SQL query.
+ *
+ * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
+ * or only $count
+ * @param array $tableData Should contain an array of data for updating the database.
+ *
+ * @return mysqli_stmt|bool Returns the $stmt object.
+ * @throws Exception
+ */
+ protected function _buildQuery($numRows = null, $tableData = null)
+ {
+ // $this->_buildJoinOld();
+ $this->_buildJoin();
+ $this->_buildInsertQuery($tableData);
+ $this->_buildCondition('WHERE', $this->_where);
+ $this->_buildGroupBy();
+ $this->_buildCondition('HAVING', $this->_having);
+ $this->_buildOrderBy();
+ $this->_buildLimit($numRows);
+ $this->_buildOnDuplicate($tableData);
+
+ if ($this->_forUpdate) {
+ $this->_query .= ' FOR UPDATE';
+ }
+ if ($this->_lockInShareMode) {
+ $this->_query .= ' LOCK IN SHARE MODE';
+ }
+
+ $this->_lastQuery = $this->replacePlaceHolders($this->_query, $this->_bindParams);
+
+ if ($this->isSubQuery) {
+ return;
+ }
+
+ // Prepare query
+ $stmt = $this->_prepareQuery();
+
+ // Bind parameters to statement if any
+ if (count($this->_bindParams) > 1) {
+ call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
+ }
+
+ return $stmt;
+ }
+
+ /**
+ * This helper method takes care of prepared statements' "bind_result method
+ * , when the number of variables to pass is unknown.
+ *
+ * @param mysqli_stmt $stmt Equal to the prepared statement object.
+ *
+ * @return array|string The results of the SQL fetch.
+ * @throws Exception
+ */
+ protected function _dynamicBindResults(mysqli_stmt $stmt)
+ {
+ $parameters = array();
+ $results = array();
+ /**
+ * @see http://php.net/manual/en/mysqli-result.fetch-fields.php
+ */
+ $mysqlLongType = 252;
+ $shouldStoreResult = false;
+
+ $meta = $stmt->result_metadata();
+
+ // if $meta is false yet sqlstate is true, there's no sql error but the query is
+ // most likely an update/insert/delete which doesn't produce any results
+ if (!$meta && $stmt->sqlstate)
+ return array();
+
+ $row = array();
+ while ($field = $meta->fetch_field()) {
+ if ($field->type == $mysqlLongType) {
+ $shouldStoreResult = true;
+ }
+
+ if ($this->_nestJoin && $field->table != $this->_tableName) {
+ $field->table = substr($field->table, strlen(self::$prefix));
+ $row[$field->table][$field->name] = null;
+ $parameters[] = & $row[$field->table][$field->name];
+ } else {
+ $row[$field->name] = null;
+ $parameters[] = & $row[$field->name];
+ }
+ }
+
+ // avoid out of memory bug in php 5.2 and 5.3. Mysqli allocates lot of memory for long*
+ // and blob* types. So to avoid out of memory issues store_result is used
+ // https://github.com/joshcam/PHP-MySQLi-Database-Class/pull/119
+ if ($shouldStoreResult) {
+ $stmt->store_result();
+ }
+
+ call_user_func_array(array($stmt, 'bind_result'), $parameters);
+
+ $this->totalCount = 0;
+ $this->count = 0;
+
+ while ($stmt->fetch()) {
+ if ($this->returnType == 'object') {
+ $result = new stdClass ();
+ foreach ($row as $key => $val) {
+ if (is_array($val)) {
+ $result->$key = new stdClass ();
+ foreach ($val as $k => $v) {
+ $result->$key->$k = $v;
+ }
+ } else {
+ $result->$key = $val;
+ }
+ }
+ } else {
+ $result = array();
+ foreach ($row as $key => $val) {
+ if (is_array($val)) {
+ foreach ($val as $k => $v) {
+ $result[$key][$k] = $v;
+ }
+ } else {
+ $result[$key] = $val;
+ }
+ }
+ }
+ $this->count++;
+ if ($this->_mapKey) {
+ $results[$row[$this->_mapKey]] = count($row) > 2 ? $result : end($result);
+ } else {
+ array_push($results, $result);
+ }
+ }
+
+ if ($shouldStoreResult) {
+ $stmt->free_result();
+ }
+
+ $stmt->close();
+
+ // stored procedures sometimes can return more then 1 resultset
+ if ($this->mysqli()->more_results()) {
+ $this->mysqli()->next_result();
+ }
+
+ if (in_array('SQL_CALC_FOUND_ROWS', $this->_queryOptions)) {
+ $stmt = $this->mysqli()->query('SELECT FOUND_ROWS()');
+ $totalCount = $stmt->fetch_row();
+ $this->totalCount = $totalCount[0];
+ }
+
+ if ($this->returnType == 'json') {
+ return json_encode($results);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Abstraction method that will build an JOIN part of the query
+ *
+ * @return void
+ */
+ protected function _buildJoinOld()
+ {
+ if (empty($this->_join)) {
+ return;
+ }
+
+ foreach ($this->_join as $data) {
+ list ($joinType, $joinTable, $joinCondition) = $data;
+
+ if (is_object($joinTable)) {
+ $joinStr = $this->_buildPair("", $joinTable);
+ } else {
+ $joinStr = $joinTable;
+ }
+
+ $this->_query .= " " . $joinType . " JOIN " . $joinStr .
+ (false !== stripos($joinCondition, 'using') ? " " : " on ")
+ . $joinCondition;
+ }
+ }
+
+ /**
+ * Insert/Update query helper
+ *
+ * @param array $tableData
+ * @param array $tableColumns
+ * @param bool $isInsert INSERT operation flag
+ *
+ * @throws Exception
+ */
+ public function _buildDataPairs($tableData, $tableColumns, $isInsert)
+ {
+ foreach ($tableColumns as $column) {
+ $value = $tableData[$column];
+
+ if (!$isInsert) {
+ if(strpos($column,'.')===false) {
+ $this->_query .= "`" . $column . "` = ";
+ } else {
+ $this->_query .= str_replace('.','.`',$column) . "` = ";
+ }
+ }
+
+ // Subquery value
+ if ($value instanceof MysqliDb) {
+ $this->_query .= $this->_buildPair("", $value) . ", ";
+ continue;
+ }
+
+ // Simple value
+ if (!is_array($value)) {
+ $this->_bindParam($value);
+ $this->_query .= '?, ';
+ continue;
+ }
+
+ // Function value
+ $key = key($value);
+ $val = $value[$key];
+ switch ($key) {
+ case '[I]':
+ $this->_query .= $column . $val . ", ";
+ break;
+ case '[F]':
+ $this->_query .= $val[0] . ", ";
+ if (!empty($val[1])) {
+ $this->_bindParams($val[1]);
+ }
+ break;
+ case '[N]':
+ if ($val == null) {
+ $this->_query .= "!" . $column . ", ";
+ } else {
+ $this->_query .= "!" . $val . ", ";
+ }
+ break;
+ default:
+ throw new Exception("Wrong operation");
+ }
+ }
+ $this->_query = rtrim($this->_query, ', ');
+ }
+
+ /**
+ * Helper function to add variables into the query statement
+ *
+ * @param array $tableData Variable with values
+ *
+ * @throws Exception
+ */
+ protected function _buildOnDuplicate($tableData)
+ {
+ if (is_array($this->_updateColumns) && !empty($this->_updateColumns)) {
+ $this->_query .= " ON DUPLICATE KEY UPDATE ";
+ if ($this->_lastInsertId) {
+ $this->_query .= $this->_lastInsertId . "=LAST_INSERT_ID (" . $this->_lastInsertId . "), ";
+ }
+
+ foreach ($this->_updateColumns as $key => $val) {
+ // skip all params without a value
+ if (is_numeric($key)) {
+ $this->_updateColumns[$val] = '';
+ unset($this->_updateColumns[$key]);
+ } else {
+ $tableData[$key] = $val;
+ }
+ }
+ $this->_buildDataPairs($tableData, array_keys($this->_updateColumns), false);
+ }
+ }
+
+ /**
+ * Abstraction method that will build an INSERT or UPDATE part of the query
+ *
+ * @param array $tableData
+ *
+ * @throws Exception
+ */
+ protected function _buildInsertQuery($tableData)
+ {
+ if (!is_array($tableData)) {
+ return;
+ }
+
+ $isInsert = preg_match('/^[INSERT|REPLACE]/', $this->_query);
+ $dataColumns = array_keys($tableData);
+ if ($isInsert) {
+ if (isset ($dataColumns[0]))
+ $this->_query .= ' (`' . implode('`, `', $dataColumns) . '`) ';
+ $this->_query .= ' VALUES (';
+ } else {
+ $this->_query .= " SET ";
+ }
+
+ $this->_buildDataPairs($tableData, $dataColumns, $isInsert);
+
+ if ($isInsert) {
+ $this->_query .= ')';
+ }
+ }
+
+ /**
+ * Abstraction method that will build the part of the WHERE conditions
+ *
+ * @param string $operator
+ * @param array $conditions
+ */
+ protected function _buildCondition($operator, &$conditions)
+ {
+ if (empty($conditions)) {
+ return;
+ }
+
+ //Prepare the where portion of the query
+ $this->_query .= ' ' . $operator;
+
+ foreach ($conditions as $cond) {
+ list ($concat, $varName, $operator, $val) = $cond;
+ $this->_query .= " " . $concat . " " . $varName;
+
+ switch (strtolower($operator)) {
+ case 'not in':
+ case 'in':
+ $comparison = ' ' . $operator . ' (';
+ if (is_object($val)) {
+ $comparison .= $this->_buildPair("", $val);
+ } else {
+ foreach ($val as $v) {
+ $comparison .= ' ?,';
+ $this->_bindParam($v);
+ }
+ }
+ $this->_query .= rtrim($comparison, ',') . ' ) ';
+ break;
+ case 'not between':
+ case 'between':
+ $this->_query .= " $operator ? AND ? ";
+ $this->_bindParams($val);
+ break;
+ case 'not exists':
+ case 'exists':
+ $this->_query.= $operator . $this->_buildPair("", $val);
+ break;
+ default:
+ if (is_array($val)) {
+ $this->_bindParams($val);
+ } elseif ($val === null) {
+ $this->_query .= ' ' . $operator . " NULL";
+ } elseif ($val != 'DBNULL' || $val == '0') {
+ $this->_query .= $this->_buildPair($operator, $val);
+ }
+ }
+ }
+ }
+
+ /**
+ * Abstraction method that will build the GROUP BY part of the WHERE statement
+ *
+ * @return void
+ */
+ protected function _buildGroupBy()
+ {
+ if (empty($this->_groupBy)) {
+ return;
+ }
+
+ $this->_query .= " GROUP BY ";
+
+ foreach ($this->_groupBy as $key => $value) {
+ $this->_query .= $value . ", ";
+ }
+
+ $this->_query = rtrim($this->_query, ', ') . " ";
+ }
+
+ /**
+ * Abstraction method that will build the LIMIT part of the WHERE statement
+ *
+ * @return void
+ */
+ protected function _buildOrderBy()
+ {
+ if (empty($this->_orderBy)) {
+ return;
+ }
+
+ $this->_query .= " ORDER BY ";
+ foreach ($this->_orderBy as $prop => $value) {
+ if (strtolower(str_replace(" ", "", $prop)) == 'rand()') {
+ $this->_query .= "rand(), ";
+ } else {
+ $this->_query .= $prop . " " . $value . ", ";
+ }
+ }
+
+ $this->_query = rtrim($this->_query, ', ') . " ";
+ }
+
+ /**
+ * Abstraction method that will build the LIMIT part of the WHERE statement
+ *
+ * @param int|array $numRows Array to define SQL limit in format Array ($offset, $count)
+ * or only $count
+ *
+ * @return void
+ */
+ protected function _buildLimit($numRows)
+ {
+ if (!isset($numRows)) {
+ return;
+ }
+
+ if (is_array($numRows)) {
+ $this->_query .= ' LIMIT ' . (int) $numRows[0] . ', ' . (int) $numRows[1];
+ } else {
+ $this->_query .= ' LIMIT ' . (int) $numRows;
+ }
+ }
+
+ /**
+ * Method attempts to prepare the SQL query
+ * and throws an error if there was a problem.
+ *
+ * @return mysqli_stmt
+ * @throws Exception
+ */
+ protected function _prepareQuery()
+ {
+ $stmt = $this->mysqli()->prepare($this->_query);
+
+ if ($stmt !== false) {
+ if ($this->traceEnabled)
+ $this->traceStartQ = microtime(true);
+ return $stmt;
+ }
+
+ if ($this->mysqli()->errno === 2006 && $this->autoReconnect === true && $this->autoReconnectCount === 0) {
+ $this->connect($this->defConnectionName);
+ $this->autoReconnectCount++;
+ return $this->_prepareQuery();
+ }
+
+ $error = $this->mysqli()->error;
+ $query = $this->_query;
+ $errno = $this->mysqli()->errno;
+ $this->reset();
+ throw new Exception(sprintf('%s query: %s', $error, $query), $errno);
+ }
+
+ /**
+ * Referenced data array is required by mysqli since PHP 5.3+
+ *
+ * @param array $arr
+ *
+ * @return array
+ */
+ protected function refValues(array &$arr)
+ {
+ //Reference in the function arguments are required for HHVM to work
+ //https://github.com/facebook/hhvm/issues/5155
+ //Referenced data array is required by mysqli since PHP 5.3+
+ if (strnatcmp(phpversion(), '5.3') >= 0) {
+ $refs = array();
+ foreach ($arr as $key => $value) {
+ $refs[$key] = & $arr[$key];
+ }
+ return $refs;
+ }
+ return $arr;
+ }
+
+ /**
+ * Function to replace ? with variables from bind variable
+ *
+ * @param string $str
+ * @param array $vals
+ *
+ * @return string
+ */
+ protected function replacePlaceHolders($str, $vals)
+ {
+ $i = 1;
+ $newStr = "";
+
+ if (empty($vals)) {
+ return $str;
+ }
+
+ while ($pos = strpos($str, "?")) {
+ $val = $vals[$i++];
+ if (is_object($val)) {
+ $val = '[object]';
+ }
+ if ($val === null) {
+ $val = 'NULL';
+ }
+ $newStr .= substr($str, 0, $pos) . "'" . $val . "'";
+ $str = substr($str, $pos + 1);
+ }
+ $newStr .= $str;
+ return $newStr;
+ }
+
+ /**
+ * Method returns last executed query
+ *
+ * @return string
+ */
+ public function getLastQuery()
+ {
+ return $this->_lastQuery;
+ }
+
+ /**
+ * Method returns mysql error
+ *
+ * @return string
+ * @throws Exception
+ */
+ public function getLastError()
+ {
+ if (!isset($this->_mysqli[$this->defConnectionName])) {
+ return "mysqli is null";
+ }
+ return trim($this->_stmtError . " " . $this->mysqli()->error);
+ }
+
+ /**
+ * Method returns mysql error code
+ *
+ * @return int
+ */
+ public function getLastErrno () {
+ return $this->_stmtErrno;
+ }
+
+ /**
+ * Mostly internal method to get query and its params out of subquery object
+ * after get() and getAll()
+ *
+ * @return array
+ */
+ public function getSubQuery()
+ {
+ if (!$this->isSubQuery) {
+ return null;
+ }
+
+ array_shift($this->_bindParams);
+ $val = Array('query' => $this->_query,
+ 'params' => $this->_bindParams,
+ 'alias' => isset($this->connectionsSettings[$this->defConnectionName]) ? $this->connectionsSettings[$this->defConnectionName]['host'] : null
+ );
+ $this->reset();
+ return $val;
+ }
+
+ /* Helper functions */
+
+ /**
+ * Method returns generated interval function as a string
+ *
+ * @param string $diff interval in the formats:
+ * "1", "-1d" or "- 1 day" -- For interval - 1 day
+ * Supported intervals [s]econd, [m]inute, [h]hour, [d]day, [M]onth, [Y]ear
+ * Default null;
+ * @param string $func Initial date
+ *
+ * @return string
+ * @throws Exception
+ */
+ public function interval($diff, $func = "NOW()")
+ {
+ $types = Array("s" => "second", "m" => "minute", "h" => "hour", "d" => "day", "M" => "month", "Y" => "year");
+ $incr = '+';
+ $items = '';
+ $type = 'd';
+
+ if ($diff && preg_match('/([+-]?) ?([0-9]+) ?([a-zA-Z]?)/', $diff, $matches)) {
+ if (!empty($matches[1])) {
+ $incr = $matches[1];
+ }
+
+ if (!empty($matches[2])) {
+ $items = $matches[2];
+ }
+
+ if (!empty($matches[3])) {
+ $type = $matches[3];
+ }
+
+ if (!in_array($type, array_keys($types))) {
+ throw new Exception("invalid interval type in '{$diff}'");
+ }
+
+ $func .= " " . $incr . " interval " . $items . " " . $types[$type] . " ";
+ }
+ return $func;
+ }
+
+ /**
+ * Method returns generated interval function as an insert/update function
+ *
+ * @param string $diff interval in the formats:
+ * "1", "-1d" or "- 1 day" -- For interval - 1 day
+ * Supported intervals [s]econd, [m]inute, [h]hour, [d]day, [M]onth, [Y]ear
+ * Default null;
+ * @param string $func Initial date
+ *
+ * @return array
+ * @throws Exception
+ */
+ public function now($diff = null, $func = "NOW()")
+ {
+ return array("[F]" => Array($this->interval($diff, $func)));
+ }
+
+ /**
+ * Method generates incremental function call
+ *
+ * @param int $num increment by int or float. 1 by default
+ *
+ * @throws Exception
+ * @return array
+ */
+ public function inc($num = 1)
+ {
+ if (!is_numeric($num)) {
+ throw new Exception('Argument supplied to inc must be a number');
+ }
+ return array("[I]" => "+" . $num);
+ }
+
+ /**
+ * Method generates decremental function call
+ *
+ * @param int $num increment by int or float. 1 by default
+ *
+ * @return array
+ * @throws Exception
+ */
+ public function dec($num = 1)
+ {
+ if (!is_numeric($num)) {
+ throw new Exception('Argument supplied to dec must be a number');
+ }
+ return array("[I]" => "-" . $num);
+ }
+
+ /**
+ * Method generates change boolean function call
+ *
+ * @param string $col column name. null by default
+ *
+ * @return array
+ */
+ public function not($col = null)
+ {
+ return array("[N]" => (string)$col);
+ }
+
+ /**
+ * Method generates user defined function call
+ *
+ * @param string $expr user function body
+ * @param array $bindParams
+ *
+ * @return array
+ */
+ public function func($expr, $bindParams = null)
+ {
+ return array("[F]" => array($expr, $bindParams));
+ }
+
+ /**
+ * Method creates new mysqlidb object for a subquery generation
+ *
+ * @param string $subQueryAlias
+ *
+ * @return MysqliDb
+ */
+ public static function subQuery($subQueryAlias = "")
+ {
+ return new self(array('host' => $subQueryAlias, 'isSubQuery' => true));
+ }
+
+ /**
+ * Method returns a copy of a mysqlidb subquery object
+ *
+ * @return MysqliDb new mysqlidb object
+ */
+ public function copy()
+ {
+ $copy = unserialize(serialize($this));
+ $copy->_mysqli = array();
+ return $copy;
+ }
+
+ /**
+ * Begin a transaction
+ *
+ * @uses mysqli->autocommit(false)
+ * @uses register_shutdown_function(array($this, "_transaction_shutdown_check"))
+ * @throws Exception
+ */
+ public function startTransaction()
+ {
+ $this->mysqli()->autocommit(false);
+ $this->_transaction_in_progress = true;
+ register_shutdown_function(array($this, "_transaction_status_check"));
+ }
+
+ /**
+ * Transaction commit
+ *
+ * @uses mysqli->commit();
+ * @uses mysqli->autocommit(true);
+ * @throws Exception
+ */
+ public function commit()
+ {
+ $result = $this->mysqli()->commit();
+ $this->_transaction_in_progress = false;
+ $this->mysqli()->autocommit(true);
+ return $result;
+ }
+
+ /**
+ * Transaction rollback function
+ *
+ * @uses mysqli->rollback();
+ * @uses mysqli->autocommit(true);
+ * @throws Exception
+ */
+ public function rollback()
+ {
+ $result = $this->mysqli()->rollback();
+ $this->_transaction_in_progress = false;
+ $this->mysqli()->autocommit(true);
+ return $result;
+ }
+
+ /**
+ * Shutdown handler to rollback uncommited operations in order to keep
+ * atomic operations sane.
+ *
+ * @uses mysqli->rollback();
+ * @throws Exception
+ */
+ public function _transaction_status_check()
+ {
+ if (!$this->_transaction_in_progress) {
+ return;
+ }
+ $this->rollback();
+ }
+
+ /**
+ * Query execution time tracking switch
+ *
+ * @param bool $enabled Enable execution time tracking
+ * @param string $stripPrefix Prefix to strip from the path in exec log
+ *
+ * @return MysqliDb
+ */
+ public function setTrace($enabled, $stripPrefix = null)
+ {
+ $this->traceEnabled = $enabled;
+ $this->traceStripPrefix = $stripPrefix;
+ return $this;
+ }
+
+ /**
+ * Get where and what function was called for query stored in MysqliDB->trace
+ *
+ * @return string with information
+ */
+ private function _traceGetCaller()
+ {
+ $dd = debug_backtrace();
+ $caller = next($dd);
+ while (isset($caller) && $caller["file"] == __FILE__) {
+ $caller = next($dd);
+ }
+
+ return __CLASS__ . "->" . $caller["function"] . "() >> file \"" .
+ str_replace($this->traceStripPrefix, '', $caller["file"]) . "\" line #" . $caller["line"] . " ";
+ }
+
+ /**
+ * Method to check if needed table is created
+ *
+ * @param array $tables Table name or an Array of table names to check
+ *
+ * @return bool True if table exists
+ * @throws Exception
+ */
+ public function tableExists($tables)
+ {
+ $tables = !is_array($tables) ? Array($tables) : $tables;
+ $count = count($tables);
+ if ($count == 0) {
+ return false;
+ }
+
+ foreach ($tables as $i => $value)
+ $tables[$i] = self::$prefix . $value;
+ $db = isset($this->connectionsSettings[$this->defConnectionName]) ? $this->connectionsSettings[$this->defConnectionName]['db'] : null;
+ $this->where('table_schema', $db);
+ $this->where('table_name', $tables, 'in');
+ $this->get('information_schema.tables', $count);
+ return $this->count == $count;
+ }
+
+ /**
+ * Return result as an associative array with $idField field value used as a record key
+ *
+ * Array Returns an array($k => $v) if get(.."param1, param2"), array ($k => array ($v, $v)) otherwise
+ *
+ * @param string $idField field name to use for a mapped element key
+ *
+ * @return MysqliDb
+ */
+ public function map($idField)
+ {
+ $this->_mapKey = $idField;
+ return $this;
+ }
+
+ /**
+ * Pagination wrapper to get()
+ *
+ * @access public
+ *
+ * @param string $table The name of the database table to work with
+ * @param int $page Page number
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ *
+ * @return array
+ * @throws Exception
+ */
+ public function paginate ($table, $page, $fields = null) {
+ $offset = $this->pageLimit * ($page - 1);
+ $res = $this->withTotalCount()->get ($table, Array ($offset, $this->pageLimit), $fields);
+ $this->totalPages = ceil($this->totalCount / $this->pageLimit);
+ return $res;
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) AND WHERE statements for the join table on part of the SQL query.
+ *
+ * @uses $dbWrapper->joinWhere('user u', 'u.id', 7)->where('user u', 'u.title', 'MyTitle');
+ *
+ * @param string $whereJoin The name of the table followed by its prefix.
+ * @param string $whereProp The name of the database field.
+ * @param mixed $whereValue The value of the database field.
+ *
+ * @param string $operator
+ * @param string $cond
+ *
+ * @return $this
+ */
+ public function joinWhere($whereJoin, $whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
+ {
+ $this->_joinAnd[self::$prefix . $whereJoin][] = Array ($cond, $whereProp, $operator, $whereValue);
+ return $this;
+ }
+
+ /**
+ * This method allows you to specify multiple (method chaining optional) OR WHERE statements for the join table on part of the SQL query.
+ *
+ * @uses $dbWrapper->joinWhere('user u', 'u.id', 7)->where('user u', 'u.title', 'MyTitle');
+ *
+ * @param string $whereJoin The name of the table followed by its prefix.
+ * @param string $whereProp The name of the database field.
+ * @param mixed $whereValue The value of the database field.
+ * @param string $operator
+ *
+ * @return $this
+ */
+ public function joinOrWhere($whereJoin, $whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
+ {
+ return $this->joinWhere($whereJoin, $whereProp, $whereValue, $operator, 'OR');
+ }
+
+ /**
+ * Abstraction method that will build an JOIN part of the query
+ */
+ protected function _buildJoin () {
+ if (empty ($this->_join))
+ return;
+
+ foreach ($this->_join as $data) {
+ list ($joinType, $joinTable, $joinCondition) = $data;
+
+ if (is_object ($joinTable))
+ $joinStr = $this->_buildPair ("", $joinTable);
+ else
+ $joinStr = $joinTable;
+
+ $this->_query .= " " . $joinType. " JOIN " . $joinStr .
+ (false !== stripos($joinCondition, 'using') ? " " : " on ")
+ . $joinCondition;
+
+ // Add join and query
+ if (!empty($this->_joinAnd) && isset($this->_joinAnd[$joinStr])) {
+ foreach($this->_joinAnd[$joinStr] as $join_and_cond) {
+ list ($concat, $varName, $operator, $val) = $join_and_cond;
+ $this->_query .= " " . $concat ." " . $varName;
+ $this->conditionToSql($operator, $val);
+ }
+ }
+ }
+ }
+
+ /**
+ * Convert a condition and value into the sql string
+ *
+ * @param String $operator The where constraint operator
+ * @param String|array $val The where constraint value
+ */
+ private function conditionToSql($operator, $val) {
+ switch (strtolower ($operator)) {
+ case 'not in':
+ case 'in':
+ $comparison = ' ' . $operator. ' (';
+ if (is_object ($val)) {
+ $comparison .= $this->_buildPair ("", $val);
+ } else {
+ foreach ($val as $v) {
+ $comparison .= ' ?,';
+ $this->_bindParam ($v);
+ }
+ }
+ $this->_query .= rtrim($comparison, ',').' ) ';
+ break;
+ case 'not between':
+ case 'between':
+ $this->_query .= " $operator ? AND ? ";
+ $this->_bindParams ($val);
+ break;
+ case 'not exists':
+ case 'exists':
+ $this->_query.= $operator . $this->_buildPair ("", $val);
+ break;
+ default:
+ if (is_array ($val))
+ $this->_bindParams ($val);
+ else if ($val === null)
+ $this->_query .= $operator . " NULL";
+ else if ($val != 'DBNULL' || $val == '0')
+ $this->_query .= $this->_buildPair ($operator, $val);
+ }
+ }
+}
+
+// END class
diff --git a/Flare/Flare_Libraries/dbObject.php b/Flare/Flare_Libraries/dbObject.php
new file mode 100644
index 0000000..304630a
--- /dev/null
+++ b/Flare/Flare_Libraries/dbObject.php
@@ -0,0 +1,807 @@
+
+ * @copyright Copyright (c) 2015-2017
+ * @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
+ * @link http://github.com/joshcam/PHP-MySQLi-Database-Class
+ * @version 2.9-master
+ *
+ * @method int count ()
+ * @method dbObject ArrayBuilder()
+ * @method dbObject JsonBuilder()
+ * @method dbObject ObjectBuilder()
+ * @method mixed byId(string $id, mixed $fields)
+ * @method mixed get(mixed $limit, mixed $fields)
+ * @method mixed getOne(mixed $fields)
+ * @method mixed paginate(int $page, array $fields)
+ * @method dbObject query($query, $numRows = null)
+ * @method dbObject rawQuery($query, $bindParams = null)
+ * @method dbObject join(string $objectName, string $key, string $joinType, string $primaryKey)
+ * @method dbObject with(string $objectName)
+ * @method dbObject groupBy(string $groupByField)
+ * @method dbObject orderBy($orderByField, $orderbyDirection = "DESC", $customFieldsOrRegExp = null)
+ * @method dbObject where($whereProp, $whereValue = 'DBNULL', $operator = '=', $cond = 'AND')
+ * @method dbObject orWhere($whereProp, $whereValue = 'DBNULL', $operator = '=')
+ * @method dbObject having($havingProp, $havingValue = 'DBNULL', $operator = '=', $cond = 'AND')
+ * @method dbObject orHaving($havingProp, $havingValue = null, $operator = null)
+ * @method dbObject setQueryOption($options)
+ * @method dbObject setTrace($enabled, $stripPrefix = null)
+ * @method dbObject withTotalCount()
+ * @method dbObject startTransaction()
+ * @method dbObject commit()
+ * @method dbObject rollback()
+ * @method dbObject ping()
+ * @method string getLastError()
+ * @method string getLastQuery()
+ */
+class dbObject {
+ /**
+ * Working instance of MysqliDb created earlier
+ *
+ * @var MysqliDb
+ */
+ private $db;
+ /**
+ * Models path
+ *
+ * @var modelPath
+ */
+ protected static $modelPath;
+ /**
+ * An array that holds object data
+ *
+ * @var array
+ */
+ public $data;
+ /**
+ * Flag to define is object is new or loaded from database
+ *
+ * @var boolean
+ */
+ public $isNew = true;
+ /**
+ * Return type: 'Array' to return results as array, 'Object' as object
+ * 'Json' as json string
+ *
+ * @var string
+ */
+ public $returnType = 'Object';
+ /**
+ * An array that holds has* objects which should be loaded togeather with main
+ * object togeather with main object
+ *
+ * @var string
+ */
+ private $_with = Array();
+ /**
+ * Per page limit for pagination
+ *
+ * @var int
+ */
+ public static $pageLimit = 20;
+ /**
+ * Variable that holds total pages count of last paginate() query
+ *
+ * @var int
+ */
+ public static $totalPages = 0;
+ /**
+ * Variable which holds an amount of returned rows during paginate queries
+ * @var string
+ */
+ public static $totalCount = 0;
+ /**
+ * An array that holds insert/update/select errors
+ *
+ * @var array
+ */
+ public $errors = null;
+ /**
+ * Primary key for an object. 'id' is a default value.
+ *
+ * @var stating
+ */
+ protected $primaryKey = 'id';
+ /**
+ * Table name for an object. Class name will be used by default
+ *
+ * @var stating
+ */
+ protected $dbTable;
+
+ /**
+ * @var array name of the fields that will be skipped during validation, preparing & saving
+ */
+ protected $toSkip = array();
+
+ /**
+ * @param array $data Data to preload on object creation
+ */
+ public function __construct ($data = null) {
+ $this->db = MysqliDb::getInstance();
+ if (empty ($this->dbTable))
+ $this->dbTable = get_class ($this);
+
+ if ($data)
+ $this->data = $data;
+ }
+
+ /**
+ * Magic setter function
+ *
+ * @return mixed
+ */
+ public function __set ($name, $value) {
+ if (property_exists ($this, 'hidden') && array_search ($name, $this->hidden) !== false)
+ return;
+
+ $this->data[$name] = $value;
+ }
+
+ /**
+ * Magic getter function
+ *
+ * @param $name Variable name
+ *
+ * @return mixed
+ */
+ public function __get ($name) {
+ if (property_exists ($this, 'hidden') && array_search ($name, $this->hidden) !== false)
+ return null;
+
+ if (isset ($this->data[$name]) && $this->data[$name] instanceof dbObject)
+ return $this->data[$name];
+
+ if (property_exists ($this, 'relations') && isset ($this->relations[$name])) {
+ $relationType = strtolower ($this->relations[$name][0]);
+ $modelName = $this->relations[$name][1];
+ switch ($relationType) {
+ case 'hasone':
+ $key = isset ($this->relations[$name][2]) ? $this->relations[$name][2] : $name;
+ $obj = new $modelName;
+ $obj->returnType = $this->returnType;
+ return $this->data[$name] = $obj->byId($this->data[$key]);
+ break;
+ case 'hasmany':
+ $key = $this->relations[$name][2];
+ $obj = new $modelName;
+ $obj->returnType = $this->returnType;
+ return $this->data[$name] = $obj->where($key, $this->data[$this->primaryKey])->get();
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (isset ($this->data[$name]))
+ return $this->data[$name];
+
+ if (property_exists ($this->db, $name))
+ return $this->db->$name;
+ }
+
+ public function __isset ($name) {
+ if (isset ($this->data[$name]))
+ return isset ($this->data[$name]);
+
+ if (property_exists ($this->db, $name))
+ return isset ($this->db->$name);
+ }
+
+ public function __unset ($name) {
+ unset ($this->data[$name]);
+ }
+
+ /**
+ * Helper function to create dbObject with Json return type
+ *
+ * @return dbObject
+ */
+ private function JsonBuilder () {
+ $this->returnType = 'Json';
+ return $this;
+ }
+
+ /**
+ * Helper function to create dbObject with Array return type
+ *
+ * @return dbObject
+ */
+ private function ArrayBuilder () {
+ $this->returnType = 'Array';
+ return $this;
+ }
+
+ /**
+ * Helper function to create dbObject with Object return type.
+ * Added for consistency. Works same way as new $objname ()
+ *
+ * @return dbObject
+ */
+ private function ObjectBuilder () {
+ $this->returnType = 'Object';
+ return $this;
+ }
+
+ /**
+ * Helper function to create a virtual table class
+ *
+ * @param string tableName Table name
+ * @return dbObject
+ */
+ public static function table ($tableName) {
+ $tableName = preg_replace ("/[^-a-z0-9_]+/i",'', $tableName);
+ if (!class_exists ($tableName))
+ eval ("class $tableName extends dbObject {}");
+ return new $tableName ();
+ }
+ /**
+ * @return mixed insert id or false in case of failure
+ */
+ public function insert () {
+ if (!empty ($this->timestamps) && in_array ("createdAt", $this->timestamps))
+ $this->createdAt = date("Y-m-d H:i:s");
+ $sqlData = $this->prepareData ();
+ if (!$this->validate ($sqlData))
+ return false;
+
+ $id = $this->db->insert ($this->dbTable, $sqlData);
+ if (!empty ($this->primaryKey) && empty ($this->data[$this->primaryKey]))
+ $this->data[$this->primaryKey] = $id;
+ $this->isNew = false;
+ $this->toSkip = array();
+ return $id;
+ }
+
+ /**
+ * @param array $data Optional update data to apply to the object
+ */
+ public function update ($data = null) {
+ if (empty ($this->dbFields))
+ return false;
+
+ if (empty ($this->data[$this->primaryKey]))
+ return false;
+
+ if ($data) {
+ foreach ($data as $k => $v) {
+ if (in_array($k, $this->toSkip))
+ continue;
+
+ $this->$k = $v;
+ }
+ }
+
+ if (!empty ($this->timestamps) && in_array ("updatedAt", $this->timestamps))
+ $this->updatedAt = date("Y-m-d H:i:s");
+
+ $sqlData = $this->prepareData ();
+ if (!$this->validate ($sqlData))
+ return false;
+
+ $this->db->where ($this->primaryKey, $this->data[$this->primaryKey]);
+ $res = $this->db->update ($this->dbTable, $sqlData);
+ $this->toSkip = array();
+ return $res;
+ }
+
+ /**
+ * Save or Update object
+ *
+ * @return mixed insert id or false in case of failure
+ */
+ public function save ($data = null) {
+ if ($this->isNew)
+ return $this->insert();
+ return $this->update ($data);
+ }
+
+ /**
+ * Delete method. Works only if object primaryKey is defined
+ *
+ * @return boolean Indicates success. 0 or 1.
+ */
+ public function delete () {
+ if (empty ($this->data[$this->primaryKey]))
+ return false;
+
+ $this->db->where ($this->primaryKey, $this->data[$this->primaryKey]);
+ $res = $this->db->delete ($this->dbTable);
+ $this->toSkip = array();
+ return $res;
+ }
+
+ /**
+ * chained method that append a field or fields to skipping
+ * @param mixed|array|false $field field name; array of names; empty skipping if false
+ * @return $this
+ */
+ public function skip($field){
+ if(is_array($field)) {
+ foreach ($field as $f) {
+ $this->toSkip[] = $f;
+ }
+ } else if($field === false) {
+ $this->toSkip = array();
+ } else{
+ $this->toSkip[] = $field;
+ }
+ return $this;
+ }
+
+ /**
+ * Get object by primary key.
+ *
+ * @access public
+ * @param $id Primary Key
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ *
+ * @return dbObject|array
+ */
+ private function byId ($id, $fields = null) {
+ $this->db->where (MysqliDb::$prefix . $this->dbTable . '.' . $this->primaryKey, $id);
+ return $this->getOne ($fields);
+ }
+
+ /**
+ * Convinient function to fetch one object. Mostly will be togeather with where()
+ *
+ * @access public
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ *
+ * @return dbObject
+ */
+ protected function getOne ($fields = null) {
+ $this->processHasOneWith ();
+ $results = $this->db->ArrayBuilder()->getOne ($this->dbTable, $fields);
+ if ($this->db->count == 0)
+ return null;
+
+ $this->processArrays ($results);
+ $this->data = $results;
+ $this->processAllWith ($results);
+ if ($this->returnType == 'Json')
+ return json_encode ($results);
+ if ($this->returnType == 'Array')
+ return $results;
+
+ $item = new static ($results);
+ $item->isNew = false;
+
+ return $item;
+ }
+
+ /**
+ * A convenient SELECT COLUMN function to get a single column value from model object
+ *
+ * @param string $column The desired column
+ * @param int $limit Limit of rows to select. Use null for unlimited..1 by default
+ *
+ * @return mixed Contains the value of a returned column / array of values
+ * @throws Exception
+ */
+ protected function getValue ($column, $limit = 1) {
+ $res = $this->db->ArrayBuilder()->getValue ($this->dbTable, $column, $limit);
+ if (!$res)
+ return null;
+ return $res;
+ }
+
+ /**
+ * A convenient function that returns TRUE if exists at least an element that
+ * satisfy the where condition specified calling the "where" method before this one.
+ *
+ * @return bool
+ * @throws Exception
+ */
+ protected function has() {
+ return $this->db->has($this->dbTable);
+ }
+
+ /**
+ * Fetch all objects
+ *
+ * @access public
+ * @param integer|array $limit Array to define SQL limit in format Array ($count, $offset)
+ * or only $count
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ *
+ * @return array Array of dbObjects
+ */
+ protected function get ($limit = null, $fields = null) {
+ $objects = Array ();
+ $this->processHasOneWith ();
+ $results = $this->db->ArrayBuilder()->get ($this->dbTable, $limit, $fields);
+ if ($this->db->count == 0)
+ return null;
+
+ foreach ($results as $k => &$r) {
+ $this->processArrays ($r);
+ $this->data = $r;
+ $this->processAllWith ($r, false);
+ if ($this->returnType == 'Object') {
+ $item = new static ($r);
+ $item->isNew = false;
+ $objects[$k] = $item;
+ }
+ }
+ $this->_with = Array();
+ if ($this->returnType == 'Object')
+ return $objects;
+
+ if ($this->returnType == 'Json')
+ return json_encode ($results);
+
+ return $results;
+ }
+
+ /**
+ * Function to set witch hasOne or hasMany objects should be loaded togeather with a main object
+ *
+ * @access public
+ * @param string $objectName Object Name
+ *
+ * @return dbObject
+ */
+ private function with ($objectName) {
+ if (!property_exists ($this, 'relations') || !isset ($this->relations[$objectName]))
+ die ("No relation with name $objectName found");
+
+ $this->_with[$objectName] = $this->relations[$objectName];
+
+ return $this;
+ }
+
+ /**
+ * Function to join object with another object.
+ *
+ * @access public
+ * @param string $objectName Object Name
+ * @param string $key Key for a join from primary object
+ * @param string $joinType SQL join type: LEFT, RIGHT, INNER, OUTER
+ * @param string $primaryKey SQL join On Second primaryKey
+ *
+ * @return dbObject
+ */
+ private function join ($objectName, $key = null, $joinType = 'LEFT', $primaryKey = null) {
+ $joinObj = new $objectName;
+ if (!$key)
+ $key = $objectName . "id";
+
+ if (!$primaryKey)
+ $primaryKey = MysqliDb::$prefix . $joinObj->dbTable . "." . $joinObj->primaryKey;
+
+ if (!strchr ($key, '.'))
+ $joinStr = MysqliDb::$prefix . $this->dbTable . ".{$key} = " . $primaryKey;
+ else
+ $joinStr = MysqliDb::$prefix . "{$key} = " . $primaryKey;
+
+ $this->db->join ($joinObj->dbTable, $joinStr, $joinType);
+ return $this;
+ }
+
+ /**
+ * Function to get a total records count
+ *
+ * @return int
+ */
+ protected function count () {
+ $res = $this->db->ArrayBuilder()->getValue ($this->dbTable, "count(*)");
+ if (!$res)
+ return 0;
+ return $res;
+ }
+
+ /**
+ * Pagination wraper to get()
+ *
+ * @access public
+ * @param int $page Page number
+ * @param array|string $fields Array or coma separated list of fields to fetch
+ * @return array
+ */
+ private function paginate ($page, $fields = null) {
+ $this->db->pageLimit = self::$pageLimit;
+ $objects = Array ();
+ $this->processHasOneWith ();
+ $res = $this->db->paginate ($this->dbTable, $page, $fields);
+ self::$totalPages = $this->db->totalPages;
+ self::$totalCount = $this->db->totalCount;
+ if ($this->db->count == 0) return null;
+
+ foreach ($res as $k => &$r) {
+ $this->processArrays ($r);
+ $this->data = $r;
+ $this->processAllWith ($r, false);
+ if ($this->returnType == 'Object') {
+ $item = new static ($r);
+ $item->isNew = false;
+ $objects[$k] = $item;
+ }
+ }
+ $this->_with = Array();
+ if ($this->returnType == 'Object')
+ return $objects;
+
+ if ($this->returnType == 'Json')
+ return json_encode ($res);
+
+ return $res;
+ }
+
+ /**
+ * Catches calls to undefined methods.
+ *
+ * Provides magic access to private functions of the class and native public mysqlidb functions
+ *
+ * @param string $method
+ * @param mixed $arg
+ *
+ * @return mixed
+ */
+ public function __call ($method, $arg) {
+ if (method_exists ($this, $method))
+ return call_user_func_array (array ($this, $method), $arg);
+
+ call_user_func_array (array ($this->db, $method), $arg);
+ return $this;
+ }
+
+ /**
+ * Catches calls to undefined static methods.
+ *
+ * Transparently creating dbObject class to provide smooth API like name::get() name::orderBy()->get()
+ *
+ * @param string $method
+ * @param mixed $arg
+ *
+ * @return mixed
+ */
+ public static function __callStatic ($method, $arg) {
+ $obj = new static;
+ $result = call_user_func_array (array ($obj, $method), $arg);
+ if (method_exists ($obj, $method))
+ return $result;
+ return $obj;
+ }
+
+ /**
+ * Converts object data to an associative array.
+ *
+ * @return array Converted data
+ */
+ public function toArray () {
+ $data = $this->data;
+ $this->processAllWith ($data);
+ foreach ($data as &$d) {
+ if ($d instanceof dbObject)
+ $d = $d->data;
+ }
+ return $data;
+ }
+
+ /**
+ * Converts object data to a JSON string.
+ *
+ * @return string Converted data
+ */
+ public function toJson () {
+ return json_encode ($this->toArray());
+ }
+
+ /**
+ * Converts object data to a JSON string.
+ *
+ * @return string Converted data
+ */
+ public function __toString () {
+ return $this->toJson ();
+ }
+
+ /**
+ * Function queries hasMany relations if needed and also converts hasOne object names
+ *
+ * @param array $data
+ */
+ private function processAllWith (&$data, $shouldReset = true) {
+ if (count ($this->_with) == 0)
+ return;
+
+ foreach ($this->_with as $name => $opts) {
+ $relationType = strtolower ($opts[0]);
+ $modelName = $opts[1];
+ if ($relationType == 'hasone') {
+ $obj = new $modelName;
+ $table = $obj->dbTable;
+ $primaryKey = $obj->primaryKey;
+
+ if (!isset ($data[$table])) {
+ $data[$name] = $this->$name;
+ continue;
+ }
+ if ($data[$table][$primaryKey] === null) {
+ $data[$name] = null;
+ } else {
+ if ($this->returnType == 'Object') {
+ $item = new $modelName ($data[$table]);
+ $item->returnType = $this->returnType;
+ $item->isNew = false;
+ $data[$name] = $item;
+ } else {
+ $data[$name] = $data[$table];
+ }
+ }
+ unset ($data[$table]);
+ }
+ else
+ $data[$name] = $this->$name;
+ }
+ if ($shouldReset)
+ $this->_with = Array();
+ }
+
+ /*
+ * Function building hasOne joins for get/getOne method
+ */
+ private function processHasOneWith () {
+ if (count ($this->_with) == 0)
+ return;
+ foreach ($this->_with as $name => $opts) {
+ $relationType = strtolower ($opts[0]);
+ $modelName = $opts[1];
+ $key = null;
+ if (isset ($opts[2]))
+ $key = $opts[2];
+ if ($relationType == 'hasone') {
+ $this->db->setQueryOption ("MYSQLI_NESTJOIN");
+ $this->join ($modelName, $key);
+ }
+ }
+ }
+
+ /**
+ * @param array $data
+ */
+ private function processArrays (&$data) {
+ if (isset ($this->jsonFields) && is_array ($this->jsonFields)) {
+ foreach ($this->jsonFields as $key)
+ $data[$key] = json_decode ($data[$key]);
+ }
+
+ if (isset ($this->arrayFields) && is_array($this->arrayFields)) {
+ foreach ($this->arrayFields as $key)
+ $data[$key] = explode ("|", $data[$key]);
+ }
+ }
+
+ /**
+ * @param array $data
+ */
+ private function validate ($data) {
+ if (!$this->dbFields)
+ return true;
+
+ foreach ($this->dbFields as $key => $desc) {
+ if(in_array($key, $this->toSkip))
+ continue;
+
+ $type = null;
+ $required = false;
+ if (isset ($data[$key]))
+ $value = $data[$key];
+ else
+ $value = null;
+
+ if (is_array ($value))
+ continue;
+
+ if (isset ($desc[0]))
+ $type = $desc[0];
+ if (isset ($desc[1]) && ($desc[1] == 'required'))
+ $required = true;
+
+ if ($required && strlen ($value) == 0) {
+ $this->errors[] = Array ($this->dbTable . "." . $key => "is required");
+ continue;
+ }
+ if ($value == null)
+ continue;
+
+ switch ($type) {
+ case "text":
+ $regexp = null;
+ break;
+ case "int":
+ $regexp = "/^[0-9]*$/";
+ break;
+ case "double":
+ $regexp = "/^[0-9\.]*$/";
+ break;
+ case "bool":
+ $regexp = '/^(yes|no|0|1|true|false)$/i';
+ break;
+ case "datetime":
+ $regexp = "/^[0-9a-zA-Z -:]*$/";
+ break;
+ default:
+ $regexp = $type;
+ break;
+ }
+ if (!$regexp)
+ continue;
+
+ if (!preg_match ($regexp, $value)) {
+ $this->errors[] = Array ($this->dbTable . "." . $key => "$type validation failed");
+ continue;
+ }
+ }
+ return !count ($this->errors) > 0;
+ }
+
+ private function prepareData () {
+ $this->errors = Array ();
+ $sqlData = Array();
+ if (count ($this->data) == 0)
+ return Array();
+
+ if (method_exists ($this, "preLoad"))
+ $this->preLoad ($this->data);
+
+ if (!$this->dbFields)
+ return $this->data;
+
+ foreach ($this->data as $key => &$value) {
+ if(in_array($key, $this->toSkip))
+ continue;
+
+ if ($value instanceof dbObject && $value->isNew == true) {
+ $id = $value->save();
+ if ($id)
+ $value = $id;
+ else
+ $this->errors = array_merge ($this->errors, $value->errors);
+ }
+
+ if (!in_array ($key, array_keys ($this->dbFields)))
+ continue;
+
+ if (!is_array($value) && !is_object($value)) {
+ $sqlData[$key] = $value;
+ continue;
+ }
+
+ if (isset ($this->jsonFields) && in_array ($key, $this->jsonFields))
+ $sqlData[$key] = json_encode($value);
+ else if (isset ($this->arrayFields) && in_array ($key, $this->arrayFields))
+ $sqlData[$key] = implode ("|", $value);
+ else
+ $sqlData[$key] = $value;
+ }
+ return $sqlData;
+ }
+
+ private static function dbObjectAutoload ($classname) {
+ $filename = static::$modelPath . $classname .".php";
+ if (file_exists ($filename))
+ include ($filename);
+ }
+
+ /*
+ * Enable models autoload from a specified path
+ *
+ * Calling autoload() without path will set path to dbObjectPath/models/ directory
+ *
+ * @param string $path
+ */
+ public static function autoload ($path = null) {
+ if ($path)
+ static::$modelPath = $path . "/";
+ else
+ static::$modelPath = __DIR__ . "/models/";
+ spl_autoload_register ("dbObject::dbObjectAutoload");
+ }
+}
diff --git a/Flare/Flare_Libraries/jdf.php b/Flare/Flare_Libraries/jdf.php
new file mode 100644
index 0000000..f581f3a
--- /dev/null
+++ b/Flare/Flare_Libraries/jdf.php
@@ -0,0 +1,656 @@
+[ 1399/11/28 = 1442/07/04 = 2021/02/16 ]
+ */
+
+/* F */
+function jdate($format, $timestamp = '', $none = '', $time_zone = 'Asia/Tehran', $tr_num = 'fa') {
+
+ $T_sec = 0;/* <= رÙع خطاي زمان سرور ØŒ با اعداد '+' Ùˆ '-' بر Øسب ثانيه */
+
+ if ($time_zone != 'local') date_default_timezone_set(($time_zone === '') ? 'Asia/Tehran' : $time_zone);
+ $ts = $T_sec + (($timestamp === '') ? time() : $this->tr_num($timestamp));
+ $date = explode('_', date('H_i_j_n_O_P_s_w_Y', $ts));
+ list($j_y, $j_m, $j_d) = $this->gregorian_to_jalali($date[8], $date[3], $date[2]);
+ $doy = ($j_m < 7) ? (($j_m - 1) * 31) + $j_d - 1 : (($j_m - 7) * 30) + $j_d + 185;
+ $kab = (((($j_y + 12) % 33) % 4) == 1) ? 1 : 0;
+ $sl = strlen($format);
+ $out = '';
+ for ($i = 0; $i < $sl; $i++) {
+ $sub = substr($format, $i, 1);
+ if ($sub == '\\') {
+ $out .= substr($format, ++$i, 1);
+ continue;
+ }
+ switch ($sub) {
+
+ case 'E':
+ case 'R':
+ case 'x':
+ case 'X':
+ $out .= 'http://jdf.scr.ir';
+ break;
+
+ case 'B':
+ case 'e':
+ case 'g':
+ case 'G':
+ case 'h':
+ case 'I':
+ case 'T':
+ case 'u':
+ case 'Z':
+ $out .= date($sub, $ts);
+ break;
+
+ case 'a':
+ $out .= ($date[0] < 12) ? 'ق.ظ' : 'ب.ظ';
+ break;
+
+ case 'A':
+ $out .= ($date[0] < 12) ? 'قبل از ظهر' : 'بعد از ظهر';
+ break;
+
+ case 'b':
+ $out .= (int) ($j_m / 3.1) + 1;
+ break;
+
+ case 'c':
+ $out .= $j_y . '/' . $j_m . '/' . $j_d . ' ،' . $date[0] . ':' . $date[1] . ':' . $date[6] . ' ' . $date[5];
+ break;
+
+ case 'C':
+ $out .= (int) (($j_y + 99) / 100);
+ break;
+
+ case 'd':
+ $out .= ($j_d < 10) ? '0' . $j_d : $j_d;
+ break;
+
+ case 'D':
+ $out .= $this->jdate_words(array('kh' => $date[7]), ' ');
+ break;
+
+ case 'f':
+ $out .= $this->jdate_words(array('ff' => $j_m), ' ');
+ break;
+
+ case 'F':
+ $out .= $this->jdate_words(array('mm' => $j_m), ' ');
+ break;
+
+ case 'H':
+ $out .= $date[0];
+ break;
+
+ case 'i':
+ $out .= $date[1];
+ break;
+
+ case 'j':
+ $out .= $j_d;
+ break;
+
+ case 'J':
+ $out .= $this->jdate_words(array('rr' => $j_d), ' ');
+ break;
+
+ case 'k';
+ $out .= $this->tr_num(100 - (int) ($doy / ($kab + 365.24) * 1000) / 10, $tr_num);
+ break;
+
+ case 'K':
+ $out .= $this->tr_num((int) ($doy / ($kab + 365.24) * 1000) / 10, $tr_num);
+ break;
+
+ case 'l':
+ $out .= $this->jdate_words(array('rh' => $date[7]), ' ');
+ break;
+
+ case 'L':
+ $out .= $kab;
+ break;
+
+ case 'm':
+ $out .= ($j_m > 9) ? $j_m : '0' . $j_m;
+ break;
+
+ case 'M':
+ $out .= $this->jdate_words(array('km' => $j_m), ' ');
+ break;
+
+ case 'n':
+ $out .= $j_m;
+ break;
+
+ case 'N':
+ $out .= $date[7] + 1;
+ break;
+
+ case 'o':
+ $jdw = ($date[7] == 6) ? 0 : $date[7] + 1;
+ $dny = 364 + $kab - $doy;
+ $out .= ($jdw > ($doy + 3) and $doy < 3) ? $j_y - 1 : (((3 - $dny) > $jdw and $dny < 3) ? $j_y + 1 : $j_y);
+ break;
+
+ case 'O':
+ $out .= $date[4];
+ break;
+
+ case 'p':
+ $out .= $this->jdate_words(array('mb' => $j_m), ' ');
+ break;
+
+ case 'P':
+ $out .= $date[5];
+ break;
+
+ case 'q':
+ $out .= $this->jdate_words(array('sh' => $j_y), ' ');
+ break;
+
+ case 'Q':
+ $out .= $kab + 364 - $doy;
+ break;
+
+ case 'r':
+ $key = $this->jdate_words(array('rh' => $date[7], 'mm' => $j_m));
+ $out .= $date[0] . ':' . $date[1] . ':' . $date[6] . ' ' . $date[4] . ' ' . $key['rh'] . '، ' . $j_d . ' ' . $key['mm'] . ' ' . $j_y;
+ break;
+
+ case 's':
+ $out .= $date[6];
+ break;
+
+ case 'S':
+ $out .= 'ام';
+ break;
+
+ case 't':
+ $out .= ($j_m != 12) ? (31 - (int) ($j_m / 6.5)) : ($kab + 29);
+ break;
+
+ case 'U':
+ $out .= $ts;
+ break;
+
+ case 'v':
+ $out .= $this->jdate_words(array('ss' => ($j_y % 100)), ' ');
+ break;
+
+ case 'V':
+ $out .= $this->jdate_words(array('ss' => $j_y), ' ');
+ break;
+
+ case 'w':
+ $out .= ($date[7] == 6) ? 0 : $date[7] + 1;
+ break;
+
+ case 'W':
+ $avs = (($date[7] == 6) ? 0 : $date[7] + 1) - ($doy % 7);
+ if ($avs < 0) $avs += 7;
+ $num = (int) (($doy + $avs) / 7);
+ if ($avs < 4) {
+ $num++;
+ } elseif ($num < 1) {
+ $num = ($avs == 4 or $avs == ((((($j_y % 33) % 4) - 2) == ((int) (($j_y % 33) * 0.05))) ? 5 : 4)) ? 53 : 52;
+ }
+ $aks = $avs + $kab;
+ if ($aks == 7) $aks = 0;
+ $out .= (($kab + 363 - $doy) < $aks and $aks < 3) ? '01' : (($num < 10) ? '0' . $num : $num);
+ break;
+
+ case 'y':
+ $out .= substr($j_y, 2, 2);
+ break;
+
+ case 'Y':
+ $out .= $j_y;
+ break;
+
+ case 'z':
+ $out .= $doy;
+ break;
+
+ default:
+ $out .= $sub;
+ }
+ }
+ return ($tr_num != 'en') ? $this->tr_num($out, 'fa', '.') : $out;
+}
+
+/* F */
+function jstrftime($format, $timestamp = '', $none = '', $time_zone = 'Asia/Tehran', $tr_num = 'fa') {
+
+ $T_sec = 0;/* <= رÙع خطاي زمان سرور ØŒ با اعداد '+' Ùˆ '-' بر Øسب ثانيه */
+
+ if ($time_zone != 'local') date_default_timezone_set(($time_zone === '') ? 'Asia/Tehran' : $time_zone);
+ $ts = $T_sec + (($timestamp === '') ? time() : $this->tr_num($timestamp));
+ $date = explode('_', date('h_H_i_j_n_s_w_Y', $ts));
+ list($j_y, $j_m, $j_d) = $this->gregorian_to_jalali($date[7], $date[4], $date[3]);
+ $doy = ($j_m < 7) ? (($j_m - 1) * 31) + $j_d - 1 : (($j_m - 7) * 30) + $j_d + 185;
+ $kab = (((($j_y + 12) % 33) % 4) == 1) ? 1 : 0;
+ $sl = strlen($format);
+ $out = '';
+ for ($i = 0; $i < $sl; $i++) {
+ $sub = substr($format, $i, 1);
+ if ($sub == '%') {
+ $sub = substr($format, ++$i, 1);
+ } else {
+ $out .= $sub;
+ continue;
+ }
+ switch ($sub) {
+
+ /* Day */
+ case 'a':
+ $out .= $this->jdate_words(array('kh' => $date[6]), ' ');
+ break;
+
+ case 'A':
+ $out .= $this->jdate_words(array('rh' => $date[6]), ' ');
+ break;
+
+ case 'd':
+ $out .= ($j_d < 10) ? '0' . $j_d : $j_d;
+ break;
+
+ case 'e':
+ $out .= ($j_d < 10) ? ' ' . $j_d : $j_d;
+ break;
+
+ case 'j':
+ $out .= str_pad($doy + 1, 3, 0, STR_PAD_LEFT);
+ break;
+
+ case 'u':
+ $out .= $date[6] + 1;
+ break;
+
+ case 'w':
+ $out .= ($date[6] == 6) ? 0 : $date[6] + 1;
+ break;
+
+ /* Week */
+ case 'U':
+ $avs = (($date[6] < 5) ? $date[6] + 2 : $date[6] - 5) - ($doy % 7);
+ if ($avs < 0) $avs += 7;
+ $num = (int) (($doy + $avs) / 7) + 1;
+ if ($avs > 3 or $avs == 1) $num--;
+ $out .= ($num < 10) ? '0' . $num : $num;
+ break;
+
+ case 'V':
+ $avs = (($date[6] == 6) ? 0 : $date[6] + 1) - ($doy % 7);
+ if ($avs < 0) $avs += 7;
+ $num = (int) (($doy + $avs) / 7);
+ if ($avs < 4) {
+ $num++;
+ } elseif ($num < 1) {
+ $num = ($avs == 4 or $avs == ((((($j_y % 33) % 4) - 2) == ((int) (($j_y % 33) * 0.05))) ? 5 : 4)) ? 53 : 52;
+ }
+ $aks = $avs + $kab;
+ if ($aks == 7) $aks = 0;
+ $out .= (($kab + 363 - $doy) < $aks and $aks < 3) ? '01' : (($num < 10) ? '0' . $num : $num);
+ break;
+
+ case 'W':
+ $avs = (($date[6] == 6) ? 0 : $date[6] + 1) - ($doy % 7);
+ if ($avs < 0) $avs += 7;
+ $num = (int) (($doy + $avs) / 7) + 1;
+ if ($avs > 3) $num--;
+ $out .= ($num < 10) ? '0' . $num : $num;
+ break;
+
+ /* Month */
+ case 'b':
+ case 'h':
+ $out .= $this->jdate_words(array('km' => $j_m), ' ');
+ break;
+
+ case 'B':
+ $out .= $this->jdate_words(array('mm' => $j_m), ' ');
+ break;
+
+ case 'm':
+ $out .= ($j_m > 9) ? $j_m : '0' . $j_m;
+ break;
+
+ /* Year */
+ case 'C':
+ $tmp = (int) ($j_y / 100);
+ $out .= ($tmp > 9) ? $tmp : '0' . $tmp;
+ break;
+
+ case 'g':
+ $jdw = ($date[6] == 6) ? 0 : $date[6] + 1;
+ $dny = 364 + $kab - $doy;
+ $out .= substr(($jdw > ($doy + 3) and $doy < 3) ? $j_y - 1 : (((3 - $dny) > $jdw and $dny < 3) ? $j_y + 1 : $j_y), 2, 2);
+ break;
+
+ case 'G':
+ $jdw = ($date[6] == 6) ? 0 : $date[6] + 1;
+ $dny = 364 + $kab - $doy;
+ $out .= ($jdw > ($doy + 3) and $doy < 3) ? $j_y - 1 : (((3 - $dny) > $jdw and $dny < 3) ? $j_y + 1 : $j_y);
+ break;
+
+ case 'y':
+ $out .= substr($j_y, 2, 2);
+ break;
+
+ case 'Y':
+ $out .= $j_y;
+ break;
+
+ /* Time */
+ case 'H':
+ $out .= $date[1];
+ break;
+
+ case 'I':
+ $out .= $date[0];
+ break;
+
+ case 'l':
+ $out .= ($date[0] > 9) ? $date[0] : ' ' . (int) $date[0];
+ break;
+
+ case 'M':
+ $out .= $date[2];
+ break;
+
+ case 'p':
+ $out .= ($date[1] < 12) ? 'قبل از ظهر' : 'بعد از ظهر';
+ break;
+
+ case 'P':
+ $out .= ($date[1] < 12) ? 'ق.ظ' : 'ب.ظ';
+ break;
+
+ case 'r':
+ $out .= $date[0] . ':' . $date[2] . ':' . $date[5] . ' ' . (($date[1] < 12) ? 'قبل از ظهر' : 'بعد از ظهر');
+ break;
+
+ case 'R':
+ $out .= $date[1] . ':' . $date[2];
+ break;
+
+ case 'S':
+ $out .= $date[5];
+ break;
+
+ case 'T':
+ $out .= $date[1] . ':' . $date[2] . ':' . $date[5];
+ break;
+
+ case 'X':
+ $out .= $date[0] . ':' . $date[2] . ':' . $date[5];
+ break;
+
+ case 'z':
+ $out .= date('O', $ts);
+ break;
+
+ case 'Z':
+ $out .= date('T', $ts);
+ break;
+
+ /* Time and Date Stamps */
+ case 'c':
+ $key = $this->jdate_words(array('rh' => $date[6], 'mm' => $j_m));
+ $out .= $date[1] . ':' . $date[2] . ':' . $date[5] . ' ' . date('P', $ts) . ' ' . $key['rh'] . '، ' . $j_d . ' ' . $key['mm'] . ' ' . $j_y;
+ break;
+
+ case 'D':
+ $out .= substr($j_y, 2, 2) . '/' . (($j_m > 9) ? $j_m : '0' . $j_m) . '/' . (($j_d < 10) ? '0' . $j_d : $j_d);
+ break;
+
+ case 'F':
+ $out .= $j_y . '-' . (($j_m > 9) ? $j_m : '0' . $j_m) . '-' . (($j_d < 10) ? '0' . $j_d : $j_d);
+ break;
+
+ case 's':
+ $out .= $ts;
+ break;
+
+ case 'x':
+ $out .= substr($j_y, 2, 2) . '/' . (($j_m > 9) ? $j_m : '0' . $j_m) . '/' . (($j_d < 10) ? '0' . $j_d : $j_d);
+ break;
+
+ /* Miscellaneous */
+ case 'n':
+ $out .= "\n";
+ break;
+
+ case 't':
+ $out .= "\t";
+ break;
+
+ case '%':
+ $out .= '%';
+ break;
+
+ default:
+ $out .= $sub;
+ }
+ }
+ return ($tr_num != 'en') ? $this->tr_num($out, 'fa', '.') : $out;
+}
+
+/* F */
+function jmktime($h = '', $m = '', $s = '', $jm = '', $jd = '', $jy = '', $none = '', $timezone = 'Asia/Tehran') {
+ if ($timezone != 'local') date_default_timezone_set($timezone);
+ if ($h === '') {
+ return time();
+ } else {
+ list($h, $m, $s, $jm, $jd, $jy) = explode('_', $this->tr_num($h . '_' . $m . '_' . $s . '_' . $jm . '_' . $jd . '_' . $jy));
+ if ($m === '') {
+ return mktime($h);
+ } else {
+ if ($s === '') {
+ return mktime($h, $m);
+ } else {
+ if ($jm === '') {
+ return mktime($h, $m, $s);
+ } else {
+ $jdate = explode('_', jdate('Y_j', '', '', $timezone, 'en'));
+ if ($jd === '') {
+ list($gy, $gm, $gd) = jalali_to_gregorian($jdate[0], $jm, $jdate[1]);
+ return mktime($h, $m, $s, $gm);
+ } else {
+ if ($jy === '') {
+ list($gy, $gm, $gd) = jalali_to_gregorian($jdate[0], $jm, $jd);
+ return mktime($h, $m, $s, $gm, $gd);
+ } else {
+ list($gy, $gm, $gd) = jalali_to_gregorian($jy, $jm, $jd);
+ return mktime($h, $m, $s, $gm, $gd, $gy);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/* F */
+function jgetdate($timestamp = '', $none = '', $timezone = 'Asia/Tehran', $tn = 'en') {
+ $ts = ($timestamp === '') ? time() : $this->tr_num($timestamp);
+ $jdate = explode('_', jdate('F_G_i_j_l_n_s_w_Y_z', $ts, '', $timezone, $tn));
+ return array(
+ 'seconds' => $this->tr_num((int) $this->tr_num($jdate[6]), $tn),
+ 'minutes' => $this->tr_num((int) $this->tr_num($jdate[2]), $tn),
+ 'hours' => $jdate[1],
+ 'mday' => $jdate[3],
+ 'wday' => $jdate[7],
+ 'mon' => $jdate[5],
+ 'year' => $jdate[8],
+ 'yday' => $jdate[9],
+ 'weekday' => $jdate[4],
+ 'month' => $jdate[0],
+ 0 => $this->tr_num($ts, $tn)
+ );
+}
+
+/* F */
+function jcheckdate($jm, $jd, $jy) {
+ list($jm, $jd, $jy) = explode('_', $this->tr_num($jm . '_' . $jd . '_' . $jy));
+ $l_d = ($jm == 12 and ((($jy + 12) % 33) % 4) != 1) ? 29 : (31 - (int) ($jm / 6.5));
+ return ($jm > 12 or $jd > $l_d or $jm < 1 or $jd < 1 or $jy < 1) ? false : true;
+}
+
+/* F */
+public function tr_num($str, $mod = 'en', $mf = 'Ù«') {
+ $num_a = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.');
+ $key_a = array('Û°', 'Û±', 'Û²', 'Û³', 'Û´', 'Ûµ', 'Û¶', 'Û·', 'Û¸', 'Û¹', $mf);
+ return ($mod == 'fa') ? str_replace($num_a, $key_a, $str) : str_replace($key_a, $num_a, $str);
+}
+
+/* F */
+function jdate_words($array, $mod = '') {
+ foreach ($array as $type => $num) {
+ $num = (int) $this->tr_num($num);
+ switch ($type) {
+
+ case 'ss':
+ $sl = strlen($num);
+ $xy3 = substr($num, 2 - $sl, 1);
+ $h3 = $h34 = $h4 = '';
+ if ($xy3 == 1) {
+ $p34 = '';
+ $k34 = array('ده', 'یازده', 'دوازده', 'سیزده', 'چهارده', 'پانزده', 'شانزده', 'Ù‡Ùده', 'هجده', 'نوزده');
+ $h34 = $k34[substr($num, 2 - $sl, 2) - 10];
+ } else {
+ $xy4 = substr($num, 3 - $sl, 1);
+ $p34 = ($xy3 == 0 or $xy4 == 0) ? '' : ' Ùˆ ';
+ $k3 = array('', '', 'بیست', 'سی', 'چهل', 'پنجاه', 'شصت', 'Ù‡Ùتاد', 'هشتاد', 'نود');
+ $h3 = $k3[$xy3];
+ $k4 = array('', 'یک', 'دو', 'سه', 'چهار', 'پنج', 'شش', 'Ù‡Ùت', 'هشت', 'نه');
+ $h4 = $k4[$xy4];
+ }
+ $array[$type] = (($num > 99) ? str_replace(
+ array('12', '13', '14', '19', '20'),
+ array('هزار و دویست', 'هزار و سیصد', 'هزار و چهارصد', 'هزار و نهصد', 'دوهزار'),
+ substr($num, 0, 2)
+ ) . ((substr($num, 2, 2) == '00') ? '' : ' Ùˆ ') : '') . $h3 . $p34 . $h34 . $h4;
+ break;
+
+ case 'mm':
+ $key = array('Ùروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسÙند');
+ $array[$type] = $key[$num - 1];
+ break;
+
+ case 'rr':
+ $key = array(
+ 'یک', 'دو', 'سه', 'چهار', 'پنج', 'شش', 'Ù‡Ùت', 'هشت', 'نه', 'ده', 'یازده', 'دوازده', 'سیزده', 'چهارده', 'پانزده', 'شانزده', 'Ù‡Ùده', 'هجده', 'نوزده', 'بیست', 'بیست Ùˆ یک', 'بیست Ùˆ دو', 'بیست Ùˆ سه', 'بیست Ùˆ چهار', 'بیست Ùˆ پنج', 'بیست Ùˆ شش', 'بیست Ùˆ Ù‡Ùت', 'بیست Ùˆ هشت', 'بیست Ùˆ نه', 'سی', 'سی Ùˆ یک'
+ );
+ $array[$type] = $key[$num - 1];
+ break;
+
+ case 'rh':
+ $key = array('یکشنبه', 'دوشنبه', 'سه شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه');
+ $array[$type] = $key[$num];
+ break;
+
+ case 'sh':
+ $key = array('مار', 'اسب', 'گوسÙند', 'میمون', 'مرغ', 'سگ', 'خوک', 'موش', 'گاو', 'پلنگ', 'خرگوش', 'نهنگ');
+ $array[$type] = $key[$num % 12];
+ break;
+
+ case 'mb':
+ $key = array('Øمل', 'ثور', 'جوزا', 'سرطان', 'اسد', 'سنبله', 'میزان', 'عقرب', 'قوس', 'جدی', 'دلو', 'Øوت');
+ $array[$type] = $key[$num - 1];
+ break;
+
+ case 'ff':
+ $key = array('بهار', 'تابستان', 'پاییز', 'زمستان');
+ $array[$type] = $key[(int) ($num / 3.1)];
+ break;
+
+ case 'km':
+ $key = array('Ùر', 'ار', 'خر', 'تیâ€', 'مر', 'شهâ€', 'مهâ€', 'آبâ€', 'آذ', 'دی', 'بهâ€', 'اسâ€');
+ $array[$type] = $key[$num - 1];
+ break;
+
+ case 'kh':
+ $key = array('ی', 'د', 'س', 'چ', 'پ', 'ج', 'ش');
+ $array[$type] = $key[$num];
+ break;
+
+ default:
+ $array[$type] = $num;
+ }
+ }
+ return ($mod === '') ? $array : implode($mod, $array);
+}
+
+
+/** Gregorian & Jalali (Hijri_Shamsi,Solar) Date Converter Functions
+Author: JDF.SCR.IR =>> Download Full Version : http://jdf.scr.ir/jdf
+License: GNU/LGPL _ Open Source & Free :: Version: 2.80 : [2020=1399]
+---------------------------------------------------------------------
+355746=361590-5844 & 361590=(30*33*365)+(30*8) & 5844=(16*365)+(16/4)
+355666=355746-79-1 & 355668=355746-79+1 & 1595=605+990 & 605=621-16
+990=30*33 & 12053=(365*33)+(32/4) & 36524=(365*100)+(100/4)-(100/100)
+1461=(365*4)+(4/4) & 146097=(365*400)+(400/4)-(400/100)+(400/400) */
+
+/* F */
+function gregorian_to_jalali($gy, $gm, $gd, $mod = '') {
+ list($gy, $gm, $gd) = explode('_', $this->tr_num($gy . '_' . $gm . '_' . $gd));/* <= Extra :اين سطر ، جزء تابع اصلي نيست */
+ $g_d_m = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
+ $gy2 = ($gm > 2) ? ($gy + 1) : $gy;
+ $days = 355666 + (365 * $gy) + ((int) (($gy2 + 3) / 4)) - ((int) (($gy2 + 99) / 100)) + ((int) (($gy2 + 399) / 400)) + $gd + $g_d_m[$gm - 1];
+ $jy = -1595 + (33 * ((int) ($days / 12053)));
+ $days %= 12053;
+ $jy += 4 * ((int) ($days / 1461));
+ $days %= 1461;
+ if ($days > 365) {
+ $jy += (int) (($days - 1) / 365);
+ $days = ($days - 1) % 365;
+ }
+ if ($days < 186) {
+ $jm = 1 + (int) ($days / 31);
+ $jd = 1 + ($days % 31);
+ } else {
+ $jm = 7 + (int) (($days - 186) / 30);
+ $jd = 1 + (($days - 186) % 30);
+ }
+ return ($mod == '') ? array($jy, $jm, $jd) : $jy . $mod . $jm . $mod . $jd;
+}
+
+/* F */
+function jalali_to_gregorian($jy, $jm, $jd, $mod = '') {
+ list($jy, $jm, $jd) = explode('_', $this->tr_num($jy . '_' . $jm . '_' . $jd));/* <= Extra :اين سطر ، جزء تابع اصلي نيست */
+ $jy += 1595;
+ $days = -355668 + (365 * $jy) + (((int) ($jy / 33)) * 8) + ((int) ((($jy % 33) + 3) / 4)) + $jd + (($jm < 7) ? ($jm - 1) * 31 : (($jm - 7) * 30) + 186);
+ $gy = 400 * ((int) ($days / 146097));
+ $days %= 146097;
+ if ($days > 36524) {
+ $gy += 100 * ((int) (--$days / 36524));
+ $days %= 36524;
+ if ($days >= 365) $days++;
+ }
+ $gy += 4 * ((int) ($days / 1461));
+ $days %= 1461;
+ if ($days > 365) {
+ $gy += (int) (($days - 1) / 365);
+ $days = ($days - 1) % 365;
+ }
+ $gd = $days + 1;
+ $sal_a = array(0, 31, (($gy % 4 == 0 and $gy % 100 != 0) or ($gy % 400 == 0)) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
+ for ($gm = 0; $gm < 13 and $gd > $sal_a[$gm]; $gm++) $gd -= $sal_a[$gm];
+ return ($mod == '') ? array($gy, $gm, $gd) : $gy . $mod . $gm . $mod . $gd;
+}
+public function jtg($tarikh ,$separator='/' ){
+
+ $tarikh = explode($separator,$tarikh) ;
+ $jdate= $this->gregorian_to_jalali(trim($tarikh['0']),trim($tarikh['1']),trim($tarikh['2']),'/') ;
+ return $jdate;
+}
+}
\ No newline at end of file
diff --git a/Flare/Global_Functions/Flare.php b/Flare/Global_Functions/Flare.php
new file mode 100644
index 0000000..525b7a1
--- /dev/null
+++ b/Flare/Global_Functions/Flare.php
@@ -0,0 +1,83 @@
+
+ .php-debug {
+ position: fixed;
+ overflow: auto;
+ z-index: 99999;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ max-height: 80%;
+ padding: 1em 2em;
+ background: #292929;
+ color: #fff;
+ opacity: 0.92;
+ }
+ .php-debug::before {
+ content: "[DEBUG]";
+ position: fixed;
+ bottom: 0;
+ right: 0;
+ color: #00f2ff;
+ padding: 1em 2em;
+ }
+ ';
+ if (isset($ret)){
+ echo '[DEBUG] ' . '';
+ echo '
+';
+
+ }
+
+}
+
diff --git a/Flare/Libraries/Captcha.php b/Flare/Libraries/Captcha.php
new file mode 100644
index 0000000..1411e24
--- /dev/null
+++ b/Flare/Libraries/Captcha.php
@@ -0,0 +1,95 @@
+";
+ }
+ }
+
+ // (C) VERIFY CAPTCHA
+ function verify ($check) {
+ // (C1) CAPTCHA NOT SET!
+// if (!isset($_SESSION['captcha'])) { throw new Exception("CAPTCHA NOT PRIMED"); }
+ if (!isset($_SESSION['captcha'])) {
+ $khata="CAPTCHA NOT PRIMED";
+ }
+
+ // (C2) CHECK
+ if(isset($_SESSION['captcha'])){
+ $_SESSION['captcha']= strtolower( $_SESSION['captcha'] ) ;
+ $check= strtolower( $check ) ;
+ if ($check == $_SESSION['captcha']) {
+ unset($_SESSION['captcha']);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ }
+}
+
+// (D) CREATE CAPTCHA OBJECT
+ // Remove if session already started
\ No newline at end of file
diff --git a/Flare/Libraries/Email.php b/Flare/Libraries/Email.php
new file mode 100644
index 0000000..25abfd1
--- /dev/null
+++ b/Flare/Libraries/Email.php
@@ -0,0 +1,49 @@
+SMTPDebug = SMTP::DEBUG_SERVER; //Enable verbose debug output
+ $mail->isSMTP(); //Send using SMTP
+ $mail->Host = 'smtp.example.com'; //Set the SMTP server to send through
+ $mail->SMTPAuth = true; //Enable SMTP authentication
+ $mail->Username = 'user@example.com'; //SMTP username
+ $mail->Password = 'secret'; //SMTP password
+ $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption
+ $mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
+
+ //Recipients
+ $mail->setFrom('from@example.com', 'Mailer');
+ $mail->addAddress('irani3057@outlook.com', 'sajjad'); //Add a recipient
+ $mail->addAddress('ellen@example.com'); //Name is optional
+ $mail->addReplyTo('info@example.com', 'Information');
+ $mail->addCC('cc@example.com');
+ $mail->addBCC('bcc@example.com');
+
+ //Attachments
+ $mail->addAttachment('/var/tmp/file.tar.gz'); //Add attachments
+ $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); //Optional name
+
+ //Content
+ $mail->isHTML(true); //Set email format to HTML
+ $mail->Subject = 'Flare';
+ $mail->Body = 'This is the HTML message body in bold! ';
+ $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
+
+ $mail->send();
+ echo 'Message has been sent';
+ } catch (Exception $e) {
+ echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Flare/View/404/404.php b/Flare/View/404/404.php
new file mode 100644
index 0000000..def4613
--- /dev/null
+++ b/Flare/View/404/404.php
@@ -0,0 +1,112 @@
+
+
+
+
+ 404
+
+
+
+
+
+
+
+
+
+
+ Error 404
+
+
+ Page Not Found
+
+
+
+
+
\ No newline at end of file
diff --git a/Flare/View/Plates/w-template.php b/Flare/View/Plates/w-template.php
new file mode 100644
index 0000000..f3d3ad6
--- /dev/null
+++ b/Flare/View/Plates/w-template.php
@@ -0,0 +1,102 @@
+
+
+
+
+ =$this->e($title)?>
+
+
+
+
+
+
+=$this->section('content')?>
+
+
+
\ No newline at end of file
diff --git a/Flare/View/Plates/welcome.php b/Flare/View/Plates/welcome.php
new file mode 100644
index 0000000..b4e4ba2
--- /dev/null
+++ b/Flare/View/Plates/welcome.php
@@ -0,0 +1,25 @@
+layout('w-template', ['title' => 'Welcome']) ?>
+
+
+
+
+
+
+ Welcome to
+
+
+ =$this->e($welcome)?>
+
+
+
+ '.$jdf->jstrftime('%c '); ;
+ ?>
+© Sajjad eftekhari = date('Y')?>
+
+
+
diff --git a/Flare/View/Welcome/Welcome.php b/Flare/View/Welcome/Welcome.php
new file mode 100644
index 0000000..6b96c7e
--- /dev/null
+++ b/Flare/View/Welcome/Welcome.php
@@ -0,0 +1,127 @@
+
+
+
+
+
+ Welcome
+
+
+
+
+
+
+
+
+
+
+
+ Welcome to
+
+
+ Flare Framework
+
+
+
+ '.$jdf->jstrftime('%c '); ;
+
+ ?>
+© Sajjad eftekhari = date('Y')?>
+
+
+
+
+
\ No newline at end of file
diff --git a/Flare/View/footer.php b/Flare/View/footer.php
new file mode 100644
index 0000000..51d6d03
--- /dev/null
+++ b/Flare/View/footer.php
@@ -0,0 +1,4 @@
+
+ Copy right all received by Sajjad eftekhari
+