eZ Publish  [trunk]
ezpersistentobject.php
Go to the documentation of this file.
00001 <?php
00002 /**
00003  * File containing the eZPersistentObject class.
00004  *
00005  * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved.
00006  * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
00007  * @version //autogentag//
00008  * @package kernel
00009  */
00010 
00011 /**
00012  * Allows for object persistence in a database
00013  *
00014  * Classes which stores simple types in databases should inherit from this
00015  * and implement the definition() function. The class will then get initialization,
00016  * fetching, listing, moving, storing and deleting for free as well as attribute
00017  * access. The new class must have a constructor which takes one parameter called
00018  * $row and pass that this constructor.
00019  *
00020  * <code>
00021  * class MyClass extends eZPersistentObject
00022  * {
00023  *     public function __construct( $row )
00024  *     {
00025  *         parent::__construct( $row );
00026  *     }
00027  * }
00028  * </code>
00029  * 
00030  * @package eZKernel
00031  * @method fetch
00032  */
00033 class eZPersistentObject
00034 {
00035     /**
00036      * Whether the data is dirty, ie needs to be stored, or not.
00037      *
00038      * @todo Change the actual access to protected instead of just marking it as such
00039      * @access protected
00040      * @var bool
00041      */
00042     public $PersistentDataDirty;
00043 
00044     /**
00045      * Initializes the object with the $row.
00046      *
00047      * It will try to set each field taken from the database row. Calls fill
00048      * to do the job. If $row is an integer, it will try to fetch it from the
00049      * database using it as the unique ID.
00050      *
00051      * @param int|array $row
00052      */
00053     public function eZPersistentObject( $row )
00054     {
00055         $this->PersistentDataDirty = false;
00056         if ( is_numeric( $row ) )
00057             $row = $this->fetch( $row, false );
00058         $this->fill( $row );
00059     }
00060 
00061     /**
00062      * Tries to fill in the data in the object by using the object definition
00063      * which is returned by the function definition() and the database row
00064      * data $row. Each field will be fetch from the definition and then
00065      * use that fieldname to fetch from the row and set the data.
00066      *
00067      * @todo Change the actual access to protected instead of just marking it as such
00068      * @access protected
00069      * @param array $row
00070      * @return bool
00071      */
00072     public function fill( $row )
00073     {
00074         if ( !is_array( $row ) )
00075             return;
00076         $def = $this->definition();
00077 
00078         foreach ( $def["fields"] as $key => $item )
00079         {
00080             if ( isset( $item['name'] ) )
00081             {
00082                 $item = $item['name'];
00083             }
00084             $this->$item = isset( $row[$key] ) ? $row[$key] : null;
00085         }
00086 
00087         return true;
00088     }
00089     
00090     /**
00091      * For the given array $fields treats its keys (for associative array) or
00092      * values (for non-associative array) as table fields names and replaces them
00093      * with short names (aliases) found in $fieldDefs.
00094      *
00095      * @todo Change the actual access to protected instead of just marking it as such
00096      * @access protected
00097      * @param eZDBInterface $db
00098      * @param array $fieldDefs
00099      * @param array $fields
00100      * @return void
00101      */
00102     public static function replaceFieldsWithShortNames( $db, $fieldDefs, &$fields )
00103     {
00104         if ( !$db->useShortNames() || !$fields )
00105             return;
00106 
00107         $short_fields_names = array();
00108         foreach ( $fields as $key => $val )
00109         {
00110             if( is_numeric( $key ) ) // $fields is not an associative array
00111             {
00112                 if ( array_key_exists( $val,  $fieldDefs ) &&
00113                      array_key_exists( 'short_name', $fieldDefs[$val] ) )
00114                 {
00115                     $short_fields_names[$key] = $fieldDefs[$val]['short_name'];
00116                 }
00117                 else
00118                     $short_fields_names[$key] = $val;
00119             }
00120             else // $fields is an associative array
00121             {
00122                 if ( array_key_exists( $key,  $fieldDefs ) &&
00123                      array_key_exists( 'short_name', $fieldDefs[$key] ) )
00124                 {
00125                     $newkey = $fieldDefs[$key]['short_name'];
00126                 }
00127                 else
00128                     $newkey = $key;
00129                 $short_fields_names[$newkey] = $val;
00130             }
00131 
00132         }
00133         $fields = $short_fields_names;
00134     }
00135 
00136     /**
00137      * Fetches the number of rows by using the object definition.
00138      *
00139      * Uses fetchObjectList for the actual SQL handling.
00140      * See {@link eZPersistentObject::fetchObjectList()} for a full description
00141      * of the input parameters.
00142      *
00143      * @param array $def A definition array of all fields, table name and sorting (see {@link eZPersistentObject::definition()} for more info)
00144      * @param array|null $conds
00145      * @param string|null $field
00146      * @return int
00147      */
00148     public static function count( $def, $conds = null, $field = null )
00149     {
00150         if ( !isset( $field ) )
00151         {
00152             $field = '*';
00153         }
00154         $customFields = array( array( 'operation' => 'COUNT( ' . $field . ' )', 'name' => 'row_count' ) );
00155         $rows = eZPersistentObject::fetchObjectList( $def, array(), $conds, array(), null, false, false, $customFields );
00156         return $rows[0]['row_count'];
00157     }
00158 
00159     /**
00160      * Fetches and returns an object based on the given parameters and returns
00161      * is either as an object or as an array
00162      *
00163      * See {@link eZPersistentObject::fetchObjectList()} for a full description
00164      * of the input parameters.
00165      *
00166      * @param array $def A definition array of all fields, table name and sorting (see {@link eZPersistentObject::definition()} for more info)
00167      * @param array $field_filters If defined determines the fields which are extracted, if not all fields are fetched
00168      * @param array $conds An array of conditions which determines which rows are fetched
00169      * @param bool $asObject If true the returned item is an object otherwise a db row (array).
00170      * @param array|null $grouping An array of elements to group by
00171      * @param array $custom_fields|null An array of extra fields to fetch, each field may be a SQL operation
00172      * @return eZPersistentObject|array|null
00173      */
00174     public static function fetchObject( $def, $field_filters, $conds, $asObject = true, $grouping = null, $custom_fields = null )
00175     {
00176         $rows = eZPersistentObject::fetchObjectList( $def, $field_filters, $conds,
00177                                                       array(), null, $asObject,
00178                                                       $grouping, $custom_fields );
00179         if ( $rows )
00180             return $rows[0];
00181         return null;
00182     }
00183 
00184     /**
00185      * Removes the object from the database, it will use the keys in the object
00186      * definition to figure out which table row should be removed unless
00187      * $conditions is defined as an array with fieldnames.
00188      *
00189      * It uses removeObject to do the real job and passes the object defintion,
00190      * conditions and extra conditions \a $extraConditions to this function.
00191      *
00192      * Note: Transaction unsafe. If you call several transaction unsafe methods
00193      * you must enclose the calls within a db transaction; thus within db->begin
00194      * and db->commit.
00195      *
00196      * @param array|null $conditions
00197      * @param array|null $extraConditions
00198      * @return void
00199      */
00200     public function remove( $conditions = null, $extraConditions = null )
00201     {
00202         $def = $this->definition();
00203         $keys = $def["keys"];
00204         if ( !is_array( $conditions ) )
00205         {
00206             $conditions = array();
00207             foreach ( $keys as $key )
00208             {
00209                 $value = $this->attribute( $key );
00210                 $conditions[$key] = $value;
00211             }
00212         }
00213         eZPersistentObject::removeObject( $def, $conditions, $extraConditions );
00214     }
00215 
00216     /**
00217      * Deletes the object from the table defined in $def with conditions $conditions
00218      * and extra conditions \a $extraConditions. The extra conditions will either be
00219      * appended to the existing conditions or overwrite existing fields.
00220      *
00221      * Uses conditionText() to create the condition SQL.
00222      *
00223      * Note: Transaction unsafe. If you call several transaction unsafe methods
00224      * you must enclose the calls within a db transaction; thus within db->begin
00225      * and db->commit.
00226      *
00227      * @param array $def A definition array of all fields, table name and sorting (see {@link eZPersistentObject::definition()} for more info)
00228      * @param array|null $conditions
00229      * @param array|null $extraConditions
00230      * @return void
00231      */
00232     public static function removeObject( $def, $conditions = null, $extraConditions = null )
00233     {
00234         $db = eZDB::instance();
00235 
00236         $table = $def["name"];
00237         if ( is_array( $extraConditions ) )
00238         {
00239             foreach ( $extraConditions as $key => $cond )
00240             {
00241                 $conditions[$key] = $cond;
00242             }
00243         }
00244 
00245         // substitute fields mentioned the conditions whith their short names (if any)
00246         $fields = $def['fields'];
00247         eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $conditions );
00248 
00249         $cond_text = eZPersistentObject::conditionText( $conditions );
00250 
00251         $db->query( "DELETE FROM $table $cond_text" );
00252     }
00253     
00254     /**
00255      * Stores the object in the database, uses storeObject() to do the actual
00256      * job and passes $fieldFilters to it.
00257      *
00258      * Note: Transaction unsafe. If you call several transaction unsafe methods
00259      * you must enclose the calls within a db transaction; thus within db->begin
00260      * and db->commit.
00261      *
00262      * @param array|null $fieldFilters
00263      * @return void
00264      */
00265     public function store( $fieldFilters = null )
00266     {
00267         eZPersistentObject::storeObject( $this, $fieldFilters );
00268     }
00269 
00270     /**
00271      * Makes sure data is stored if the data is considered dirty.
00272      *
00273      * Note: Transaction unsafe. If you call several transaction unsafe methods
00274      * you must enclose the calls within a db transaction; thus within db->begin
00275      * and db->commit.
00276      *
00277      * @param array|null $fieldFilters
00278      * @return void
00279      */
00280     public function sync( $fieldFilters = null )
00281     {
00282         if ( $this->hasDirtyData() )
00283             $this->store( $fieldFilters );
00284     }
00285 
00286     /**
00287      * Stores the data in $obj to database.
00288      *
00289      * Note: Transaction unsafe. If you call several transaction unsafe methods
00290      * you must enclose the calls within a db transaction; thus within db->begin
00291      * and db->commit.
00292      *
00293      * @todo Change the actual access to protected instead of just marking it as such
00294      * @access protected
00295      * @param eZPersistentObject $obj
00296      * @param array|null $fieldFilters If specified only certain fields will be stored.
00297      * @return void
00298      */
00299     public static function storeObject( $obj, $fieldFilters = null )
00300     {
00301         $db = eZDB::instance();
00302         $useFieldFilters = ( isset( $fieldFilters ) && is_array( $fieldFilters ) && $fieldFilters );
00303 
00304         $def = $obj->definition();
00305         $fields = $def["fields"];
00306         $keys = $def["keys"];
00307         $table = $def["name"];
00308         $relations = isset( $def["relations"] ) ? $def["relations"] : null;
00309         $insert_object = false;
00310         $exclude_fields = array();
00311         foreach ( $keys as $key )
00312         {
00313             $value = $obj->attribute( $key );
00314             if ( $value === null )
00315             {
00316                 $insert_object = true;
00317                 $exclude_fields[] = $key;
00318             }
00319         }
00320 
00321         if ( $useFieldFilters )
00322             $insert_object = false;
00323 
00324         $use_fields = array_diff( array_keys( $fields ), $exclude_fields );
00325         // If we filter out some of the fields we need to intersect it with $use_fields
00326         if ( is_array( $fieldFilters ) )
00327             $use_fields = array_intersect( $use_fields, $fieldFilters );
00328         $doNotEscapeFields = array();
00329         $changedValueFields = array();
00330         $numericDataTypes = array( 'integer', 'float', 'double' );
00331 
00332         foreach ( $use_fields as $field_name  )
00333         {
00334             $field_def = $fields[$field_name];
00335             $value = $obj->attribute( $field_name );
00336 
00337             if ( $value === null )
00338             {
00339                 if ( ! is_array( $field_def ) )
00340                 {
00341                     $exclude_fields[] = $field_name;
00342                 }
00343                 else
00344                 {
00345                     if ( array_key_exists( 'default', $field_def ) &&
00346                          ( $field_def['default'] !== null ||
00347                           ( $field_name == 'data_int' &&
00348                             array_key_exists( 'required', $field_def ) &&
00349                             $field_def[ 'required' ] == false ) ) )
00350                     {
00351                         $obj->setAttribute( $field_name, $field_def[ 'default' ] );
00352                     }
00353                     else
00354                     {
00355                         //if ( in_array( $field_def['datatype'], $numericDataTypes )
00356                         $exclude_fields[] = $field_name;
00357                     }
00358                 }
00359             }
00360 
00361             if ( strlen( $value ) == 0 &&
00362                  is_array( $field_def ) &&
00363                  in_array( $field_def['datatype'], $numericDataTypes  ) &&
00364                  array_key_exists( 'default', $field_def ) &&
00365                  ( $field_def[ 'default' ] === null || is_numeric( $field_def[ 'default' ] ) ) )
00366             {
00367                 $obj->setAttribute( $field_name, $field_def[ 'default' ] );
00368             }
00369 
00370             if ( $value !== null                                &&
00371                  $field_def['datatype'] === 'string'            &&
00372                  array_key_exists( 'max_length', $field_def )   &&
00373                  $field_def['max_length'] > 0                   &&
00374                  strlen( $value ) > $field_def['max_length'] )
00375             {
00376                 $obj->setAttribute( $field_name, substr( $value, 0, $field_def['max_length'] ) );
00377                 eZDebug::writeDebug( $value, "truncation of $field_name to max_length=". $field_def['max_length'] );
00378             }
00379             $bindDataTypes = array( 'text' );
00380             if ( $db->bindingType() != eZDBInterface::BINDING_NO &&
00381                  strlen( $value ) > 2000 &&
00382                  is_array( $field_def ) &&
00383                  in_array( $field_def['datatype'], $bindDataTypes  )
00384                  )
00385             {
00386                 $boundValue = $db->bindVariable( $value, $field_def );
00387 //                $obj->setAttribute( $field_name, $value );
00388                 $doNotEscapeFields[] = $field_name;
00389                 $changedValueFields[$field_name] = $boundValue;
00390             }
00391 
00392         }
00393         $key_conds = array();
00394         foreach ( $keys as $key )
00395         {
00396             $value = $obj->attribute( $key );
00397             $key_conds[$key] = $value;
00398         }
00399         unset( $value );
00400 
00401         $important_keys = $keys;
00402         if ( is_array( $relations ) )
00403         {
00404 //            $important_keys = array();
00405             foreach( $relations as $relation => $relation_data )
00406             {
00407                 if ( !in_array( $relation, $keys ) )
00408                     $important_keys[] = $relation;
00409             }
00410         }
00411         if ( count( $important_keys ) == 0 && !$useFieldFilters )
00412         {
00413             $insert_object = true;
00414         }
00415         else if ( !$insert_object )
00416         {
00417             $rows = eZPersistentObject::fetchObjectList( $def, $keys, $key_conds,
00418                                                           array(), null, false,
00419                                                           null, null );
00420             if ( count( $rows ) == 0 )
00421             {
00422                 /* If we only want to update some fields in a record
00423                  * and that records does not exist, then we should do nothing, only return.
00424                  */
00425                 if ( $useFieldFilters )
00426                     return;
00427 
00428                 $insert_object = true;
00429             }
00430         }
00431 
00432         if ( $insert_object )
00433         {
00434             // Note: When inserting we cannot hone the $fieldFilters parameters
00435 
00436             $use_fields = array_diff( array_keys( $fields ), $exclude_fields );
00437             $use_field_names = $use_fields;
00438             if ( $db->useShortNames() )
00439             {
00440                 $use_short_field_names = $use_field_names;
00441                 eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $use_short_field_names );
00442                 $field_text = implode( ', ', $use_short_field_names );
00443                 unset( $use_short_field_names );
00444             }
00445             else
00446                 $field_text = implode( ', ', $use_field_names );
00447 
00448             $use_values_hash = array();
00449             $escapeFields = array_diff( $use_fields, $doNotEscapeFields );
00450 
00451             foreach ( $escapeFields as $key )
00452             {
00453                 $value = $obj->attribute( $key );
00454                 $field_def = $fields[$key];
00455 
00456                 if ( $field_def['datatype'] == 'float' || $field_def['datatype'] == 'double' )
00457                 {
00458                     if ( $value === null )
00459                     {
00460                         $use_values_hash[$key] = 'NULL';
00461                     }
00462                     else
00463                     {
00464                         $use_values_hash[$key] = sprintf( '%F', $value );
00465                     }
00466                 }
00467                 else if ( $field_def['datatype'] == 'int' || $field_def['datatype'] == 'integer' )
00468                 {
00469                     if ( $value === null )
00470                     {
00471                         $use_values_hash[$key] = 'NULL';
00472                     }
00473                     else
00474                     {
00475                         $use_values_hash[$key] = sprintf( '%d', $value );
00476                     }
00477                 }
00478                 else
00479                 {
00480                     // Note: for more colherence, we might use NULL for sql strings if the php value is NULL and not an empty sring
00481                     //       but to keep compatibility with ez db, where most string columns are "not null default ''",
00482                     //       and code feeding us a php null value without meaning it, we do not.
00483                     $use_values_hash[$key] = "'" . $db->escapeString( $value ) . "'";
00484                 }
00485             }
00486             foreach ( $doNotEscapeFields as $key )
00487             {
00488                 $use_values_hash[$key] = $changedValueFields[$key];
00489             }
00490             $use_values = array();
00491             foreach ( $use_field_names as $field )
00492                 $use_values[] = $use_values_hash[$field];
00493             unset( $use_values_hash );
00494             $value_text = implode( ", ", $use_values );
00495 
00496             $sql = "INSERT INTO $table ($field_text) VALUES($value_text)";
00497             $db->query( $sql );
00498 
00499             if ( isset( $def["increment_key"] ) &&
00500                  is_string( $def["increment_key"] ) &&
00501                  !( $obj->attribute( $def["increment_key"] ) > 0 ) )
00502             {
00503                 $inc = $def["increment_key"];
00504                 $id = $db->lastSerialID( $table, $inc );
00505                 if ( $id !== false )
00506                     $obj->setAttribute( $inc, $id );
00507             }
00508         }
00509         else
00510         {
00511             $use_fields = array_diff( array_keys( $fields ), array_merge( $keys, $exclude_fields ) );
00512             if ( count( $use_fields ) > 0 )
00513             {
00514                 // If we filter out some of the fields we need to intersect it with $use_fields
00515                 if ( is_array( $fieldFilters ) )
00516                     $use_fields = array_intersect( $use_fields, $fieldFilters );
00517                 $use_field_names = array();
00518                 foreach ( $use_fields as $key )
00519                 {
00520                     if ( $db->useShortNames() && is_array( $fields[$key] ) && array_key_exists( 'short_name', $fields[$key] ) && strlen( $fields[$key]['short_name'] ) > 0 )
00521                         $use_field_names[$key] = $fields[$key]['short_name'];
00522                     else
00523                         $use_field_names[$key] = $key;
00524                 }
00525 
00526                 $field_text = "";
00527                 $field_text_len = 0;
00528                 $i = 0;
00529 
00530 
00531                 foreach ( $use_fields as $key )
00532                 {
00533                     $value = $obj->attribute( $key );
00534 
00535                     if ( $fields[$key]['datatype'] == 'float' || $fields[$key]['datatype'] == 'double' )
00536                     {
00537                         if ( $value === null )
00538                             $field_text_entry = $use_field_names[$key] . '=NULL';
00539                         else
00540                             $field_text_entry = $use_field_names[$key] . "=" . sprintf( '%F', $value );
00541                     }
00542                     else if ($fields[$key]['datatype'] == 'int' || $fields[$key]['datatype'] == 'integer' )
00543                     {
00544                         if ( $value === null )
00545                             $field_text_entry = $use_field_names[$key] . '=NULL';
00546                         else
00547                             $field_text_entry = $use_field_names[$key] . "=" . sprintf( '%d', $value );
00548                     }
00549                     else if ( in_array( $use_field_names[$key], $doNotEscapeFields ) )
00550                     {
00551                         $field_text_entry = $use_field_names[$key] . "=" .  $changedValueFields[$key];
00552                     }
00553                     else
00554                     {
00555                         $field_text_entry = $use_field_names[$key] . "='" . $db->escapeString( $value ) . "'";
00556                     }
00557 
00558                     $field_text_len += strlen( $field_text_entry );
00559                     $needNewline = false;
00560                     if ( $field_text_len > 60 )
00561                     {
00562                         $needNewline = true;
00563                         $field_text_len = 0;
00564                     }
00565                     if ( $i > 0 )
00566                         $field_text .= "," . ($needNewline ? "\n    " : ' ');
00567                     $field_text .= $field_text_entry;
00568                     ++$i;
00569                 }
00570                 $cond_text = eZPersistentObject::conditionText( $key_conds );
00571                 $sql = "UPDATE $table SET $field_text$cond_text";
00572                 $db->query( $sql );
00573             }
00574         }
00575         $obj->setHasDirtyData( false );
00576     }
00577     
00578     /**
00579      * Calls conditionTextByRow with an empty row and $conditions.
00580      *
00581      * @param array $conditions
00582      * @return string
00583      */
00584     public static function conditionText( $conditions )
00585     {
00586         return eZPersistentObject::conditionTextByRow( $conditions, null );
00587     }
00588 
00589     /**
00590      * Generates an SQL sentence from the conditions \a $conditions and
00591      * row data $row.
00592      *
00593      * If $row is empty (or null) it uses the condition data instead of row data.
00594      *
00595      * @param array $conditions
00596      * @param array|null $row
00597      * @return string
00598      */
00599     public static function conditionTextByRow( $conditions, $row )
00600     {
00601         $db = eZDB::instance();
00602 
00603         $where_text = "";
00604         if ( is_array( $conditions ) and
00605              count( $conditions ) > 0 )
00606         {
00607             $where_text = " WHERE  ";
00608             $i = 0;
00609             foreach ( $conditions as $id => $cond )
00610             {
00611                 if ( $i > 0 )
00612                     $where_text .= " AND ";
00613                 if ( is_array( $row ) )
00614                 {
00615                     $where_text .= $cond . "='" . $db->escapeString( $row[$cond] ) . "'";
00616                 }
00617                 else
00618                 {
00619                     if ( is_array( $cond ) )
00620                     {
00621                         if ( is_array( $cond[0] ) )
00622                         {
00623                             $where_text .= $id . ' IN ( ';
00624                             $j = 0;
00625                             foreach ( $cond[0] as $value )
00626                             {
00627                                 if ( $j > 0 )
00628                                     $where_text .= ", ";
00629                                 $where_text .= "'" . $db->escapeString( $value ) . "'";
00630                                 ++$j;
00631                             }
00632                             $where_text .= ' ) ';
00633                         }
00634                         else if ( is_array( $cond[1] ) )
00635                         {
00636                             $range = $cond[1];
00637                             $where_text .= "$id BETWEEN '" . $db->escapeString( $range[0] ) . "' AND '" . $db->escapeString( $range[1] ) . "'";
00638                         }
00639                         else
00640                         {
00641                           switch ( $cond[0] )
00642                           {
00643                               case '>=':
00644                               case '<=':
00645                               case '<':
00646                               case '>':
00647                               case '=':
00648                               case '<>':
00649                               case '!=':
00650                               case 'like':
00651                                   {
00652                                       $where_text .= $db->escapeString( $id ) . " " . $cond[0] . " '" . $db->escapeString( $cond[1] ) . "'";
00653                                   } break;
00654                               default:
00655                                   {
00656                                       eZDebug::writeError( "Conditional operator '$cond[0]' is not supported.", __METHOD__ );
00657                                   } break;
00658                           }
00659 
00660                         }
00661                     }
00662                     else
00663                         $where_text .= $db->escapeString( $id ) . "='" . $db->escapeString( $cond ) . "'";
00664                 }
00665                 ++$i;
00666             }
00667         }
00668         return $where_text;
00669     }
00670 
00671     /**
00672      * Creates an SQL query out of the different parameters and returns an array with the result.
00673      *
00674      * A full example:
00675      * <code>
00676      * $filter = array( 'id', 'name' );
00677      * $conds = array( 'type' => 5,
00678      *                 'size' => array( false, array( 200, 500 ) ) );
00679      * $sorts = array( 'name' => 'asc' );
00680      * $limit = array( 'offset' => 50, 'length' => 10 );
00681      * eZPersistentObject::fetchObjectList( $def, $filter, $conds, $sorts, $limit, true, false, null )
00682      * </code>
00683      *
00684      * Counting number of elements.
00685      * <code>
00686      * $custom = array( array( 'operation' => 'count( id )',
00687      *                         'name' => 'count' ) );
00688      * // Here $field_filters is set to an empty array, that way only count is used in fields
00689      * $rows = eZPersistentObject::fetchObjectList( $def, array(), null, null, null, false, false, $custom );
00690      * return $rows[0]['count'];
00691      * </code>
00692      *
00693      * Counting elements per type using grouping
00694      * <code>
00695      * $custom = array( array( 'operation' => 'count( id )',
00696      *                         'name' => 'count' ) );
00697      * $group = array( 'type' );
00698      * $rows = eZPersistentObject::fetchObjectList( $def, array(), null, null, null, false, $group, $custom );
00699      * return $rows[0]['count'];
00700      * </code>
00701      *
00702      * Example to fetch a result with custom conditions. The following example will fetch the attributes to
00703      * the contentobject with id 1 and add the contentobject.name in each attribute row with the array key
00704      * contentobject_name.
00705      * <code>
00706      * $objectDef = eZContentObject::definition();
00707      * $objectAttributeDef = eZContentObjectAttribute::definition();
00708      *
00709      * $fields = array();
00710      * $conds = array( $objectDef['name'] . '.id' => 1 );
00711      * $sorts = array( $objectAttributeDef['name'] . '.sort_key_string' => 'asc' );
00712      *
00713      * $limit = null;
00714      * $asObject = false;
00715      * $group = false;
00716      *
00717      * $customFields = array( $objectAttributeDef['name'] . '.*',
00718      *                        array( 'operation' => $objectDef['name'] . '.name',
00719      *                               'name' => 'contentobject_name' ) );
00720      *
00721      * $customTables = array( $objectDef['name'] );
00722      *
00723      * $languageCode = 'eng-GB';
00724      * $customConds = ' AND ' . $objectDef['name'] . '.current_version=' . $objectAttributeDef['name'] . '.version' .
00725      *                ' AND ' . $objectDef['name'] . '.id=' . $objectAttributeDef['name'] . '.contentobject_id' .
00726      *                ' AND ' . $objectAttributeDef['name'] . '.language_code=\'' . $languageCode . '\'';
00727      *
00728      * $rows = eZPersistentObject::fetchObjectList( $objectAttributeDef, $fields, $conds, $sorts, $limit, $asObject,
00729      *                                              $group, $customFields, $customTables, $customConds );
00730      * </code>
00731      * 
00732      * @param array $def                    A definition array of all fields, table name and sorting (see {@link eZPersistentObject::definition()} for more info)
00733      * @param array|null $field_filters     If defined determines the fields which are extracted (array of field names), if not all fields are fetched
00734      * @param array|null $conds             null for no special condition or an associative array of fields to filter on.
00735      *                                      Syntax is FIELD => CONDITION
00736      *                                      CONDITION can be one of:
00737      *                                      - Scalar value: Creates a condition where FIELD must match the value, e.g
00738      *                                      <code>array( 'id' => 5 )</code> generates <code>SQL id = 5</code>
00739      *                                      - Array with two scalar values: The first value is the match operator, the second is the scalar value
00740      *                                      <code>array( 'priority' => array( '>', 5 ) )</code> generates SQL <code>priority > 5</code>
00741      *                                      - Array with range: The first value is <code>false</code>, the second value is an array with start and stop of range in array
00742      *                                      <code>array( 'type' => array( false, array( 1, 5 ) ) )</code> generates SQL <code>type BETWEEN 1 AND 5</code>
00743      *                                      - Array with multiple elements: The first value is the field identifier, the second is an array with scalar values
00744      *                                       <code>array( 'id' => array( array( 1, 5, 7 ) ) )</code> generates SQL <code>id IN ( 1, 5, 7 )</code>
00745      * @param array|null|bool $sorts        An associative array of sorting conditions, if set to false ignores settings in $def, if set to null uses settingss in $def.
00746      *                                      Syntax is FIELD => DIRECTION.
00747      *                                      DIRECTION must either be 'asc' for ascending or 'desc' for descending.
00748      * @param array|null $limit             An associative array with limitiations, can contain
00749      *                                      - 'offset': Numerical value defining the start offset for the fetch
00750      *                                      - 'length': Numerical value defining the max number of items to return
00751      * @param bool $asObject                If true then it will return an array with objects, objects are created from class defined in $def.
00752      *                                      If falseit will just return the rows fetch from database.
00753      * @param array|null|bool $grouping     An array of fields to group by or null to use grouping in defintion $def.
00754      * @param null $custom_fields           Array of FIELD elements to add to SQL, can be used to perform custom fetches, e.g counts.
00755      *                                      FIELD is an associative array containing:
00756      *                                      - 'operation': A text field which is included in the field list
00757      *                                      - 'name': If present it adds <code>AS name</code> to the operation.
00758      * @param array|null $custom_tables     Array of additional tables
00759      * @param string|null $custom_conds     String with sql conditions for 'WHERE' clause.
00760      * @return eZPersistentObject[]|array|null                   An array of objects or rows, null on error
00761      */
00762     public static function fetchObjectList( $def,
00763                               $field_filters = null,
00764                               $conds = null,
00765                               $sorts = null,
00766                               $limit = null,
00767                               $asObject = true,
00768                               $grouping = false,
00769                               $custom_fields = null,
00770                               $custom_tables = null,
00771                               $custom_conds = null )
00772     {
00773         $db = eZDB::instance();
00774         $fields = $def["fields"];
00775         $tables = $def["name"];
00776         $class_name = $def["class_name"];
00777         if ( is_array( $custom_tables ) )
00778         {
00779             foreach( $custom_tables as $custom_table )
00780                 $tables .= ', ' . $db->escapeString( $custom_table );
00781         }
00782         eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $conds );
00783         if ( is_array( $field_filters ) )
00784             $field_array = array_unique( array_intersect(
00785                                              $field_filters, array_keys( $fields ) ) );
00786         else
00787             $field_array = array_keys( $fields );
00788         if ( $custom_fields !== null and is_array( $custom_fields ) )
00789         {
00790             foreach( $custom_fields as $custom_field )
00791             {
00792                 if ( is_array( $custom_field ) )
00793                 {
00794                     $custom_text = $custom_field["operation"];
00795                     if ( isset( $custom_field["name"] ) )
00796                     {
00797                         $field_name = $custom_field["name"];
00798                         $custom_text .= " AS $field_name";
00799                     }
00800                 }
00801                 else
00802                 {
00803                     $custom_text = $custom_field;
00804                 }
00805                 $field_array[] = $custom_text;
00806             }
00807         }
00808         eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $field_array );
00809         $field_text = '';
00810         $i = 0;
00811         foreach ( $field_array as $field_item )
00812         {
00813             if ( ( $i % 7 ) == 0 and
00814                  $i > 0 )
00815                 $field_text .= ",       ";
00816             else if ( $i > 0 )
00817                 $field_text .= ', ';
00818             $field_text .= $field_item;
00819             ++$i;
00820         }
00821 
00822         $where_text = eZPersistentObject::conditionText( $conds );
00823         if ( $custom_conds )
00824             $where_text .= $custom_conds;
00825 
00826         $sort_text = "";
00827         if ( $sorts !== false and ( isset( $def["sort"] ) or is_array( $sorts ) ) )
00828         {
00829             $sort_list = array();
00830             if ( is_array( $sorts ) )
00831             {
00832                 $sort_list = $sorts;
00833             }
00834             else if ( isset( $def['sort'] ) )
00835             {
00836                 $sort_list = $def["sort"];
00837             }
00838             if ( count( $sort_list ) > 0 )
00839             {
00840                 $sort_text = " ORDER BY ";
00841                 $i = 0;
00842                 foreach ( $sort_list as $sort_id => $sort_type )
00843                 {
00844                     if ( $i > 0 )
00845                         $sort_text .= ", ";
00846                     if ( $sort_type == "desc" )
00847                         $sort_text .= "$sort_id DESC";
00848                     else
00849                         $sort_text .= "$sort_id ASC";
00850                     ++$i;
00851                 }
00852             }
00853         }
00854 
00855         $grouping_text = "";
00856         if ( isset( $def["grouping"] ) or ( is_array( $grouping ) and count( $grouping ) > 0 ) )
00857         {
00858             $grouping_list = isset( $def["grouping"] ) ? $def["grouping"] : array();
00859             if ( is_array( $grouping ) )
00860                 $grouping_list = $grouping;
00861             if ( count( $grouping_list ) > 0 )
00862             {
00863                 $grouping_text = " GROUP BY ";
00864                 $i = 0;
00865                 foreach ( $grouping_list as $grouping_id )
00866                 {
00867                     if ( $i > 0 )
00868                         $grouping_text .= ", ";
00869                     $grouping_text .= "$grouping_id";
00870                     ++$i;
00871                 }
00872             }
00873         }
00874 
00875         $db_params = array();
00876         if ( is_array( $limit ) )
00877         {
00878             if ( isset( $limit["offset"] ) )
00879             {
00880                 $db_params["offset"] = $limit["offset"];
00881             }
00882             if ( isset( $limit['limit'] ) )
00883             {
00884                 $db_params["limit"] = $limit["limit"];
00885             }
00886             else
00887             {
00888                 $db_params["limit"] = $limit["length"];
00889             }
00890         }
00891 
00892         $sqlText = "SELECT $field_text
00893                     FROM   $tables" .
00894                     $where_text .
00895                     $grouping_text .
00896                     $sort_text;
00897         $rows = $db->arrayQuery( $sqlText,
00898                                  $db_params );
00899 
00900         // Indicate that a DB error occured.
00901         if ( $rows === false )
00902             return null;
00903 
00904         $objectList = eZPersistentObject::handleRows( $rows, $class_name, $asObject );
00905         return $objectList;
00906     }
00907     
00908     /**
00909      * Creates PHP objects out of the database rows $rows.
00910      *
00911      * Each object is created from class \$ class_name and is passed
00912      * as a row array as parameter.
00913      *
00914      * @param array $rows
00915      * @param string $class_name
00916      * @param bool $asObject If true then objects will be created, if not it just returns $rows as it is.
00917      * @return eZPersistentObject[]|array
00918      */
00919     public static function handleRows( $rows, $class_name, $asObject )
00920     {
00921         if ( $asObject )
00922         {
00923             $objects = array();
00924             if ( is_array( $rows ) )
00925             {
00926                 foreach ( $rows as $row )
00927                 {
00928                     $objects[] = new $class_name( $row );
00929                 }
00930             }
00931             return $objects;
00932         }
00933         else
00934             return $rows;
00935     }
00936     
00937     /**
00938      * Sets row id $id2 to have the placement of row id $id1.
00939      *
00940      * Note: Transaction unsafe. If you call several transaction unsafe methods
00941      * you must enclose the calls within a db transaction; thus within db->begin
00942      * and db->commit.
00943      *
00944      * @param string $table
00945      * @param array $keys
00946      * @param int $order_id
00947      * @param array $rows
00948      * @param int $id1
00949      * @param int $id2
00950      * @return string
00951      */
00952     public static function swapRow( $table, $keys, $order_id, $rows, $id1, $id2 )
00953     {
00954         $db = eZDB::instance();
00955         $text = $order_id . "='" . $db->escapeString( $rows[$id1][$order_id] ) . "' WHERE ";
00956         $i = 0;
00957         foreach ( $keys as $key )
00958         {
00959             if ( $i > 0 )
00960                 $text .= " AND ";
00961             $text .= $key . "='" . $db->escapeString( $rows[$id2][$key] ) . "'";
00962             ++$i;
00963         }
00964         return "UPDATE $table SET $text";
00965     }
00966     
00967     /**
00968      * Returns an order value which can be used for new items in table,
00969      * for instance placement.
00970      *
00971      * Uses $def, $orderField and $conditions to figure out the currently maximum
00972      * order value and returns one that is larger.
00973      *
00974      * @param array $def A definition array of all fields, table name and sorting (see {@link eZPersistentObject::definition()} for more info)
00975      * @param string $orderField
00976      * @param array $conditions
00977      * @return int
00978      */
00979     public static function newObjectOrder( $def, $orderField, $conditions )
00980     {
00981         $db = eZDB::instance();
00982         $table = $def["name"];
00983         $keys = $def["keys"];
00984 
00985         $cond_text = eZPersistentObject::conditionText( $conditions );
00986         $rows = $db->arrayQuery( "SELECT MAX($orderField) AS $orderField FROM $table $cond_text" );
00987         if ( count( $rows ) > 0 and isset( $rows[0][$orderField] ) )
00988             return $rows[0][$orderField] + 1;
00989         else
00990             return 1;
00991     }
00992 
00993     /**
00994      * Moves a row in a database table.
00995      *
00996      * Uses $orderField to determine the order of objects in a table, usually this
00997      * is a placement of some kind. It uses this order field to figure out how move
00998      * the row, the row is either swapped with another row which is either above or
00999      * below according to whether $down is true or false, or it is swapped
01000      * with the first item or the last item depending on whether this row
01001      * is first or last.
01002      * Uses $conditions to figure out unique rows.
01003      *
01004      * Note: Transaction unsafe. If you call several transaction unsafe methods
01005      * you must enclose the calls within a db transaction; thus within db->begin
01006      * and db->commit.
01007      *
01008      * @param array $def A definition array of all fields, table name and sorting (see {@link eZPersistentObject::definition()} for more info)
01009      * @param array $orderField Associative array with one element, the key is the order id and values is order value.
01010      * @param array $conditions
01011      * @param bool $down
01012      * @return void
01013      */
01014     public static function reorderObject( $def, $orderField, $conditions, $down = true )
01015     {
01016         $db = eZDB::instance();
01017         $table = $def["name"];
01018         $keys = $def["keys"];
01019 
01020         reset( $orderField );
01021         $order_id = key( $orderField );
01022         $order_val = $orderField[$order_id];
01023         if ( $down )
01024         {
01025             $order_operator = ">=";
01026             $order_type = "asc";
01027             $order_add = -1;
01028         }
01029         else
01030         {
01031             $order_operator = "<=";
01032             $order_type = "desc";
01033             $order_add = 1;
01034         }
01035         $fields = array_merge( $keys, array( $order_id ) );
01036         $rows = eZPersistentObject::fetchObjectList( $def,
01037                                                       $fields,
01038                                                       array_merge( $conditions,
01039                                                                    array( $order_id => array( $order_operator,
01040                                                                                               $order_val ) ) ),
01041                                                       array( $order_id => $order_type ),
01042                                                       array( "length" => 2 ),
01043                                                       false );
01044         if ( count( $rows ) == 2 )
01045         {
01046             $swapSQL1 = eZPersistentObject::swapRow( $table, $keys, $order_id, $rows, 1, 0 );
01047             $swapSQL2 = eZPersistentObject::swapRow( $table, $keys, $order_id, $rows, 0, 1 );
01048             $db->begin();
01049             $db->query( $swapSQL1 );
01050             $db->query( $swapSQL2 );
01051             $db->commit();
01052         }
01053         else
01054         {
01055             $tmp = eZPersistentObject::fetchObjectList( $def,
01056                                                          $fields,
01057                                                          $conditions,
01058                                                          array( $order_id => $order_type ),
01059                                                          array( "length" => 1 ),
01060                                                          false );
01061             $where_text = eZPersistentObject::conditionTextByRow( $keys, $rows[0] );
01062             $db->query( "UPDATE $table SET $order_id='" . ( $tmp[0][$order_id] + $order_add ) .
01063                         "'$where_text"  );
01064         }
01065     }
01066 
01067     /**
01068      * Returns the definition for the object, the default implementation
01069      * is to return an empty array. It's upto each inheriting class
01070      * to return a proper definition array.
01071      *
01072      * The definition array is an associative array consists of these keys:
01073      * - fields:    an associative array of fields which defines which database
01074      *              field (the key) is to fetched and how they map to object
01075      *              member variables (the value).
01076      * - keys:      an array of fields which is used for uniquely identifying
01077      *              the object in the table.
01078      * - function_attributes:   an associative array of attributes which maps
01079      *                          to member functions, used for fetching
01080      *                          data with functions.
01081      *
01082      * - set_functions: an associative array of attributes which maps to member
01083      *                  functions, used for setting data with functions.
01084      * - increment_key: the field which is incremented on table inserts.
01085      * - class_name:    the classname which is used for instantiating new objecs
01086      *                  when fetching from the database.
01087      * - sort:  an associative array which defines the default sorting of lists,
01088      *          the key is the table field while the value is the sorting method
01089      *          which is either 'asc' or 'desc'.
01090      * - name:  the name of the database table
01091      *
01092      * Example:
01093      * <code>
01094      * public static function definition()
01095      * {
01096      *     return array(    "fields" => array( "id" => "ID",
01097      *                                         "version" => "Version",
01098      *                                         "name" => "Name" ),
01099      *                      "keys" => array( "id", "version" ),
01100      *                      "function_attributes" => array( "current" => "currentVersion",
01101      *                                                      "class_name" => "className" ),
01102      *                      "increment_key" => "id",
01103      *                      "class_name" => "eZContentClass",
01104      *                      "sort" => array( "id" => "asc" ),
01105      *                      "name" => "ezcontentclass" );
01106      * }
01107      * </code>
01108      *
01109      * @return array
01110      */
01111     public static function definition()
01112     {
01113         return array();
01114     }
01115 
01116     /**
01117      * Escapes strings in an array with the help of {@link eZDBInterface::escapeString()}
01118      *
01119      * @param array $array
01120      * @return array
01121      */
01122     public static function escapeArray( $array )
01123     {
01124         $db = eZDB::instance();
01125         $out = array();
01126         foreach( $array as $key => $value )
01127         {
01128             if ( is_array( $value ) )
01129             {
01130                 $tmp = array();
01131                 foreach( $value as $valueItem )
01132                 {
01133                     $tmp[] = $db->escapeString( $valueItem );
01134                 }
01135                 $out[$key] = $tmp;
01136                 unset( $tmp );
01137             }
01138             else
01139                 $out[$key] = $db->escapeString( $value );
01140         }
01141         return $out;
01142     }
01143 
01144     /**
01145      * Updates rows matching the given parameters
01146      *
01147      * Note: Transaction unsafe. If you call several transaction unsafe methods
01148      * you must enclose the calls within a db transaction; thus within db->begin
01149      * and db->commit.
01150      *
01151      * @param array $parameters
01152      * @return void
01153      */
01154     public static function updateObjectList( $parameters )
01155     {
01156         $db = eZDB::instance();
01157         $def = $parameters['definition'];
01158         $table = $def['name'];
01159         $fields = $def['fields'];
01160         $keys = $def['keys'];
01161 
01162         $updateFields = $parameters['update_fields'];
01163         $conditions = $parameters['conditions'];
01164 
01165         $query = "UPDATE $table SET ";
01166         $i = 0;
01167         $valueBound = false;
01168 
01169         foreach( $updateFields as $field => $value )
01170         {
01171             $fieldDef = $fields[ $field ];
01172             $numericDataTypes = array( 'integer', 'float', 'double' );
01173             if ( strlen( $value ) == 0 &&
01174                  is_array( $fieldDef ) &&
01175                  in_array( $fieldDef['datatype'], $numericDataTypes  ) &&
01176                  array_key_exists( 'default', $fieldDef ) &&
01177                  $fieldDef[ 'default' ] !== null )
01178             {
01179                 $value = $fieldDef[ 'default' ];
01180             }
01181 
01182             $bindDataTypes = array( 'text' );
01183             if ( $db->bindingType() != eZDBInterface::BINDING_NO &&
01184                  strlen( $value ) > 2000 &&
01185                  is_array( $fieldDef ) &&
01186                  in_array( $fieldDef['datatype'], $bindDataTypes  )
01187                  )
01188             {
01189                 $value = $db->bindVariable( $value, $fieldDef );
01190                 $valueBound = true;
01191             }
01192             else
01193                 $valueBound = false;
01194 
01195             if ( $i > 0 )
01196                 $query .= ', ';
01197             if ( $valueBound )
01198                 $query .= $field . "=" . $value;
01199             else
01200                 $query .= $field . "='" . $db->escapeString( $value ) . "'";
01201             ++$i;
01202         }
01203         $query .= ' WHERE ';
01204         $i = 0;
01205         foreach( $conditions as $conditionKey => $condition )
01206         {
01207             if ( $i > 0 )
01208                 $query .= ' AND ';
01209             if ( is_array( $condition ) )
01210             {
01211                 $query .= $conditionKey . ' IN (';
01212                 $j = 0;
01213                 foreach( $condition as $conditionValue )
01214                 {
01215                     if ( $j > 0 )
01216                         $query .= ', ';
01217                     $query .= "'" . $db->escapeString( $conditionValue ) . "'";
01218                     ++$j;
01219                 }
01220                 $query .= ')';
01221             }
01222             else
01223                 $query .= $conditionKey . "='" . $db->escapeString( $condition ) . "'";
01224             ++$i;
01225         }
01226         $db->query( $query );
01227     }
01228 
01229     /**
01230      * Returns the attributes for this object, taken from the definition fields
01231      * and function attributes.
01232      *
01233      * @see eZPersistentObject::definition()
01234      *
01235      * @return array
01236      */
01237     public function attributes()
01238     {
01239         $def = $this->definition();
01240         $attrs = array_keys( $def["fields"] );
01241         if ( isset( $def["function_attributes"] ) )
01242             $attrs = array_unique( array_merge( $attrs, array_keys( $def["function_attributes"] ) ) );
01243         if ( isset( $def["functions"] ) )
01244             $attrs = array_unique( array_merge( $attrs, array_keys( $def["functions"] ) ) );
01245         return $attrs;
01246     }
01247     
01248     /**
01249      * Checks if $attr is part of the definition fields or function attributes.
01250      *
01251      * @param string $attr
01252      * @return bool
01253      */
01254     public function hasAttribute( $attr )
01255     {
01256         $def = $this->definition();
01257         $has_attr = isset( $def["fields"][$attr] );
01258         if ( !$has_attr and isset( $def["function_attributes"] ) )
01259             $has_attr = isset( $def["function_attributes"][$attr] );
01260         if ( !$has_attr and isset( $def["functions"] ) )
01261             $has_attr = isset( $def["functions"][$attr] );
01262         return $has_attr;
01263     }
01264 
01265     /**
01266      * Returns the attribute data for $attr, this is either returned from the
01267      * member variables or a member function depending on whether the definition
01268      * field or function attributes matched.
01269      *
01270      * @param string $attr
01271      * @param bool $noFunction
01272      * @return mixed
01273      */
01274     public function attribute( $attr, $noFunction = false )
01275     {
01276         $def = $this->definition();
01277         $attrFunctions = isset( $def["function_attributes"] ) ? $def["function_attributes"] : null;
01278         if ( $noFunction === false && isset( $attrFunctions[$attr] ) )
01279         {
01280             $functionName = $attrFunctions[$attr];
01281             if ( method_exists( $this, $functionName ) )
01282             {
01283                 return $this->$functionName();
01284             }
01285 
01286             eZDebug::writeError( 'Could not find function : "' . get_class( $this ) . '::' . $functionName . '()".', __METHOD__ );
01287             return null;
01288         }
01289 
01290         $fields = $def["fields"];
01291         if ( isset( $fields[$attr] ) )
01292         {
01293             $attrName = $fields[$attr];
01294             if ( isset( $attrName['name'] ) )
01295             {
01296                 $attrName = $attrName['name'];
01297             }
01298             return $this->$attrName;
01299         }
01300 
01301         if ( isset( $def["functions"][$attr] ) )
01302         {
01303             return $this->$functions[$attr]();
01304         }
01305 
01306         eZDebug::writeError( "Attribute '$attr' does not exist", $def['class_name'] . '::attribute' );
01307         return null;
01308     }
01309 
01310     /**
01311      * Sets the attribute $attr to the value $val.
01312      *
01313      * The attribute must be present in the objects definition fields or set functions.
01314      *
01315      * @param string $attr
01316      * @param mixed $val
01317      * @return void
01318      */
01319     public function setAttribute( $attr, $val )
01320     {
01321         $def = $this->definition();
01322         $fields = $def["fields"];
01323         $functions = isset( $def["set_functions"] ) ? $def["set_functions"] : null;
01324         if ( isset( $fields[$attr] ) )
01325         {
01326             $attrName = $fields[$attr];
01327             if ( is_array( $attrName ) )
01328             {
01329                 $attrName = $attrName['name'];
01330             }
01331 
01332             $oldValue = null;
01333             if ( isset( $this->$attrName ) )
01334                 $oldValue = $this->$attrName;
01335             $this->$attrName = $val;
01336             if ( $oldValue === null ||
01337                  $oldValue !== $val )
01338                 $this->setHasDirtyData( true );
01339         }
01340         else if ( isset( $functions[$attr] ) )
01341         {
01342             $functionName = $functions[$attr];
01343             $oldValue = $this->$functionName( $val );
01344             if ( $oldValue === null or $oldValue !== $val )
01345                 $this->setHasDirtyData( true );
01346         }
01347         else
01348         {
01349             eZDebug::writeError( "Undefined attribute '$attr', cannot set",
01350                                  $def['class_name'] );
01351         }
01352     }
01353     
01354     /**
01355      * Returns true if the data is considered dirty and needs to be stored.
01356      *
01357      * @return bool
01358      */
01359     public function hasDirtyData()
01360     {
01361         return $this->PersistentDataDirty;
01362     }
01363 
01364     /**
01365      * Sets whether the object has dirty data or not.
01366      *
01367      * @param bool $hasDirtyData
01368      * @return void
01369      */
01370     public function setHasDirtyData( $hasDirtyData )
01371     {
01372         $this->PersistentDataDirty = $hasDirtyData;
01373     }
01374 
01375     /**
01376      * Returns the short attribute name (alias) if it's defined, given attribute name otherwise
01377      *
01378      * @param eZDBInterface $db
01379      * @param array $def A definition array of all fields, table name and sorting (see {@link eZPersistentObject::definition()} for more info)
01380      * @param string $attrName
01381      * @return string
01382      */
01383     public static function getShortAttributeName( $db, $def, $attrName )
01384     {
01385         $fields = $def['fields'];
01386 
01387         if ( $db->useShortNames() && isset( $fields[$attrName] ) && array_key_exists( 'short_name', $fields[$attrName] ) && $fields[$attrName]['short_name'] )
01388             return $fields[$attrName]['short_name'];
01389 
01390         return $attrName;
01391     }
01392 }
01393 
01394 ?>