/**
  *  Stores the data in \a $obj to database.
  *
  *  a little modification - stores tehe placeholdervalues for the virtual list so we now if we should get
  *  a value from parent list
  *
  *  fieldFilters If specified only certain fields will be stored.
  * \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  * the calls within a db transaction; thus within db->begin and db->commit.
  *
  * @param unknown_type $obj
  * @param unknown_type $fieldFilters
  * @see eZPersistentObject::storeObject
  */
 static function storeObject($obj, $fieldFilters = null)
 {
     $db = eZDB::instance();
     $useFieldFilters = isset($fieldFilters) && is_array($fieldFilters) && $fieldFilters;
     $def = $obj->definition();
     $fields = $def["fields"];
     $keys = $def["keys"];
     $table = $def["name"];
     $relations = isset($def["relations"]) ? $def["relations"] : null;
     $insert_object = false;
     $exclude_fields = array();
     foreach ($keys as $key) {
         // $value = $obj->attribute( $key );
         $value = $obj->attributeContentToStore($key);
         if ($value === null) {
             $insert_object = true;
             $exclude_fields[] = $key;
         }
     }
     if ($useFieldFilters) {
         $insert_object = false;
     }
     $use_fields = array_diff(array_keys($fields), $exclude_fields);
     // If we filter out some of the fields we need to intersect it with $use_fields
     if (is_array($fieldFilters)) {
         $use_fields = array_intersect($use_fields, $fieldFilters);
     }
     $doNotEscapeFields = array();
     $changedValueFields = array();
     $numericDataTypes = array('integer', 'float', 'double');
     foreach ($use_fields as $field_name) {
         $field_def = $fields[$field_name];
         //$value = $obj->attribute( $field_name );
         $value = $obj->attributeContentToStore($field_name);
         if ($value === null) {
             if (!is_array($field_def)) {
                 $exclude_fields[] = $field_name;
             } else {
                 if (array_key_exists('default', $field_def) && ($field_def['default'] !== null || $field_name == 'data_int' && array_key_exists('required', $field_def) && $field_def['required'] == false)) {
                     $obj->setAttribute($field_name, $field_def['default']);
                 } else {
                     //if ( in_array( $field_def['datatype'], $numericDataTypes )
                     $exclude_fields[] = $field_name;
                 }
             }
         }
         if (strlen($value) == 0 && is_array($field_def) && in_array($field_def['datatype'], $numericDataTypes) && array_key_exists('default', $field_def) && ($field_def['default'] === null || is_numeric($field_def['default']))) {
             $obj->setAttribute($field_name, $field_def['default']);
         }
         if ($value !== null && $field_def['datatype'] === 'string' && array_key_exists('max_length', $field_def) && $field_def['max_length'] > 0 && strlen($value) > $field_def['max_length']) {
             $obj->setAttribute($field_name, substr($value, 0, $field_def['max_length']));
             eZDebug::writeDebug($value, "truncation of {$field_name} to max_length=" . $field_def['max_length']);
         }
         $bindDataTypes = array('text');
         if ($db->bindingType() != eZDBInterface::BINDING_NO && strlen($value) > 2000 && is_array($field_def) && in_array($field_def['datatype'], $bindDataTypes)) {
             $boundValue = $db->bindVariable($value, $field_def);
             //                $obj->setAttribute( $field_name, $value );
             $doNotEscapeFields[] = $field_name;
             $changedValueFields[$field_name] = $boundValue;
         }
     }
     $key_conds = array();
     foreach ($keys as $key) {
         //$value = $obj->attribute( $key );
         $value = $obj->attributeContentToStore($key);
         $key_conds[$key] = $value;
     }
     unset($value);
     $important_keys = $keys;
     if (is_array($relations)) {
         //            $important_keys = array();
         foreach ($relations as $relation => $relation_data) {
             if (!in_array($relation, $keys)) {
                 $important_keys[] = $relation;
             }
         }
     }
     if (count($important_keys) == 0 && !$useFieldFilters) {
         $insert_object = true;
     } else {
         if (!$insert_object) {
             $rows = eZPersistentObject::fetchObjectList($def, $keys, $key_conds, array(), null, false, null, null);
             if (count($rows) == 0) {
                 /* If we only want to update some fields in a record
                  * and that records does not exist, then we should do nothing, only return.
                  */
                 if ($useFieldFilters) {
                     return;
                 }
                 $insert_object = true;
             }
         }
     }
     if ($insert_object) {
         // Note: When inserting we cannot hone the $fieldFilters parameters
         $use_fields = array_diff(array_keys($fields), $exclude_fields);
         $use_field_names = $use_fields;
         if ($db->useShortNames()) {
             $use_short_field_names = $use_field_names;
             eZPersistentObject::replaceFieldsWithShortNames($db, $fields, $use_short_field_names);
             $field_text = implode(', ', $use_short_field_names);
             unset($use_short_field_names);
         } else {
             $field_text = implode(', ', $use_field_names);
         }
         $use_values_hash = array();
         $escapeFields = array_diff($use_fields, $doNotEscapeFields);
         foreach ($escapeFields as $key) {
             //$value = $obj->attribute( $key );
             $value = $obj->attributeContentToStore($key);
             $field_def = $fields[$key];
             if ($field_def['datatype'] == 'float' || $field_def['datatype'] == 'double') {
                 if ($value === null) {
                     $use_values_hash[$key] = 'NULL';
                 } else {
                     $use_values_hash[$key] = sprintf('%F', $value);
                 }
             } else {
                 if ($field_def['datatype'] == 'int' || $field_def['datatype'] == 'integer') {
                     if ($value === null) {
                         $use_values_hash[$key] = 'NULL';
                     } else {
                         $use_values_hash[$key] = sprintf('%d', $value);
                     }
                 } else {
                     // Note: for more colherence, we might use NULL for sql strings if the php value is NULL and not an empty sring
                     //       but to keep compatibility with ez db, where most string columns are "not null default ''",
                     //       and code feeding us a php null value without meaning it, we do not.
                     $use_values_hash[$key] = "'" . $db->escapeString($value) . "'";
                 }
             }
         }
         foreach ($doNotEscapeFields as $key) {
             $use_values_hash[$key] = $changedValueFields[$key];
         }
         $use_values = array();
         foreach ($use_field_names as $field) {
             $use_values[] = $use_values_hash[$field];
         }
         unset($use_values_hash);
         $value_text = implode(", ", $use_values);
         $sql = "INSERT INTO {$table} ({$field_text}) VALUES({$value_text})";
         $db->query($sql);
         if (isset($def["increment_key"]) && is_string($def["increment_key"]) && !($obj->attributeContentToStore($def["increment_key"]) > 0)) {
             $inc = $def["increment_key"];
             $id = $db->lastSerialID($table, $inc);
             if ($id !== false) {
                 $obj->setAttribute($inc, $id);
             }
         }
     } else {
         $use_fields = array_diff(array_keys($fields), array_merge($keys, $exclude_fields));
         if (count($use_fields) > 0) {
             // If we filter out some of the fields we need to intersect it with $use_fields
             if (is_array($fieldFilters)) {
                 $use_fields = array_intersect($use_fields, $fieldFilters);
             }
             $use_field_names = array();
             foreach ($use_fields as $key) {
                 if ($db->useShortNames() && is_array($fields[$key]) && array_key_exists('short_name', $fields[$key]) && strlen($fields[$key]['short_name']) > 0) {
                     $use_field_names[$key] = $fields[$key]['short_name'];
                 } else {
                     $use_field_names[$key] = $key;
                 }
             }
             $field_text = "";
             $field_text_len = 0;
             $i = 0;
             foreach ($use_fields as $key) {
                 //$value = $obj->attribute( $key );
                 $value = $obj->attributeContentToStore($key);
                 if ($fields[$key]['datatype'] == 'float' || $fields[$key]['datatype'] == 'double') {
                     if ($value === null) {
                         $field_text_entry = $use_field_names[$key] . '=NULL';
                     } else {
                         $field_text_entry = $use_field_names[$key] . "=" . sprintf('%F', $value);
                     }
                 } else {
                     if ($fields[$key]['datatype'] == 'int' || $fields[$key]['datatype'] == 'integer') {
                         if ($value === null) {
                             $field_text_entry = $use_field_names[$key] . '=NULL';
                         } else {
                             $field_text_entry = $use_field_names[$key] . "=" . sprintf('%d', $value);
                         }
                     } else {
                         if (in_array($use_field_names[$key], $doNotEscapeFields)) {
                             $field_text_entry = $use_field_names[$key] . "=" . $changedValueFields[$key];
                         } else {
                             $field_text_entry = $use_field_names[$key] . "='" . $db->escapeString($value) . "'";
                         }
                     }
                 }
                 $field_text_len += strlen($field_text_entry);
                 $needNewline = false;
                 if ($field_text_len > 60) {
                     $needNewline = true;
                     $field_text_len = 0;
                 }
                 if ($i > 0) {
                     $field_text .= "," . ($needNewline ? "\n    " : ' ');
                 }
                 $field_text .= $field_text_entry;
                 ++$i;
             }
             $cond_text = eZPersistentObject::conditionText($key_conds);
             $sql = "UPDATE {$table} SET {$field_text}{$cond_text}";
             $db->query($sql);
         }
     }
     $obj->setHasDirtyData(false);
 }
 /**
  * Creates an SQL query out of the different parameters and returns an array with the result.
  *
  * A full example:
  * <code>
  * $filter = array( 'id', 'name' );
  * $conds = array( 'type' => 5,
  *                 'size' => array( false, array( 200, 500 ) ) );
  * $sorts = array( 'name' => 'asc' );
  * $limit = array( 'offset' => 50, 'length' => 10 );
  * eZPersistentObject::fetchObjectList( $def, $filter, $conds, $sorts, $limit, true, false, null )
  * </code>
  *
  * Counting number of elements.
  * <code>
  * $custom = array( array( 'operation' => 'count( id )',
  *                         'name' => 'count' ) );
  * // Here $field_filters is set to an empty array, that way only count is used in fields
  * $rows = eZPersistentObject::fetchObjectList( $def, array(), null, null, null, false, false, $custom );
  * return $rows[0]['count'];
  * </code>
  *
  * Counting elements per type using grouping
  * <code>
  * $custom = array( array( 'operation' => 'count( id )',
  *                         'name' => 'count' ) );
  * $group = array( 'type' );
  * $rows = eZPersistentObject::fetchObjectList( $def, array(), null, null, null, false, $group, $custom );
  * return $rows[0]['count'];
  * </code>
  *
  * Example to fetch a result with custom conditions. The following example will fetch the attributes to
  * the contentobject with id 1 and add the contentobject.name in each attribute row with the array key
  * contentobject_name.
  * <code>
  * $objectDef = eZContentObject::definition();
  * $objectAttributeDef = eZContentObjectAttribute::definition();
  *
  * $fields = array();
  * $conds = array( $objectDef['name'] . '.id' => 1 );
  * $sorts = array( $objectAttributeDef['name'] . '.sort_key_string' => 'asc' );
  *
  * $limit = null;
  * $asObject = false;
  * $group = false;
  *
  * $customFields = array( $objectAttributeDef['name'] . '.*',
  *                        array( 'operation' => $objectDef['name'] . '.name',
  *                               'name' => 'contentobject_name' ) );
  *
  * $customTables = array( $objectDef['name'] );
  *
  * $languageCode = 'eng-GB';
  * $customConds = ' AND ' . $objectDef['name'] . '.current_version=' . $objectAttributeDef['name'] . '.version' .
  *                ' AND ' . $objectDef['name'] . '.id=' . $objectAttributeDef['name'] . '.contentobject_id' .
  *                ' AND ' . $objectAttributeDef['name'] . '.language_code=\'' . $languageCode . '\'';
  *
  * $rows = eZPersistentObject::fetchObjectList( $objectAttributeDef, $fields, $conds, $sorts, $limit, $asObject,
  *                                              $group, $customFields, $customTables, $customConds );
  * </code>
  * 
  * @param array $def                    A definition array of all fields, table name and sorting (see {@link eZPersistentObject::definition()} for more info)
  * @param array|null $field_filters     If defined determines the fields which are extracted (array of field names), if not all fields are fetched
  * @param array|null $conds             null for no special condition or an associative array of fields to filter on.
  *                                      Syntax is FIELD => CONDITION
  *                                      CONDITION can be one of:
  *                                      - Scalar value: Creates a condition where FIELD must match the value, e.g
  *                                      <code>array( 'id' => 5 )</code> generates <code>SQL id = 5</code>
  *                                      - Array with two scalar values: The first value is the match operator, the second is the scalar value
  *                                      <code>array( 'priority' => array( '>', 5 ) )</code> generates SQL <code>priority > 5</code>
  *                                      - Array with range: The first value is <code>false</code>, the second value is an array with start and stop of range in array
  *                                      <code>array( 'type' => array( false, array( 1, 5 ) ) )</code> generates SQL <code>type BETWEEN 1 AND 5</code>
  *                                      - Array with multiple elements: The first value is the field identifier, the second is an array with scalar values
  *                                       <code>array( 'id' => array( array( 1, 5, 7 ) ) )</code> generates SQL <code>id IN ( 1, 5, 7 )</code>
  * @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.
  *                                      Syntax is FIELD => DIRECTION.
  *                                      DIRECTION must either be 'asc' for ascending or 'desc' for descending.
  * @param array|null $limit             An associative array with limitiations, can contain
  *                                      - 'offset': Numerical value defining the start offset for the fetch
  *                                      - 'length': Numerical value defining the max number of items to return
  * @param bool $asObject                If true then it will return an array with objects, objects are created from class defined in $def.
  *                                      If falseit will just return the rows fetch from database.
  * @param array|null|bool $grouping     An array of fields to group by or null to use grouping in defintion $def.
  * @param null $custom_fields           Array of FIELD elements to add to SQL, can be used to perform custom fetches, e.g counts.
  *                                      FIELD is an associative array containing:
  *                                      - 'operation': A text field which is included in the field list
  *                                      - 'name': If present it adds <code>AS name</code> to the operation.
  * @param array|null $custom_tables     Array of additional tables
  * @param string|null $custom_conds     String with sql conditions for 'WHERE' clause.
  * @return eZPersistentObject[]|array|null                   An array of objects or rows, null on error
  */
 public static function fetchObjectList($def, $field_filters = null, $conds = null, $sorts = null, $limit = null, $asObject = true, $grouping = false, $custom_fields = null, $custom_tables = null, $custom_conds = null)
 {
     $db = eZDB::instance();
     $fields = $def["fields"];
     $tables = $def["name"];
     $class_name = $def["class_name"];
     if (is_array($custom_tables)) {
         foreach ($custom_tables as $custom_table) {
             $tables .= ', ' . $db->escapeString($custom_table);
         }
     }
     eZPersistentObject::replaceFieldsWithShortNames($db, $fields, $conds);
     if (is_array($field_filters)) {
         $field_array = array_unique(array_intersect($field_filters, array_keys($fields)));
     } else {
         $field_array = array_keys($fields);
     }
     if ($custom_fields !== null and is_array($custom_fields)) {
         foreach ($custom_fields as $custom_field) {
             if (is_array($custom_field)) {
                 $custom_text = $custom_field["operation"];
                 if (isset($custom_field["name"])) {
                     $field_name = $custom_field["name"];
                     $custom_text .= " AS {$field_name}";
                 }
             } else {
                 $custom_text = $custom_field;
             }
             $field_array[] = $custom_text;
         }
     }
     eZPersistentObject::replaceFieldsWithShortNames($db, $fields, $field_array);
     $field_text = '';
     $i = 0;
     foreach ($field_array as $field_item) {
         if ($i % 7 == 0 and $i > 0) {
             $field_text .= ",       ";
         } else {
             if ($i > 0) {
                 $field_text .= ', ';
             }
         }
         $field_text .= $field_item;
         ++$i;
     }
     $where_text = eZPersistentObject::conditionText($conds);
     if ($custom_conds) {
         $where_text .= $custom_conds;
     }
     $sort_text = "";
     if ($sorts !== false and (isset($def["sort"]) or is_array($sorts))) {
         $sort_list = array();
         if (is_array($sorts)) {
             $sort_list = $sorts;
         } else {
             if (isset($def['sort'])) {
                 $sort_list = $def["sort"];
             }
         }
         if (count($sort_list) > 0) {
             $sort_text = " ORDER BY ";
             $i = 0;
             foreach ($sort_list as $sort_id => $sort_type) {
                 if ($i > 0) {
                     $sort_text .= ", ";
                 }
                 if ($sort_type == "desc") {
                     $sort_text .= "{$sort_id} DESC";
                 } else {
                     $sort_text .= "{$sort_id} ASC";
                 }
                 ++$i;
             }
         }
     }
     $grouping_text = "";
     if (isset($def["grouping"]) or is_array($grouping) and count($grouping) > 0) {
         $grouping_list = isset($def["grouping"]) ? $def["grouping"] : array();
         if (is_array($grouping)) {
             $grouping_list = $grouping;
         }
         if (count($grouping_list) > 0) {
             $grouping_text = " GROUP BY ";
             $i = 0;
             foreach ($grouping_list as $grouping_id) {
                 if ($i > 0) {
                     $grouping_text .= ", ";
                 }
                 $grouping_text .= "{$grouping_id}";
                 ++$i;
             }
         }
     }
     $db_params = array();
     if (is_array($limit)) {
         if (isset($limit["offset"])) {
             $db_params["offset"] = $limit["offset"];
         }
         if (isset($limit['limit'])) {
             $db_params["limit"] = $limit["limit"];
         } else {
             $db_params["limit"] = $limit["length"];
         }
     }
     $sqlText = "SELECT {$field_text}\n                    FROM   {$tables}" . $where_text . $grouping_text . $sort_text;
     $rows = $db->arrayQuery($sqlText, $db_params);
     // Indicate that a DB error occured.
     if ($rows === false) {
         return null;
     }
     $objectList = eZPersistentObject::handleRows($rows, $class_name, $asObject);
     return $objectList;
 }