├── application └── libraries │ └── Datatables.php └── readme.txt /application/libraries/Datatables.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Vincent Bambico 13 | * Yusuf Ozdemir 14 | * @link http://ellislab.com/forums/viewthread/160896/ 15 | */ 16 | class Datatables 17 | { 18 | /** 19 | * Global container variables for chained argument results 20 | * 21 | */ 22 | private $ci; 23 | private $table; 24 | private $distinct; 25 | private $group_by = array(); 26 | private $select = array(); 27 | private $joins = array(); 28 | private $columns = array(); 29 | private $where = array(); 30 | private $or_where = array(); 31 | private $where_in = array(); 32 | private $like = array(); 33 | private $or_like = array(); 34 | private $filter = array(); 35 | private $add_columns = array(); 36 | private $edit_columns = array(); 37 | private $unset_columns = array(); 38 | 39 | /** 40 | * Copies an instance of CI 41 | */ 42 | public function __construct() 43 | { 44 | $this->ci =& get_instance(); 45 | } 46 | 47 | /** 48 | * If you establish multiple databases in config/database.php this will allow you to 49 | * set the database (other than $active_group) - more info: http://ellislab.com/forums/viewthread/145901/#712942 50 | */ 51 | public function set_database($db_name) 52 | { 53 | $db_data = $this->ci->load->database($db_name, TRUE); 54 | $this->ci->db = $db_data; 55 | } 56 | 57 | /** 58 | * Generates the SELECT portion of the query 59 | * 60 | * @param string $columns 61 | * @param bool $backtick_protect 62 | * @return mixed 63 | */ 64 | public function select($columns, $backtick_protect = TRUE) 65 | { 66 | foreach($this->explode(',', $columns) as $val) 67 | { 68 | $column = trim(preg_replace('/(.*)\s+as\s+(\w*)/i', '$2', $val)); 69 | $column = preg_replace('/.*\.(.*)/i', '$1', $column); // get name after `.` 70 | $this->columns[] = $column; 71 | $this->select[$column] = trim(preg_replace('/(.*)\s+as\s+(\w*)/i', '$1', $val)); 72 | } 73 | 74 | $this->ci->db->select($columns, $backtick_protect); 75 | return $this; 76 | } 77 | 78 | /** 79 | * Generates the DISTINCT portion of the query 80 | * 81 | * @param string $column 82 | * @return mixed 83 | */ 84 | public function distinct($column) 85 | { 86 | $this->distinct = $column; 87 | $this->ci->db->distinct($column); 88 | return $this; 89 | } 90 | 91 | /** 92 | * Generates a custom GROUP BY portion of the query 93 | * 94 | * @param string $val 95 | * @return mixed 96 | */ 97 | public function group_by($val) 98 | { 99 | $this->group_by[] = $val; 100 | $this->ci->db->group_by($val); 101 | return $this; 102 | } 103 | 104 | /** 105 | * Generates the FROM portion of the query 106 | * 107 | * @param string $table 108 | * @return mixed 109 | */ 110 | public function from($table) 111 | { 112 | $this->table = $table; 113 | return $this; 114 | } 115 | 116 | /** 117 | * Generates the JOIN portion of the query 118 | * 119 | * @param string $table 120 | * @param string $fk 121 | * @param string $type 122 | * @return mixed 123 | */ 124 | public function join($table, $fk, $type = NULL) 125 | { 126 | $this->joins[] = array($table, $fk, $type); 127 | $this->ci->db->join($table, $fk, $type); 128 | return $this; 129 | } 130 | 131 | /** 132 | * Generates the WHERE portion of the query 133 | * 134 | * @param mixed $key_condition 135 | * @param string $val 136 | * @param bool $backtick_protect 137 | * @return mixed 138 | */ 139 | public function where($key_condition, $val = NULL, $backtick_protect = TRUE) 140 | { 141 | $this->where[] = array($key_condition, $val, $backtick_protect); 142 | $this->ci->db->where($key_condition, $val, $backtick_protect); 143 | return $this; 144 | } 145 | 146 | /** 147 | * Generates the WHERE portion of the query 148 | * 149 | * @param mixed $key_condition 150 | * @param string $val 151 | * @param bool $backtick_protect 152 | * @return mixed 153 | */ 154 | public function or_where($key_condition, $val = NULL, $backtick_protect = TRUE) 155 | { 156 | $this->or_where[] = array($key_condition, $val, $backtick_protect); 157 | $this->ci->db->or_where($key_condition, $val, $backtick_protect); 158 | return $this; 159 | } 160 | 161 | /** 162 | * Generates the WHERE IN portion of the query 163 | * 164 | * @param mixed $key_condition 165 | * @param string $val 166 | * @param bool $backtick_protect 167 | * @return mixed 168 | */ 169 | public function where_in($key_condition, $val = NULL) 170 | { 171 | $this->where_in[] = array($key_condition, $val); 172 | $this->ci->db->where_in($key_condition, $val); 173 | return $this; 174 | } 175 | 176 | /** 177 | * Generates the WHERE portion of the query 178 | * 179 | * @param mixed $key_condition 180 | * @param string $val 181 | * @param bool $backtick_protect 182 | * @return mixed 183 | */ 184 | public function filter($key_condition, $val = NULL, $backtick_protect = TRUE) 185 | { 186 | $this->filter[] = array($key_condition, $val, $backtick_protect); 187 | return $this; 188 | } 189 | 190 | /** 191 | * Generates a %LIKE% portion of the query 192 | * 193 | * @param mixed $key_condition 194 | * @param string $val 195 | * @param bool $backtick_protect 196 | * @return mixed 197 | */ 198 | public function like($key_condition, $val = NULL, $side = 'both') 199 | { 200 | $this->like[] = array($key_condition, $val, $side); 201 | $this->ci->db->like($key_condition, $val, $side); 202 | return $this; 203 | } 204 | 205 | /** 206 | * Generates the OR %LIKE% portion of the query 207 | * 208 | * @param mixed $key_condition 209 | * @param string $val 210 | * @param bool $backtick_protect 211 | * @return mixed 212 | */ 213 | public function or_like($key_condition, $val = NULL, $side = 'both') 214 | { 215 | $this->or_like[] = array($key_condition, $val, $side); 216 | $this->ci->db->or_like($key_condition, $val, $side); 217 | return $this; 218 | } 219 | 220 | /** 221 | * Sets additional column variables for adding custom columns 222 | * 223 | * @param string $column 224 | * @param string $content 225 | * @param string $match_replacement 226 | * @return mixed 227 | */ 228 | public function add_column($column, $content, $match_replacement = NULL) 229 | { 230 | $this->add_columns[$column] = array('content' => $content, 'replacement' => $this->explode(',', $match_replacement)); 231 | return $this; 232 | } 233 | 234 | /** 235 | * Sets additional column variables for editing columns 236 | * 237 | * @param string $column 238 | * @param string $content 239 | * @param string $match_replacement 240 | * @return mixed 241 | */ 242 | public function edit_column($column, $content, $match_replacement) 243 | { 244 | $this->edit_columns[$column][] = array('content' => $content, 'replacement' => $this->explode(',', $match_replacement)); 245 | return $this; 246 | } 247 | 248 | /** 249 | * Unset column 250 | * 251 | * @param string $column 252 | * @return mixed 253 | */ 254 | public function unset_column($column) 255 | { 256 | $column=explode(',',$column); 257 | $this->unset_columns=array_merge($this->unset_columns,$column); 258 | return $this; 259 | } 260 | 261 | /** 262 | * Builds all the necessary query segments and performs the main query based on results set from chained statements 263 | * 264 | * @param string $output 265 | * @param string $charset 266 | * @return string 267 | */ 268 | public function generate($output = 'json', $charset = 'UTF-8') 269 | { 270 | if(strtolower($output) == 'json') 271 | $this->get_paging(); 272 | 273 | $this->get_ordering(); 274 | $this->get_filtering(); 275 | return $this->produce_output(strtolower($output), strtolower($charset)); 276 | } 277 | 278 | /** 279 | * Generates the LIMIT portion of the query 280 | * 281 | * @return mixed 282 | */ 283 | private function get_paging() 284 | { 285 | $iStart = $this->ci->input->post('start'); 286 | $iLength = $this->ci->input->post('length'); 287 | 288 | if($iLength != '' && $iLength != '-1') 289 | $this->ci->db->limit($iLength, ($iStart)? $iStart : 0); 290 | } 291 | 292 | /** 293 | * Generates the ORDER BY portion of the query 294 | * 295 | * @return mixed 296 | */ 297 | private function get_ordering() 298 | { 299 | 300 | $Data = $this->ci->input->post('columns'); 301 | 302 | 303 | if ($this->ci->input->post('order')) 304 | foreach ($this->ci->input->post('order') as $key) 305 | if($this->check_cType()) 306 | $this->ci->db->order_by($Data[$key['column']]['data'], $key['dir']); 307 | else 308 | $this->ci->db->order_by($this->columns[$key['column']] , $key['dir']); 309 | 310 | } 311 | 312 | /** 313 | * Generates a %LIKE% portion of the query 314 | * 315 | * @return mixed 316 | */ 317 | private function get_filtering() 318 | { 319 | 320 | $mColArray = $this->ci->input->post('columns'); 321 | 322 | $sWhere = ''; 323 | $search = $this->ci->input->post('search'); 324 | $sSearch = $this->ci->db->escape_like_str(trim($search['value'])); 325 | $columns = array_values(array_diff($this->columns, $this->unset_columns)); 326 | 327 | if($sSearch != '') 328 | for($i = 0; $i < count($mColArray); $i++) 329 | if ($mColArray[$i]['searchable'] == 'true' && !array_key_exists($mColArray[$i]['data'], $this->add_columns)) 330 | if($this->check_cType()) 331 | $sWhere .= $this->select[$mColArray[$i]['data']] . " LIKE '%" . $sSearch . "%' OR "; 332 | else 333 | $sWhere .= $this->select[$this->columns[$i]] . " LIKE '%" . $sSearch . "%' OR "; 334 | 335 | 336 | $sWhere = substr_replace($sWhere, '', -3); 337 | 338 | if($sWhere != '') 339 | $this->ci->db->where('(' . $sWhere . ')'); 340 | 341 | // TODO : sRangeSeparator 342 | 343 | foreach($this->filter as $val) 344 | $this->ci->db->where($val[0], $val[1], $val[2]); 345 | } 346 | 347 | /** 348 | * Compiles the select statement based on the other functions called and runs the query 349 | * 350 | * @return mixed 351 | */ 352 | private function get_display_result() 353 | { 354 | return $this->ci->db->get($this->table); 355 | } 356 | 357 | /** 358 | * Builds an encoded string data. Returns JSON by default, and an array of aaData if output is set to raw. 359 | * 360 | * @param string $output 361 | * @param string $charset 362 | * @return mixed 363 | */ 364 | private function produce_output($output, $charset) 365 | { 366 | $aaData = array(); 367 | $rResult = $this->get_display_result(); 368 | 369 | if($output == 'json') 370 | { 371 | $iTotal = $this->get_total_results(); 372 | $iFilteredTotal = $this->get_total_results(TRUE); 373 | } 374 | 375 | foreach($rResult->result_array() as $row_key => $row_val) 376 | { 377 | $aaData[$row_key] = ($this->check_cType())? $row_val : array_values($row_val); 378 | 379 | foreach($this->add_columns as $field => $val) 380 | if($this->check_cType()) 381 | $aaData[$row_key][$field] = $this->exec_replace($val, $aaData[$row_key]); 382 | else 383 | $aaData[$row_key][] = $this->exec_replace($val, $aaData[$row_key]); 384 | 385 | 386 | foreach($this->edit_columns as $modkey => $modval) 387 | foreach($modval as $val) 388 | $aaData[$row_key][($this->check_cType())? $modkey : array_search($modkey, $this->columns)] = $this->exec_replace($val, $aaData[$row_key]); 389 | 390 | $aaData[$row_key] = array_diff_key($aaData[$row_key], ($this->check_cType())? $this->unset_columns : array_intersect($this->columns, $this->unset_columns)); 391 | 392 | if(!$this->check_cType()) 393 | $aaData[$row_key] = array_values($aaData[$row_key]); 394 | 395 | } 396 | 397 | if($output == 'json') 398 | { 399 | $sOutput = array 400 | ( 401 | 'draw' => intval($this->ci->input->post('draw')), 402 | 'recordsTotal' => $iTotal, 403 | 'recordsFiltered' => $iFilteredTotal, 404 | 'data' => $aaData 405 | ); 406 | 407 | if($charset == 'utf-8') 408 | return json_encode($sOutput); 409 | else 410 | return $this->jsonify($sOutput); 411 | } 412 | else 413 | return array('aaData' => $aaData); 414 | } 415 | 416 | /** 417 | * Get result count 418 | * 419 | * @return integer 420 | */ 421 | private function get_total_results($filtering = FALSE) 422 | { 423 | if($filtering) 424 | $this->get_filtering(); 425 | 426 | foreach($this->joins as $val) 427 | $this->ci->db->join($val[0], $val[1], $val[2]); 428 | 429 | foreach($this->where as $val) 430 | $this->ci->db->where($val[0], $val[1], $val[2]); 431 | 432 | foreach($this->or_where as $val) 433 | $this->ci->db->or_where($val[0], $val[1], $val[2]); 434 | 435 | foreach($this->where_in as $val) 436 | $this->ci->db->where_in($val[0], $val[1]); 437 | 438 | foreach($this->group_by as $val) 439 | $this->ci->db->group_by($val); 440 | 441 | foreach($this->like as $val) 442 | $this->ci->db->like($val[0], $val[1], $val[2]); 443 | 444 | foreach($this->or_like as $val) 445 | $this->ci->db->or_like($val[0], $val[1], $val[2]); 446 | 447 | if(strlen($this->distinct) > 0) 448 | { 449 | $this->ci->db->distinct($this->distinct); 450 | $this->ci->db->select($this->columns); 451 | } 452 | $subquery = $this->ci->db->get_compiled_select($this->table); 453 | $countingsql = "SELECT COUNT(*) FROM (" . $subquery . ") SqueryAux"; 454 | $query = $this->ci->db->query($countingsql); 455 | $result = $query->row_array(); 456 | $count = $result['COUNT(*)']; 457 | return $count; 458 | } 459 | 460 | /** 461 | * Runs callback functions and makes replacements 462 | * 463 | * @param mixed $custom_val 464 | * @param mixed $row_data 465 | * @return string $custom_val['content'] 466 | */ 467 | private function exec_replace($custom_val, $row_data) 468 | { 469 | $replace_string = ''; 470 | 471 | // Go through our array backwards, else $1 (foo) will replace $11, $12 etc with foo1, foo2 etc 472 | $custom_val['replacement'] = array_reverse($custom_val['replacement'], true); 473 | 474 | if(isset($custom_val['replacement']) && is_array($custom_val['replacement'])) 475 | { 476 | //Added this line because when the replacement has over 10 elements replaced the variable "$1" first by the "$10" 477 | $custom_val['replacement'] = array_reverse($custom_val['replacement'], true); 478 | foreach($custom_val['replacement'] as $key => $val) 479 | { 480 | $sval = preg_replace("/(? $args_val) 488 | { 489 | $args_val = preg_replace("/(?columns))? ($row_data[($this->check_cType())? $args_val : array_search($args_val, $this->columns)]) : $args_val; 491 | } 492 | 493 | $replace_string = call_user_func_array($func, $args); 494 | } 495 | elseif(in_array($sval, $this->columns)) 496 | $replace_string = $row_data[($this->check_cType())? $sval : array_search($sval, $this->columns)]; 497 | else 498 | $replace_string = $sval; 499 | 500 | $custom_val['content'] = str_ireplace('$' . ($key + 1), $replace_string, $custom_val['content']); 501 | } 502 | } 503 | 504 | return $custom_val['content']; 505 | } 506 | 507 | /** 508 | * Check column type -numeric or column name 509 | * 510 | * @return bool 511 | */ 512 | private function check_cType() 513 | { 514 | $column = $this->ci->input->post('columns'); 515 | if(is_numeric($column[0]['data'])) 516 | return FALSE; 517 | else 518 | return TRUE; 519 | } 520 | 521 | 522 | /** 523 | * Return the difference of open and close characters 524 | * 525 | * @param string $str 526 | * @param string $open 527 | * @param string $close 528 | * @return string $retval 529 | */ 530 | private function balanceChars($str, $open, $close) 531 | { 532 | $openCount = substr_count($str, $open); 533 | $closeCount = substr_count($str, $close); 534 | $retval = $openCount - $closeCount; 535 | return $retval; 536 | } 537 | 538 | /** 539 | * Explode, but ignore delimiter until closing characters are found 540 | * 541 | * @param string $delimiter 542 | * @param string $str 543 | * @param string $open 544 | * @param string $close 545 | * @return mixed $retval 546 | */ 547 | private function explode($delimiter, $str, $open = '(', $close=')') 548 | { 549 | $retval = array(); 550 | $hold = array(); 551 | $balance = 0; 552 | $parts = explode($delimiter, $str); 553 | 554 | foreach($parts as $part) 555 | { 556 | $hold[] = $part; 557 | $balance += $this->balanceChars($part, $open, $close); 558 | 559 | if($balance < 1) 560 | { 561 | $retval[] = implode($delimiter, $hold); 562 | $hold = array(); 563 | $balance = 0; 564 | } 565 | } 566 | 567 | if(count($hold) > 0) 568 | $retval[] = implode($delimiter, $hold); 569 | 570 | return $retval; 571 | } 572 | 573 | /** 574 | * Workaround for json_encode's UTF-8 encoding if a different charset needs to be used 575 | * 576 | * @param mixed $result 577 | * @return string 578 | */ 579 | private function jsonify($result = FALSE) 580 | { 581 | if(is_null($result)) 582 | return 'null'; 583 | 584 | if($result === FALSE) 585 | return 'false'; 586 | 587 | if($result === TRUE) 588 | return 'true'; 589 | 590 | if(is_scalar($result)) 591 | { 592 | if(is_float($result)) 593 | return floatval(str_replace(',', '.', strval($result))); 594 | 595 | if(is_string($result)) 596 | { 597 | static $jsonReplaces = array(array('\\', '/', '\n', '\t', '\r', '\b', '\f', '"'), array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"')); 598 | return '"' . str_replace($jsonReplaces[0], $jsonReplaces[1], $result) . '"'; 599 | } 600 | else 601 | return $result; 602 | } 603 | 604 | $isList = TRUE; 605 | 606 | for($i = 0, reset($result); $i < count($result); $i++, next($result)) 607 | { 608 | if(key($result) !== $i) 609 | { 610 | $isList = FALSE; 611 | break; 612 | } 613 | } 614 | 615 | $json = array(); 616 | 617 | if($isList) 618 | { 619 | foreach($result as $value) 620 | $json[] = $this->jsonify($value); 621 | 622 | return '[' . join(',', $json) . ']'; 623 | } 624 | else 625 | { 626 | foreach($result as $key => $value) 627 | $json[] = $this->jsonify($key) . ':' . $this->jsonify($value); 628 | 629 | return '{' . join(',', $json) . '}'; 630 | } 631 | } 632 | 633 | /** 634 | * returns the sql statement of the last query run 635 | * @return type 636 | */ 637 | public function last_query() 638 | { 639 | return $this->ci->db->last_query(); 640 | } 641 | } 642 | /* End of file Datatables.php */ 643 | /* Location: ./application/libraries/Datatables.php */ 644 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | ********************** 2 | * Ignited Datatables * 3 | ********************** 4 | 5 | Ignited Datatables is a wrapper class/library based on the native Datatables server-side implementation by Allan Jardine 6 | found at http://datatables.net/examples/data_sources/server_side.html for CodeIgniter 7 | 8 | Fork : https://github.com/IgnitedDatatables/Ignited-Datatables 9 | Wiki : https://github.com/IgnitedDatatables/Ignited-Datatables/wiki 10 | Discuss: http://ellislab.com/forums/viewthread/160896/ 11 | Contact: Vincent Bambico 12 | Yusuf Ozdemir 13 | 14 | ============ 15 | Requirements 16 | ============ 17 | jQuery 1.5+ 18 | DataTables 1.10+ 19 | CodeIgniter "Reactor" 20 | 21 | ======= 22 | Install 23 | ======= 24 | To install the library, copy the libraries/datatables.php file into your application/libraries folder. 25 | 26 | ======= 27 | License 28 | ======= 29 | DON'T BE A DICK PUBLIC LICENSE 30 | 31 | Version 1, December 2009 32 | 33 | Copyright (C) 2009 Philip Sturgeon 34 | Everyone is permitted to copy and distribute verbatim or modified 35 | copies of this license document, and changing it is allowed as long 36 | as the name is changed. 37 | 38 | DON'T BE A DICK PUBLIC LICENSE 39 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 40 | 41 | 1. Do whatever you like with the original work, just don't be a dick. 42 | 43 | Being a dick includes - but is not limited to - the following instances: 44 | 45 | 1a. Outright copyright infringement - Don't just copy this and change the name. 46 | 1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick. 47 | 1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick. 48 | 49 | 2. If you become rich through modifications, related works/services, or supporting the original work, 50 | share the love. Only a dick would make loads off this work and not buy the original works 51 | creator(s) a pint. 52 | 53 | 3. Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes 54 | you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back. 55 | --------------------------------------------------------------------------------