+ * @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
+