| 297 | | |
|---|
| 298 | | |
|---|
| 299 | | /** |
|---|
| 300 | | * If this macro is used, only those attributed named in it will be accessible |
|---|
| 301 | | * for mass-assignment, such as new ModelName($attributes) and $this->attributes($attributes). |
|---|
| 302 | | * This is the more conservative choice for mass-assignment protection. |
|---|
| 303 | | * If you'd rather start from an all-open default and restrict attributes as needed, |
|---|
| 304 | | * have a look at AkActiveRecord::setProtectedAttributes(). |
|---|
| 305 | | */ |
|---|
| 306 | | function setAccessibleAttributes() |
|---|
| 307 | | { |
|---|
| 308 | | $args = func_get_args(); |
|---|
| 309 | | $this->_accessibleAttributes = array_unique(array_merge((array)$this->_accessibleAttributes, $args)); |
|---|
| 310 | | } |
|---|
| 311 | | |
|---|
| 312 | | /** |
|---|
| 313 | | * Attributes named in this macro are protected from mass-assignment, such as |
|---|
| 314 | | * new ModelName($attributes) and $this->attributes(attributes). Their assignment |
|---|
| 315 | | * will simply be ignored. Instead, you can use the direct writer methods to do assignment. |
|---|
| 316 | | * This is meant to protect sensitive attributes to be overwritten by URL/form hackers. |
|---|
| 317 | | * |
|---|
| 318 | | * Example: |
|---|
| 319 | | * <code> |
|---|
| 320 | | * class Customer extends AkActiveRecord |
|---|
| 321 | | * { |
|---|
| 322 | | * function Customer() |
|---|
| 323 | | * { |
|---|
| 324 | | * $this->setProtectedAttributes('credit_rating'); |
|---|
| 325 | | * } |
|---|
| 326 | | * } |
|---|
| 327 | | * |
|---|
| 328 | | * $Customer = new Customer('name' => 'David', 'credit_rating' => 'Excellent'); |
|---|
| 329 | | * $Customer->credit_rating // => null |
|---|
| 330 | | * $Customer->attributes(array('description' => 'Jolly fellow', 'credit_rating' => 'Superb')); |
|---|
| 331 | | * $Customer->credit_rating // => null |
|---|
| 332 | | * |
|---|
| 333 | | * $Customer->credit_rating = 'Average' |
|---|
| 334 | | * $Customer->credit_rating // => 'Average' |
|---|
| 335 | | * </code> |
|---|
| 336 | | */ |
|---|
| 337 | | function setProtectedAttributes() |
|---|
| 338 | | { |
|---|
| 339 | | $args = func_get_args(); |
|---|
| 340 | | $this->_protectedAttributes = array_unique(array_merge((array)$this->_protectedAttributes, $args)); |
|---|
| 341 | | } |
|---|
| 342 | | |
|---|
| 343 | | |
|---|
| 344 | | /** |
|---|
| 345 | | * Returns true if a connection that?s accessible to this class have already been opened. |
|---|
| 346 | | */ |
|---|
| 347 | | function isConnected() |
|---|
| 348 | | { |
|---|
| 349 | | return isset($this->_db); |
|---|
| 350 | | } |
|---|
| 351 | | |
|---|
| 352 | | /** |
|---|
| 353 | | * Returns the connection currently associated with the class. This can also be used to |
|---|
| 354 | | * "borrow" the connection to do database work unrelated to any of the specific Active Records. |
|---|
| 355 | | */ |
|---|
| 356 | | function &getConnection() |
|---|
| 357 | | { |
|---|
| 358 | | return $this->_db; |
|---|
| 359 | | } |
|---|
| 360 | | |
|---|
| 361 | | /** |
|---|
| 362 | | * Set the connection for the class. |
|---|
| 363 | | */ |
|---|
| 364 | | function setConnection($dns = null, $connection_id = null) |
|---|
| 365 | | { |
|---|
| 366 | | $this->_db =& Ak::db($dns, $connection_id); |
|---|
| 367 | | } |
|---|
| 368 | | |
|---|
| 369 | | /** |
|---|
| 370 | | * Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count", |
|---|
| 371 | | * and columns used for single table inheritance has been removed. |
|---|
| 372 | | */ |
|---|
| 373 | | function getContentColumns() |
|---|
| 374 | | { |
|---|
| 375 | | $inheritance_column = $this->getInheritanceColumn(); |
|---|
| 376 | | $columns = $this->getColumns(); |
|---|
| 377 | | foreach ($columns as $name=>$details){ |
|---|
| 378 | | if((substr($name,-3) == '_id' || substr($name,-6) == '_count') || |
|---|
| 379 | | !empty($details['primaryKey']) || ($inheritance_column !== false && $inheritance_column == $name)){ |
|---|
| 380 | | unset($columns[$name]); |
|---|
| 381 | | } |
|---|
| 382 | | } |
|---|
| 383 | | return $columns; |
|---|
| 384 | | } |
|---|
| 385 | | |
|---|
| 386 | | |
|---|
| 387 | | |
|---|
| | 298 | /** |
|---|
| | 299 | * New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved |
|---|
| | 300 | * (pass an array with key names matching the associated table column names). |
|---|
| | 301 | * In both instances, valid attribute keys are determined by the column names of the associated table; hence you can't |
|---|
| | 302 | * have attributes that aren't part of the table columns. |
|---|
| | 303 | */ |
|---|
| | 304 | function newRecord($attributes) |
|---|
| | 305 | { |
|---|
| | 306 | $this->_newRecord = true; |
|---|
| | 307 | |
|---|
| | 308 | if(AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS && empty($attributes)){ |
|---|
| | 309 | return; |
|---|
| | 310 | } |
|---|
| | 311 | |
|---|
| | 312 | if(isset($attributes) && !is_array($attributes)){ |
|---|
| | 313 | $attributes = func_get_args(); |
|---|
| | 314 | } |
|---|
| | 315 | $this->setAttributes($this->attributesFromColumnDefinition(),true); |
|---|
| | 316 | $this->setAttributes($attributes); |
|---|
| | 317 | } |
|---|
| | 318 | |
|---|
| | 319 | |
|---|
| | 320 | /** |
|---|
| | 321 | * Returns a clone of the record that hasn't been assigned an id yet and is treated as a new record. |
|---|
| | 322 | */ |
|---|
| | 323 | function cloneRecord() |
|---|
| | 324 | { |
|---|
| | 325 | $model_name = $this->getModelName(); |
|---|
| | 326 | $attributes = $this->getAttributesBeforeTypeCast(); |
|---|
| | 327 | if(isset($attributes[$this->getPrimaryKey()])){ |
|---|
| | 328 | unset($attributes[$this->getPrimaryKey()]); |
|---|
| | 329 | } |
|---|
| | 330 | return new $model_name($attributes); |
|---|
| | 331 | } |
|---|
| | 332 | |
|---|
| | 333 | |
|---|
| | 334 | /** |
|---|
| | 335 | * Returns true if this object hasn't been saved yet that is, a record for the object doesn't exist yet. |
|---|
| | 336 | */ |
|---|
| | 337 | function isNewRecord() |
|---|
| | 338 | { |
|---|
| | 339 | if(!isset($this->_newRecord) && !isset($this->{$this->getPrimaryKey()})){ |
|---|
| | 340 | $this->_newRecord = true; |
|---|
| | 341 | } |
|---|
| | 342 | return $this->_newRecord; |
|---|
| | 343 | } |
|---|
| | 344 | |
|---|
| | 345 | |
|---|
| | 346 | |
|---|
| | 347 | /** |
|---|
| | 348 | * Reloads the attributes of this object from the database. |
|---|
| | 349 | */ |
|---|
| | 350 | function reload() |
|---|
| | 351 | { |
|---|
| | 352 | /** |
|---|
| | 353 | * @todo clear cache |
|---|
| | 354 | */ |
|---|
| | 355 | if($object = $this->find($this->getId())){ |
|---|
| | 356 | $this->setAttributes($object->getAttributes(), true); |
|---|
| | 357 | return true; |
|---|
| | 358 | }else { |
|---|
| | 359 | return false; |
|---|
| | 360 | } |
|---|
| | 361 | } |
|---|
| | 362 | |
|---|
| | 363 | |
|---|
| | 364 | |
|---|
| | 365 | /** |
|---|
| | 366 | Creating records |
|---|
| | 367 | ==================================================================== |
|---|
| | 368 | */ |
|---|
| 409 | | |
|---|
| | 390 | function createOrUpdate($validate = true) |
|---|
| | 391 | { |
|---|
| | 392 | if($validate && !$this->isValid() || !($this->isNewRecord() ? $this->afterValidationOnCreate() : $this->afterValidationOnUpdate())){ |
|---|
| | 393 | $this->transactionFail(); |
|---|
| | 394 | return false; |
|---|
| | 395 | } |
|---|
| | 396 | return $this->isNewRecord() ? $this->_create() : $this->_update(); |
|---|
| | 397 | } |
|---|
| | 398 | |
|---|
| | 399 | |
|---|
| | 400 | /** |
|---|
| | 401 | * Creates a new record with values matching those of the instance attributes. |
|---|
| | 402 | * Must be called as a result of a call to createOrUpdate. |
|---|
| | 403 | */ |
|---|
| | 404 | function _create() |
|---|
| | 405 | { |
|---|
| | 406 | if($this->isFrozen()){ |
|---|
| | 407 | $this->transactionFail(); |
|---|
| | 408 | return false; |
|---|
| | 409 | } |
|---|
| | 410 | |
|---|
| | 411 | if($this->beforeCreate()){ |
|---|
| | 412 | |
|---|
| | 413 | $this->notifyObservers('beforeCreate'); |
|---|
| | 414 | |
|---|
| | 415 | if($this->_recordTimestamps){ |
|---|
| | 416 | if ($this->hasColumn('created_at')){ |
|---|
| | 417 | $this->setAttribute('created_at', Ak::getDate()); |
|---|
| | 418 | } |
|---|
| | 419 | if ($this->hasColumn('created_on')){ |
|---|
| | 420 | $this->setAttribute('created_on', Ak::getDate(null, 'Y-m-d')); |
|---|
| | 421 | } |
|---|
| | 422 | |
|---|
| | 423 | if(isset($this->expires_on)){ |
|---|
| | 424 | if(isset($this->expires_at) && $this->hasColumn('expires_at')){ |
|---|
| | 425 | $this->setAttribute('expires_at',Ak::getDate(strtotime($this->expires_at) + (defined('AK_TIME_DIFFERENCE') ? AK_TIME_DIFFERENCE*60 : 0))); |
|---|
| | 426 | }elseif(isset($this->expires_on) && $this->hasColumn('expires_on')){ |
|---|
| | 427 | $this->setAttribute('expires_on',Ak::getDate(strtotime($this->expires_on) + (defined('AK_TIME_DIFFERENCE') ? AK_TIME_DIFFERENCE*60 : 0), 'Y-m-d')); |
|---|
| | 428 | } |
|---|
| | 429 | } |
|---|
| | 430 | } |
|---|
| | 431 | |
|---|
| | 432 | $attributes = $this->getColumnsForAtrributes($this->getAttributes()); |
|---|
| | 433 | |
|---|
| | 434 | if($this->isLockingEnabled()){ |
|---|
| | 435 | $attributes['lock_version'] = 1; |
|---|
| | 436 | $this->setAttribute('lock_version',1); |
|---|
| | 437 | } |
|---|
| | 438 | |
|---|
| | 439 | $pk = $this->getPrimaryKey(); |
|---|
| | 440 | $table = $this->getTableName(); |
|---|
| | 441 | |
|---|
| | 442 | foreach ($attributes as $column=>$value){ |
|---|
| | 443 | $attributes[$column] = $this->castAttributeForDatabase($column,$value); |
|---|
| | 444 | } |
|---|
| | 445 | |
|---|
| | 446 | /** |
|---|
| | 447 | * @todo sanitize attributes |
|---|
| | 448 | * 'beforeValidationOnCreate', 'afterValidationOnCreate' |
|---|
| | 449 | */ |
|---|
| | 450 | if(!isset($this->_generateSequence) || (isset($this->_generateSequence) && $this->_generateSequence !== false)){ |
|---|
| | 451 | if((empty($attributes[$pk]) || (!empty($attributes[$pk]) && (integer)$attributes[$pk] > 0 ))){ |
|---|
| | 452 | if($this->_getDatabaseType() != 'mysql'){ |
|---|
| | 453 | $table_details = $this->_databaseTableInternals('seq_'.$table); |
|---|
| | 454 | if(!isset($table_details['ID'])){ |
|---|
| | 455 | $this->_db->CreateSequence('seq_'.$table); |
|---|
| | 456 | } |
|---|
| | 457 | $attributes[$pk] = $this->_db->GenID('seq_'.$table); |
|---|
| | 458 | } |
|---|
| | 459 | } |
|---|
| | 460 | } |
|---|
| | 461 | |
|---|
| | 462 | $__attributes = $attributes; |
|---|
| | 463 | $attributes = array_diff($attributes, array('',"''")); |
|---|
| | 464 | |
|---|
| | 465 | $sql = 'INSERT INTO '.$table.' '. |
|---|
| | 466 | '('.join(', ',array_keys($attributes)).') '. |
|---|
| | 467 | 'VALUES ('.join(',',array_values($attributes)).')'; |
|---|
| | 468 | |
|---|
| | 469 | if(!$this->_executeSql($sql, false)){ |
|---|
| | 470 | AK_DEBUG ? trigger_error($this->_db->ErrorMsg(), E_USER_NOTICE) : null; |
|---|
| | 471 | } |
|---|
| | 472 | |
|---|
| | 473 | $id = !empty($attributes[$pk]) ? $attributes[$pk] : $this->_db->Insert_ID($table, $pk); |
|---|
| | 474 | $this->setId($id); |
|---|
| | 475 | |
|---|
| | 476 | if(!$this->transactionHasFailed()){ |
|---|
| | 477 | $this->_newRecord = false; |
|---|
| | 478 | if(!$this->afterCreate()){ |
|---|
| | 479 | $this->transactionFail(); |
|---|
| | 480 | }else{ |
|---|
| | 481 | $this->notifyObservers('afterCreate'); |
|---|
| | 482 | } |
|---|
| | 483 | } |
|---|
| | 484 | }else{ |
|---|
| | 485 | $this->transactionFail(); |
|---|
| | 486 | } |
|---|
| | 487 | return $this; |
|---|
| | 488 | } |
|---|
| | 489 | |
|---|
| | 490 | /*/Creating records*/ |
|---|
| | 491 | |
|---|
| | 492 | |
|---|
| | 493 | /** |
|---|
| | 494 | Saving records |
|---|
| | 495 | ==================================================================== |
|---|
| | 496 | */ |
|---|
| | 497 | /** |
|---|
| | 498 | * - No record exists: Creates a new record with values matching those of the object attributes. |
|---|
| | 499 | * - A record does exist: Updates the record with values matching those of the object attributes. |
|---|
| | 500 | */ |
|---|
| | 501 | function save($validate = true) |
|---|
| | 502 | { |
|---|
| | 503 | if($this->isFrozen()){ |
|---|
| | 504 | return false; |
|---|
| | 505 | } |
|---|
| | 506 | $result = false; |
|---|
| | 507 | $this->transactionStart(); |
|---|
| | 508 | if($this->beforeSave() && $this->notifyObservers('beforeSave')){ |
|---|
| | 509 | $result = $this->createOrUpdate($validate); |
|---|
| | 510 | if(!$this->transactionHasFailed()){ |
|---|
| | 511 | if(!$this->afterSave()){ |
|---|
| | 512 | $this->transactionFail(); |
|---|
| | 513 | }else{ |
|---|
| | 514 | if(!$this->notifyObservers('afterSave')){ |
|---|
| | 515 | $this->transactionFail(); |
|---|
| | 516 | } |
|---|
| | 517 | } |
|---|
| | 518 | } |
|---|
| | 519 | }else{ |
|---|
| | 520 | $this->transactionFail(); |
|---|
| | 521 | } |
|---|
| | 522 | |
|---|
| | 523 | $result = $this->transactionHasFailed() ? false : $result; |
|---|
| | 524 | $this->transactionComplete(); |
|---|
| | 525 | |
|---|
| | 526 | return $result; |
|---|
| | 527 | } |
|---|
| | 528 | |
|---|
| | 529 | /*/Saving records*/ |
|---|
| | 530 | |
|---|
| | 531 | /** |
|---|
| | 532 | Counting Records |
|---|
| | 533 | ==================================================================== |
|---|
| | 534 | See also: Counting Attributes. |
|---|
| | 535 | */ |
|---|
| | 536 | |
|---|
| 432 | | |
|---|
| 433 | | /** |
|---|
| 434 | | * Increments the specified counter by one. So $DiscussionBoard->incrementCounter("post_count", |
|---|
| 435 | | * $discussion_board_id); would increment the "post_count" counter on the board responding to |
|---|
| 436 | | * $discussion_board_id. This is used for caching aggregate values, so that they doesn't need to |
|---|
| 437 | | * be computed every time. Especially important for looping over a collection where each element |
|---|
| 438 | | * require a number of aggregate values. Like the $DiscussionBoard that needs to list both the number of posts and comments. |
|---|
| 439 | | */ |
|---|
| 440 | | function incrementCounter($counter_name, $id, $difference = 1) |
|---|
| 441 | | { |
|---|
| 442 | | $new_value = $this->getAttribute($counter_name) + $difference; |
|---|
| 443 | | if($this->updateAll($counter_name.' = '.$new_value, $this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey(), $id)) === 0){ |
|---|
| 444 | | return false; |
|---|
| 445 | | } |
|---|
| 446 | | return $new_value; |
|---|
| 447 | | } |
|---|
| 448 | | |
|---|
| 449 | | /** |
|---|
| 450 | | * Works like AkActiveRecord::incrementCounter, but decrements instead. |
|---|
| 451 | | */ |
|---|
| 452 | | function decrementCounter($counter_name, $id, $difference = 1) |
|---|
| 453 | | { |
|---|
| 454 | | $new_value = $this->getAttribute($counter_name) - $difference; |
|---|
| 455 | | |
|---|
| 456 | | if(!$this->updateAll($counter_name.' = '.$new_value, $this->getPrimaryKey().' = '.$this->castAttributeForDatabase($this->getPrimaryKey(), $id)) === 0){ |
|---|
| 457 | | return false; |
|---|
| 458 | | } |
|---|
| 459 | | return $new_value; |
|---|
| 460 | | } |
|---|
| 461 | | |
|---|
| 462 | | |
|---|
| 463 | | |
|---|
| | 559 | /*/Counting Records*/ |
|---|
| | 560 | |
|---|
| | 561 | /** |
|---|
| | 562 | Updating records |
|---|
| | 563 | ==================================================================== |
|---|
| | 564 | See also: Callbacks. |
|---|
| | 565 | */ |
|---|
| | 566 | |
|---|
| | 636 | * Updates the associated record with values matching those of the instance attributes. |
|---|
| | 637 | * Must be called as a result of a call to createOrUpdate. |
|---|
| | 638 | */ |
|---|
| | 639 | function _update() |
|---|
| | 640 | { |
|---|
| | 641 | if($this->isFrozen()){ |
|---|
| | 642 | $this->transactionFail(); |
|---|
| | 643 | return false; |
|---|
| | 644 | } |
|---|
| | 645 | if($this->beforeUpdate()){ |
|---|
| | 646 | $this->notifyObservers('beforeUpdate'); |
|---|
| | 647 | |
|---|
| | 648 | if($this->_recordTimestamps){ |
|---|
| | 649 | if ($this->hasColumn('updated_at')){ |
|---|
| | 650 | $this->setAttribute('updated_at', Ak::getDate()); |
|---|
| | 651 | } |
|---|
| | 652 | if ($this->hasColumn('updated_on')){ |
|---|
| | 653 | $this->setAttribute('updated_on', Ak::getDate(null, 'Y-m-d')); |
|---|
| | 654 | } |
|---|
| | 655 | } |
|---|
| | 656 | |
|---|
| | 657 | |
|---|
| | 658 | $lock_check = ''; |
|---|
| | 659 | if ($this->isLockingEnabled()){ |
|---|
| | 660 | $previous_value = $this->lock_version; |
|---|
| | 661 | $this->setAttribute('lock_version', $previous_value + 1); |
|---|
| | 662 | $lock_check = ' AND lock_version = '.$previous_value; |
|---|
| | 663 | } |
|---|
| | 664 | |
|---|
| | 665 | $quoted_attributes = $this->getAvailableAttributesQuoted(); |
|---|
| | 666 | |
|---|
| | 667 | if(!empty($quoted_attributes)){ |
|---|
| | 668 | $sql = 'UPDATE '.$this->getTableName().' '. |
|---|
| | 669 | 'SET '.join(', ', $quoted_attributes) .' '. |
|---|
| | 670 | 'WHERE '.$this->getPrimaryKey().'='.$this->quotedId().$lock_check; |
|---|
| | 671 | } |
|---|
| | 672 | |
|---|
| | 673 | if(!$this->_executeSql($sql, false)){ |
|---|
| | 674 | $this->transactionFail(); |
|---|
| | 675 | AK_DEBUG ? trigger_error($this->_db->ErrorMsg(), E_USER_NOTICE) : null; |
|---|
| | 676 | } |
|---|
| | 677 | |
|---|
| | 678 | if ($this->isLockingEnabled()){ |
|---|
| | 679 | if($this->_db->Affected_Rows() != 1){ |
|---|
| | 680 | $this->setAttribute('lock_version', $previous_value); |
|---|
| | 681 | $this->transactionFail(); |
|---|
| | 682 | trigger_error(Ak::t('Attempted to update a stale object'), E_USER_NOTICE); |
|---|
| | 683 | return false; |
|---|
| | 684 | } |
|---|
| | 685 | } |
|---|
| | 686 | |
|---|
| | 687 | if(!$this->transactionHasFailed()){ |
|---|
| | 688 | if($this->afterUpdate()){ |
|---|
| | 689 | $this->notifyObservers('afterUpdate'); |
|---|
| | 690 | }else { |
|---|
| | 691 | $this->transactionFail(); |
|---|
| | 692 | } |
|---|
| | 693 | } |
|---|
| | 694 | |
|---|
| | 695 | }else{ |
|---|
| | 696 | $this->transactionFail(); |
|---|
| | 697 | } |
|---|
| | 698 | return $this; |
|---|
| | 699 | } |
|---|
| | 700 | |
|---|
| | 701 | /*/Updating records*/ |
|---|
| | 702 | |
|---|
| | 703 | |
|---|
| | 704 | |
|---|
| | 705 | /** |
|---|
| | 706 | Deleting records |
|---|
| | 707 | ==================================================================== |
|---|
| | 708 | See also: Callbacks. |
|---|
| | 709 | */ |
|---|
| | 710 | |
|---|
| | 711 | /** |
|---|
| 692 | | |
|---|
| 693 | | |
|---|
| 694 | | |
|---|
| 695 | | |
|---|
| 696 | | |
|---|
| 697 | | /** |
|---|
| 698 | | * Establishes the connection to the database. Accepts an array as input where the 'adapter' |
|---|
| 699 | | * key must be specified with the name of a database adapter (in lower-case) example for regular |
|---|
| 700 | | * databases (MySQL, Postgresql, etc): |
|---|
| 701 | | * |
|---|
| 702 | | * $AkActiveRecord->establishConnection( |
|---|
| 703 | | * array( |
|---|
| 704 | | * 'adapter' => "mysql", |
|---|
| 705 | | * 'host' => "localhost", |
|---|
| 706 | | * 'username' => "myuser", |
|---|
| 707 | | * 'password' => "mypass", |
|---|
| 708 | | * 'database' => "somedatabase" |
|---|
| 709 | | * )); |
|---|
| 710 | | * |
|---|
| 711 | | * Example for SQLite database: |
|---|
| 712 | | * |
|---|
| 713 | | * $AkActiveRecord->establishConnection( |
|---|
| 714 | | * array( |
|---|
| 715 | | * 'adapter' => "sqlite", |
|---|
| 716 | | * 'dbfile' => "path/to/dbfile" |
|---|
| 717 | | * ) |
|---|
| 718 | | * ) |
|---|
| 719 | | */ |
|---|
| 720 | | function &establishConnection($spec = null) |
|---|
| 721 | | { |
|---|
| 722 | | if(isset($spec)){ |
|---|
| 723 | | $dns = is_string($spec) ? $spec : ''; |
|---|
| 724 | | if(!empty($spec['adapter'])){ |
|---|
| 725 | | $dsn = $spec['adapter'] == 'sqlite' ? |
|---|
| 726 | | 'sqlite://'.urlencode($spec['dbfile']).'/' : |
|---|
| 727 | | $spec['adapter'].'://'.@$spec['username'].':'.@$spec['password'].'@'.@$spec['host'].'/'.@$spec['database']; |
|---|
| 728 | | } |
|---|
| 729 | | $dsn .= isset($spec['persist']) && $spec['persist'] === false ? '' : '?persist'; |
|---|
| 730 | | return $this->setConnection($dns); |
|---|
| 731 | | }else{ |
|---|
| 732 | | return false; |
|---|
| 733 | | } |
|---|
| 734 | | } |
|---|
| 735 | | |
|---|
| 736 | | |
|---|
| 737 | | /** |
|---|
| 738 | | * Just freeze the attributes hash, such that associations are still accessible even on destroyed records. |
|---|
| 739 | | * |
|---|
| 740 | | * @todo implement freeze correctly for its intended use |
|---|
| 741 | | */ |
|---|
| 742 | | function freeze() |
|---|
| 743 | | { |
|---|
| 744 | | $this->_freeze = true; |
|---|
| 745 | | } |
|---|
| 746 | | |
|---|
| 747 | | function isFrozen() |
|---|
| 748 | | { |
|---|
| 749 | | return !empty($this->_freeze); |
|---|
| 750 | | } |
|---|
| 751 | | |
|---|
| | 841 | |
|---|
| | 842 | /*/Deleting records*/ |
|---|
| | 843 | |
|---|
| | 844 | |
|---|
| | 845 | |
|---|
| | 846 | |
|---|
| | 847 | /** |
|---|
| | 848 | Finding records |
|---|
| | 849 | ==================================================================== |
|---|
| | 850 | */ |
|---|
| 1011 | | function &objectCache() |
|---|
| 1012 | | { |
|---|
| 1013 | | static $cache; |
|---|
| 1014 | | $args =& func_get_args(); |
|---|
| 1015 | | if(count($args) == 2){ |
|---|
| 1016 | | if(!isset($cache[$args[0]])){ |
|---|
| 1017 | | $cache[$args[0]] =& $args[1]; |
|---|
| 1018 | | } |
|---|
| 1019 | | }elseif(!isset($cache[$args[0]])){ |
|---|
| 1020 | | return false; |
|---|
| 1021 | | } |
|---|
| 1022 | | return $cache[$args[0]]; |
|---|
| 1023 | | } |
|---|
| 1024 | | |
|---|
| 1025 | | /** |
|---|
| 1026 | | * Gets an array from a string. |
|---|
| 1027 | | * |
|---|
| 1028 | | * Acts like Php explode() function but uses any of this as valid separators ' AND ',' and ',' + ',' ',',',';' |
|---|
| 1029 | | */ |
|---|
| 1030 | | function getArrayFromAkString($string) |
|---|
| 1031 | | { |
|---|
| 1032 | | if(is_array($string)){ |
|---|
| 1033 | | return $string; |
|---|
| 1034 | | } |
|---|
| 1035 | | $string = str_replace(array(' AND ',' and ',' + ',' ',',',';'),array('|','|','|','','|','|'),trim($string)); |
|---|
| 1036 | | return strstr($string,'|') ? explode('|', $string) : array($string); |
|---|
| 1037 | | } |
|---|
| 1038 | | |
|---|
| 1039 | | // Gets the column name for use with single table inheritance ? can be overridden in subclasses. |
|---|
| 1040 | | function getInheritanceColumn() |
|---|
| 1041 | | { |
|---|
| 1042 | | return empty($this->_inheritanceColumn) ? ($this->hasColumn('type') ? 'type' : false ) : $this->_inheritanceColumn; |
|---|
| 1043 | | } |
|---|
| 1044 | | |
|---|
| 1045 | | // Defines the column name for use with single table inheritance ? can be overridden in subclasses. |
|---|
| 1046 | | function setInheritanceColumn($column_name) |
|---|
| 1047 | | { |
|---|
| 1048 | | if(!$this->hasColumn($column_name)){ |
|---|
| 1049 | | trigger_error(Ak::t('Could not set "%column_name" as the inheritance column as this column is not available on the database.',array('%column_name'=>$column_name)), E_USER_NOTICE); |
|---|
| 1050 | | return false; |
|---|
| 1051 | | }elseif($this->getColumnType($column_name) != 'string'){ |
|---|
| 1052 | | trigger_error(Ak::t('Could not set %column_name as the inheritance column as this column type is "%column_type" instead of "string".',array('%column_name'=>$column_name,'%column_type'=>$this->getColumnType($column_name))), E_USER_NOTICE); |
|---|
| 1053 | | return false; |
|---|
| 1054 | | }else{ |
|---|
| 1055 | | $this->_inheritanceColumn = $column_name; |
|---|
| 1056 | | return true; |
|---|
| 1057 | | } |
|---|
| 1058 | | } |
|---|
| 1059 | | |
|---|
| 1060 | | function getColumnsWithRegexBoundaries() |
|---|
| 1061 | | { |
|---|
| 1062 | | $columns = array_keys($this->getColumns()); |
|---|
| 1063 | | foreach ($columns as $k=>$column){ |
|---|
| 1064 | | $columns[$k] = '/([^\.])\b('.$column.')\b/'; |
|---|
| 1065 | | } |
|---|
| 1066 | | return $columns; |
|---|
| 1067 | | } |
|---|
| 1068 | | |
|---|
| 1069 | | |
|---|
| 1070 | | |
|---|
| 1071 | | //SELECT t.code as codigo , c.description as descripcion FROM technical_listings as t LEFT OUTER JOIN categories as c ON c.id = t.category_id |
|---|
| 1072 | | |
|---|
| 1073 | | |
|---|
| 1074 | | |
|---|
| 1296 | | /** |
|---|
| 1297 | | * Finder methods must instantiate through this method to work with the single-table inheritance model and |
|---|
| 1298 | | * eager loading associations. |
|---|
| 1299 | | * that makes it possible to create objects of different types from the same table. |
|---|
| 1300 | | */ |
|---|
| 1301 | | function &instantiate($record, $set_as_new = true) |
|---|
| 1302 | | { |
|---|
| 1303 | | $inheritance_column = $this->getInheritanceColumn(); |
|---|
| 1304 | | if(!empty($record[$inheritance_column])){ |
|---|
| 1305 | | $inheritance_column = $record[$inheritance_column]; |
|---|
| 1306 | | $inheritance_model_name = AkInflector::camelize($inheritance_column); |
|---|
| 1307 | | @require_once(AkInflector::toModelFilename($inheritance_model_name)); |
|---|
| 1308 | | if(!class_exists($inheritance_model_name)){ |
|---|
| 1309 | | trigger_error($this->t("The single-table inheritance mechanism failed to locate the subclass: '%class_name'. ". |
|---|
| 1310 | | "This error is raised because the column '%column' is reserved for storing the class in case of inheritance. ". |
|---|
| 1311 | | "Please rename this column if you didn't intend it to be used for storing the inheritance class ". |
|---|
| 1312 | | "or overwrite #{self.to_s}.inheritance_column to use another column for that information.", |
|---|
| 1313 | | array('%class_name'=>$inheritance_model_name, '%column'=>$this->getInheritanceColumn())),E_USER_ERROR); |
|---|
| 1314 | | } |
|---|
| 1315 | | } |
|---|
| 1316 | | |
|---|
| 1317 | | $model_name = isset($inheritance_model_name) ? $inheritance_model_name : $this->getModelName(); |
|---|
| 1318 | | $object =& new $model_name('attributes', $record); |
|---|
| 1319 | | |
|---|
| 1320 | | $object->_newRecord = $set_as_new; |
|---|
| 1321 | | |
|---|
| 1322 | | (AK_CLI && AK_ENVIRONMENT == 'development') ? $object ->toString() : null; |
|---|
| 1323 | | |
|---|
| 1324 | | return $object; |
|---|
| 1325 | | } |
|---|
| 1326 | | |
|---|
| 1327 | | |
|---|
| 1328 | | |
|---|
| 1411 | | |
|---|
| 1412 | | function descendsFromActiveRecord(&$object) |
|---|
| 1413 | | { |
|---|
| 1414 | | if(substr(strtolower(get_parent_class($object)),-12) == 'activerecord'){ |
|---|
| 1415 | | return true; |
|---|
| 1416 | | } |
|---|
| 1417 | | if(!method_exists($object, 'getInheritanceColumn')){ |
|---|
| 1418 | | return false; |
|---|
| 1419 | | } |
|---|
| 1420 | | $inheritance_column = $object->getInheritanceColumn(); |
|---|
| 1421 | | return !empty($inheritance_column); |
|---|
| 1422 | | } |
|---|
| 1423 | | |
|---|
| 1424 | | |
|---|
| 1425 | | function typeCondition() |
|---|
| 1426 | | { |
|---|
| 1427 | | $inheritance_column = $this->getInheritanceColumn(); |
|---|
| 1428 | | $type_condition = array(); |
|---|
| 1429 | | $table_name = $this->getTableName(); |
|---|
| 1430 | | $available_types = array_merge(array($this->getModelName()),$this->getSubclasses()); |
|---|
| 1431 | | foreach ($available_types as $subclass){ |
|---|
| 1432 | | $type_condition[] = ' '.$table_name.'.'.$inheritance_column.' = \''.AkInflector::demodulize($subclass).'\' '; |
|---|
| 1433 | | } |
|---|
| 1434 | | return empty($type_condition) ? '' : '('.join('OR',$type_condition).') '; |
|---|
| 1435 | | } |
|---|
| 1436 | | |
|---|
| 1437 | | function getSubclasses() |
|---|
| 1438 | | { |
|---|
| 1439 | | $current_class = get_class($this); |
|---|
| 1440 | | $subclasses = array(); |
|---|
| 1441 | | $classes = get_declared_classes(); |
|---|
| 1442 | | |
|---|
| 1443 | | while ($class = array_shift($classes)) { |
|---|
| 1444 | | $parent_class = get_parent_class($class); |
|---|
| 1445 | | if($parent_class == $current_class || in_array($parent_class,$subclasses)){ |
|---|
| 1446 | | $subclasses[] = $class; |
|---|
| 1447 | | }elseif(!empty($parent_class)){ |
|---|
| 1448 | | $classes[] = $parent_class; |
|---|
| 1449 | | } |
|---|
| 1450 | | } |
|---|
| 1451 | | $subclasses = array_unique(array_map(array(&$this,'_getModelName'),$subclasses)); |
|---|
| 1452 | | return $subclasses; |
|---|
| 1453 | | } |
|---|
| 1454 | | |
|---|
| 1461 | | /** |
|---|
| 1462 | | * Parses an special formated array as a list of keys and values |
|---|
| 1463 | | * |
|---|
| 1464 | | * This function generates an array with values and keys from an array with numeric keys. |
|---|
| 1465 | | * |
|---|
| 1466 | | * This allows to parse an array to a function in the following manner. |
|---|
| 1467 | | * create('first_name->', 'Bermi', 'last_name->', 'Ferrer'); |
|---|
| 1468 | | * //Previous code will be the same that |
|---|
| 1469 | | * create(array('first_name'=>'Bermi', 'last_name'=> 'Ferrer')); |
|---|
| 1470 | | * |
|---|
| 1471 | | * Use this syntax only for quick testings, not for production environments. If the number of arguments varies, the result might be unpredictable. |
|---|
| 1472 | | * |
|---|
| 1473 | | * This function syntax is disabled by default. You need to define('AK_ENABLE_AKELOS_ARGS', true) |
|---|
| 1474 | | * if you need this functionality. |
|---|
| 1475 | | * |
|---|
| 1476 | | * @deprecated |
|---|
| 1477 | | */ |
|---|
| 1478 | | function parseAkelosArgs(&$args) |
|---|
| 1479 | | { |
|---|
| 1480 | | if(!AK_ENABLE_AKELOS_ARGS){ |
|---|
| 1481 | | return ; |
|---|
| 1482 | | } |
|---|
| 1483 | | $k = array_keys($args); |
|---|
| 1484 | | if(isset($k[1]) && substr($args[$k[0]],-1) == '>'){ |
|---|
| 1485 | | $size = sizeOf($k); |
|---|
| 1486 | | $params = array(); |
|---|
| 1487 | | for($i = 0; $i < $size; $i++ ) { |
|---|
| 1488 | | $v = $args[$k[$i]]; |
|---|
| 1489 | | if(!isset($key) && is_string($args[$k[$i]]) && substr($v,-1) == '>'){ |
|---|
| 1490 | | $key = rtrim($v, '=-> '); |
|---|
| 1491 | | }elseif(isset($key)) { |
|---|
| 1492 | | $params[$key] = $v; |
|---|
| 1493 | | unset($key); |
|---|
| 1494 | | }else{ |
|---|
| 1495 | | $params[$k[$i]] = $v; |
|---|
| 1496 | | } |
|---|
| 1497 | | } |
|---|
| 1498 | | if(!empty($params)){ |
|---|
| 1499 | | $args = $params; |
|---|
| 1500 | | } |
|---|
| 1501 | | } |
|---|
| 1502 | | $this->_castDateParametersFromDateHelper_($args); |
|---|
| 1503 | | } |
|---|
| 1504 | | |
|---|
| 1505 | | /** |
|---|
| 1506 | | * Joins date arguments into a single attribute. Like the array generated by the date_helper, so |
|---|
| 1507 | | * array('published_on(1i)' => 2002, 'published_on(2i)' => 'January', 'published_on(3i)' => 24) |
|---|
| 1508 | | * Will be converted to array('published_on'=>'2002-01-24') |
|---|
| 1509 | | */ |
|---|
| 1510 | | function _castDateParametersFromDateHelper_(&$params) |
|---|
| 1511 | | { |
|---|
| 1512 | | if(empty($params)){ |
|---|
| 1513 | | return; |
|---|
| 1514 | | } |
|---|
| 1515 | | $date_attributes = array(); |
|---|
| 1516 | | foreach ($params as $k=>$v) { |
|---|
| 1517 | | if(preg_match('/^([A-Za-z0-9_]+)\(([1-5]{1})i\)$/',$k,$match)){ |
|---|
| 1518 | | $date_attributes[$match[1]][$match[2]] = $v; |
|---|
| 1519 | | unset($params[$k]); |
|---|
| 1520 | | } |
|---|
| 1521 | | } |
|---|
| 1522 | | foreach ($date_attributes as $attribute=>$date){ |
|---|
| 1523 | | $params[$attribute] = Ak::getDate(Ak::getTimestamp(trim(@$date[1].'-'.@$date[2].'-'.@$date[3].' '.@$date[4].':'.@$date[5],' :-'))); |
|---|
| 1524 | | } |
|---|
| 1525 | | } |
|---|
| 1569 | | |
|---|
| 1570 | | /** |
|---|
| 1571 | | * New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved |
|---|
| 1572 | | * (pass an array with key names matching the associated table column names). |
|---|
| 1573 | | * In both instances, valid attribute keys are determined by the column names of the associated table ? hence you can't |
|---|
| 1574 | | * have attributes that aren't part of the table columns. |
|---|
| 1575 | | */ |
|---|
| 1576 | | function newRecord($attributes) |
|---|
| 1577 | | { |
|---|
| 1578 | | $this->_newRecord = true; |
|---|
| 1579 | | |
|---|
| 1580 | | if(AK_ACTIVE_RECORD_SKIP_SETTING_ACTIVE_RECORD_DEFAULTS && empty($attributes)){ |
|---|
| 1581 | | return; |
|---|
| 1582 | | } |
|---|
| 1583 | | |
|---|
| 1584 | | if(isset($attributes) && !is_array($attributes)){ |
|---|
| 1585 | | $attributes = func_get_args(); |
|---|
| 1586 | | } |
|---|
| 1587 | | $this->setAttributes($this->attributesFromColumnDefinition(),true); |
|---|
| 1588 | | $this->setAttributes($attributes); |
|---|
| 1589 | | } |
|---|
| 1590 | | |
|---|
| 1591 | | |
|---|
| | 1465 | /** |
|---|
| | 1466 | * Finder methods must instantiate through this method to work with the single-table inheritance model and |
|---|
| | 1467 | * eager loading associations. |
|---|
| | 1468 | * that makes it possible to create objects of different types from the same table. |
|---|
| | 1469 | */ |
|---|
| | 1470 | function &instantiate($record, $set_as_new = true) |
|---|
| | 1471 | { |
|---|
| | 1472 | $inheritance_column = $this->getInheritanceColumn(); |
|---|
| | 1473 | if(!empty($record[$inheritance_column])){ |
|---|
| | 1474 | $inheritance_column = $record[$inheritance_column]; |
|---|
| | 1475 | $inheritance_model_name = AkInflector::camelize($inheritance_column); |
|---|
| | 1476 | @require_once(AkInflector::toModelFilename($inheritance_model_name)); |
|---|
| | 1477 | if(!class_exists($inheritance_model_name)){ |
|---|
| | 1478 | trigger_error($this->t("The single-table inheritance mechanism failed to locate the subclass: '%class_name'. ". |
|---|
| | 1479 | "This error is raised because the column '%column' is reserved for storing the class in case of inheritance. ". |
|---|
| | 1480 | "Please rename this column if you didn't intend it to be used for storing the inheritance class ". |
|---|
| | 1481 | "or overwrite #{self.to_s}.inheritance_column to use another column for that information.", |
|---|
| | 1482 | array('%class_name'=>$inheritance_model_name, '%column'=>$this->getInheritanceColumn())),E_USER_ERROR); |
|---|
| | 1483 | } |
|---|
| | 1484 | } |
|---|
| | 1485 | |
|---|
| | 1486 | $model_name = isset($inheritance_model_name) ? $inheritance_model_name : $this->getModelName(); |
|---|
| | 1487 | $object =& new $model_name('attributes', $record); |
|---|
| | 1488 | |
|---|
| | 1489 | $object->_newRecord = $set_as_new; |
|---|
| | 1490 | |
|---|
| | 1491 | (AK_CLI && AK_ENVIRONMENT == 'development') ? $object ->toString() : null; |
|---|
| | 1492 | |
|---|
| | 1493 | return $object; |
|---|
| | 1494 | } |
|---|
| | 1495 | |
|---|
| | 1496 | /*/Finding records*/ |
|---|
| | 1497 | |
|---|
| | 1498 | |
|---|
| | 1499 | |
|---|
| | 1500 | /** |
|---|
| | 1501 | Table inheritance |
|---|
| | 1502 | ==================================================================== |
|---|
| | 1503 | */ |
|---|
| | 1504 | function descendsFromActiveRecord(&$object) |
|---|
| | 1505 | { |
|---|
| | 1506 | if(substr(strtolower(get_parent_class($object)),-12) == 'activerecord'){ |
|---|
| | 1507 | return true; |
|---|
| | 1508 | } |
|---|
| | 1509 | if(!method_exists($object, 'getInheritanceColumn')){ |
|---|
| | 1510 | return false; |
|---|
| | 1511 | } |
|---|
| | 1512 | |
|---|