409 lines
11 KiB
PHP
409 lines
11 KiB
PHP
<?php defined('SYSPATH') or die('No direct access allowed.');
|
|
|
|
/**
|
|
* This class overrides Kohana's ORM
|
|
*
|
|
* This file contains enhancements for Kohana, that should be considered upstream and maybe havent been yet.
|
|
*
|
|
* @package lnApp
|
|
* @category Modifications
|
|
* @author Deon George
|
|
* @copyright (c) 2014 Deon George
|
|
* @license http://dev.leenooks.net/license.html
|
|
*/
|
|
abstract class lnApp_ORM extends Kohana_ORM {
|
|
protected $_table_names_plural = FALSE;
|
|
protected $_model_names_plural = FALSE;
|
|
private $_object_formated = array();
|
|
private $_formated = FALSE;
|
|
|
|
protected $_created_column = array('column'=>'date_orig','format'=>TRUE);
|
|
protected $_updated_column = array('column'=>'date_last','format'=>TRUE);
|
|
|
|
// Our filters used to display values in a friendly format
|
|
protected $_display_filters = array();
|
|
|
|
// Our attributes used in forms.
|
|
protected $_form = array();
|
|
|
|
// Our attribute blobs that should be compressed
|
|
protected $_compress_column = array();
|
|
|
|
// Our attributes that should be converted to NULL when empty
|
|
protected $_nullifempty = array();
|
|
|
|
// Our attribute values that need to be stored as serialized
|
|
protected $_serialize_column = array();
|
|
|
|
// If we need to load any sub items on loading this model
|
|
protected $_sub_items = array();
|
|
protected $_sub_items_load = array();
|
|
protected $_sub_items_sorted = FALSE;
|
|
|
|
// Whether to show a SystemMessage when a record is saved.
|
|
protected $_save_message = FALSE;
|
|
|
|
/**
|
|
* Auto process some data as it comes from the database
|
|
* @see parent::__get()
|
|
*/
|
|
public function __get($column) {
|
|
if (array_key_exists($column,$this->_table_columns)) {
|
|
// If the column is a blob, we'll decode it automatically
|
|
if (
|
|
$this->_table_columns[$column]['data_type'] == 'blob'
|
|
AND in_array($column,$this->_compress_column)
|
|
AND ! is_null($this->_object[$column])
|
|
AND ! isset($this->_changed[$column])
|
|
AND (! isset($this->_table_columns[$column]['auto_convert']) OR ! $this->_table_columns[$column]['auto_convert'])
|
|
) {
|
|
|
|
// In case our blob hasnt been saved as one.
|
|
try {
|
|
$this->_object[$column] = $this->_blob($this->_object[$column]);
|
|
}
|
|
catch(Exception $e) {
|
|
HTTP_Exception::factory(501,Kohana_Exception::text($e));
|
|
}
|
|
|
|
$this->_table_columns[$column]['auto_convert'] = TRUE;
|
|
}
|
|
|
|
// If the column is a serialized object, we'll unserialize it.
|
|
if (
|
|
in_array($column,$this->_serialize_column)
|
|
AND is_string($this->_object[$column])
|
|
AND ! is_null($this->_object[$column])
|
|
AND ! isset($this->_changed[$column])
|
|
AND (! isset($this->_table_columns[$column]['unserialized']) OR ! $this->_table_columns[$column]['unserialized'])
|
|
) {
|
|
|
|
// In case our object hasnt been saved as serialized.
|
|
try {
|
|
$this->_object[$column] = unserialize($this->_object[$column]);
|
|
}
|
|
catch(Exception $e) {
|
|
HTTP_Exception::factory(501,Kohana_Exception::text($e));
|
|
}
|
|
|
|
$this->_table_columns[$column]['unserialized'] = TRUE;
|
|
}
|
|
}
|
|
|
|
return parent::__get($column);
|
|
}
|
|
|
|
/**
|
|
* Retrieve and Store DB BLOB data in compressed format.
|
|
*/
|
|
private function _blob($data,$set=FALSE) {
|
|
try {
|
|
return $set ? gzcompress($this->_serialize($data,$set)) : $this->_serialize(gzuncompress($data));
|
|
|
|
// Maybe the data isnt compressed?
|
|
} catch (Exception $e) {
|
|
return $this->_serialize($data,$set);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format fields for display purposes
|
|
*
|
|
* @param string column name
|
|
* @return mixed
|
|
*/
|
|
private function _format() {
|
|
foreach ($this->_display_filters as $column => $formats)
|
|
$this->_object_formated[$column] = $this->run_filter($column,$this->__get($column),array($column=>$formats));
|
|
|
|
$this->_formated = TRUE;
|
|
}
|
|
|
|
/**
|
|
* Intercept our object load, so that we can load our subitems
|
|
*/
|
|
protected function _load_values(array $values) {
|
|
parent::_load_values($values);
|
|
|
|
if ($this->_sub_items_load AND count($this->_sub_items_load) > 1)
|
|
throw HTTP_Exception::factory('501','Sub Items doesnt support more than 1 load');
|
|
|
|
$sort = array();
|
|
if ($this->_loaded AND $this->_sub_items_load)
|
|
foreach ($this->_sub_items_load as $item => $sort)
|
|
$this->_sub_items = $this->$item->find_all()->as_array();
|
|
|
|
if ($sort) {
|
|
Sort::MAsort($this->_sub_items,$sort);
|
|
$this->_sub_items_sorted = TRUE;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* If a column is marked to be nullified if it is empty, this is where it is done.
|
|
*/
|
|
private function _nullifempty(array $array) {
|
|
foreach ($array as $k=>$v) {
|
|
if (is_array($v)) {
|
|
if (is_null($x=$this->_nullifempty($v)))
|
|
unset($array[$k]);
|
|
else
|
|
$array[$k] = $x;
|
|
|
|
} elseif (! $v AND $v !== 0 AND $v !== '0')
|
|
unset($array[$k]);
|
|
|
|
}
|
|
|
|
return count($array) ? $array : NULL;
|
|
}
|
|
|
|
/**
|
|
* Try and (un)serialize our data, and if it fails, just return it.
|
|
*/
|
|
private function _serialize($data,$set=FALSE) {
|
|
try {
|
|
return $set ? serialize($data) : unserialize($data);
|
|
|
|
// Maybe the data serialized?
|
|
} catch (Exception $e) {
|
|
return $data;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overrides Kohana cache so that it can be globally disabled.
|
|
*/
|
|
public function cached($lifetime=NULL) {
|
|
return $this->_db->caching($this->_table_name) ? parent::cached($lifetime) : $this;
|
|
}
|
|
|
|
public function clear() {
|
|
$this->_formated = FALSE;
|
|
$this->_object_formated = array();
|
|
|
|
return parent::clear();
|
|
}
|
|
|
|
/**
|
|
* Return a formated columns, as per the model definition
|
|
*/
|
|
public function display($column,$default='') {
|
|
// Trigger a load of the record.
|
|
$value = $this->__get($column);
|
|
|
|
if (! $value)
|
|
$value = $default;
|
|
|
|
// If some of our fields need to be formated for display purposes.
|
|
if (! $this->_formated AND $this->_display_filters)
|
|
$this->_format();
|
|
|
|
if (isset($this->_object_formated[$column]))
|
|
return $this->_object_formated[$column];
|
|
else
|
|
return is_array($value) ? join(', ',$value) : $value;
|
|
}
|
|
|
|
public function display_filters(array $filters) {
|
|
$this->_display_filters = Arr::merge($this->_display_filters,$filters);
|
|
}
|
|
|
|
public function dump() {
|
|
$result = array();
|
|
|
|
$result['this'] = $this->object();
|
|
|
|
if (isset($this->_sub_items))
|
|
foreach ($this->_sub_items as $o)
|
|
$result['sub'][] = $o->dump();
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* This function is our AJAX helper, used by module list_autocomplete()
|
|
*/
|
|
public function list_autocomplete($term,$index,$value,array $label,array $limit=array(),array $options=NULL) {
|
|
$result = array();
|
|
|
|
$query = empty($options['object']) ? $this : $options['object'];
|
|
|
|
foreach ($limit as $w) {
|
|
list($k,$s,$v) = $w;
|
|
|
|
$query->and_where($k,$s,$v);
|
|
}
|
|
|
|
$c = 0;
|
|
foreach ((empty($options['object']) ? $query->find_all() : $query->execute()) as $o) {
|
|
// If we got here via a DB query, we need to reload our ORM object from the result.
|
|
if (! is_object($o)) {
|
|
if (empty($options['key']))
|
|
throw new Kohana_Exception('Missing key for non object');
|
|
|
|
$o = $this->clear()->where($options['key'],'=',$o[$options['key']])->find();
|
|
}
|
|
|
|
switch ($index) {
|
|
case 'url':
|
|
if (empty($options['urlprefix']))
|
|
throw new Kohana_Exception('Missing URL Prefix');
|
|
|
|
$v = $options['urlprefix'].$o->resolve($value);
|
|
|
|
break;
|
|
|
|
default: $v = $o->resolve($value);
|
|
}
|
|
|
|
$k = '';
|
|
foreach ($label as $k => $details)
|
|
foreach ($details as $lvalue)
|
|
$k = preg_replace('/%s/',$o->resolve($lvalue),$k,1);
|
|
|
|
$result[$c++] = array(
|
|
'value'=>$v,
|
|
'label'=>$k,
|
|
);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Return an array of data that can be used in a SELECT statement.
|
|
* The ID and VALUE is defined in the model for the select.
|
|
*/
|
|
public function list_select($blank=FALSE,$object=NULL) {
|
|
$result = array();
|
|
|
|
if (is_null($object))
|
|
$object = $this->find_all();
|
|
|
|
if ($blank)
|
|
$result[NULL] = '';
|
|
|
|
if ($this->_form AND array_intersect(array('id','value'),$this->_form))
|
|
foreach ($object as $o)
|
|
$result[$o->{$this->_form['id']}] = $o->resolve($this->_form['value']);
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Replace this ORM object with one in the database
|
|
*/
|
|
public function replace(array $key,$id='id') {
|
|
$o = ORM::factory(ucfirst($this->_object_name),$key);
|
|
|
|
foreach ($this->changed() as $k)
|
|
$o->{$k} = $this->{$k};
|
|
|
|
$o->_sub_items = $this->_sub_items;
|
|
|
|
return $o->save();
|
|
}
|
|
|
|
/**
|
|
* This function is used so that methods can be called via variables
|
|
*/
|
|
public function resolve($key) {
|
|
eval("\$x = \$this->$key;");
|
|
|
|
return $x;
|
|
}
|
|
|
|
public function save(Validation $validation=NULL) {
|
|
// Find any fields that have changed, and process them.
|
|
if ($this->_changed)
|
|
foreach ($this->_changed as $c) {
|
|
// Any fields that are blobs, and encode them.
|
|
if (! is_null($this->_object[$c]) AND $this->_table_columns[$c]['data_type'] == 'blob' AND in_array($c,$this->_compress_column)) {
|
|
$this->_object[$c] = $this->_blob($this->_object[$c],TRUE);
|
|
|
|
// We need to reset our auto_convert flag
|
|
if (isset($this->_table_columns[$c]['auto_convert']))
|
|
$this->_table_columns[$c]['auto_convert'] = FALSE;
|
|
|
|
// Any fields that should be seriailzed, we'll do that.
|
|
} elseif (is_array($this->_object[$c]) AND in_array($c,$this->_serialize_column)) {
|
|
$this->_object[$c] = serialize($this->_object[$c]);
|
|
}
|
|
|
|
// Test if the value has still changed
|
|
if ($this->_original_values AND $this->_object[$c] == $this->_original_values[$c])
|
|
unset($this->_changed[$c]);
|
|
}
|
|
|
|
parent::save();
|
|
|
|
if ($this->saved() AND $this->_save_message AND (PHP_SAPI !== 'cli'))
|
|
SystemMessage::factory()
|
|
->title('Record Updated')
|
|
->type('success')
|
|
->body(sprintf('Record %s:%s Updated',$this->_table_name,$this->id));
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function subitems() {
|
|
return $this->_sub_items;
|
|
}
|
|
|
|
public function subitem_add(Model $item) {
|
|
array_push($this->_sub_items,$item);
|
|
}
|
|
|
|
public function subitem_get($model,array $key) {
|
|
$class = 'Model_'.$model;
|
|
|
|
foreach ($this->_sub_items as $o)
|
|
if ($o instanceof $class AND array_intersect($o->object(),$key) === $key)
|
|
return $o;
|
|
|
|
$item = ORM::factory($model);
|
|
array_push($this->_sub_items,$item);
|
|
|
|
return $item;
|
|
}
|
|
|
|
/**
|
|
* Override the Kohana processing so we can null values if required.
|
|
* We override this function, because we do set our own primary key value
|
|
*/
|
|
public function values(array $values, array $expected = NULL) {
|
|
foreach ($values as $k=>$v) {
|
|
// Convert to NULL
|
|
if (in_array($k,$this->_nullifempty)) {
|
|
if (is_array($v))
|
|
$values[$k] = $this->_nullifempty($v);
|
|
|
|
elseif (! $v AND $v !== 0 AND $v !== '0')
|
|
$values[$k] = NULL;
|
|
}
|
|
}
|
|
|
|
parent::values($values,$expected);
|
|
|
|
if (isset($values[$this->_primary_key]))
|
|
$this->{$this->_primary_key} = $values[$this->_primary_key];
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function what_changed() {
|
|
$result = array();
|
|
|
|
foreach ($this->changed() as $k) {
|
|
$result[$k]['old'] = ($x=Arr::get($this->_original_values,$k)) ? $x : serialize($x);
|
|
$result[$k]['new'] = ($x=Arr::get($this->_object,$k)) ? $x : serialize($x);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|
|
?>
|