├── .gitignore ├── diary.db ├── README.md ├── LICENSE ├── database.php ├── diary.php └── medoo.php /.gitignore: -------------------------------------------------------------------------------- 1 | token.php 2 | -------------------------------------------------------------------------------- /diary.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banghasan/telegrambotphpsql/HEAD/diary.db -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Bot 2 | # PHP dan Database (SQL) 3 | 4 | Ini adalah file-file materi ebook ke-2. 5 | 6 | Buatlah file `token.php` yang berisi : 7 | 8 | 'sqlite', 19 | 'database_file' => 'diary.db', 20 | ]); 21 | 22 | // uncomment ini jika menggunakan mySQL atau mariaDB 23 | // sesuaikan nama database, host, user, dan passwordnya 24 | /* 25 | $database = new medoo([ 26 | 'database_type' => 'mysql', 27 | 'database_name' => 'diary', 28 | 'server' => 'localhost', 29 | 'username' => 'root', 30 | 'password' => 'rahasiakusendiri', 31 | 'charset' => 'utf8' 32 | ]); 33 | */ 34 | 35 | // fungsi untuk menambah diary 36 | function diarytambah($iduser, $pesan) 37 | { 38 | global $database; 39 | $last_id = $database->insert('catatan', [ 40 | 'id' => $iduser, 41 | 'waktu' => date('Y-m-d H:i:s').' WIB', 42 | 'pesan' => $pesan, 43 | ]); 44 | 45 | return $last_id; 46 | } 47 | 48 | // fungsi menghapus diary 49 | function diaryhapus($iduser, $idpesan) 50 | { 51 | global $database; 52 | $database->delete('catatan', [ 53 | 'AND' => [ 54 | 'id' => $iduser, 55 | 'no' => $idpesan, 56 | ], 57 | ]); 58 | 59 | return '⛔️ telah dilaksanakan..'; 60 | } 61 | 62 | // fungsi melihat daftar diary user 63 | function diarylist($iduser, $page = 0) 64 | { 65 | global $database; 66 | $hasil = '😢 Maaf ya, tidak ada catatan diary di hatiku..'; 67 | $datas = $database->select('catatan', [ 68 | 'no', 69 | 'id', 70 | 'waktu', 71 | 'pesan', 72 | ], [ 73 | 'id' => $iduser, 74 | ]); 75 | $jml = count($datas); 76 | if ($jml > 0) { 77 | $hasil = "✍🏽 *$jml Catatan Diary-mu Kusimpan Rapi Dihati:*\n"; 78 | $n = 0; 79 | foreach ($datas as $data) { 80 | $n++; 81 | $hasil .= "\n$n. ".substr($data['pesan'], 0, 10)."...\n⌛️ `$data[waktu]`\n"; 82 | $hasil .= "\n👀 /view\_$data[no]\n"; 83 | } 84 | } 85 | 86 | return $hasil; 87 | } 88 | 89 | // fungsi melihat isi pesan diary 90 | function diaryview($iduser, $idpesan) 91 | { 92 | global $database; 93 | $hasil = "😢 Maaf ya, diarymu yang itu tidak ditemukan dihatiku.\nMungkin saja bukan buatmu.."; 94 | $datas = $database->select('catatan', [ 95 | 'no', 96 | 'id', 97 | 'waktu', 98 | 'pesan', 99 | ], [ 100 | 'AND' => [ 101 | 'id' => $iduser, 102 | 'no' => $idpesan, 103 | ], 104 | ]); 105 | $jml = count($datas); 106 | if ($jml > 0) { 107 | $data = $datas[0]; 108 | $hasil = "✍🏽 Diary nomor $data[no] yang tersimpan dihatiku berisi:\n~~~~~~~~~~~~~~~~~~~~~~~\n"; 109 | $hasil .= "\n$data[pesan]\n\n⌛️ `$data[waktu]`"; 110 | $hasil .= "\n\n📛 Hapus? /hapus\_$data[no]"; 111 | } 112 | 113 | return $hasil; 114 | } 115 | 116 | // fungsi mencari pesan di diary 117 | function diarycari($iduser, $pesan) 118 | { 119 | global $database; 120 | $hasil = '😢 Maaf ya, apa yang kau cari selama ini tidak ditemukan..'; 121 | $datas = $database->select('catatan', [ 122 | 'no', 123 | 'id', 124 | 'waktu', 125 | 'pesan', 126 | ], [ 127 | 'pesan[~]' => $pesan, 128 | ]); 129 | $jml = count($datas); 130 | if ($jml > 0) { 131 | $hasil = "✍🏽 *$jml Catatan Diary-mu yang kau cari selalu kusimpan di hatiku*\n"; 132 | $n = 0; 133 | foreach ($datas as $data) { 134 | $n++; 135 | $hasil .= "\n$n. ".substr($data['pesan'], 0, 10)."...\n⌛️ `$data[waktu]`\n"; 136 | $hasil .= "\n👀 /view\_$data[no]\n"; 137 | } 138 | } 139 | 140 | return $hasil; 141 | } 142 | -------------------------------------------------------------------------------- /diary.php: -------------------------------------------------------------------------------- 1 | = 500) { 72 | // do not wat to DDOS server if something goes wrong 73 | sleep(10); 74 | 75 | return false; 76 | } elseif ($http_code != 200) { 77 | $response = json_decode($response, true); 78 | error_log("Request has failed with error {$response['error_code']}: {$response['description']}\n"); 79 | if ($http_code == 401) { 80 | throw new Exception('Invalid access token provided'); 81 | } 82 | 83 | return false; 84 | } else { 85 | $response = json_decode($response, true); 86 | if (isset($response['description'])) { 87 | error_log("Request was successfull: {$response['description']}\n"); 88 | } 89 | $response = $response['result']; 90 | } 91 | 92 | return $response; 93 | } 94 | 95 | function apiRequest($method, $parameters = null) 96 | { 97 | if (!is_string($method)) { 98 | error_log("Method name must be a string\n"); 99 | 100 | return false; 101 | } 102 | 103 | if (!$parameters) { 104 | $parameters = []; 105 | } elseif (!is_array($parameters)) { 106 | error_log("Parameters must be an array\n"); 107 | 108 | return false; 109 | } 110 | 111 | foreach ($parameters as $key => &$val) { 112 | // encoding to JSON array parameters, for example reply_markup 113 | if (!is_numeric($val) && !is_string($val)) { 114 | $val = json_encode($val); 115 | } 116 | } 117 | $url = API_URL.$method.'?'.http_build_query($parameters); 118 | 119 | $handle = curl_init($url); 120 | curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); 121 | curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5); 122 | curl_setopt($handle, CURLOPT_TIMEOUT, 60); 123 | curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false); 124 | 125 | return exec_curl_request($handle); 126 | } 127 | 128 | function apiRequestJson($method, $parameters) 129 | { 130 | if (!is_string($method)) { 131 | error_log("Method name must be a string\n"); 132 | 133 | return false; 134 | } 135 | 136 | if (!$parameters) { 137 | $parameters = []; 138 | } elseif (!is_array($parameters)) { 139 | error_log("Parameters must be an array\n"); 140 | 141 | return false; 142 | } 143 | 144 | $parameters['method'] = $method; 145 | 146 | $handle = curl_init(API_URL); 147 | curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); 148 | curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5); 149 | curl_setopt($handle, CURLOPT_TIMEOUT, 60); 150 | curl_setopt($handle, CURLOPT_POSTFIELDS, json_encode($parameters)); 151 | curl_setopt($handle, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); 152 | 153 | return exec_curl_request($handle); 154 | } 155 | 156 | // jebakan token, klo ga diisi akan mati 157 | if (strlen(BOT_TOKEN) < 20) { 158 | die(PHP_EOL."-> -> Token BOT API nya mohon diisi dengan benar!\n"); 159 | } 160 | 161 | function getUpdates($last_id = null) 162 | { 163 | $params = []; 164 | if (!empty($last_id)) { 165 | $params = ['offset' => $last_id + 1, 'limit' => 1]; 166 | } 167 | //echo print_r($params, true); 168 | return apiRequest('getUpdates', $params); 169 | } 170 | 171 | // matikan ini jika ingin bot berjalan 172 | die('baca dengan teliti yak!'); 173 | 174 | // ----------- pantengin mulai ini 175 | function sendMessage($idpesan, $idchat, $pesan) 176 | { 177 | $data = [ 178 | 'chat_id' => $idchat, 179 | 'text' => $pesan, 180 | 'parse_mode' => 'Markdown', 181 | 'reply_to_message_id' => $idpesan, 182 | ]; 183 | 184 | return apiRequest('sendMessage', $data); 185 | } 186 | 187 | function processMessage($message) 188 | { 189 | global $database; 190 | if ($GLOBALS['debug']) { 191 | print_r($message); 192 | } 193 | 194 | if (isset($message['message'])) { 195 | $sumber = $message['message']; 196 | $idpesan = $sumber['message_id']; 197 | $idchat = $sumber['chat']['id']; 198 | 199 | $namamu = $sumber['from']['first_name']; 200 | $iduser = $sumber['from']['id']; 201 | 202 | if (isset($sumber['text'])) { 203 | $pesan = $sumber['text']; 204 | 205 | if (preg_match("/^\/view_(\d+)$/i", $pesan, $cocok)) { 206 | $pesan = "/view $cocok[1]"; 207 | } 208 | 209 | if (preg_match("/^\/hapus_(\d+)$/i", $pesan, $cocok)) { 210 | $pesan = "/hapus $cocok[1]"; 211 | } 212 | 213 | // print_r($pesan); 214 | 215 | $pecah = explode(' ', $pesan, 2); 216 | $katapertama = strtolower($pecah[0]); 217 | switch ($katapertama) { 218 | case '/start': 219 | $text = "Hai `$namamu`.. Akhirnya kita bertemu!\n\nUntuk bantuan ketik: /help"; 220 | break; 221 | 222 | case '/help': 223 | $text = '💁🏼 Aku adalah *diary bot* ver.`'.myVERSI."`\n"; 224 | $text .= "🎓 Oleh _Hasanudin HS_\n⌛️".lastUPDATE."\n\n"; 225 | $text .= "💌 Berikut menu yang tersedia spesial buat kamu, iya kamu..\n\n"; 226 | $text .= "➕ /tambah `[pesan]` untuk menambah catatan\n"; 227 | $text .= "🔃 /list melihat daftar catatan tersedia\n"; 228 | $text .= "🔍 /cari mencari catatan\n"; 229 | $text .= "⌛️ /time info waktu sekarang\n"; 230 | $text .= "🆘 /help info bantuan ini\n\n"; 231 | $text .= '😎 *Ingin diskusi?* Silakan bergabung ke @botphp'; 232 | break; 233 | 234 | case '/time': 235 | $text = "⌛️ Waktu Sekarang :\n"; 236 | $text .= date('d-m-Y H:i:s'); 237 | break; 238 | 239 | case '/tambah': 240 | if (isset($pecah[1])) { 241 | $pesanproses = $pecah[1]; 242 | $r = diarytambah($iduser, $pesanproses); 243 | $text = '😘 Goresan catatan indahmu telah berhasil kusematkan di dalam hatiku!'; 244 | } else { 245 | $text = '⛔️ *ERROR:* _Pesan yang ditambahkan tidak boleh kosong!_'; 246 | $text .= "\n\nContoh: `/pesan besok aku sahur mau puasa sunnah`"; 247 | } 248 | break; 249 | 250 | case '/view': 251 | if (isset($pecah[1])) { 252 | $pesanproses = $pecah[1]; 253 | $text = diaryview($iduser, $pesanproses); 254 | } else { 255 | $text = '⛔️ *ERROR:* `nomor pesan tidak boleh kosong.`'; 256 | } 257 | break; 258 | 259 | case '/hapus': 260 | if (isset($pecah[1])) { 261 | $pesanproses = $pecah[1]; 262 | $text = diaryhapus($iduser, $pesanproses); 263 | } else { 264 | $text = '⛔️ *ERROR:* `nomor pesan tidak boleh kosong.`'; 265 | } 266 | break; 267 | 268 | case '/list': 269 | $text = diarylist($iduser); 270 | if ($GLOBALS['debug']) { 271 | print_r($text); 272 | } 273 | break; 274 | 275 | case '/cari': 276 | // saya gunakan pregmatch ini salah satunya untuk mencegah SQL injection 277 | // hanya huruf dan angka saja yang akan diproses 278 | if (preg_match("/^\/cari ((\w| )+)$/i", $pesan, $cocok)) { 279 | $pesanproses = $cocok[1]; 280 | $text = diarycari($iduser, $pesanproses); 281 | } else { 282 | $text = '⛔️ *ERROR:* `kata kunci harus berupa kata (huruf dan angka) saja.`'; 283 | } 284 | break; 285 | 286 | default: 287 | $text = '😥 _aku tidak mengerti apa maksudmu, namun tetap akan kudengarkan sepenuh hatiku.._'; 288 | break; 289 | } 290 | } else { 291 | $text = 'Ada sesuatu di bola matamu..'; 292 | } 293 | 294 | $hasil = sendMessage($idpesan, $idchat, $text); 295 | if ($GLOBALS['debug']) { 296 | // hanya nampak saat metode poll dan debug = true; 297 | echo 'Pesan yang dikirim: '.$text.PHP_EOL; 298 | print_r($hasil); 299 | } 300 | } 301 | } 302 | 303 | // pencetakan versi dan info waktu server, berfungsi jika test hook 304 | echo 'Ver. '.myVERSI.' OK Start!'.PHP_EOL.date('Y-m-d H:i:s').PHP_EOL; 305 | 306 | function printUpdates($result) 307 | { 308 | foreach ($result as $obj) { 309 | // echo $obj['message']['text'].PHP_EOL; 310 | processMessage($obj); 311 | $last_id = $obj['update_id']; 312 | } 313 | 314 | return $last_id; 315 | } 316 | 317 | // AKTIFKAN INI jika menggunakan metode poll 318 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 319 | $last_id = null; 320 | while (true) { 321 | $result = getUpdates($last_id); 322 | if (!empty($result)) { 323 | echo '+'; 324 | $last_id = printUpdates($result); 325 | } else { 326 | echo '-'; 327 | } 328 | 329 | sleep(1); 330 | } 331 | 332 | // AKTIFKAN INI jika menggunakan metode webhook 333 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 334 | /*$content = file_get_contents("php://input"); 335 | $update = json_decode($content, true); 336 | 337 | if (!$update) { 338 | // ini jebakan jika ada yang iseng mengirim sesuatu ke hook 339 | // dan tidak sesuai format JSON harus ditolak! 340 | exit; 341 | } else { 342 | // sesuai format JSON, proses pesannya 343 | processMessage($update); 344 | }*/ 345 | 346 | /* 347 | 348 | Sekian. 349 | 350 | */ 351 | -------------------------------------------------------------------------------- /medoo.php: -------------------------------------------------------------------------------- 1 | $value) { 45 | $this->$option = $value; 46 | } 47 | } else { 48 | return false; 49 | } 50 | if ( 51 | isset($this->port) && 52 | is_int($this->port * 1) 53 | ) { 54 | $port = $this->port; 55 | } 56 | $type = strtolower($this->database_type); 57 | $is_port = isset($port); 58 | if (isset($options['prefix'])) { 59 | $this->prefix = $options['prefix']; 60 | } 61 | switch ($type) { 62 | case 'mariadb': 63 | $type = 'mysql'; 64 | case 'mysql': 65 | if ($this->socket) { 66 | $dsn = $type.':unix_socket='.$this->socket.';dbname='.$this->database_name; 67 | } else { 68 | $dsn = $type.':host='.$this->server.($is_port ? ';port='.$port : '').';dbname='.$this->database_name; 69 | } 70 | // Make MySQL using standard quoted identifier 71 | $commands[] = 'SET SQL_MODE=ANSI_QUOTES'; 72 | break; 73 | case 'pgsql': 74 | $dsn = $type.':host='.$this->server.($is_port ? ';port='.$port : '').';dbname='.$this->database_name; 75 | break; 76 | case 'sybase': 77 | $dsn = 'dblib:host='.$this->server.($is_port ? ':'.$port : '').';dbname='.$this->database_name; 78 | break; 79 | case 'oracle': 80 | $dbname = $this->server ? 81 | '//'.$this->server.($is_port ? ':'.$port : ':1521').'/'.$this->database_name : 82 | $this->database_name; 83 | $dsn = 'oci:dbname='.$dbname.($this->charset ? ';charset='.$this->charset : ''); 84 | break; 85 | case 'mssql': 86 | $dsn = strstr(PHP_OS, 'WIN') ? 87 | 'sqlsrv:server='.$this->server.($is_port ? ','.$port : '').';database='.$this->database_name : 88 | 'dblib:host='.$this->server.($is_port ? ':'.$port : '').';dbname='.$this->database_name; 89 | // Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting 90 | $commands[] = 'SET QUOTED_IDENTIFIER ON'; 91 | break; 92 | case 'sqlite': 93 | $dsn = $type.':'.$this->database_file; 94 | $this->username = null; 95 | $this->password = null; 96 | break; 97 | } 98 | if ( 99 | in_array($type, ['mariadb', 'mysql', 'pgsql', 'sybase', 'mssql']) && 100 | $this->charset 101 | ) { 102 | $commands[] = "SET NAMES '".$this->charset."'"; 103 | } 104 | $this->pdo = new PDO( 105 | $dsn, 106 | $this->username, 107 | $this->password, 108 | $this->option 109 | ); 110 | foreach ($commands as $value) { 111 | $this->pdo->exec($value); 112 | } 113 | } catch (PDOException $e) { 114 | throw new Exception($e->getMessage()); 115 | } 116 | } 117 | 118 | public function query($query) 119 | { 120 | if ($this->debug_mode) { 121 | echo $query; 122 | $this->debug_mode = false; 123 | 124 | return false; 125 | } 126 | $this->logs[] = $query; 127 | 128 | return $this->pdo->query($query); 129 | } 130 | 131 | public function exec($query) 132 | { 133 | if ($this->debug_mode) { 134 | echo $query; 135 | $this->debug_mode = false; 136 | 137 | return false; 138 | } 139 | $this->logs[] = $query; 140 | 141 | return $this->pdo->exec($query); 142 | } 143 | 144 | public function quote($string) 145 | { 146 | return $this->pdo->quote($string); 147 | } 148 | 149 | protected function table_quote($table) 150 | { 151 | return '"'.$this->prefix.$table.'"'; 152 | } 153 | 154 | protected function column_quote($string) 155 | { 156 | preg_match('/(\(JSON\)\s*|^#)?([a-zA-Z0-9_]*)\.([a-zA-Z0-9_]*)/', $string, $column_match); 157 | if (isset($column_match[2], $column_match[3])) { 158 | return '"'.$this->prefix.$column_match[2].'"."'.$column_match[3].'"'; 159 | } 160 | 161 | return '"'.$string.'"'; 162 | } 163 | 164 | protected function column_push(&$columns) 165 | { 166 | if ($columns == '*') { 167 | return $columns; 168 | } 169 | if (is_string($columns)) { 170 | $columns = [$columns]; 171 | } 172 | $stack = []; 173 | foreach ($columns as $key => $value) { 174 | if (is_array($value)) { 175 | $stack[] = $this->column_push($value); 176 | } else { 177 | preg_match('/([a-zA-Z0-9_\-\.]*)\s*\(([a-zA-Z0-9_\-]*)\)/i', $value, $match); 178 | if (isset($match[1], $match[2])) { 179 | $stack[] = $this->column_quote($match[1]).' AS '.$this->column_quote($match[2]); 180 | $columns[$key] = $match[2]; 181 | } else { 182 | $stack[] = $this->column_quote($value); 183 | } 184 | } 185 | } 186 | 187 | return implode($stack, ','); 188 | } 189 | 190 | protected function array_quote($array) 191 | { 192 | $temp = []; 193 | foreach ($array as $value) { 194 | $temp[] = is_int($value) ? $value : $this->pdo->quote($value); 195 | } 196 | 197 | return implode($temp, ','); 198 | } 199 | 200 | protected function inner_conjunct($data, $conjunctor, $outer_conjunctor) 201 | { 202 | $haystack = []; 203 | foreach ($data as $value) { 204 | $haystack[] = '('.$this->data_implode($value, $conjunctor).')'; 205 | } 206 | 207 | return implode($outer_conjunctor.' ', $haystack); 208 | } 209 | 210 | protected function fn_quote($column, $string) 211 | { 212 | return (strpos($column, '#') === 0 && preg_match('/^[A-Z0-9\_]*\([^)]*\)$/', $string)) ? 213 | $string : 214 | $this->quote($string); 215 | } 216 | 217 | protected function data_implode($data, $conjunctor, $outer_conjunctor = null) 218 | { 219 | $wheres = []; 220 | foreach ($data as $key => $value) { 221 | $type = gettype($value); 222 | if ( 223 | preg_match("/^(AND|OR)(\s+#.*)?$/i", $key, $relation_match) && 224 | $type == 'array' 225 | ) { 226 | $wheres[] = 0 !== count(array_diff_key($value, array_keys(array_keys($value)))) ? 227 | '('.$this->data_implode($value, ' '.$relation_match[1]).')' : 228 | '('.$this->inner_conjunct($value, ' '.$relation_match[1], $conjunctor).')'; 229 | } else { 230 | preg_match('/(#?)([\w\.\-]+)(\[(\>|\>\=|\<|\<\=|\!|\<\>|\>\<|\!?~)\])?/i', $key, $match); 231 | $column = $this->column_quote($match[2]); 232 | if (isset($match[4])) { 233 | $operator = $match[4]; 234 | if ($operator == '!') { 235 | switch ($type) { 236 | case 'NULL': 237 | $wheres[] = $column.' IS NOT NULL'; 238 | break; 239 | case 'array': 240 | $wheres[] = $column.' NOT IN ('.$this->array_quote($value).')'; 241 | break; 242 | case 'integer': 243 | case 'double': 244 | $wheres[] = $column.' != '.$value; 245 | break; 246 | case 'boolean': 247 | $wheres[] = $column.' != '.($value ? '1' : '0'); 248 | break; 249 | case 'string': 250 | $wheres[] = $column.' != '.$this->fn_quote($key, $value); 251 | break; 252 | } 253 | } 254 | if ($operator == '<>' || $operator == '><') { 255 | if ($type == 'array') { 256 | if ($operator == '><') { 257 | $column .= ' NOT'; 258 | } 259 | if (is_numeric($value[0]) && is_numeric($value[1])) { 260 | $wheres[] = '('.$column.' BETWEEN '.$value[0].' AND '.$value[1].')'; 261 | } else { 262 | $wheres[] = '('.$column.' BETWEEN '.$this->quote($value[0]).' AND '.$this->quote($value[1]).')'; 263 | } 264 | } 265 | } 266 | if ($operator == '~' || $operator == '!~') { 267 | if ($type != 'array') { 268 | $value = [$value]; 269 | } 270 | $like_clauses = []; 271 | foreach ($value as $item) { 272 | $item = strval($item); 273 | $suffix = mb_substr($item, -1, 1); 274 | if (preg_match('/^(?!(%|\[|_])).+(?fn_quote($key, $item); 278 | } 279 | $wheres[] = implode(' OR ', $like_clauses); 280 | } 281 | if (in_array($operator, ['>', '>=', '<', '<='])) { 282 | if (is_numeric($value)) { 283 | $wheres[] = $column.' '.$operator.' '.$value; 284 | } elseif (strpos($key, '#') === 0) { 285 | $wheres[] = $column.' '.$operator.' '.$this->fn_quote($key, $value); 286 | } else { 287 | $wheres[] = $column.' '.$operator.' '.$this->quote($value); 288 | } 289 | } 290 | } else { 291 | switch ($type) { 292 | case 'NULL': 293 | $wheres[] = $column.' IS NULL'; 294 | break; 295 | case 'array': 296 | $wheres[] = $column.' IN ('.$this->array_quote($value).')'; 297 | break; 298 | case 'integer': 299 | case 'double': 300 | $wheres[] = $column.' = '.$value; 301 | break; 302 | case 'boolean': 303 | $wheres[] = $column.' = '.($value ? '1' : '0'); 304 | break; 305 | case 'string': 306 | $wheres[] = $column.' = '.$this->fn_quote($key, $value); 307 | break; 308 | } 309 | } 310 | } 311 | } 312 | 313 | return implode($conjunctor.' ', $wheres); 314 | } 315 | 316 | protected function where_clause($where) 317 | { 318 | $where_clause = ''; 319 | if (is_array($where)) { 320 | $where_keys = array_keys($where); 321 | $where_AND = preg_grep("/^AND\s*#?$/i", $where_keys); 322 | $where_OR = preg_grep("/^OR\s*#?$/i", $where_keys); 323 | $single_condition = array_diff_key($where, array_flip( 324 | ['AND', 'OR', 'GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH'] 325 | )); 326 | if ($single_condition != []) { 327 | $condition = $this->data_implode($single_condition, ''); 328 | if ($condition != '') { 329 | $where_clause = ' WHERE '.$condition; 330 | } 331 | } 332 | if (!empty($where_AND)) { 333 | $value = array_values($where_AND); 334 | $where_clause = ' WHERE '.$this->data_implode($where[$value[0]], ' AND'); 335 | } 336 | if (!empty($where_OR)) { 337 | $value = array_values($where_OR); 338 | $where_clause = ' WHERE '.$this->data_implode($where[$value[0]], ' OR'); 339 | } 340 | if (isset($where['MATCH'])) { 341 | $MATCH = $where['MATCH']; 342 | if (is_array($MATCH) && isset($MATCH['columns'], $MATCH['keyword'])) { 343 | $where_clause .= ($where_clause != '' ? ' AND ' : ' WHERE ').' MATCH ("'.str_replace('.', '"."', implode($MATCH['columns'], '", "')).'") AGAINST ('.$this->quote($MATCH['keyword']).')'; 344 | } 345 | } 346 | if (isset($where['GROUP'])) { 347 | $where_clause .= ' GROUP BY '.$this->column_quote($where['GROUP']); 348 | if (isset($where['HAVING'])) { 349 | $where_clause .= ' HAVING '.$this->data_implode($where['HAVING'], ' AND'); 350 | } 351 | } 352 | if (isset($where['ORDER'])) { 353 | $ORDER = $where['ORDER']; 354 | if (is_array($ORDER)) { 355 | $stack = []; 356 | foreach ($ORDER as $column => $value) { 357 | if (is_array($value)) { 358 | $stack[] = 'FIELD('.$this->column_quote($column).', '.$this->array_quote($value).')'; 359 | } elseif ($value === 'ASC' || $value === 'DESC') { 360 | $stack[] = $this->column_quote($column).' '.$value; 361 | } elseif (is_int($column)) { 362 | $stack[] = $this->column_quote($value); 363 | } 364 | } 365 | $where_clause .= ' ORDER BY '.implode($stack, ','); 366 | } else { 367 | $where_clause .= ' ORDER BY '.$this->column_quote($ORDER); 368 | } 369 | } 370 | if (isset($where['LIMIT'])) { 371 | $LIMIT = $where['LIMIT']; 372 | if (is_numeric($LIMIT)) { 373 | $where_clause .= ' LIMIT '.$LIMIT; 374 | } 375 | if ( 376 | is_array($LIMIT) && 377 | is_numeric($LIMIT[0]) && 378 | is_numeric($LIMIT[1]) 379 | ) { 380 | if ($this->database_type === 'pgsql') { 381 | $where_clause .= ' OFFSET '.$LIMIT[0].' LIMIT '.$LIMIT[1]; 382 | } else { 383 | $where_clause .= ' LIMIT '.$LIMIT[0].','.$LIMIT[1]; 384 | } 385 | } 386 | } 387 | } else { 388 | if ($where != null) { 389 | $where_clause .= ' '.$where; 390 | } 391 | } 392 | 393 | return $where_clause; 394 | } 395 | 396 | protected function select_context($table, $join, &$columns = null, $where = null, $column_fn = null) 397 | { 398 | preg_match('/([a-zA-Z0-9_\-]*)\s*\(([a-zA-Z0-9_\-]*)\)/i', $table, $table_match); 399 | if (isset($table_match[1], $table_match[2])) { 400 | $table = $this->table_quote($table_match[1]); 401 | $table_query = $this->table_quote($table_match[1]).' AS '.$this->table_quote($table_match[2]); 402 | } else { 403 | $table = $this->table_quote($table); 404 | $table_query = $table; 405 | } 406 | $join_key = is_array($join) ? array_keys($join) : null; 407 | if ( 408 | isset($join_key[0]) && 409 | strpos($join_key[0], '[') === 0 410 | ) { 411 | $table_join = []; 412 | $join_array = [ 413 | '>' => 'LEFT', 414 | '<' => 'RIGHT', 415 | '<>' => 'FULL', 416 | '><' => 'INNER', 417 | ]; 418 | foreach ($join as $sub_table => $relation) { 419 | preg_match('/(\[(\<|\>|\>\<|\<\>)\])?([a-zA-Z0-9_\-]*)\s?(\(([a-zA-Z0-9_\-]*)\))?/', $sub_table, $match); 420 | if ($match[2] != '' && $match[3] != '') { 421 | if (is_string($relation)) { 422 | $relation = 'USING ("'.$relation.'")'; 423 | } 424 | if (is_array($relation)) { 425 | // For ['column1', 'column2'] 426 | if (isset($relation[0])) { 427 | $relation = 'USING ("'.implode($relation, '", "').'")'; 428 | } else { 429 | $joins = []; 430 | foreach ($relation as $key => $value) { 431 | $joins[] = ( 432 | strpos($key, '.') > 0 ? 433 | // For ['tableB.column' => 'column'] 434 | $this->column_quote($key) : 435 | // For ['column1' => 'column2'] 436 | $table.'."'.$key.'"' 437 | ). 438 | ' = '. 439 | $this->table_quote(isset($match[5]) ? $match[5] : $match[3]).'."'.$value.'"'; 440 | } 441 | $relation = 'ON '.implode($joins, ' AND '); 442 | } 443 | } 444 | $table_name = $this->table_quote($match[3]).' '; 445 | if (isset($match[5])) { 446 | $table_name .= 'AS '.$this->table_quote($match[5]).' '; 447 | } 448 | $table_join[] = $join_array[$match[2]].' JOIN '.$table_name.$relation; 449 | } 450 | } 451 | $table_query .= ' '.implode($table_join, ' '); 452 | } else { 453 | if (is_null($columns)) { 454 | if (is_null($where)) { 455 | if ( 456 | is_array($join) && 457 | isset($column_fn) 458 | ) { 459 | $where = $join; 460 | $columns = null; 461 | } else { 462 | $where = null; 463 | $columns = $join; 464 | } 465 | } else { 466 | $where = $join; 467 | $columns = null; 468 | } 469 | } else { 470 | $where = $columns; 471 | $columns = $join; 472 | } 473 | } 474 | if (isset($column_fn)) { 475 | if ($column_fn == 1) { 476 | $column = '1'; 477 | if (is_null($where)) { 478 | $where = $columns; 479 | } 480 | } else { 481 | if (empty($columns)) { 482 | $columns = '*'; 483 | $where = $join; 484 | } 485 | $column = $column_fn.'('.$this->column_push($columns).')'; 486 | } 487 | } else { 488 | $column = $this->column_push($columns); 489 | } 490 | 491 | return 'SELECT '.$column.' FROM '.$table_query.$this->where_clause($where); 492 | } 493 | 494 | protected function data_map($index, $key, $value, $data, &$stack) 495 | { 496 | if (is_array($value)) { 497 | $sub_stack = []; 498 | foreach ($value as $sub_key => $sub_value) { 499 | if (is_array($sub_value)) { 500 | $current_stack = $stack[$index][$key]; 501 | $this->data_map(false, $sub_key, $sub_value, $data, $current_stack); 502 | $stack[$index][$key][$sub_key] = $current_stack[0][$sub_key]; 503 | } else { 504 | $this->data_map(false, preg_replace('/^[\w]*\./i', '', $sub_value), $sub_key, $data, $sub_stack); 505 | $stack[$index][$key] = $sub_stack; 506 | } 507 | } 508 | } else { 509 | if ($index !== false) { 510 | $stack[$index][$value] = $data[$value]; 511 | } else { 512 | $stack[$key] = $data[$key]; 513 | } 514 | } 515 | } 516 | 517 | public function select($table, $join, $columns = null, $where = null) 518 | { 519 | $column = $where == null ? $join : $columns; 520 | $is_single_column = (is_string($column) && $column !== '*'); 521 | 522 | $query = $this->query($this->select_context($table, $join, $columns, $where)); 523 | $stack = []; 524 | $index = 0; 525 | if (!$query) { 526 | return false; 527 | } 528 | if ($columns === '*') { 529 | return $query->fetchAll(PDO::FETCH_ASSOC); 530 | } 531 | if ($is_single_column) { 532 | return $query->fetchAll(PDO::FETCH_COLUMN); 533 | } 534 | while ($row = $query->fetch(PDO::FETCH_ASSOC)) { 535 | foreach ($columns as $key => $value) { 536 | if (is_array($value)) { 537 | $this->data_map($index, $key, $value, $row, $stack); 538 | } else { 539 | $this->data_map($index, $key, preg_replace('/^[\w]*\./i', '', $value), $row, $stack); 540 | } 541 | } 542 | $index++; 543 | } 544 | 545 | return $stack; 546 | } 547 | 548 | public function insert($table, $datas) 549 | { 550 | $lastId = []; 551 | // Check indexed or associative array 552 | if (!isset($datas[0])) { 553 | $datas = [$datas]; 554 | } 555 | foreach ($datas as $data) { 556 | $values = []; 557 | $columns = []; 558 | foreach ($data as $key => $value) { 559 | $columns[] = preg_replace("/^(\(JSON\)\s*|#)/i", '', $key); 560 | switch (gettype($value)) { 561 | case 'NULL': 562 | $values[] = 'NULL'; 563 | break; 564 | case 'array': 565 | preg_match("/\(JSON\)\s*([\w]+)/i", $key, $column_match); 566 | $values[] = isset($column_match[0]) ? 567 | $this->quote(json_encode($value)) : 568 | $this->quote(serialize($value)); 569 | break; 570 | case 'boolean': 571 | $values[] = ($value ? '1' : '0'); 572 | break; 573 | case 'integer': 574 | case 'double': 575 | case 'string': 576 | $values[] = $this->fn_quote($key, $value); 577 | break; 578 | } 579 | } 580 | $this->exec('INSERT INTO '.$this->table_quote($table).' ('.implode(', ', $columns).') VALUES ('.implode($values, ', ').')'); 581 | $lastId[] = $this->pdo->lastInsertId(); 582 | } 583 | 584 | return count($lastId) > 1 ? $lastId : $lastId[0]; 585 | } 586 | 587 | public function update($table, $data, $where = null) 588 | { 589 | $fields = []; 590 | foreach ($data as $key => $value) { 591 | preg_match('/([\w]+)(\[(\+|\-|\*|\/)\])?/i', $key, $match); 592 | if (isset($match[3])) { 593 | if (is_numeric($value)) { 594 | $fields[] = $this->column_quote($match[1]).' = '.$this->column_quote($match[1]).' '.$match[3].' '.$value; 595 | } 596 | } else { 597 | $column = $this->column_quote(preg_replace("/^(\(JSON\)\s*|#)/i", '', $key)); 598 | switch (gettype($value)) { 599 | case 'NULL': 600 | $fields[] = $column.' = NULL'; 601 | break; 602 | case 'array': 603 | preg_match("/\(JSON\)\s*([\w]+)/i", $key, $column_match); 604 | $fields[] = $column.' = '.$this->quote( 605 | isset($column_match[0]) ? json_encode($value) : serialize($value) 606 | ); 607 | break; 608 | case 'boolean': 609 | $fields[] = $column.' = '.($value ? '1' : '0'); 610 | break; 611 | case 'integer': 612 | case 'double': 613 | case 'string': 614 | $fields[] = $column.' = '.$this->fn_quote($key, $value); 615 | break; 616 | } 617 | } 618 | } 619 | 620 | return $this->exec('UPDATE '.$this->table_quote($table).' SET '.implode(', ', $fields).$this->where_clause($where)); 621 | } 622 | 623 | public function delete($table, $where) 624 | { 625 | return $this->exec('DELETE FROM '.$this->table_quote($table).$this->where_clause($where)); 626 | } 627 | 628 | public function truncate($table) 629 | { 630 | return $this->exec('TRUNCATE "'.$this->prefix.$table.'"'); 631 | } 632 | 633 | public function replace($table, $columns, $search = null, $replace = null, $where = null) 634 | { 635 | if (is_array($columns)) { 636 | $replace_query = []; 637 | foreach ($columns as $column => $replacements) { 638 | foreach ($replacements as $replace_search => $replace_replacement) { 639 | $replace_query[] = $column.' = REPLACE('.$this->column_quote($column).', '.$this->quote($replace_search).', '.$this->quote($replace_replacement).')'; 640 | } 641 | } 642 | $replace_query = implode(', ', $replace_query); 643 | $where = $search; 644 | } else { 645 | if (is_array($search)) { 646 | $replace_query = []; 647 | foreach ($search as $replace_search => $replace_replacement) { 648 | $replace_query[] = $columns.' = REPLACE('.$this->column_quote($columns).', '.$this->quote($replace_search).', '.$this->quote($replace_replacement).')'; 649 | } 650 | $replace_query = implode(', ', $replace_query); 651 | $where = $replace; 652 | } else { 653 | $replace_query = $columns.' = REPLACE('.$this->column_quote($columns).', '.$this->quote($search).', '.$this->quote($replace).')'; 654 | } 655 | } 656 | 657 | return $this->exec('UPDATE '.$this->table_quote($table).' SET '.$replace_query.$this->where_clause($where)); 658 | } 659 | 660 | public function get($table, $join = null, $columns = null, $where = null) 661 | { 662 | $column = $where == null ? $join : $columns; 663 | $is_single_column = (is_string($column) && $column !== '*'); 664 | $query = $this->query($this->select_context($table, $join, $columns, $where).' LIMIT 1'); 665 | if ($query) { 666 | $data = $query->fetchAll(PDO::FETCH_ASSOC); 667 | if (isset($data[0])) { 668 | if ($is_single_column) { 669 | return $data[0][preg_replace('/^[\w]*\./i', '', $column)]; 670 | } 671 | 672 | if ($column === '*') { 673 | return $data[0]; 674 | } 675 | $stack = []; 676 | foreach ($columns as $key => $value) { 677 | if (is_array($value)) { 678 | $this->data_map(0, $key, $value, $data[0], $stack); 679 | } else { 680 | $this->data_map(0, $key, preg_replace('/^[\w]*\./i', '', $value), $data[0], $stack); 681 | } 682 | } 683 | 684 | return $stack[0]; 685 | } else { 686 | return false; 687 | } 688 | } else { 689 | return false; 690 | } 691 | } 692 | 693 | public function has($table, $join, $where = null) 694 | { 695 | $column = null; 696 | $query = $this->query('SELECT EXISTS('.$this->select_context($table, $join, $column, $where, 1).')'); 697 | if ($query) { 698 | return $query->fetchColumn() === '1'; 699 | } else { 700 | return false; 701 | } 702 | } 703 | 704 | public function count($table, $join = null, $column = null, $where = null) 705 | { 706 | $query = $this->query($this->select_context($table, $join, $column, $where, 'COUNT')); 707 | 708 | return $query ? 0 + $query->fetchColumn() : false; 709 | } 710 | 711 | public function max($table, $join, $column = null, $where = null) 712 | { 713 | $query = $this->query($this->select_context($table, $join, $column, $where, 'MAX')); 714 | if ($query) { 715 | $max = $query->fetchColumn(); 716 | 717 | return is_numeric($max) ? $max + 0 : $max; 718 | } else { 719 | return false; 720 | } 721 | } 722 | 723 | public function min($table, $join, $column = null, $where = null) 724 | { 725 | $query = $this->query($this->select_context($table, $join, $column, $where, 'MIN')); 726 | if ($query) { 727 | $min = $query->fetchColumn(); 728 | 729 | return is_numeric($min) ? $min + 0 : $min; 730 | } else { 731 | return false; 732 | } 733 | } 734 | 735 | public function avg($table, $join, $column = null, $where = null) 736 | { 737 | $query = $this->query($this->select_context($table, $join, $column, $where, 'AVG')); 738 | 739 | return $query ? 0 + $query->fetchColumn() : false; 740 | } 741 | 742 | public function sum($table, $join, $column = null, $where = null) 743 | { 744 | $query = $this->query($this->select_context($table, $join, $column, $where, 'SUM')); 745 | 746 | return $query ? 0 + $query->fetchColumn() : false; 747 | } 748 | 749 | public function action($actions) 750 | { 751 | if (is_callable($actions)) { 752 | $this->pdo->beginTransaction(); 753 | $result = $actions($this); 754 | if ($result === false) { 755 | $this->pdo->rollBack(); 756 | } else { 757 | $this->pdo->commit(); 758 | } 759 | } else { 760 | return false; 761 | } 762 | } 763 | 764 | public function debug() 765 | { 766 | $this->debug_mode = true; 767 | 768 | return $this; 769 | } 770 | 771 | public function error() 772 | { 773 | return $this->pdo->errorInfo(); 774 | } 775 | 776 | public function last_query() 777 | { 778 | return end($this->logs); 779 | } 780 | 781 | public function log() 782 | { 783 | return $this->logs; 784 | } 785 | 786 | public function info() 787 | { 788 | $output = [ 789 | 'server' => 'SERVER_INFO', 790 | 'driver' => 'DRIVER_NAME', 791 | 'client' => 'CLIENT_VERSION', 792 | 'version' => 'SERVER_VERSION', 793 | 'connection' => 'CONNECTION_STATUS', 794 | ]; 795 | foreach ($output as $key => $value) { 796 | $output[$key] = $this->pdo->getAttribute(constant('PDO::ATTR_'.$value)); 797 | } 798 | 799 | return $output; 800 | } 801 | } 802 | --------------------------------------------------------------------------------