commit 4673a7a133e934d7a650ea40170de3caffcff3ff Author: Benjamin Renard Date: Wed Nov 15 05:23:00 2017 +0100 Initial version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c5f88a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +.*.swp diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..317c1c4 --- /dev/null +++ b/README.txt @@ -0,0 +1,6 @@ +SMS Gateway Application +======================= + +Could be use with "My SMS Gateway" Android app : + +https://play.google.com/store/apps/details?id=fr.nope.smsgateway diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..a6184bd --- /dev/null +++ b/data/.gitignore @@ -0,0 +1 @@ +app.log diff --git a/data/mysql.init.sql b/data/mysql.init.sql new file mode 100644 index 0000000..2f1fd3e --- /dev/null +++ b/data/mysql.init.sql @@ -0,0 +1,82 @@ +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `smsgw` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `incoming_msg` +-- + +CREATE TABLE `incoming_msg` ( + `uuid` varchar(40) NOT NULL, + `number` text NOT NULL, + `text` text NOT NULL, + `timestampms` int(11) NOT NULL, + `status` varchar(20) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `outgoing_msg` +-- + +CREATE TABLE `outgoing_msg` ( + `datetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `uuid` varchar(40) NOT NULL, + `number` text NOT NULL, + `text` text NOT NULL, + `uniqueid` bigint(11) UNSIGNED NOT NULL, + `nbfrag` int(11) NOT NULL, + `status` varchar(30) NOT NULL DEFAULT 'pending', + `lastupdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `outgoing_msg_frag` +-- + +CREATE TABLE `outgoing_msg_frag` ( + `msguid` bigint(11) UNSIGNED NOT NULL, + `fragid` int(11) NOT NULL, + `status` varchar(20) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `incoming_msg` +-- +ALTER TABLE `incoming_msg` + ADD PRIMARY KEY (`uuid`); + +-- +-- Indexes for table `outgoing_msg` +-- +ALTER TABLE `outgoing_msg` + ADD PRIMARY KEY (`uuid`), + ADD UNIQUE KEY `uniqueid` (`uniqueid`); + +-- +-- Indexes for table `outgoing_msg_frag` +-- +ALTER TABLE `outgoing_msg_frag` + ADD PRIMARY KEY (`fragid`,`msguid`); + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/includes/.gitignore b/includes/.gitignore new file mode 100644 index 0000000..529bf01 --- /dev/null +++ b/includes/.gitignore @@ -0,0 +1 @@ +config.local.php diff --git a/includes/config.inc.php b/includes/config.inc.php new file mode 100644 index 0000000..0b11c67 --- /dev/null +++ b/includes/config.inc.php @@ -0,0 +1,96 @@ + '1.0', + 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed', + 'Content-Transfer-Encoding' => '8bit', +); + +// Mail sender address +$mail_sender='sms-noreply@example.fr'; + +// Catch all email to the following specified address +//$mail_catch='root@example.fr'; + +// Load local configuration file is present +if (is_file($root_dir_path.'/includes/config.local.php')) { + require $root_dir_path.'/includes/config.local.php'; +} diff --git a/includes/core.php b/includes/core.php new file mode 100644 index 0000000..8650bdd --- /dev/null +++ b/includes/core.php @@ -0,0 +1,25 @@ + debug = function ($q) { + $time = sprintf('%0.3f', $q->getTime() * 1000) . ' ms'; + $rows = ($q->getResult()) ? $q->getResult()->rowCount() : 0; + $query = $q->getQuery(); + $msg = "# DB query ($time; rows = $rows) : $query"; + + $parameters = $q->getParameters(); + if ($parameters) { + if (is_array($parameters)) { + $msg .= "\n# Parameters: '" . implode("', '", $parameters) . "'"; + } + else { + $msg .= "\n# Parameters: '" . varDump($parameters) . "'"; + } + } + + logging('DEBUG',$msg); + }; +} +catch(Exception $e) { + logging('ERROR',"Fail to connect to DB (DSN : '$db_dsn') : ".$e->getMessage()); + fatal_error("Impossible de se connecter à la base de données"); +} + +function db_now() { + // 1970-01-01 00:00:01 + return date('Y-m-d G:i:s'); +} + +function get_incoming_msg($uuid) { + global $fpdo; + $result = $fpdo -> from('incoming_msg') + -> where('uuid=?', $uuid) + -> execute(); + + if ($result !== false) { + return $result -> fetch(); + } + return -1; +} + +function create_incoming_msg($number, $text, $timestampms) { + global $fpdo; + + $uuid = generate_uuid(); + $values = array ( + 'uuid' => $uuid, + 'number' => $number, + 'text' => $text, + 'timestampms' => $timestampms, + ); + $result = $fpdo -> insertInto('incoming_msg',$values) + -> execute(); + if ($result === false) + return -1; + + return $uuid; +} + +function get_outgoing_msg($uuid, $status=false) { + global $fpdo; + $where=array(); + if ($uuid) + $where['uuid']=$uuid; + if ($status) + $where['status']=$status; + if (empty($where)) + return -1; + $result = $fpdo -> from('outgoing_msg') + -> where($where) + -> execute(); + + if ($result !== false) { + return $result -> fetch(); + } + return -1; +} + +function get_outgoing_msg_by_uniqueid($uniqueid) { + global $fpdo; + $where=array('uniqueid' => $uniqueid); + $result = $fpdo -> from('outgoing_msg') + -> where($where) + -> execute(); + + if ($result !== false) { + return $result -> fetch(); + } + return -1; +} + +function create_outgoing_msg($number, $text) { + global $fpdo; + + $uuid = generate_uuid(); + $values = array ( + 'uuid' => $uuid, + 'number' => $number, + 'text' => $text, + ); + $result = $fpdo -> insertInto('outgoing_msg',$values) + -> execute(); + if ($result === false) + return -1; + + return $uuid; +} + +function handle_outgoing_msg($msg) { + if (!is_array($msg)) + $msg=get_outgoing_msg($msg); + if (is_array($msg)) { + if ($msg['status']=='pending') { + global $smsgw; + /* Exemple : + { + "number": "0612345678", + "text": "Hello world", + "nbfrag": 1, + "id": "1510712464683" + } + */ + $return=$smsgw->send_sms($msg['number'], $msg['text']); + if (is_array($return)) { + global $fpdo; + $result = $fpdo -> update('outgoing_msg') + -> set( + array ( + 'status' => 'pushed', + 'uniqueid' => $return['id'], + 'nbfrag' => $return['nbfrag'], + 'lastupdate' => db_now(), + ) + ) + -> where( + array ( + 'uuid' => $msg['uuid'], + ) + ) + -> execute(); + if ($result === false) + return -1; + return True; + } + else + return -1; + } + return True; + } + return -1; +} + +function get_outgoing_msg_frag($msguid, $fragid) { + global $fpdo; + $where=array( + 'msguid' => $msguid, + 'fragid' => $fragid, + ); + $result = $fpdo -> from('outgoing_msg_frag') + -> where($where) + -> execute(); + + if ($result !== false) { + return $result -> fetch(); + } + return -1; +} + +function get_outgoing_msg_status_from_frags($msg) { + if (!is_array($msg)) + $msg=get_outgoing_msg($msg); + + if (!is_array($msg)) + return -1; + + global $fpdo; + $where=array( + 'msguid' => $msg['uniqueid'], + ); + $result = $fpdo -> from('outgoing_msg_frag') + -> where($where) + -> execute(); + + if ($result !== false) { + $frags=$result -> fetchAll(); + if (!is_array($frags)) + return -1; + logging('DEBUG', "Frags : ".print_r($frags,1)); + $frag_states=array('pending','pushed','sent','delivered'); + $state_idx=0; + $state_frag_count=0; + foreach($frags as $frag) { + $idx=array_search($frag['status'], $frag_states); + if ($idx !== false) { + if ($idx > $state_idx) { + $state_idx=$idx; + $state_frag_count=1; + } + elseif ($state_idx==$idx) + $state_frag_count++; + } + } + $status=$frag_states[$state_idx]; + if ($status=="pending" || $status=="pushed" || $state_frag_count==$msg['nbfrag']) { + return $status; + } + else { + return "partially_$status"; + } + } + return -1; +} + +function update_outgoing_msg_status($uuid, $status) { + global $fpdo; + $result = $fpdo -> update('outgoing_msg') + -> set( + array ( + 'status' => $status, + 'lastupdate' => db_now(), + ) + ) + -> where( + array ( + 'uuid' => $uuid, + ) + ) + -> execute(); + if ($result === false) + return -1; + return True; +} + +function create_outgoing_msg_frag($msguid, $fragid, $status=false) { + global $fpdo; + + $values = array ( + 'msguid' => $msguid, + 'fragid' => $fragid, + ); + if ($status) + $values['status']=strtolower($status); + + $result = $fpdo -> insertInto('outgoing_msg_frag',$values) + -> execute(); + if ($result === false) + return -1; + + return true; +} + +function update_outgoing_msg_frag($msguid, $fragid, $status) { + global $fpdo; + + $frag=get_outgoing_msg_frag($msguid, $fragid); + + if ($frag==-1) + return -1; + + if (is_array($frag)) { + $result = $fpdo -> update('outgoing_msg_frag') + -> set( + array ( + 'status' => strtolower($status), + ) + ) + -> where( + array ( + 'msguid' => $msguid, + 'fragid' => $fragid, + ) + ) + -> execute(); + if ($result === false) + return -1; + return True; + } + else { + return create_outgoing_msg_frag($msguid, $fragid, $status); + } +} + +function get_smsq() { + global $fpdo; + $where=array( + 'status != ?' => 'delivered', + ); + $result = $fpdo -> from('outgoing_msg') + -> where($where) + -> execute(); + + if ($result !== false) { + return $result -> fetchAll(); + } + return -1; +} diff --git a/includes/functions.php b/includes/functions.php new file mode 100644 index 0000000..b2d5f7c --- /dev/null +++ b/includes/functions.php @@ -0,0 +1,158 @@ + "a", + "á" => "a", + "â" => "a", + "ã" => "a", + "ä" => "a", + "ç" => "c", + "è" => "e", + "é" => "e", + "ê" => "e", + "ë" => "e", + "ì" => "i", + "í" => "i", + "î" => "i", + "ï" => "i", + "ñ" => "n", + "ò" => "o", + "ó" => "o", + "ô" => "o", + "õ" => "o", + "ö" => "o", + "ù" => "u", + "ú" => "u", + "û" => "u", + "ü" => "u", + "ý" => "y", + "ÿ" => "y", + "À" => "A", + "Á" => "A", + "Â" => "A", + "Ã" => "A", + "Ä" => "A", + "Ç" => "C", + "È" => "E", + "É" => "E", + "Ê" => "E", + "Ë" => "E", + "Ì" => "I", + "Í" => "I", + "Î" => "I", + "Ï" => "I", + "Ñ" => "N", + "Ò" => "O", + "Ó" => "O", + "Ô" => "O", + "Õ" => "O", + "Ö" => "O", + "Ù" => "U", + "Ú" => "U", + "Û" => "U", + "Ü" => "U", + "Ý" => "Y" + ); + return strtr($string, $replaceAccent); +} + +function varDump($data) { + ob_start(); + var_dump($data); + $data=ob_get_contents(); + ob_end_clean(); + return $data; +} diff --git a/includes/logging.php b/includes/logging.php new file mode 100644 index 0000000..f67363f --- /dev/null +++ b/includes/logging.php @@ -0,0 +1,43 @@ + 0, + 'INFO' => 1, + 'WARNING' => 2, + 'ERROR' => 3, +); + +function logging($level,$message) { + global $log_file, $_log_file_fd, $_log_levels, $log_level; + + if (!array_key_exists($level, $_log_levels)) $level=='INFO'; + $level_id=$_log_levels[$level]; + + if (!array_key_exists($log_level, $_log_levels)) $log_level=='INFO'; + $log_level_id=$_log_levels[$log_level]; + + if ($level_id<$log_level_id) return true; + if(is_null($_log_file_fd)) { + $_log_file_fd=fopen($log_file,'a'); + } + + $msg=date('Y/m/d H:i:s').' - '.$_SERVER['REQUEST_URI'].' - '.$_SERVER['REMOTE_ADDR']." - $level - $message\n"; + + fwrite($_log_file_fd,$msg); + + return true; +} diff --git a/includes/sms_gw_api.php b/includes/sms_gw_api.php new file mode 100644 index 0000000..9e56bb2 --- /dev/null +++ b/includes/sms_gw_api.php @@ -0,0 +1,111 @@ + ws_url = $ws_url; + $this -> ws_ssl_verify = $ws_ssl_verify; + } + + /* + * REST Request + */ + + private function _ws_call($method, $url, $data = false, $json = true) { + if (is_null($this -> ws_url)) return; + + $curl = curl_init(); + + switch ($method) { + case "POST": + curl_setopt($curl, CURLOPT_POST, 1); + + if ($data) + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + break; + case "PUT": + curl_setopt($curl, CURLOPT_PUT, 1); + break; + default: + $method='GET'; + if ($data) + $url = sprintf("%s?%s", $url, http_build_query($data)); + } + + if ($this -> ws_ssl_verify) { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, TRUE); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); + } + else { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); + } + + $complete_url=$this -> ws_url.'/'.$url; + curl_setopt($curl, CURLOPT_URL, $complete_url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + + logging("DEBUG", "sms_gw_api : Call URL $complete_url"); + + $result = curl_exec($curl); + + if(curl_errno($curl)) { + logging("ERROR", "sms_gw_api : Error during $method request to ".$complete_url." : ".curl_error($curl)); + curl_close($curl); + return False; + } + + curl_close($curl); + + logging("DEBUG","sms_gw_api : Return => '$result'"); + + if (!$json) + return $result; + + try { + $data = json_decode($result,true); + logging("DEBUG","sms_gw_api : Decoded data :".print_r($data,1)); + return $data; + } + catch (Exception $e) { + logging("ERROR","sms_gw_api : Error parsing $method request JSON result on ".$complete_url." : ".$e); + return False; + } + } + + /** + * send sms + * @param string number + * @param string text + * @return struct sms gw return struct + */ + function send_sms($number, $text){ + return self :: _ws_call( + 'GET', + "", + array( + 'number' => $number, + 'text' => $text, + ) + ); + } + + /** + * check gateway is alive + * @return boolean sms gw status + */ + function check_gateway(){ + $return=self :: _ws_call( + 'GET', + "", + array(), + false + ); + return ($return !== false && preg_match('/My SMS Gateway/', $return)); + } + +} diff --git a/public_html/.htaccess b/public_html/.htaccess new file mode 100644 index 0000000..9184119 --- /dev/null +++ b/public_html/.htaccess @@ -0,0 +1,9 @@ +RewriteEngine On +RewriteBase / + +# If the request is not for a valid file +RewriteCond %{REQUEST_FILENAME} !-f +# If the request is not for a valid link +RewriteCond %{REQUEST_FILENAME} !-l +RewriteRule ^([^?]+)(\?(.*))?$ index.php?go=$1&$3 [L,QSA] + diff --git a/public_html/index.php b/public_html/index.php new file mode 100644 index 0000000..455345e --- /dev/null +++ b/public_html/index.php @@ -0,0 +1,126 @@ + 1510702925705 + [state] => Delivered + [frag] => 0 + [text] => qsdmlkqsdmlqskdmqlksdmqlsdkmqlsdqsdmlkqsdmlqskdmqlksdmqlsdkmqlsdqsdmlkqsdmlqskdmqlksdmqlsdkmqlsdqsdmlkqsdmlqskdmqlksdmqlsdkmqlsdqsdmlkqsdmlqskdmqlksdmqls + ) + */ + if (is_array($post_body) && check_integer($post_body['id']) && check_integer($post_body['frag']) && check_frag_state($post_body['state'])) { + logging('INFO','Ack for message '.$post_body['id'].' (Frag : '.$post_body['frag'].') with status '.$post_body['state']); + $msg=get_outgoing_msg_by_uniqueid($post_body['id']); + if (is_array($msg)) { + logging('INFO',"Corresponding outgoing msg : ".$msg['uuid']); + logging('DEBUG',"Outgoing msg : ".print_r($msg,1)); + if (update_outgoing_msg_frag($post_body['id'], $post_body['frag'], strtolower($post_body['state']))===True) { + $msg_status=get_outgoing_msg_status_from_frags($msg); + if ($msg_status!=$mgs['status'] && update_outgoing_msg_status($msg['uuid'], $msg_status)==-1) { + $data['status']='error'; + $data['msg']='Error updating outgoing msg status'; + } + else { + $data['status']='ok'; + } + } + else { + $data['status']='error'; + $data['msg']='Fail to update outgoing msg frag'; + } + } + else { + $data['status']='error'; + $data['msg']='Outgoing message not found'; + } + } + else { + $data['status']='error'; + $data['msg']='Invalid parameters'; + } + break; + case 'incoming': + /* + Post body example : Array ( + [number] => +33612345678 + [text] => qsdmlkqsdmlqskdmqlksd + [timestampMillis] => 1510702926820 + ) + */ + if (is_array($post_body) && check_phone_number($post_body['number']) && check_sms_text($post_body['text']) && check_integer($post_body['timestampMillis'])) { + logging('INFO','Incoming SMS from '.$post_body['number']); + $msg=create_incoming_msg($post_body['number'], $post_body['text'], $post_body['timestampMillis']); + if (is_array($msg)) { + $data['status']='ok'; + } + else { + $data['status']='error'; + $data['msg']='Error creating your incoming message'; + } + } + else { + $data['status']='error'; + $data['msg']='Invalid parameters'; + } + + break; + case 'smsq': + $data['messages']=get_smsq(); + logging('DEBUG','SMSQ : '.print_r($data['messages'],1)); + break; + case 'check_gateway': + $data['status']=($smsgw->check_gateway()?'alive':'not responding'); + logging('DEBUG','SMS Gateway Status : '.$data['status']); + break; + case '': + print "Welcome on SMS Gateway WebService"; + exit(); + break; + default: + $data['status']='error'; + $data['msg']='Invalid request'; +} +print json_encode($data, (isset($_REQUEST['pretty'])?JSON_PRETTY_PRINT:0)); diff --git a/tests/db-get-outgoing-msg-by-uniqueid.php b/tests/db-get-outgoing-msg-by-uniqueid.php new file mode 100644 index 0000000..3fddb1e --- /dev/null +++ b/tests/db-get-outgoing-msg-by-uniqueid.php @@ -0,0 +1,8 @@ +fpdo = $fpdo; + $this->clauses = $clauses; + $this->initClauses(); + } + + /** + * Initialize statement and parameter clauses. + */ + private function initClauses() { + foreach ($this->clauses as $clause => $value) { + if ($value) { + $this->statements[$clause] = array(); + $this->parameters[$clause] = array(); + } else { + $this->statements[$clause] = null; + $this->parameters[$clause] = null; + } + } + } + + /** + * Add statement for all kind of clauses + * + * @param $clause + * @param $statement + * @param array $parameters + * + * @return $this + */ + protected function addStatement($clause, $statement, $parameters = array()) { + if ($statement === null) { + return $this->resetClause($clause); + } + // $statement !== null + if ($this->clauses[$clause]) { + if (is_array($statement)) { + $this->statements[$clause] = array_merge($this->statements[$clause], $statement); + } else { + $this->statements[$clause][] = $statement; + } + $this->parameters[$clause] = array_merge($this->parameters[$clause], $parameters); + } else { + $this->statements[$clause] = $statement; + $this->parameters[$clause] = $parameters; + } + + return $this; + } + + /** + * Remove all prev defined statements + * + * @param $clause + * + * @return $this + */ + protected function resetClause($clause) { + $this->statements[$clause] = null; + $this->parameters[$clause] = array(); + if (isset($this->clauses[$clause]) && $this->clauses[$clause]) { + $this->statements[$clause] = array(); + } + + return $this; + } + + /** + * Implements method from IteratorAggregate + * + * @return \PDOStatement + */ + public function getIterator() { + return $this->execute(); + } + + /** + * Execute query with earlier added parameters + * + * @return \PDOStatement + */ + public function execute() { + $query = $this->buildQuery(); + $parameters = $this->buildParameters(); + + $result = $this->fpdo->getPdo()->prepare($query); + + // At this point, $result is a PDOStatement instance, or false. + // PDO::prepare() does not reliably return errors. Some database drivers + // do not support prepared statements, and PHP emulates them. Postgresql + // does support prepared statements, but PHP does not call Postgresql's + // prepare function until we call PDOStatement::execute() below. + // If PDO::prepare() worked properly, this is where we would check + // for prepare errors, such as invalid SQL. + + if ($this->object !== false) { + if (class_exists($this->object)) { + $result->setFetchMode(PDO::FETCH_CLASS, $this->object); + } else { + $result->setFetchMode(PDO::FETCH_OBJ); + } + } elseif ($this->fpdo->getPdo()->getAttribute(PDO::ATTR_DEFAULT_FETCH_MODE) == PDO::FETCH_BOTH) { + $result->setFetchMode(PDO::FETCH_ASSOC); + } + + $time = microtime(true); + if ($result && $result->execute($parameters)) { + $this->time = microtime(true) - $time; + } else { + $result = false; + } + + $this->result = $result; + $this->debugger(); + + return $result; + } + + /** + * Echo/pass a debug string + */ + private function debugger() { + if ($this->fpdo->debug) { + if (!is_callable($this->fpdo->debug)) { + $backtrace = ''; + $query = $this->getQuery(); + $parameters = $this->getParameters(); + $debug = ''; + if ($parameters) { + $debug = '# parameters: ' . implode(', ', array_map(array($this, 'quote'), $parameters)) . "\n"; + } + $debug .= $query; + $pattern = '(^' . preg_quote(__DIR__) . '(\\.php$|[/\\\\]))'; // can be static + foreach (debug_backtrace() as $backtrace) { + if (isset($backtrace['file']) && !preg_match($pattern, $backtrace['file'])) { + // stop on first file outside FluentPDO source codes + break; + } + } + $time = sprintf('%0.3f', $this->time * 1000) . ' ms'; + $rows = ($this->result) ? $this->result->rowCount() : 0; + $finalString = "# $backtrace[file]:$backtrace[line] ($time; rows = $rows)\n$debug\n\n"; + if (is_resource(STDERR)) { // if STDERR is set, send there, otherwise just output the string + fwrite(STDERR, $finalString); + } + else { + echo $finalString; + } + } else { + call_user_func($this->fpdo->debug, $this); + } + } + } + + /** + * @return \PDO + */ + protected function getPDO() { + return $this->fpdo->getPdo(); + } + + /** + * @return \FluentStructure + */ + protected function getStructure() { + return $this->fpdo->getStructure(); + } + + /** + * Get PDOStatement result + * + * @return \PDOStatement + */ + public function getResult() { + return $this->result; + } + + /** + * Get time of execution + * + * @return float + */ + public function getTime() { + return $this->time; + } + + /** + * Get query parameters + * + * @return array + */ + public function getParameters() { + return $this->buildParameters(); + } + + /** + * Get query string + * + * @param bool $formatted - Return formatted query + * + * @return string + */ + public function getQuery($formatted = true) { + $query = $this->buildQuery(); + if ($formatted) { + $query = FluentUtils::formatQuery($query); + } + + return $query; + } + + /** + * Generate query + * + * @return string + * @throws Exception + */ + protected function buildQuery() { + $query = ''; + foreach ($this->clauses as $clause => $separator) { + if ($this->clauseNotEmpty($clause)) { + if (is_string($separator)) { + $query .= " $clause " . implode($separator, $this->statements[$clause]); + } elseif ($separator === null) { + $query .= " $clause " . $this->statements[$clause]; + } elseif (is_callable($separator)) { + $query .= call_user_func($separator); + } else { + throw new Exception("Clause '$clause' is incorrectly set to '$separator'."); + } + } + } + + return trim($query); + } + + /** + * @param $clause + * + * @return bool + */ + private function clauseNotEmpty($clause) { + if ($this->clauses[$clause]) { + return (boolean)count($this->statements[$clause]); + } else { + return (boolean)$this->statements[$clause]; + } + } + + /** + * @return array + */ + protected function buildParameters() { + $parameters = array(); + foreach ($this->parameters as $clauses) { + if (is_array($clauses)) { + foreach ($clauses as $value) { + if (is_array($value) && is_string(key($value)) && substr(key($value), 0, 1) == ':') { + // this is named params e.g. (':name' => 'Mark') + $parameters = array_merge($parameters, $value); + } else { + $parameters[] = $value; + } + } + } else { + if ($clauses) { + $parameters[] = $clauses; + } + } + } + + return $parameters; + } + + /** + * @param $value + * + * @return string + */ + protected function quote($value) { + if (!isset($value)) { + return "NULL"; + } + if (is_array($value)) { // (a, b) IN ((1, 2), (3, 4)) + return "(" . implode(", ", array_map(array($this, 'quote'), $value)) . ")"; + } + $value = $this->formatValue($value); + if (is_float($value)) { + return sprintf("%F", $value); // otherwise depends on setlocale() + } + if ($value === false) { + return "0"; + } + if (is_int($value) || $value instanceof FluentLiteral) { // number or SQL code - for example "NOW()" + return (string)$value; + } + + return $this->fpdo->getPdo()->quote($value); + } + + /** + * @param \DateTime $val + * + * @return string + */ + private function formatValue($val) { + if ($val instanceof DateTime) { + return $val->format("Y-m-d H:i:s"); //! may be driver specific + } + + return $val; + } + + /** + * Select an item as object + * + * @param boolean|object $object If set to true, items are returned as stdClass, otherwise a class + * name can be passed and a new instance of this class is return. + * Can be set to false to return items as an associative array. + * + * @return \BaseQuery + */ + public function asObject($object = true) { + $this->object = $object; + + return $this; + } + +} diff --git a/vendor/FluentPDO/CommonQuery.php b/vendor/FluentPDO/CommonQuery.php new file mode 100644 index 0000000..1c4f2ee --- /dev/null +++ b/vendor/FluentPDO/CommonQuery.php @@ -0,0 +1,290 @@ +isSmartJoinEnabled = true; + + return $this; + } + + /** + * @return $this + */ + public function disableSmartJoin() { + $this->isSmartJoinEnabled = false; + + return $this; + } + + /** + * @return bool + */ + public function isSmartJoinEnabled() { + return $this->isSmartJoinEnabled; + } + + /** + * Add where condition, more calls appends with AND + * + * @param string $condition possibly containing ? or :name (PDO syntax) + * @param mixed $parameters array or a scalar value + * + * @return \SelectQuery + */ + public function where($condition, $parameters = array()) { + if ($condition === null) { + return $this->resetClause('WHERE'); + } + if (!$condition) { + return $this; + } + if (is_array($condition)) { // where(array("column1" => 1, "column2 > ?" => 2)) + foreach ($condition as $key => $val) { + $this->where($key, $val); + } + + return $this; + } + $args = func_get_args(); + if (count($args) == 1) { + return $this->addStatement('WHERE', $condition); + } + + // check that there are 2 arguments, a condition and a parameter value + // if the condition contains a parameter simply add them + // since its up to the user if it's valid sql or not + // Otherwise we're probably with just an identifier. So lets + // construct a new condition based on the passed parameter value. + if (count($args) == 2 && !preg_match('/(\?|:\w+)/i', $condition)) { + // condition is column only + if (is_null($parameters)) { + return $this->addStatement('WHERE', "$condition is NULL"); + } elseif ($args[1] === array()) { + return $this->addStatement('WHERE', 'FALSE'); + } elseif (is_array($args[1])) { + $in = $this->quote($args[1]); + + return $this->addStatement('WHERE', "$condition IN $in"); + } + $condition = "$condition = ?"; + } + array_shift($args); + + return $this->addStatement('WHERE', $condition, $args); + } + + /** + * @param $clause + * @param array $parameters - first is $statement followed by $parameters + * + * @return $this|\SelectQuery + */ + public function __call($clause, $parameters = array()) { + $clause = FluentUtils::toUpperWords($clause); + if ($clause == 'GROUP') { + $clause = 'GROUP BY'; + } + if ($clause == 'ORDER') { + $clause = 'ORDER BY'; + } + if ($clause == 'FOOT NOTE') { + $clause = "\n--"; + } + $statement = array_shift($parameters); + if (strpos($clause, 'JOIN') !== false) { + return $this->addJoinStatements($clause, $statement, $parameters); + } + + return $this->addStatement($clause, $statement, $parameters); + } + + /** + * @return string + */ + protected function getClauseJoin() { + return implode(' ', $this->statements['JOIN']); + } + + /** + * Statement can contain more tables (e.g. "table1.table2:table3:") + * + * @param $clause + * @param $statement + * @param array $parameters + * + * @return $this|\SelectQuery + */ + private function addJoinStatements($clause, $statement, $parameters = array()) { + if ($statement === null) { + $this->joins = array(); + + return $this->resetClause('JOIN'); + } + if (array_search(substr($statement, 0, -1), $this->joins) !== false) { + return $this; + } + + // match "tables AS alias" + preg_match('/`?([a-z_][a-z0-9_\.:]*)`?(\s+AS)?(\s+`?([a-z_][a-z0-9_]*)`?)?/i', $statement, $matches); + $joinAlias = ''; + $joinTable = ''; + if ($matches) { + $joinTable = $matches[1]; + if (isset($matches[4]) && !in_array(strtoupper($matches[4]), array('ON', 'USING'))) { + $joinAlias = $matches[4]; + } + } + + if (strpos(strtoupper($statement), ' ON ') || strpos(strtoupper($statement), ' USING')) { + if (!$joinAlias) { + $joinAlias = $joinTable; + } + if (in_array($joinAlias, $this->joins)) { + return $this; + } else { + $this->joins[] = $joinAlias; + $statement = " $clause $statement"; + + return $this->addStatement('JOIN', $statement, $parameters); + } + } + + // $joinTable is list of tables for join e.g.: table1.table2:table3.... + if (!in_array(substr($joinTable, -1), array('.', ':'))) { + $joinTable .= '.'; + } + + preg_match_all('/([a-z_][a-z0-9_]*[\.:]?)/i', $joinTable, $matches); + $mainTable = ''; + if (isset($this->statements['FROM'])) { + $mainTable = $this->statements['FROM']; + } elseif (isset($this->statements['UPDATE'])) { + $mainTable = $this->statements['UPDATE']; + } + $lastItem = array_pop($matches[1]); + array_push($matches[1], $lastItem); + foreach ($matches[1] as $joinItem) { + if ($mainTable == substr($joinItem, 0, -1)) { + continue; + } + + // use $joinAlias only for $lastItem + $alias = ''; + if ($joinItem == $lastItem) { + $alias = $joinAlias; + } + + $newJoin = $this->createJoinStatement($clause, $mainTable, $joinItem, $alias); + if ($newJoin) { + $this->addStatement('JOIN', $newJoin, $parameters); + } + $mainTable = $joinItem; + } + + return $this; + } + + /** + * Create join string + * + * @param $clause + * @param $mainTable + * @param $joinTable + * @param string $joinAlias + * + * @return string + */ + private function createJoinStatement($clause, $mainTable, $joinTable, $joinAlias = '') { + if (in_array(substr($mainTable, -1), array(':', '.'))) { + $mainTable = substr($mainTable, 0, -1); + } + $referenceDirection = substr($joinTable, -1); + $joinTable = substr($joinTable, 0, -1); + $asJoinAlias = ''; + if ($joinAlias) { + $asJoinAlias = " AS $joinAlias"; + } else { + $joinAlias = $joinTable; + } + if (in_array($joinAlias, $this->joins)) { + // if join exists don't create same again + return ''; + } else { + $this->joins[] = $joinAlias; + } + if ($referenceDirection == ':') { + // back reference + $primaryKey = $this->getStructure()->getPrimaryKey($mainTable); + $foreignKey = $this->getStructure()->getForeignKey($mainTable); + + return " $clause $joinTable$asJoinAlias ON $joinAlias.$foreignKey = $mainTable.$primaryKey"; + } else { + $primaryKey = $this->getStructure()->getPrimaryKey($joinTable); + $foreignKey = $this->getStructure()->getForeignKey($joinTable); + + return " $clause $joinTable$asJoinAlias ON $joinAlias.$primaryKey = $mainTable.$foreignKey"; + } + } + + /** + * @return string + */ + protected function buildQuery() { + // first create extra join from statements with columns with referenced tables + $statementsWithReferences = array('WHERE', 'SELECT', 'GROUP BY', 'ORDER BY'); + foreach ($statementsWithReferences as $clause) { + if (array_key_exists($clause, $this->statements)) { + $this->statements[$clause] = array_map(array($this, 'createUndefinedJoins'), $this->statements[$clause]); + } + } + + return parent::buildQuery(); + } + + /** + * Create undefined joins from statement with column with referenced tables + * + * @param string $statement + * + * @return string rewrited $statement (e.g. tab1.tab2:col => tab2.col) + */ + private function createUndefinedJoins($statement) { + if (!$this->isSmartJoinEnabled) { + return $statement; + } + + preg_match_all('/\\b([a-z_][a-z0-9_.:]*[.:])[a-z_]*/i', $statement, $matches); + foreach ($matches[1] as $join) { + if (!in_array(substr($join, 0, -1), $this->joins)) { + $this->addJoinStatements('LEFT JOIN', $join); + } + } + + // don't rewrite table from other databases + foreach ($this->joins as $join) { + if (strpos($join, '.') !== false && strpos($statement, $join) === 0) { + return $statement; + } + } + + // remove extra referenced tables (rewrite tab1.tab2:col => tab2.col) + $statement = preg_replace('/(?:\\b[a-z_][a-z0-9_.:]*[.:])?([a-z_][a-z0-9_]*)[.:]([a-z_*])/i', '\\1.\\2', $statement); + + return $statement; + } + +} diff --git a/vendor/FluentPDO/DeleteQuery.php b/vendor/FluentPDO/DeleteQuery.php new file mode 100644 index 0000000..f3a133d --- /dev/null +++ b/vendor/FluentPDO/DeleteQuery.php @@ -0,0 +1,94 @@ + array($this, 'getClauseDeleteFrom'), + 'DELETE' => array($this, 'getClauseDelete'), + 'FROM' => null, + 'JOIN' => array($this, 'getClauseJoin'), + 'WHERE' => ' AND ', + 'ORDER BY' => ', ', + 'LIMIT' => null, + ); + + parent::__construct($fpdo, $clauses); + + $this->statements['DELETE FROM'] = $table; + $this->statements['DELETE'] = $table; + } + + /** + * Forces delete operation to fail silently + * + * @return \DeleteQuery + */ + public function ignore() { + $this->ignore = true; + + return $this; + } + + /** + * @return string + */ + protected function buildQuery() { + if ($this->statements['FROM']) { + unset($this->clauses['DELETE FROM']); + } else { + unset($this->clauses['DELETE']); + } + + return parent::buildQuery(); + } + + /** + * Execute DELETE query + * + * @return bool + */ + public function execute() { + $result = parent::execute(); + if ($result) { + return $result->rowCount(); + } + + return false; + } + + /** + * @return string + */ + protected function getClauseDelete() { + return 'DELETE' . ($this->ignore ? " IGNORE" : '') . ' ' . $this->statements['DELETE']; + } + + /** + * @return string + */ + protected function getClauseDeleteFrom() { + return 'DELETE' . ($this->ignore ? " IGNORE" : '') . ' FROM ' . $this->statements['DELETE FROM']; + } + +} diff --git a/vendor/FluentPDO/FluentLiteral.php b/vendor/FluentPDO/FluentLiteral.php new file mode 100644 index 0000000..ccfd199 --- /dev/null +++ b/vendor/FluentPDO/FluentLiteral.php @@ -0,0 +1,30 @@ +value = $value; + } + + /** + * Get literal value + * + * @return string + */ + function __toString() { + return $this->value; + } + +} diff --git a/vendor/FluentPDO/FluentPDO.php b/vendor/FluentPDO/FluentPDO.php new file mode 100644 index 0000000..02e83e9 --- /dev/null +++ b/vendor/FluentPDO/FluentPDO.php @@ -0,0 +1,164 @@ +pdo = $pdo; + if (!$structure) { + $structure = new FluentStructure(); + } + $this->structure = $structure; + } + + /** + * Create SELECT query from $table + * + * @param string $table - db table name + * @param integer $primaryKey - return one row by primary key + * + * @return \SelectQuery + */ + public function from($table, $primaryKey = null) { + $query = new SelectQuery($this, $table); + if ($primaryKey !== null) { + $tableTable = $query->getFromTable(); + $tableAlias = $query->getFromAlias(); + $primaryKeyName = $this->structure->getPrimaryKey($tableTable); + $query = $query->where("$tableAlias.$primaryKeyName", $primaryKey); + } + + return $query; + } + + /** + * Create INSERT INTO query + * + * @param string $table + * @param array $values - accepts one or multiple rows, @see docs + * + * @return \InsertQuery + */ + public function insertInto($table, $values = array()) { + $query = new InsertQuery($this, $table, $values); + + return $query; + } + + /** + * Create UPDATE query + * + * @param string $table + * @param array|string $set + * @param string $primaryKey + * + * @return \UpdateQuery + */ + public function update($table, $set = array(), $primaryKey = null) { + $query = new UpdateQuery($this, $table); + $query->set($set); + if ($primaryKey) { + $primaryKeyName = $this->getStructure()->getPrimaryKey($table); + $query = $query->where($primaryKeyName, $primaryKey); + } + + return $query; + } + + /** + * Create DELETE query + * + * @param string $table + * @param string $primaryKey delete only row by primary key + * + * @return \DeleteQuery + */ + public function delete($table, $primaryKey = null) { + $query = new DeleteQuery($this, $table); + if ($primaryKey) { + $primaryKeyName = $this->getStructure()->getPrimaryKey($table); + $query = $query->where($primaryKeyName, $primaryKey); + } + + return $query; + } + + /** + * Create DELETE FROM query + * + * @param string $table + * @param string $primaryKey + * + * @return \DeleteQuery + */ + public function deleteFrom($table, $primaryKey = null) { + $args = func_get_args(); + + return call_user_func_array(array($this, 'delete'), $args); + } + + /** + * @return \PDO + */ + public function getPdo() { + return $this->pdo; + } + + /** + * @return \FluentStructure + */ + public function getStructure() { + return $this->structure; + } + + /** + * Closes the \PDO connection to the database + * + * @return null + */ + public function close() { + $this->pdo = null; + } + +} diff --git a/vendor/FluentPDO/FluentStructure.php b/vendor/FluentPDO/FluentStructure.php new file mode 100644 index 0000000..acac327 --- /dev/null +++ b/vendor/FluentPDO/FluentStructure.php @@ -0,0 +1,60 @@ +primaryKey = $primaryKey; + $this->foreignKey = $foreignKey; + } + + /** + * @param string $table + * + * @return string + */ + public function getPrimaryKey($table) { + return $this->key($this->primaryKey, $table); + } + + /** + * @param string $table + * + * @return string + */ + public function getForeignKey($table) { + return $this->key($this->foreignKey, $table); + } + + /** + * @param string|callback $key + * @param string $table + * + * @return string + */ + private function key($key, $table) { + if (is_callable($key)) { + return $key($table); + } + + return sprintf($key, $table); + } + +} diff --git a/vendor/FluentPDO/FluentUtils.php b/vendor/FluentPDO/FluentUtils.php new file mode 100644 index 0000000..a4ae98b --- /dev/null +++ b/vendor/FluentPDO/FluentUtils.php @@ -0,0 +1,86 @@ +getColumnMeta($i); $i++) + { + $type = $columnMeta['native_type']; + + switch($type) + { + case 'DECIMAL': + case 'TINY': + case 'SHORT': + case 'LONG': + case 'LONGLONG': + case 'INT24': + if(isset($rows[$columnMeta['name']])){ + $rows[$columnMeta['name']] = $rows[$columnMeta['name']] + 0; + }else{ + if(is_array($rows) || $rows instanceof Traversable){ + foreach($rows as &$row){ + if(isset($row[$columnMeta['name']])){ + $row[$columnMeta['name']] = $row[$columnMeta['name']] + 0; + } + } + } + } + break; + case 'DATETIME': + case 'DATE': + case 'TIMESTAMP': + // convert to date type? + break; + // default: keep as string + } + } + return $rows; + } + +} diff --git a/vendor/FluentPDO/InsertQuery.php b/vendor/FluentPDO/InsertQuery.php new file mode 100644 index 0000000..d7f4552 --- /dev/null +++ b/vendor/FluentPDO/InsertQuery.php @@ -0,0 +1,221 @@ + array($this, 'getClauseInsertInto'), + 'VALUES' => array($this, 'getClauseValues'), + 'ON DUPLICATE KEY UPDATE' => array($this, 'getClauseOnDuplicateKeyUpdate'), + ); + parent::__construct($fpdo, $clauses); + + $this->statements['INSERT INTO'] = $table; + $this->values($values); + } + + /** + * Execute insert query + * + * @param mixed $sequence + * + * @return integer last inserted id or false + */ + public function execute($sequence = null) { + $result = parent::execute(); + if ($result) { + return $this->getPDO()->lastInsertId($sequence); + } + + return false; + } + + /** + * Add ON DUPLICATE KEY UPDATE + * + * @param array $values + * + * @return \InsertQuery + */ + public function onDuplicateKeyUpdate($values) { + $this->statements['ON DUPLICATE KEY UPDATE'] = array_merge( + $this->statements['ON DUPLICATE KEY UPDATE'], $values + ); + + return $this; + } + + /** + * Add VALUES + * + * @param $values + * + * @return \InsertQuery + * @throws Exception + */ + public function values($values) { + if (!is_array($values)) { + throw new Exception('Param VALUES for INSERT query must be array'); + } + $first = current($values); + if (is_string(key($values))) { + // is one row array + $this->addOneValue($values); + } elseif (is_array($first) && is_string(key($first))) { + // this is multi values + foreach ($values as $oneValue) { + $this->addOneValue($oneValue); + } + } + + return $this; + } + + /** + * Force insert operation to fail silently + * + * @return \InsertQuery + */ + public function ignore() { + $this->ignore = true; + + return $this; + } + + /** Force insert operation delay support + * + * @return \InsertQuery + */ + public function delayed() { + $this->delayed = true; + + return $this; + } + + /** + * @return string + */ + protected function getClauseInsertInto() { + return 'INSERT' . ($this->ignore ? " IGNORE" : '') . ($this->delayed ? " DELAYED" : '') . ' INTO ' . $this->statements['INSERT INTO']; + } + + /** + * @param $param + * + * @return string + */ + protected function parameterGetValue($param) { + return $param instanceof FluentLiteral ? (string)$param : '?'; + } + + /** + * @return string + */ + protected function getClauseValues() { + $valuesArray = array(); + foreach ($this->statements['VALUES'] as $rows) { + // literals should not be parametrized. + // They are commonly used to call engine functions or literals. + // Eg: NOW(), CURRENT_TIMESTAMP etc + $placeholders = array_map(array($this, 'parameterGetValue'), $rows); + $valuesArray[] = '(' . implode(', ', $placeholders) . ')'; + } + + $columns = implode(', ', $this->columns); + $values = implode(', ', $valuesArray); + + return " ($columns) VALUES $values"; + } + + /** + * Removes all FluentLiteral instances from the argument + * since they are not to be used as PDO parameters but rather injected directly into the query + * + * @param $statements + * + * @return array + */ + protected function filterLiterals($statements) { + $f = function ($item) { + return !$item instanceof FluentLiteral; + }; + + return array_map(function ($item) use ($f) { + if (is_array($item)) { + return array_filter($item, $f); + } + + return $item; + }, array_filter($statements, $f)); + } + + /** + * @return array + */ + protected function buildParameters() { + $this->parameters = array_merge( + $this->filterLiterals($this->statements['VALUES']), + $this->filterLiterals($this->statements['ON DUPLICATE KEY UPDATE']) + ); + + return parent::buildParameters(); + } + + /** + * @return string + */ + protected function getClauseOnDuplicateKeyUpdate() { + $result = array(); + foreach ($this->statements['ON DUPLICATE KEY UPDATE'] as $key => $value) { + $result[] = "$key = " . $this->parameterGetValue($value); + } + + return ' ON DUPLICATE KEY UPDATE ' . implode(', ', $result); + } + + /** + * @param array $oneValue + * + * @throws Exception + */ + private function addOneValue($oneValue) { + // check if all $keys are strings + foreach ($oneValue as $key => $value) { + if (!is_string($key)) { + throw new Exception('INSERT query: All keys of value array have to be strings.'); + } + } + if (!$this->firstValue) { + $this->firstValue = $oneValue; + } + if (!$this->columns) { + $this->columns = array_keys($oneValue); + } + if ($this->columns != array_keys($oneValue)) { + throw new Exception('INSERT query: All VALUES have to same keys (columns).'); + } + $this->statements['VALUES'][] = $oneValue; + } + +} diff --git a/vendor/FluentPDO/SelectQuery.php b/vendor/FluentPDO/SelectQuery.php new file mode 100644 index 0000000..3ded784 --- /dev/null +++ b/vendor/FluentPDO/SelectQuery.php @@ -0,0 +1,194 @@ + ', ', + 'FROM' => null, + 'JOIN' => array($this, 'getClauseJoin'), + 'WHERE' => ' AND ', + 'GROUP BY' => ',', + 'HAVING' => ' AND ', + 'ORDER BY' => ', ', + 'LIMIT' => null, + 'OFFSET' => null, + "\n--" => "\n--", + ); + parent::__construct($fpdo, $clauses); + + // initialize statements + $fromParts = explode(' ', $from); + $this->fromTable = reset($fromParts); + $this->fromAlias = end($fromParts); + + $this->statements['FROM'] = $from; + $this->statements['SELECT'][] = $this->fromAlias . '.*'; + $this->joins[] = $this->fromAlias; + + if(isset($fpdo->convertTypes) && $fpdo->convertTypes){ + $this->convertTypes = true; + } + } + + /** Return table name from FROM clause + * + * @internal + */ + public function getFromTable() { + return $this->fromTable; + } + + /** Return table alias from FROM clause + * + * @internal + */ + public function getFromAlias() { + return $this->fromAlias; + } + + /** + * Returns a single column + * + * @param int $columnNumber + * + * @return string + */ + public function fetchColumn($columnNumber = 0) { + if (($s = $this->execute()) !== false) { + return $s->fetchColumn($columnNumber); + } + + return $s; + } + + /** + * Fetch first row or column + * + * @param string $column column name or empty string for the whole row + * + * @return mixed string, array or false if there is no row + */ + public function fetch($column = '') { + $s = $this->execute(); + if ($s === false) { + return false; + } + $row = $s->fetch(); + + if($this->convertTypes){ + $row = FluentUtils::convertToNativeTypes($s,$row); + } + + if ($row && $column != '') { + if (is_object($row)) { + return $row->{$column}; + } else { + return $row[$column]; + } + } + + return $row; + } + + /** + * Fetch pairs + * + * @param $key + * @param $value + * @param $object + * + * @return array of fetched rows as pairs + */ + public function fetchPairs($key, $value, $object = false) { + if (($s = $this->select(null)->select("$key, $value")->asObject($object)->execute()) !== false) { + return $s->fetchAll(PDO::FETCH_KEY_PAIR); + } + + return $s; + } + + /** Fetch all row + * + * @param string $index specify index column + * @param string $selectOnly select columns which could be fetched + * + * @return array of fetched rows + */ + public function fetchAll($index = '', $selectOnly = '') { + if ($selectOnly) { + $this->select(null)->select($index . ', ' . $selectOnly); + } + if ($index) { + $data = array(); + foreach ($this as $row) { + if (is_object($row)) { + $data[$row->{$index}] = $row; + } else { + $data[$row[$index]] = $row; + } + } + + return $data; + } else { + if (($s = $this->execute()) !== false) { + if($this->convertTypes){ + return FluentUtils::convertToNativeTypes($s,$s->fetchAll()); + }else{ + return $s->fetchAll(); + } + } + + return $s; + } + } + + /** + * Countable interface doesn't break current \FluentPDO select query + * + * @return int + */ + public function count() { + $fpdo = clone $this; + + return (int)$fpdo->select(null)->select('COUNT(*)')->fetchColumn(); + } + + public function getIterator() { + if($this->convertTypes){ + return new ArrayIterator($this->fetchAll()); + }else{ + return $this->execute(); + } + } + +} diff --git a/vendor/FluentPDO/UpdateQuery.php b/vendor/FluentPDO/UpdateQuery.php new file mode 100644 index 0000000..eff4731 --- /dev/null +++ b/vendor/FluentPDO/UpdateQuery.php @@ -0,0 +1,107 @@ + array($this, 'getClauseUpdate'), + 'JOIN' => array($this, 'getClauseJoin'), + 'SET' => array($this, 'getClauseSet'), + 'WHERE' => ' AND ', + 'ORDER BY' => ', ', + 'LIMIT' => null, + ); + parent::__construct($fpdo, $clauses); + + $this->statements['UPDATE'] = $table; + + $tableParts = explode(' ', $table); + $this->joins[] = end($tableParts); + } + + /** + * @param string|array $fieldOrArray + * @param bool|string $value + * + * @return $this + * @throws Exception + */ + public function set($fieldOrArray, $value = false) { + if (!$fieldOrArray) { + return $this; + } + if (is_string($fieldOrArray) && $value !== false) { + $this->statements['SET'][$fieldOrArray] = $value; + } else { + if (!is_array($fieldOrArray)) { + throw new Exception('You must pass a value, or provide the SET list as an associative array. column => value'); + } else { + foreach ($fieldOrArray as $field => $value) { + $this->statements['SET'][$field] = $value; + } + } + } + + return $this; + } + + /** + * Execute update query + * + * @param boolean $getResultAsPdoStatement true to return the pdo statement instead of row count + * + * @return int|boolean|\PDOStatement + */ + public function execute($getResultAsPdoStatement = false) { + $result = parent::execute(); + if ($getResultAsPdoStatement) { + return $result; + } + if ($result) { + return $result->rowCount(); + } + + return false; + } + + /** + * @return string + */ + protected function getClauseUpdate() { + return 'UPDATE ' . $this->statements['UPDATE']; + } + + /** + * @return string + */ + protected function getClauseSet() { + $setArray = array(); + foreach ($this->statements['SET'] as $field => $value) { + if ($value instanceof FluentLiteral) { + $setArray[] = $field . ' = ' . $value; + } else { + $setArray[] = $field . ' = ?'; + $this->parameters['SET'][$field] = $value; + } + } + + return ' SET ' . implode(', ', $setArray); + } + +}