1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517:
<?php
// The query cache
// force UTF-8 Ø
$_zp_object_cache = array();
define('OBJECT_CACHE_DEPTH', 150); // how many objects to hold for each object class
/**
* Persistent Object Class
*
* Parent ABSTRACT class of all persistent objects. This class should not be
* instantiated, only used for subclasses. This cannot be enforced, but please
* follow it!
*
* A child class should run the follwing in its constructor:
*
* $new = $this->instantiate('tablename',
* array('uniquestring'=>$value, 'uniqueid'=>$uniqueid));
*
* where 'tablename' is the name of the database table to use for this object
* type, and array('uniquestring'=>$value, ...) defines a unique set of columns
* (keys) and their current values which uniquely identifies a single record in
* that database table for this object.
*
* Note: This is a persistable model that does not save automatically. You MUST
* call $this->save(); explicitly to persist the data in child classes.
*
* @package core
* @subpackage classes\objects
*/
class PersistentObject {
public $loaded = false;
public $exists = false;
public $table;
public $transient;
protected $id = 0;
private $unique_set = NULL;
private $cache_by;
private $use_cache = false;
private $tempdata = NULL;
private $data = NULL;
private $updates = NULL;
/**
*
* @deprecated Zenphoto 1.6 - Unser instantiate() instead
* @since 1.4.6
*/
function __construct($tablename, $unique_set, $cache_by = NULL, $use_cache = true, $is_transient = false, $allowCreate = true) {
return instantiate($tablename, $unique_set, $cache_by, $use_cache, $is_transient, $allowCreate);
}
/**
}
*
* Prime instantiator for Zenphoto objects
* @param $tablename The name of the database table
* @param $unique_set An array of unique fields
* @param $cache_by
* @param $use_cache
* @param $is_transient Set true to prevent database insertion
* @param $allowCreate Set true to allow a new object to be made.
* @return bool will be true if the unique_set does not already exist
*/
function instantiate($tablename, $unique_set, $cache_by = NULL, $use_cache = true, $is_transient = false, $allowCreate = true) {
global $_zp_object_cache;
// insure a cache entry
$classname = get_class($this);
if (!isset($_zp_object_cache[$classname])) {
$_zp_object_cache[$classname] = array();
}
// Initialize the variables.
// Load the data into the data array using $this->load()
$this->data = $this->tempdata = $this->updates = array();
$this->loaded = false;
$this->table = $tablename;
$this->unique_set = $unique_set;
if (is_null($cache_by)) {
$this->cache_by = serialize($unique_set);
} else {
$this->cache_by = $this->unique_set[$cache_by];
}
$this->use_cache = $use_cache;
$this->transient = $is_transient;
return $this->load($allowCreate);
}
/**
*
* check the cache for presence of the entry and return it if found
* @param $entry
*/
private function getFromCache() {
global $_zp_object_cache;
if (isset($_zp_object_cache[$c = get_class($this)]) && isset($_zp_object_cache[$c][$this->cache_by])) {
return $_zp_object_cache[$c][$this->cache_by];
}
return NULL;
}
/**
*
* add the entry to the cache
* @param $entry
*/
private function addToCache($entry) {
global $_zp_object_cache;
if ($entry) {
if (count($_zp_object_cache[$classname = get_class($this)]) >= OBJECT_CACHE_DEPTH) {
array_shift($_zp_object_cache[$classname]); // discard the oldest
}
$_zp_object_cache[$classname][$this->cache_by] = $entry;
}
}
/**
* Clears the object cache by setting it to an empty array completely or a specific object (class name) cache.
*
* Note: You normally never need to use this. But on certain occasions it may be necessary
* to avoid memory issues if you loop through a lot of object creations.
*
* @since ZenphotoCMS 1.5.8
*
* @global array $_zp_object_cache
* @param string $classname A classname to clear the cache specifially (optional, default null)
*/
function clearCache($classname = null) {
global $_zp_object_cache;
if (!is_null($classname) && array_key_exists($classname, $_zp_object_cache)) {
unset($_zp_object_cache[$classname]);
} else {
$_zp_object_cache = array();
}
}
/**
* Set a variable in this object. Does not persist to the database until
* save() is called. So, IMPORTANT: Call save() after set() to persist.
* If the requested variable is not in the database, sets it in temp storage,
* which won't be persisted to the database.
*/
function set($var, $value) {
if (empty($var))
return false;
if ($this->loaded && !array_key_exists($var, $this->data)) {
$this->tempdata[$var] = $value;
} else {
$this->updates[$var] = $value;
}
return true;
}
/**
* Sets default values for new objects using the set() method.
* Should do nothing in the base class; subclasses should override.
*/
protected function setDefaults() {
}
/**
* Change one or more values of the unique set assigned to this record.
* Checks if the record already exists first, if so returns false.
* If successful returns true and changes $this->unique_set
* A call to move is instant, it does not require a save() following it.
*/
function move($new_unique_set) {
// Check if we have a row
$result = query_single_row('SELECT * FROM ' . prefix($this->table) . getWhereClause($new_unique_set) . ' LIMIT 1;');
if (!$result || $result['id'] == $this->id) { // we should not find an entry for the new unique set!
if (!zp_apply_filter('move_object', true, $this, $new_unique_set)) {
return false;
}
$sql = 'UPDATE ' . prefix($this->table) . getSetClause($new_unique_set) . ' ' . getWhereClause($this->unique_set);
$result = query($sql);
if ($result && db_affected_rows() == 1) { // and the update should have effected just one record
$this->unique_set = $new_unique_set;
return true;
}
}
return false;
}
/**
* Copy this record to another unique set. Checks if the record exists there
* first, if so returns false. If successful returns true. No changes are made
* to this object and no other objects are created, just the database entry.
* A call to copy is instant, it does not require a save() following it.
*/
function copy($new_unique_set) {
// Check if we have a row
$result = query('SELECT * FROM ' . prefix($this->table) . getWhereClause($new_unique_set) . ' LIMIT 1;');
if ($result && db_num_rows($result) == 0) {
if (!zp_apply_filter('copy_object', true, $this, $new_unique_set)) {
return false;
}
// Note: It's important for $new_unique_set to come last, as its values should override.
$insert_data = array_merge($this->data, $this->updates, $this->tempdata, $new_unique_set);
unset($insert_data['id']);
unset($insert_data['hitcounter']); // start fresh on new copy
if (empty($insert_data)) {
return true;
}
$sql = 'INSERT INTO ' . prefix($this->table) . ' (';
$i = 0;
foreach (array_keys($insert_data) as $col) {
if ($i > 0)
$sql .= ", ";
$sql .= "`$col`";
$i++;
}
$sql .= ') VALUES (';
$i = 0;
foreach (array_values($insert_data) as $value) {
if ($i > 0)
$sql .= ', ';
if (is_null($value)) {
$sql .= 'NULL';
} else {
$sql .= db_quote($value);
}
$i++;
}
$sql .= ');';
$success = query($sql);
if ($success && db_affected_rows() == 1) {
return zp_apply_filter('copy_object', db_insert_id(), $this);
}
}
return false;
}
/**
* Deletes object from the database
*
* @return bool
*/
function remove() {
if (!zp_apply_filter('remove_object', true, $this)) {
return false;
}
$id = $this->id;
if (empty($id)) {
$id = ' is NULL'; // allow delete of bad item!
} else {
$id = '=' . $id;
}
$sql = 'DELETE FROM ' . prefix($this->table) . ' WHERE `id`' . $id;
$this->loaded = false;
$this->transient = true;
return query($sql);
}
/**
* Returns the id
*
* @return string
*/
function getID() {
return $this->id;
}
/**
*
* returns the database record of the object
* @return array
*/
function getData() {
$this->save();
return $this->data;
}
/**
* Get the value of a variable. If $current is false, return the value
* as of the last save of this object.
*/
function get($var, $current = true) {
if ($current && isset($this->updates[$var])) {
return $this->updates[$var];
} else if (isset($this->data[$var])) {
return $this->data[$var];
} else if (isset($this->tempdata[$var])) {
return $this->tempdata[$var];
} else {
return null;
}
}
/**
* Load the data array from the database, using the unique id set to get the unique record.
*
* @param bool $allowCreate set to true to enable new object creation.
* @return false if the record already exists, true if a new record was created.
*/
private function load($allowCreate) {
$new = $entry = null;
// Set up the SQL query in case we need it...
$sql = 'SELECT * FROM ' . prefix($this->table) . getWhereClause($this->unique_set) . ' LIMIT 1;';
// But first, try the cache.
if ($this->use_cache) {
$entry = $this->getFromCache();
}
// Check the database if: 1) not using cache, or 2) didn't get a hit.
if (empty($entry)) {
$entry = query_single_row($sql, false);
// Save this entry into the cache so we get a hit next time.
if ($entry)
$this->addToCache($entry);
}
// If we don't have an entry yet, this is a new record. Create it.
if (empty($entry)) {
if ($this->transient) { // no don't save it in the DB!
$entry = array_merge($this->unique_set, $this->updates, $this->tempdata);
$entry['id'] = 0;
} else if (!$allowCreate) {
return NULL; // does not exist and we are not allowed to create it
} else {
$new = true;
$this->save();
$entry = query_single_row($sql);
// If we still don't have an entry, something went wrong...
if (!$entry)
return null;
// Save this new entry into the cache so we get a hit next time.
$this->addToCache($entry);
}
}
$this->data = $entry;
$this->id = (int) $entry['id'];
$this->loaded = true;
return $new;
}
/**
* Save the updates made to this object since the last update. Returns
* true if successful, false if not.
* @param bool $checkupdates Default false. If true the internal $updates property is checked for actual changes so unnecessary saving is skipped. Applies to already existing objects only.
*/
function save($checkupdates = false) {
if ($this->transient)
return false; // If this object isn't supposed to be persisted, don't save it.
if (!$this->unique_set) { // If we don't have a unique set, then this is incorrect. Don't attempt to save.
zp_error('empty $this->unique set is empty');
return false;
}
if (!$this->id) {
$this->setDefaults();
// Create a new object and set the id from the one returned.
$insert_data = array_merge($this->unique_set, $this->updates, $this->tempdata);
if (empty($insert_data)) {
return true;
}
$i = 0;
$cols = $vals = '';
foreach ($insert_data as $col => $value) {
if ($i > 0)
$cols .= ", ";
$cols .= "`$col`";
if ($i > 0)
$vals .= ", ";
if (is_null($value)) {
$vals .= "NULL";
} else {
$vals .= db_quote($value);
}
$i++;
}
$sql = 'INSERT INTO ' . prefix($this->table) . ' (' . $cols . ') VALUES (' . $vals . ')';
$success = query($sql);
if (!$success || db_affected_rows() != 1) {
return false;
}
foreach ($insert_data as $key => $value) { // copy over any changes
$this->data[$key] = $value;
}
$this->data['id'] = $this->id = (int) db_insert_id(); // so 'get' will retrieve it!
$this->loaded = true;
$this->updates = null;
$this->tempdata = array();
} else {
if ($checkupdates) {
$this->checkChanges();
}
// Save the existing object (updates only) based on the existing id.
if (empty($this->updates)) {
return true;
} else {
if($checkupdates) {
$this->setLastChange();
if (!isset($this->updates['lastchangeuser'])) {
$this->setLastChangeUser('');
}
}
$sql = 'UPDATE ' . prefix($this->table) . ' SET';
$i = 0;
foreach ($this->updates as $col => $value) {
if ($i > 0)
$sql .= ",";
if (is_null($value)) {
$sql .= " `$col` = NULL";
} else {
$sql .= " `$col` = " . db_quote($value);
}
$this->data[$col] = $value;
$i++;
}
$sql .= ' WHERE id=' . $this->id . ';';
$success = query($sql);
if (!$success || db_affected_rows() != 1) {
return false;
}
foreach ($this->updates as $key => $value) {
$this->data[$key] = $value;
}
$this->updates = array();
}
}
zp_apply_filter('save_object', true, $this);
$this->addToCache($this->data);
return true;
}
/**
*
* "Magic" function to return a string identifying the object when it is treated as a string
* @return string
*/
public function __toString() {
return $this->table . " (" . $this->id . ")";
}
/**
* Returns the last change user
*
* @return string
*/
function getLastChange() {
return $this->get("lastchange");
}
/**
* stores the current date in the format 'Y-m-d H:i:s' as the last change date
*/
function setLastChange() {
$this->set('lastchange', date('Y-m-d H:i:s'));
}
/**
* Returns the last change user
*
* @since ZenphotoCMS 1.5.2
* @return string
*/
function getLastChangeUser() {
return $this->get("lastchangeuser");
}
/**
* stores the last change user
*
* @since ZenphotoCMS 1.5.2
*/
function setLastchangeUser($a) {
$this->set("lastchangeuser", $a);
}
/**
* By default the object is saved if there are any updates within the property `$updates` no matter if these are actual changes to existing data.
* This checks that and internally updates the `$updates` property with the actual changes obky so you optionally can skip unnecessary object saves.
*
* Standard object fields `lastchange` and `lastchangeuser` are exclude because `lastchange` always changes and both make no sense
* if there is no actual content change at all.
*
* This can be used before calling the save() method or enabled within the save() method optionally
*
* @see save()
*
* @param bool $update True (default) to also update the $updates property with changes found or clear it. False to only check for changes.
*
* @since ZenphotoCMS 1.5.2
*
* @return boolean
*/
function checkChanges($update = true) {
$changes = array();
$excluded = array('lastchange', 'lastchangeuser');
if (!empty($this->updates)) {
foreach ($this->updates as $key => $val) {
if (!in_array($key, $excluded) && $val != $this->data[$key]) {
$changes[$key] = $val;
}
}
if (empty($changes)) {
if ($update) {
$this->updates = array();
}
return false;
} else {
if ($update) {
//Make sure we add these back!
foreach($excluded as $exlude) {
if(isset($this->updates[$exlude])) {
$changes[$exlude] = $this->updates[$exlude];
}
}
$this->updates = $changes;
}
return true;
}
}
}
}