9 | * @version 2.1
10 | *
11 | * @link http://github.com/jamierumbelow/codeigniter-base-model
12 | * @link https://github.com/phpfour/MY_Model
13 | * @link https://github.com/ronisaha/ci-base-model
14 | *
15 | */
16 |
17 | class CI_Base_Model extends CI_Model
18 | {
19 |
20 | /* --------------------------------------------------------------
21 | * VARIABLES
22 | * ------------------------------------------------------------ */
23 | /**
24 | * This model's default database table. Automatically
25 | * guessed by pluralising the model name.
26 | */
27 | protected $_table;
28 |
29 | /**
30 | * The database connection object. Will be set to the default
31 | * connection. This allows individual models to use different DBs
32 | * without overwriting CI's global $this->db connection.
33 | */
34 | protected $_database;
35 |
36 | protected $_database_group = null;
37 | protected static $_connection_cache = array();
38 |
39 | /**
40 | * This model's default primary key or unique identifier.
41 | * Used by the get(), update() and delete() functions.
42 | */
43 | protected $primary_key = NULL;
44 |
45 | /**
46 | * Support for soft deletes and this model's 'deleted' key
47 | */
48 | protected $deleted_at_key = 'deleted_at';
49 | protected $deleted_by_key = 'deleted_by';
50 | protected $_temporary_with_deleted = FALSE;
51 | protected $_temporary_only_deleted = FALSE;
52 | private $soft_delete = NULL;
53 |
54 | /**
55 | * Support for Timestampable
56 | */
57 | protected $timestampable;
58 | protected $created_at_key = 'created_at';
59 | protected $updated_at_key = 'updated_at';
60 |
61 | /**
62 | * Support for Blamable
63 | */
64 | protected $blamable;
65 | protected $created_by_key = 'created_by';
66 | protected $updated_by_key = 'updated_by';
67 |
68 |
69 | /**
70 | * The various default callbacks available to the model. Each are
71 | * simple lists of method names (methods will be run on $this).
72 | */
73 | protected $before_create = array();
74 | protected $after_create = array();
75 | protected $before_update = array();
76 | protected $after_update = array();
77 | protected $before_get = array();
78 | protected $after_get = array();
79 | protected $before_delete = array();
80 | protected $after_delete = array();
81 |
82 | protected $event_listeners = array(
83 | 'before_create' => array(),
84 | 'after_create' => array(),
85 | 'before_update' => array(),
86 | 'after_update' => array(),
87 | 'before_get' => array(),
88 | 'after_get' => array(),
89 | 'before_delete' => array(),
90 | 'after_delete' => array(),
91 | );
92 |
93 | protected $callback_parameters = array();
94 |
95 | /**
96 | * Protected, non-modifiable attributes
97 | */
98 | protected $protected_attributes = array();
99 |
100 | /**
101 | * Relationship arrays. Use flat strings for defaults or string
102 | * => array to customise the class name and primary key
103 | */
104 | protected $belongs_to = array();
105 | protected $has_many = array();
106 |
107 | protected $_with = array();
108 |
109 | /**
110 | * An array of validation rules. This needs to be the same format
111 | * as validation rules passed to the Form_validation library.
112 | */
113 | protected $validate = array();
114 |
115 | /**
116 | * Optionally skip the validation. Used in conjunction with
117 | * skip_validation() to skip data validation for any future calls.
118 | */
119 | protected $skip_validation = FALSE;
120 |
121 | /**
122 | * By default we return our results as objects. If we need to override
123 | * this, we can, or, we could use the `as_array()` and `as_object()` scopes.
124 | */
125 | protected $return_type = 'object';
126 | protected $_temporary_return_type = NULL;
127 |
128 | /**
129 | * @var array list of fields
130 | */
131 | protected $_fields = array();
132 |
133 | /**
134 | * @var int returned number of rows of a query
135 | */
136 | protected $num_rows = NULL;
137 |
138 | private $_base_model_instance = NULL;
139 |
140 | /* --------------------------------------------------------------
141 | * GENERIC METHODS
142 | * ------------------------------------------------------------ */
143 |
144 | /**
145 | * Initialise the model, tie into the CodeIgniter superobject
146 | */
147 | public function __construct()
148 | {
149 | parent::__construct();
150 |
151 | $this->load->helper('inflector');
152 |
153 | $this->_initialize_event_listeners();
154 | $this->_initialize_schema();
155 |
156 | $this->_temporary_return_type = $this->return_type;
157 | }
158 |
159 |
160 | /**
161 | * Initialize the schema for special use cases
162 | * and try our best to guess the table name, primary_key
163 | * and the blamable, timestampable, softDeletable status
164 | */
165 | protected function _initialize_schema()
166 | {
167 | $this->set_database($this->_database_group);
168 |
169 | $this->_fetch_table();
170 | $this->_fetch_primary_key();
171 |
172 | if($this->primary_key == null && $this->is_base_model_instance()) {
173 | return;
174 | }
175 |
176 | $this->_fields = $this->get_fields();
177 |
178 | $this->_guess_is_soft_deletable();
179 | $this->_guess_is_blamable();
180 | $this->_guess_is_timestampable();
181 |
182 | }
183 |
184 | /**
185 | * Initialize all default listeners
186 | */
187 | protected function _initialize_event_listeners()
188 | {
189 | foreach($this->event_listeners as $event_listener => $e)
190 | {
191 | if(isset($this->$event_listener) && !empty($this->$event_listener)){
192 | foreach($this->$event_listener as $event){
193 | $this->subscribe($event_listener, $event);
194 | }
195 | }
196 | }
197 |
198 | $this->subscribe('before_update', 'protect_attributes', TRUE);
199 | }
200 |
201 | /* --------------------------------------------------------------
202 | * CRUD INTERFACE
203 | * ------------------------------------------------------------ */
204 |
205 | /**
206 | * Fetch a single record based on the primary key. Returns an object.
207 | */
208 | public function get($primary_value)
209 | {
210 | return $this->get_by($this->primary_key, $primary_value);
211 | }
212 |
213 | /**
214 | * Fetch a single record based on an arbitrary WHERE call. Can be
215 | * any valid value to $this->_database->where().
216 | */
217 | public function get_by()
218 | {
219 | $where = func_get_args();
220 |
221 | $this->apply_soft_delete_filter();
222 |
223 | $this->_set_where($where);
224 |
225 | $this->trigger('before_get');
226 |
227 | $this->limit(1);
228 |
229 | $result = $this->_database->get($this->_table);
230 |
231 | $this->num_rows = count((array)$result);
232 |
233 | $row = $result->{$this->_get_return_type_method()}();
234 | $this->_temporary_return_type = $this->return_type;
235 |
236 | $row = $this->trigger('after_get', $row);
237 |
238 | $this->_with = array();
239 | return $row;
240 | }
241 |
242 | /**
243 | * Fetch an array of records based on an array of primary values.
244 | */
245 | public function get_many($values)
246 | {
247 | $this->apply_soft_delete_filter();
248 |
249 | $this->_database->where_in($this->primary_key, $values);
250 |
251 | return $this->get_all();
252 | }
253 |
254 | /**
255 | * Fetch an array of records based on an arbitrary WHERE call.
256 | */
257 | public function get_many_by()
258 | {
259 | $where = func_get_args();
260 |
261 | $this->apply_soft_delete_filter();
262 |
263 | $this->_set_where($where);
264 |
265 | return $this->get_all();
266 | }
267 |
268 | /**
269 | * Fetch all the records in the table. Can be used as a generic call
270 | * to $this->_database->get() with scoped methods.
271 | */
272 | public function get_all()
273 | {
274 | $this->trigger('before_get');
275 |
276 | $this->apply_soft_delete_filter();
277 |
278 | $result = $this->_database->get($this->_table)
279 | ->{$this->_get_return_type_method(true)}();
280 | $this->_temporary_return_type = $this->return_type;
281 |
282 | $this->num_rows = count($result);
283 |
284 | foreach ($result as $key => &$row)
285 | {
286 | $row = $this->trigger('after_get', $row, ($key == count($result) - 1));
287 | }
288 |
289 | $this->_with = array();
290 | return $result;
291 | }
292 |
293 | /**
294 | * @param $methodName
295 | * @param $args
296 | *
297 | * @return mixed
298 | */
299 | public function __call($methodName, $args)
300 | {
301 | $watch = array('find_by', 'find_all_by', 'find_field_by', 'findBy', 'findAllBy', 'findFieldBy');
302 |
303 | foreach ($watch as $found) {
304 | if ($methodName == $found) {
305 | break;
306 | }
307 |
308 | if (stristr($methodName, $found)) {
309 | $field = $this->underscore_from_camel_case(ltrim(str_replace($found, '', $methodName), '_'));
310 | $method = $this->underscore_from_camel_case($found);
311 | return $this->$method($field, $args);
312 | }
313 | }
314 |
315 | $method = self::underscore_from_camel_case($methodName);
316 |
317 | if (method_exists($this, $method)) {
318 | return call_user_func_array(array($this, $method), $args);
319 | }
320 |
321 | return $this->_handle_exception($methodName);
322 |
323 | }
324 |
325 | public static function underscore_from_camel_case($str) {
326 | $str[0] = strtolower($str[0]);
327 | $func = create_function('$c', 'return "_" . strtolower($c[1]);');
328 | return preg_replace_callback('/([A-Z])/', $func, $str);
329 | }
330 |
331 | /**
332 | * Returns a property value based on its name.
333 | * Do not call this method. This is a PHP magic method that we override
334 | * to allow using the following syntax to read a property or obtain event handlers:
335 | *
336 | * $value=$model->propertyName; [will be called $this->get_property_name()]
337 | * $value=$model->property_name; [will be called $this->get_property_name()]
338 | * $value=$model->load; [will be called $controller->load]
339 | *
340 | *
341 | * @param string $name the property name
342 | *
343 | * @return mixed the property value
344 | * @see __set
345 | */
346 | public function __get($name)
347 | {
348 | $getter = 'get' . $name;
349 | $getter2 = 'get_' . $name;
350 | $getter3 = 'get_' . self::underscore_from_camel_case($name);
351 | if (method_exists($this, $getter))
352 | return $this->$getter();
353 | elseif (method_exists($this, $getter2)){
354 | return $this->$getter2();
355 | }
356 | elseif (method_exists($this, $getter3)){
357 | return $this->$getter3();
358 | }
359 |
360 | return parent::__get($name);
361 | }
362 |
363 |
364 | /**
365 | * @param $field
366 | * @param $value
367 | * @param string $fields
368 | * @param null $order
369 | *
370 | * @return bool
371 | */
372 | public function find_by($field, $value, $fields = '*', $order = NULL)
373 | {
374 | $arg_list = array();
375 | if (is_array($value)) {
376 | $arg_list = $value;
377 | $value = $arg_list[0];
378 | }
379 | $fields = isset($arg_list[1]) ? $arg_list[1] : $fields;
380 | $order = isset($arg_list[2]) ? $arg_list[2] : $order;
381 |
382 | $where = array($field => $value);
383 | return $this->find($where, $fields, $order);
384 | }
385 |
386 | /**
387 | * @param $field
388 | * @param $value
389 | * @param string $fields
390 | * @param null $order
391 | * @param int $start
392 | * @param null $limit
393 | *
394 | * @return mixed
395 | */
396 | public function find_all_by($field, $value, $fields = '*', $order = NULL, $start = 0, $limit = NULL)
397 | {
398 | $arg_list = array();
399 | if (is_array($value)) {
400 | $arg_list = $value;
401 | $value = $arg_list[0];
402 | }
403 | $fields = isset($arg_list[1]) ? $arg_list[1] : $fields;
404 | $order = isset($arg_list[2]) ? $arg_list[2] : $order;
405 | $start = isset($arg_list[3]) ? $arg_list[3] : $start;
406 | $limit = isset($arg_list[4]) ? $arg_list[4] : $limit;
407 |
408 | $where = array($field => $value);
409 | return $this->find_all($where, $fields, $order, $start, $limit);
410 | }
411 |
412 | /**
413 | *
414 | * @param $field
415 | * @param $value
416 | * @param string $fields
417 | * @param null $order
418 | *
419 | * @return mixed
420 | */
421 | public function find_field_by($field, $value, $fields = '*', $order = NULL)
422 | {
423 | $arg_list = array();
424 |
425 | if (is_array($value)) {
426 | $arg_list = $value;
427 | $value = $arg_list[0];
428 | }
429 |
430 | $fields = isset($arg_list[1]) ? $arg_list[1] : $fields;
431 | $order = isset($arg_list[2]) ? $arg_list[2] : $order;
432 | $where = array($field => $value);
433 |
434 | return $this->field($where, $fields, $fields, $order);
435 | }
436 |
437 | /**
438 | * Insert a new row into the table. $data should be an associative array
439 | * of data to be inserted. Returns newly created ID.
440 | * @param $data
441 | * @return bool
442 | */
443 | public function insert($data)
444 | {
445 | if (false !== $data = $this->_do_pre_create($data)) {
446 | $this->_database->insert($this->_table, $data);
447 | $insert_id = $this->_database->insert_id();
448 |
449 | $this->trigger('after_create', $insert_id);
450 |
451 | return $insert_id;
452 | }
453 |
454 | return false;
455 | }
456 |
457 | /**
458 | * Insert multiple rows into the table. Returns an array of multiple IDs.
459 | * @param $data
460 | * @param bool $insert_individual
461 | * @return array
462 | */
463 | public function insert_many($data, $insert_individual = false)
464 | {
465 | if($insert_individual){
466 | return $this->_insert_individual($data);
467 | }
468 |
469 | return $this->_insert_batch($data);
470 | }
471 |
472 | private function _insert_individual($data)
473 | {
474 | $ids = array();
475 |
476 | foreach ($data as $key => $row)
477 | {
478 | if(FALSE !== $row = $this->_do_pre_create($row)) {
479 | $ids[] = $this->insert($row);
480 | }
481 | }
482 |
483 | return $ids;
484 | }
485 |
486 | private function _insert_batch($data)
487 | {
488 | $_data = array();
489 | foreach ($data as $key => $row)
490 | {
491 | if(FALSE !== $row = $this->_do_pre_create($row)){
492 | $_data[$key] = $row;
493 | }
494 | }
495 |
496 | return $this->_database->insert_batch($this->_table, $_data);
497 | }
498 |
499 | /**
500 | * Updated a record based on the primary value.
501 | * @param $primary_value
502 | * @param $data
503 | * @return bool
504 | */
505 | public function update($primary_value, $data)
506 | {
507 | $data = $this->_do_pre_update($data);
508 |
509 | if ($data !== FALSE)
510 | {
511 | $result = $this->_database->where($this->primary_key, $primary_value)
512 | ->set($data)
513 | ->update($this->_table);
514 |
515 | $this->trigger('after_update', array($data, $result));
516 |
517 | return $result;
518 | }
519 | else
520 | {
521 | return FALSE;
522 | }
523 | }
524 |
525 | /**
526 | * Update many records, based on an array of primary values.
527 | * @param $primary_values
528 | * @param $data
529 | * @return bool
530 | */
531 | public function update_many($primary_values, $data)
532 | {
533 | $data = $this->_do_pre_update($data);
534 |
535 | if ($data !== FALSE)
536 | {
537 | $result = $this->_database->where_in($this->primary_key, $primary_values)
538 | ->set($data)
539 | ->update($this->_table);
540 |
541 | $this->trigger('after_update', array($data, $result));
542 |
543 | return $result;
544 | }
545 |
546 | return FALSE;
547 | }
548 |
549 | /**
550 | * Updated a record based on an arbitrary WHERE clause.
551 | */
552 | public function update_by()
553 | {
554 | $args = func_get_args();
555 | $data = array_pop($args);
556 |
557 | $data = $this->_do_pre_update($data);
558 |
559 | if ($data !== FALSE)
560 | {
561 | $this->_set_where($args);
562 | return $this->_update($data);
563 | }
564 |
565 | return FALSE;
566 | }
567 |
568 | /**
569 | * Update all records
570 | * @param $data
571 | * @return mixed
572 | */
573 | public function update_all($data)
574 | {
575 | $data = $this->_do_pre_update($data);
576 | return $this->_update($data);
577 | }
578 |
579 | /**
580 | * Update all records
581 | * @param $data
582 | * @param $where_key
583 | * @return
584 | */
585 | public function update_batch($data, $where_key)
586 | {
587 | $_data = array();
588 |
589 | foreach ($data as $key => $row) {
590 |
591 | if (false !== $row = $this->_do_pre_update($row)) {
592 | $_data[$key] = $row;
593 | }
594 | }
595 |
596 | return $this->_database->update_batch($this->_table, $_data, $where_key);
597 | }
598 |
599 |
600 |
601 | /**
602 | * @param null $data
603 | * @param null $update
604 | *
605 | * @return bool
606 | */
607 | public function on_duplicate_update($data = NULL, $update = NULL)
608 | {
609 | if (is_null($data)) {
610 | return FALSE;
611 | }
612 |
613 | if (is_null($update)) {
614 | $update = $data;
615 | }
616 |
617 | $sql = $this->_duplicate_insert_sql($data, $update);
618 |
619 | return $this->execute_query($sql);
620 | }
621 |
622 | /**
623 | * @param $values
624 | * @param null $update
625 | *
626 | * @return string
627 | */
628 | protected function _duplicate_insert_sql($values, $update)
629 | {
630 | $updateStr = array();
631 | $keyStr = array();
632 | $valStr = array();
633 |
634 | $values = $this->trigger('before_create', $values);
635 | $update = $this->trigger('before_update', $update);
636 |
637 | foreach ($values as $key => $val) {
638 | $keyStr[] = $key;
639 | $valStr[] = $this->_database->escape($val);
640 | }
641 |
642 | foreach ($update as $key => $val) {
643 | $updateStr[] = $key . " = '{$val}'";
644 | }
645 |
646 | $sql = "INSERT INTO `" . $this->_database->dbprefix($this->_table) . "` (" . implode(', ', $keyStr) . ") ";
647 | $sql .= "VALUES (" . implode(', ', $valStr) . ") ";
648 | $sql .= "ON DUPLICATE KEY UPDATE " . implode(", ", $updateStr);
649 |
650 | return $sql;
651 | }
652 |
653 | /**
654 | * @param $condition
655 | * @param string $time
656 | * @return bool|mixed|void
657 | */
658 | protected function _delete($condition, $time = 'NOW()')
659 | {
660 | $this->trigger('before_delete', $condition);
661 |
662 | if ($this->soft_delete) {
663 | $escape = $time != 'NOW()';
664 |
665 | if(!is_string($time)){
666 | $time = $this->get_mysql_time($time);
667 | }
668 |
669 | $this->_database->set($this->deleted_at_key, $time, $escape);
670 | $result = $this->_database->update($this->_table);
671 | } else {
672 | $result = $this->_database->delete($this->_table);
673 | }
674 |
675 | return $this->trigger('after_delete', $result);
676 | }
677 |
678 | protected function get_mysql_time($time)
679 | {
680 | if($time instanceof DateTime){
681 | return $time->format('Y-m-d H:i:s');
682 | }
683 |
684 | return date('Y-m-d H:i:s', $time);
685 | }
686 |
687 | protected function prevent_if_not_soft_deletable()
688 | {
689 | if (!$this->soft_delete || !isset($this->deleted_at_key) || empty($this->deleted_at_key)) {
690 | throw new Exception('This model does not setup properly to use soft delete');
691 | }
692 | }
693 |
694 |
695 | /**
696 | * Delete a row from the table by the primary value
697 | */
698 | public function delete($id, $time = 'NOW()')
699 | {
700 | $this->_database->where($this->primary_key, $id);
701 |
702 | return $this->_delete($id, $time);
703 | }
704 |
705 |
706 | /**
707 | * Alias for delete
708 | *
709 | * @param $id
710 | * @param $time
711 | * @return bool|mixed|void
712 | */
713 | public function delete_at($id, $time)
714 | {
715 | $this->prevent_if_not_soft_deletable();
716 |
717 | return $this->delete($id, $time);
718 | }
719 |
720 | /**
721 | * Alias for delete_by
722 | *
723 | * @param $condition
724 | * @param $time
725 | * @return bool|mixed|void
726 | */
727 | public function delete_by_at($condition, $time)
728 | {
729 | $this->prevent_if_not_soft_deletable();
730 | $this->_set_where($condition);
731 |
732 | return $this->_delete($condition, $time);
733 | }
734 |
735 | /**
736 | * Alias for delete_many
737 | *
738 | * @param $primary_values
739 | * @param $time
740 | * @return bool|mixed|void
741 | */
742 | public function delete_many_at($primary_values, $time)
743 | {
744 | $this->prevent_if_not_soft_deletable();
745 |
746 | return $this->delete_many($primary_values, $time);
747 | }
748 |
749 | /**
750 | * Delete a row from the database table by an arbitrary WHERE clause
751 | */
752 | public function delete_by()
753 | {
754 | $where = func_get_args();
755 | $this->_set_where($where);
756 |
757 | return $this->_delete($where);
758 | }
759 |
760 | /**
761 | * Delete many rows from the database table by multiple primary values
762 | */
763 | public function delete_many($primary_values, $time='NOW()')
764 | {
765 | $this->_database->where_in($this->primary_key, $primary_values);
766 |
767 | return $this->_delete($primary_values, $time);
768 | }
769 |
770 |
771 | /**
772 | * Truncates the table
773 | */
774 | public function truncate()
775 | {
776 | $result = $this->_database->truncate($this->_table);
777 |
778 | return $result;
779 | }
780 |
781 | /* --------------------------------------------------------------
782 | * RELATIONSHIPS
783 | * ------------------------------------------------------------ */
784 |
785 | public function with($relationship)
786 | {
787 | $this->_with[] = $relationship;
788 |
789 | if (!$this->is_subscribed('after_get', 'relate'))
790 | {
791 | $this->subscribe('after_get', 'relate');
792 | }
793 |
794 | return $this;
795 | }
796 |
797 | public function relate($row)
798 | {
799 | if (empty($row))
800 | {
801 | return $row;
802 | }
803 |
804 | foreach ($this->belongs_to as $key => $value)
805 | {
806 | if (is_string($value))
807 | {
808 | $relationship = $value;
809 | $options = array( 'primary_key' => $value . '_id', 'model' => $value . '_model' );
810 | }
811 | else
812 | {
813 | $relationship = $key;
814 | $options = $value;
815 | }
816 |
817 | if (in_array($relationship, $this->_with))
818 | {
819 | $this->load->model($options['model'], $relationship . '_model');
820 |
821 | if (is_object($row))
822 | {
823 | $row->{$relationship} = $this->{$relationship . '_model'}->get($row->{$options['primary_key']});
824 | }
825 | else
826 | {
827 | $row[$relationship] = $this->{$relationship . '_model'}->get($row[$options['primary_key']]);
828 | }
829 | }
830 | }
831 |
832 | foreach ($this->has_many as $key => $value)
833 | {
834 | if (is_string($value))
835 | {
836 | $relationship = $value;
837 | $options = array( 'primary_key' => singular($this->_table) . '_id', 'model' => singular($value) . '_model' );
838 | }
839 | else
840 | {
841 | $relationship = $key;
842 | $options = $value;
843 | }
844 |
845 | if (in_array($relationship, $this->_with))
846 | {
847 | $this->load->model($options['model'], $relationship . '_model');
848 |
849 | if (is_object($row))
850 | {
851 | $row->{$relationship} = $this->{$relationship . '_model'}->get_many_by($options['primary_key'], $row->{$this->primary_key});
852 | }
853 | else
854 | {
855 | $row[$relationship] = $this->{$relationship . '_model'}->get_many_by($options['primary_key'], $row[$this->primary_key]);
856 | }
857 | }
858 | }
859 |
860 | return $row;
861 | }
862 |
863 | /* --------------------------------------------------------------
864 | * UTILITY METHODS
865 | * ------------------------------------------------------------ */
866 |
867 | /**
868 | * Retrieve and generate a form_dropdown friendly array
869 | */
870 | function dropdown()
871 | {
872 | $args = func_get_args();
873 |
874 | if(count($args) == 2)
875 | {
876 | list($key, $value) = $args;
877 | }
878 | else
879 | {
880 | $key = $this->primary_key;
881 | $value = $args[0];
882 | }
883 |
884 | $this->trigger('before_dropdown', array( $key, $value ));
885 |
886 | $this->apply_soft_delete_filter();
887 |
888 | $result = $this->_database->select(array($key, $value))
889 | ->get($this->_table)
890 | ->result();
891 |
892 | $options = array();
893 |
894 | foreach ($result as $row)
895 | {
896 | $options[$row->{$key}] = $row->{$value};
897 | }
898 |
899 | $options = $this->trigger('after_dropdown', $options);
900 |
901 | return $options;
902 | }
903 |
904 | /*---------------------------
905 | *Event Callback functions
906 | *---------------------------*/
907 | protected function subscribe($event, $observer, $handler = FALSE)
908 | {
909 | if (!isset($this->event_listeners[$event])) {
910 | $this->event_listeners[$event] = array();
911 | }
912 |
913 | if (is_string($handler) || (is_string($observer) && !$handler)) {
914 | $handler = !$handler ? $observer : $handler;
915 | $this->event_listeners[$event][$handler] = $observer;
916 | return $this;
917 | }
918 |
919 | $strategy = $handler ? 'array_unshift' : 'array_push';
920 |
921 | $strategy($this->event_listeners[$event], $observer);
922 |
923 | return $this;
924 | }
925 |
926 | /**
927 | * @param mixed $database
928 | *
929 | * @return $this
930 | */
931 | public function set_database($database = null)
932 | {
933 | switch (true) {
934 | case ($database === null) :
935 | $this->_database = $this->db;
936 | break;
937 | case is_string($database) :
938 | $this->_database = $this->_load_database_by_group($database);
939 | break;
940 | case ($database instanceof CI_DB_driver):
941 | $this->_database = $database;
942 | break;
943 | default :
944 | $this->_show_error('You have specified an invalid database connection/group.');
945 | }
946 |
947 | return $this;
948 | }
949 |
950 | private function _load_database_by_group($group)
951 | {
952 | if (!isset(self::$_connection_cache[$group])) {
953 | self::$_connection_cache[$group] = $this->load->database($group);
954 | }
955 |
956 | return self::$_connection_cache[$group];
957 | }
958 |
959 | protected function unsubscribe($event, $handler)
960 | {
961 | if (!isset($this->event_listeners[$event][$handler])) {
962 | unset($this->event_listeners[$event][$handler]);
963 | }
964 |
965 | return $this;
966 | }
967 |
968 | protected function is_subscribed($event, $handler)
969 | {
970 | return isset($this->event_listeners[$event][$handler]);
971 | }
972 |
973 | /**
974 | * Fetch a count of rows based on an arbitrary WHERE call.
975 | */
976 | public function count_by()
977 | {
978 | $where = func_get_args();
979 | $this->_set_where($where);
980 | $this->apply_soft_delete_filter();
981 |
982 | return $this->_database->count_all_results($this->_table);
983 | }
984 |
985 | /**
986 | * Fetch a total count of rows, disregarding any previous conditions
987 | */
988 | public function count_all()
989 | {
990 | $this->apply_soft_delete_filter();
991 |
992 | return $this->_database->count_all($this->_table);
993 | }
994 |
995 | /**
996 | * Tell the class to skip the insert validation
997 | */
998 | public function skip_validation()
999 | {
1000 | $this->skip_validation = TRUE;
1001 |
1002 | return $this;
1003 | }
1004 |
1005 | /**
1006 | * Tell the class to skip the insert validation
1007 | */
1008 | public function enable_validation()
1009 | {
1010 | $this->skip_validation = FALSE;
1011 |
1012 | return $this;
1013 | }
1014 |
1015 | /**
1016 | * Return the next auto increment of the table. Only tested on MySQL.
1017 | */
1018 | public function get_next_id()
1019 | {
1020 | return (int) $this->_database->select('AUTO_INCREMENT')
1021 | ->from('information_schema.TABLES')
1022 | ->where('TABLE_NAME', $this->_database->dbprefix($this->get_table()))
1023 | ->where('TABLE_SCHEMA', $this->_database->database)->get()->row()->AUTO_INCREMENT;
1024 | }
1025 |
1026 | /**
1027 | * Getter for the table name
1028 | */
1029 | public function get_table()
1030 | {
1031 | return $this->_table;
1032 | }
1033 |
1034 | /**
1035 | * Getter for the primary key
1036 | */
1037 | public function primary_key()
1038 | {
1039 | return $this->primary_key;
1040 | }
1041 |
1042 | /**
1043 | * @param $sql
1044 | *
1045 | * @return mixed
1046 | */
1047 | public function execute_query($sql)
1048 | {
1049 | return $this->_database->query($sql);
1050 | }
1051 |
1052 | /**
1053 | * @return mixed
1054 | */
1055 | public function get_last_query()
1056 | {
1057 | return $this->_database->last_query();
1058 | }
1059 |
1060 | /**
1061 | * Alias for db->insert_string()
1062 | * @param $data
1063 | *
1064 | * @return mixed
1065 | */
1066 | public function get_insert_string($data)
1067 | {
1068 | return $this->_database->insert_string($this->get_table(), $data);
1069 | }
1070 |
1071 | /**
1072 | * @return int
1073 | */
1074 | public function get_num_rows()
1075 | {
1076 | return $this->num_rows;
1077 | }
1078 |
1079 | /**
1080 | * @return null|int
1081 | */
1082 | public function get_insert_id()
1083 | {
1084 | return $this->_database->insert_id();
1085 | }
1086 |
1087 | /**
1088 | * @return null|mix
1089 | */
1090 | public function get_affected_rows()
1091 | {
1092 | return $this->_database->affected_rows();
1093 | }
1094 |
1095 |
1096 | /* --------------------------------------------------------------
1097 | * GLOBAL SCOPES
1098 | * ------------------------------------------------------------ */
1099 |
1100 | /**
1101 | * Return the next call as an array rather than an object
1102 | */
1103 | public function as_array()
1104 | {
1105 | $this->_temporary_return_type = 'array';
1106 | return $this;
1107 | }
1108 |
1109 | /**
1110 | * Return the next call as an object rather than an array
1111 | */
1112 | public function as_object()
1113 | {
1114 | $this->_temporary_return_type = 'object';
1115 | return $this;
1116 | }
1117 |
1118 | /**
1119 | * Don't care about soft deleted rows on the next call
1120 | */
1121 | public function with_deleted()
1122 | {
1123 | $this->_temporary_with_deleted = TRUE;
1124 | return $this;
1125 | }
1126 |
1127 | /**
1128 | * Only get deleted rows on the next call
1129 | */
1130 | public function only_deleted()
1131 | {
1132 | $this->_temporary_only_deleted = TRUE;
1133 | return $this;
1134 | }
1135 |
1136 | /* --------------------------------------------------------------
1137 | * OBSERVERS
1138 | * ------------------------------------------------------------ */
1139 |
1140 | /**
1141 | * MySQL DATETIME created_at and updated_at
1142 | */
1143 | public function created_at($row)
1144 | {
1145 | if (is_object($row))
1146 | {
1147 | $row->{$this->created_at_key} = date('Y-m-d H:i:s');
1148 | }
1149 | else
1150 | {
1151 | $row[$this->created_at_key] = date('Y-m-d H:i:s');
1152 | }
1153 |
1154 | return $row;
1155 | }
1156 |
1157 | public function updated_at($row)
1158 | {
1159 | if (is_object($row))
1160 | {
1161 | $row->{$this->updated_at_key} = date('Y-m-d H:i:s');
1162 | }
1163 | else
1164 | {
1165 | $row[$this->updated_at_key] = date('Y-m-d H:i:s');
1166 | }
1167 |
1168 | return $row;
1169 | }
1170 |
1171 |
1172 | public function created_by($row)
1173 | {
1174 | if (is_object($row))
1175 | {
1176 | $row->{$this->created_by_key} = $this->get_current_user();
1177 | }
1178 | else
1179 | {
1180 | $row[$this->created_by_key] = $this->get_current_user();
1181 | }
1182 |
1183 | return $row;
1184 | }
1185 |
1186 | public function updated_by($row)
1187 | {
1188 | if (is_object($row))
1189 | {
1190 | $row->{$this->updated_by_key} = $this->get_current_user();
1191 | }
1192 | else
1193 | {
1194 | $row[$this->updated_by_key] = $this->get_current_user();
1195 | }
1196 |
1197 | return $row;
1198 | }
1199 |
1200 | public function update_deleted_by($id)
1201 | {
1202 | $this->_database->set($this->deleted_by_key, $this->get_current_user());
1203 | return $id;
1204 | }
1205 |
1206 | /**
1207 | * Serialises data for you automatically, allowing you to pass
1208 | * through objects and let it handle the serialisation in the background
1209 | */
1210 | public function serialize_row($row)
1211 | {
1212 | foreach ($this->callback_parameters as $column)
1213 | {
1214 | $row[$column] = serialize($row[$column]);
1215 | }
1216 |
1217 | return $row;
1218 | }
1219 |
1220 | public function unserialize_row($row)
1221 | {
1222 | foreach ($this->callback_parameters as $column)
1223 | {
1224 | if (is_array($row))
1225 | {
1226 | $row[$column] = unserialize($row[$column]);
1227 | }
1228 | else
1229 | {
1230 | $row->$column = unserialize($row->$column);
1231 | }
1232 | }
1233 |
1234 | return $row;
1235 | }
1236 |
1237 | /**
1238 | * Protect attributes by removing them from $row array
1239 | */
1240 | public function protect_attributes($row)
1241 | {
1242 | foreach ($this->protected_attributes as $attr)
1243 | {
1244 | if (is_object($row))
1245 | {
1246 | unset($row->$attr);
1247 | }
1248 | else
1249 | {
1250 | unset($row[$attr]);
1251 | }
1252 | }
1253 |
1254 | return $row;
1255 | }
1256 |
1257 | /* --------------------------------------------------------------
1258 | * QUERY BUILDER DIRECT ACCESS METHODS
1259 | * ------------------------------------------------------------ */
1260 |
1261 | /**
1262 | * A wrapper to $this->_database->order_by()
1263 | *
1264 | * call the ci->db->order_by method as per provided param
1265 | * The param can be string just like default order_by function expect
1266 | * or can be array with set of param!!
1267 | *
1268 | * $model->order_by('fieldName DESC');
1269 | * or
1270 | * $model->order_by(array('fieldName','DESC'));
1271 | * or
1272 | * $model->order_by(array('fieldName'=>'DESC', 'fieldName2'=>'ASC'));
1273 | * or
1274 | * $model->order_by(array(array('fieldName','DESC'),'fieldName DESC'));
1275 | *
1276 | *
1277 | * @param $criteria
1278 | * @param string $order
1279 | * @internal param mixed $orders
1280 | *
1281 | * @return bool
1282 | */
1283 | public function order_by($criteria, $order = null)
1284 | {
1285 |
1286 | if ($criteria == NULL) {
1287 | return $this;
1288 | }
1289 |
1290 | if (is_array($criteria)) { //Multiple order by provided!
1291 | //check if we got single order by passed as array!!
1292 | if (isset($criteria[1]) && (strtolower($criteria[1]) == 'asc' || strtolower($criteria[1]) == 'desc' || strtolower($criteria[1]) == 'random')) {
1293 | $this->_database->order_by($criteria[0], $criteria[1]);
1294 | return $this;
1295 | }
1296 | foreach ($criteria as $key => $value)
1297 | {
1298 | if(is_array($value)){
1299 | $this->order_by($value);
1300 | }else{
1301 | $order_criteria = is_int($key) ? $value : $key;
1302 | $lower_key = strtolower($value);
1303 | $order = ($lower_key == 'asc' || $lower_key == 'desc' || $lower_key == 'random') ? $value : null;
1304 | $this->_database->order_by($order_criteria, $order);
1305 | }
1306 | }
1307 |
1308 | return $this;
1309 | }
1310 |
1311 | $this->_database->order_by($criteria, $order); //its a string just call db order_by
1312 |
1313 | return $this;
1314 | }
1315 |
1316 | /**
1317 | * A wrapper to $this->_database->limit()
1318 | */
1319 | public function limit($limit, $offset = 0)
1320 | {
1321 | $this->_database->limit($limit, $offset);
1322 | return $this;
1323 | }
1324 |
1325 | /**
1326 | * @param null|string|array $conditions
1327 | * @param string $fields
1328 | * @param null|string|array $order
1329 | * @param int $start
1330 | * @param null|int $limit
1331 | *
1332 | * @return mixed
1333 | */
1334 | public function find_all($conditions = NULL, $fields = '*', $order = NULL, $start = 0, $limit = NULL)
1335 | {
1336 | if ($conditions != NULL) {
1337 | if (is_array($conditions)) {
1338 | $this->_database->where($conditions);
1339 | } else {
1340 | $this->_database->where($conditions, NULL, FALSE);
1341 | }
1342 | }
1343 |
1344 | if ($fields != NULL) {
1345 | $this->_database->select($fields);
1346 | }
1347 |
1348 | if ($order != NULL) {
1349 | $this->order_by($order);
1350 | }
1351 |
1352 | if ($limit != NULL) {
1353 | $this->_database->limit($limit, $start);
1354 | }
1355 |
1356 | return $this->get_all();
1357 | }
1358 |
1359 |
1360 | /**
1361 | * @param null|string|array $conditions
1362 | * @param string $fields
1363 | * @param null|string|array $order
1364 | *
1365 | * @return bool
1366 | */
1367 | public function find($conditions = NULL, $fields = '*', $order = NULL)
1368 | {
1369 | $data = $this->find_all($conditions, $fields, $order, 0, 1);
1370 |
1371 | if ($data) {
1372 | return $data[0];
1373 | } else {
1374 | return FALSE;
1375 | }
1376 | }
1377 |
1378 | /**
1379 | * @param null $conditions
1380 | * @param $name
1381 | * @param string $fields
1382 | * @param null $order
1383 | *
1384 | * @return bool
1385 | */
1386 | public function field($conditions = NULL, $name, $fields = '*', $order = NULL)
1387 | {
1388 | $data = $this->find_all($conditions, $fields, $order, 0, 1);
1389 |
1390 | if ($data) {
1391 | $row = $data[0];
1392 | if (isset($row[$name])) {
1393 | return $row[$name];
1394 | }
1395 | }
1396 |
1397 | return FALSE;
1398 | }
1399 |
1400 | /**
1401 | * Alias of count_by
1402 | * @param null $conditions
1403 | *
1404 | * @return integer
1405 | */
1406 | public function find_count($conditions = NULL)
1407 | {
1408 | return $this->count_by($conditions);
1409 | }
1410 |
1411 | /* --------------------------------------------------------------
1412 | * INTERNAL METHODS
1413 | * ------------------------------------------------------------ */
1414 |
1415 |
1416 | /**
1417 | * Trigger an event and call its observers. Pass through the event name
1418 | * (which looks for an instance variable $this->event_listeners[event_name] or $this->event_name), an array of
1419 | * parameters to pass through and an optional 'last in iteration' boolean
1420 | *
1421 | * @param $event
1422 | * @param bool|array|void|int $data
1423 | * @param bool $last
1424 | * @return bool|mixed
1425 | */
1426 | public function trigger($event, $data = FALSE, $last = TRUE)
1427 | {
1428 | if (isset($this->event_listeners[$event]) && is_array($this->event_listeners[$event])) {
1429 | $data = $this->_trigger_event($this->event_listeners[$event], $data, $last);
1430 | }elseif (isset($this->$event) && is_array($this->$event)){
1431 | $data = $this->_trigger_event($this->$event, $data, $last);
1432 | }
1433 |
1434 | return $data;
1435 | }
1436 |
1437 | private function _trigger_event($event_listeners, $data, $last)
1438 | {
1439 | foreach ($event_listeners as $method) {
1440 | if (is_string($method) && strpos($method, '(')) {
1441 | preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches);
1442 |
1443 | $method = $matches[1];
1444 | $this->callback_parameters = explode(',', $matches[3]);
1445 | }
1446 |
1447 | $callable = $this->getCallableFunction($method);
1448 |
1449 | if (!$callable) {
1450 | $this->callback_parameters = array();
1451 | continue;
1452 | }
1453 |
1454 | $data = call_user_func_array($callable, array($data, $last));
1455 | }
1456 |
1457 | return $data;
1458 | }
1459 |
1460 |
1461 | /**
1462 | * Get callable as per given method
1463 | *
1464 | * @param $method
1465 | * @return array|bool|callable
1466 | */
1467 | private function getCallableFunction($method)
1468 | {
1469 | if (is_callable($method)) {
1470 | return $method;
1471 | }
1472 |
1473 | if (is_string($method) && is_callable(array($this, $method))) {
1474 | return array($this, $method);
1475 | }
1476 |
1477 | return FALSE;
1478 | }
1479 |
1480 | /**
1481 | * filter data as per delete status
1482 | */
1483 | protected function apply_soft_delete_filter()
1484 | {
1485 | if ($this->soft_delete && $this->_temporary_with_deleted !== TRUE) {
1486 | if($this->_temporary_only_deleted)
1487 | {
1488 | $where = "`{$this->deleted_at_key}` <= NOW()";
1489 | }
1490 | else
1491 | {
1492 | $where = sprintf('(%1$s > NOW() OR %1$s IS NULL OR %1$s = \'0000-00-00 00:00:00\')', $this->deleted_at_key);
1493 | }
1494 |
1495 | $this->_database->where($where);
1496 | }
1497 | }
1498 |
1499 |
1500 | /**
1501 | * Run validation on the passed data
1502 | */
1503 | public function validate($data)
1504 | {
1505 | if($this->skip_validation)
1506 | {
1507 | return $data;
1508 | }
1509 |
1510 | if(!empty($this->validate))
1511 | {
1512 | foreach($data as $key => $val)
1513 | {
1514 | $_POST[$key] = $val;
1515 | }
1516 |
1517 | $this->load->library('form_validation');
1518 |
1519 | if(is_array($this->validate))
1520 | {
1521 | $this->form_validation->set_rules($this->validate);
1522 |
1523 | if ($this->form_validation->run() === TRUE)
1524 | {
1525 | return $data;
1526 | }
1527 | else
1528 | {
1529 | return FALSE;
1530 | }
1531 | }
1532 | else
1533 | {
1534 | if ($this->form_validation->run($this->validate) === TRUE)
1535 | {
1536 | return $data;
1537 | }
1538 | else
1539 | {
1540 | return FALSE;
1541 | }
1542 | }
1543 | }
1544 | else
1545 | {
1546 | return $data;
1547 | }
1548 | }
1549 |
1550 | /**
1551 | * Guess the table name by pluralising the model name
1552 | */
1553 | private function _fetch_table()
1554 | {
1555 | if ($this->_table == NULL)
1556 | {
1557 | $this->_table = plural(preg_replace('/(_m|_model)?$/', '', strtolower(get_class($this))));
1558 | }
1559 | }
1560 |
1561 | /**
1562 | * Guess the primary key for current table
1563 | */
1564 | protected function _fetch_primary_key()
1565 | {
1566 | if($this->is_base_model_instance()) {
1567 | return;
1568 | }
1569 |
1570 | if ($this->primary_key == NULL && $this->_database) {
1571 | $this->primary_key = $this->execute_query("SHOW KEYS FROM `" . $this->_database->dbprefix($this->_table) . "` WHERE Key_name = 'PRIMARY'")->row()->Column_name;
1572 | }
1573 | }
1574 |
1575 | private function is_base_model_instance()
1576 | {
1577 | if($this->_base_model_instance == null){
1578 | $subclass_prefix = $this->config->item('subclass_prefix');
1579 | $this->_base_model_instance = get_class($this) == $subclass_prefix . "Model";
1580 | }
1581 |
1582 | return $this->_base_model_instance;
1583 | }
1584 |
1585 | public function get_fields()
1586 | {
1587 | if (empty($this->_fields) && $this->_database) {
1588 | $this->_fields = (array)$this->_database->list_fields($this->_table);
1589 | }
1590 |
1591 | return $this->_fields;
1592 | }
1593 |
1594 | protected function isFieldExist($field)
1595 | {
1596 | return in_array($field, $this->get_fields());
1597 | }
1598 |
1599 | protected function _guess_is_soft_deletable()
1600 | {
1601 | if ($this->soft_delete === NULL) {
1602 | $this->soft_delete = $this->isFieldExist($this->deleted_at_key);
1603 | }
1604 |
1605 | if (!$this->soft_delete) {
1606 | return;
1607 | }
1608 |
1609 | if ($this->isFieldExist($this->deleted_by_key) && $this->get_current_user()) {
1610 | $this->subscribe('before_delete', 'update_deleted_by','update_deleted_by');
1611 | }
1612 | }
1613 |
1614 |
1615 | protected function _guess_is_blamable()
1616 | {
1617 | if ($this->blamable === NULL) {
1618 | $this->blamable = $this->isFieldExist($this->created_by_key)
1619 | && $this->isFieldExist($this->updated_by_key)
1620 | && $this->get_current_user();
1621 | }
1622 |
1623 | if ($this->timestampable) {
1624 | $this->subscribe('before_create', 'created_by');
1625 | $this->subscribe('before_create', 'updated_by');
1626 | $this->subscribe('before_update', 'updated_by');
1627 | }
1628 | }
1629 |
1630 | protected function _guess_is_timestampable()
1631 | {
1632 | if ($this->timestampable === NULL) {
1633 | $this->timestampable = $this->isFieldExist($this->created_at_key)
1634 | && $this->isFieldExist($this->updated_at_key);
1635 | }
1636 |
1637 | if ($this->timestampable) {
1638 | $this->subscribe('before_create', 'created_at');
1639 | $this->subscribe('before_create', 'updated_at');
1640 | $this->subscribe('before_update', 'updated_at');
1641 | }
1642 | }
1643 |
1644 | /**
1645 | * Set WHERE parameters, cleverly
1646 | * @param $params
1647 | */
1648 | protected function _set_where($params)
1649 | {
1650 | if (count($params) == 1)
1651 | {
1652 | $this->_database->where($params[0]);
1653 | }
1654 | else if(count($params) == 2)
1655 | {
1656 | $this->_database->where($params[0], $params[1]);
1657 | }
1658 | else if(count($params) == 3)
1659 | {
1660 | $this->_database->where($params[0], $params[1], $params[2]);
1661 | }
1662 | else
1663 | {
1664 | $this->_database->where($params);
1665 | }
1666 | }
1667 |
1668 | /**
1669 | * Return the method name for the current return type
1670 | * @param bool $multi
1671 | * @return string
1672 | */
1673 | protected function _get_return_type_method($multi = FALSE)
1674 | {
1675 | $method = ($multi) ? 'result' : 'row';
1676 | return $this->_temporary_return_type == 'array' ? $method . '_array' : $method;
1677 | }
1678 |
1679 |
1680 | /**
1681 | * you Must implement this function in MY_Model Class to use blamable an deleted_by feature
1682 | */
1683 | protected function get_current_user()
1684 | {
1685 | return false;
1686 | }
1687 |
1688 | /**
1689 | * @param $data
1690 | * @return mixed
1691 | */
1692 | private function _update($data)
1693 | {
1694 | $result = $this->_database->set($data)
1695 | ->update($this->_table);
1696 | $this->trigger('after_update', array($data, $result));
1697 |
1698 | return $result;
1699 | }
1700 |
1701 | /**
1702 | * @param $msg
1703 | */
1704 | private function _show_error($msg)
1705 | {
1706 | if (function_exists('show_error')) {
1707 | show_error($msg);
1708 | }
1709 | }
1710 |
1711 | /**
1712 | * @param $data
1713 | * @return bool|mixed
1714 | */
1715 | private function _do_pre_update($data)
1716 | {
1717 | $data = $this->validate($data);
1718 | $data = $this->trigger('before_update', $data);
1719 |
1720 | return $data;
1721 | }
1722 |
1723 | /**
1724 | * @param $data
1725 | * @return bool|mixed
1726 | */
1727 | private function _do_pre_create($data)
1728 | {
1729 | $data = $this->validate($data);
1730 | $data = $this->trigger('before_create', $data);
1731 |
1732 | return $data;
1733 | }
1734 |
1735 | /**
1736 | * @param $methodName
1737 | * @return null
1738 | */
1739 | private function _handle_exception($methodName)
1740 | {
1741 | if (!function_exists('_exception_handler')) {
1742 | return null;
1743 | }
1744 |
1745 | $trace = debug_backtrace(null, 1);
1746 | $errMsg = 'Undefined method : ' . get_class($this) . "::" . $methodName . " called";
1747 | _exception_handler(E_USER_NOTICE, $errMsg, $trace[0]['file'], $trace[0]['line']);
1748 | }
1749 | }
--------------------------------------------------------------------------------