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