PageRenderTime 75ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/kernel/classes/ezpersistentobject.php

https://github.com/granitegreg/ezpublish
PHP | 1313 lines | 888 code | 79 blank | 346 comment | 151 complexity | ba075fc936642485492461be011bdd44 MD5 | raw file
Possible License(s): GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. //
  3. // Definition of eZPersistentObject class
  4. //
  5. // Created on: <16-Apr-2002 11:08:14 amos>
  6. //
  7. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  8. // SOFTWARE NAME: eZ Publish
  9. // SOFTWARE RELEASE: 4.1.x
  10. // COPYRIGHT NOTICE: Copyright (C) 1999-2011 eZ Systems AS
  11. // SOFTWARE LICENSE: GNU General Public License v2.0
  12. // NOTICE: >
  13. // This program is free software; you can redistribute it and/or
  14. // modify it under the terms of version 2.0 of the GNU General
  15. // Public License as published by the Free Software Foundation.
  16. //
  17. // This program is distributed in the hope that it will be useful,
  18. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. // GNU General Public License for more details.
  21. //
  22. // You should have received a copy of version 2.0 of the GNU General
  23. // Public License along with this program; if not, write to the Free
  24. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  25. // MA 02110-1301, USA.
  26. //
  27. //
  28. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  29. //
  30. /*!
  31. \defgroup eZKernel Kernel system
  32. */
  33. /*!
  34. \class eZPersistentObject ezpersistentobject.php
  35. \ingroup eZKernel
  36. \brief Allows for object persistence in a database
  37. Classes which stores simple types in databases should inherit from this
  38. and implement the definition() function. The class will then get initialization,
  39. fetching, listing, moving, storing and deleting for free as well as attribute
  40. access. The new class must have a constructor which takes one parameter called
  41. \c $row and pass that this constructor.
  42. \code
  43. class MyClass extends eZPersistentObject
  44. {
  45. function MyClass( $row )
  46. {
  47. $this->eZPersistentObject( $row );
  48. }
  49. }
  50. \endcode
  51. */
  52. class eZPersistentObject
  53. {
  54. /*!
  55. Initializes the object with the row \a $row. It will try to set
  56. each field taken from the database row. Calls fill to do the job.
  57. If the parameter \a $row is an integer it will try to fetch it
  58. from the database using it as the unique ID.
  59. */
  60. function eZPersistentObject( $row )
  61. {
  62. $this->PersistentDataDirty = false;
  63. if ( is_numeric( $row ) )
  64. $row = $this->fetch( $row, false );
  65. $this->fill( $row );
  66. }
  67. /*!
  68. Tries to fill in the data in the object by using the object definition
  69. which is returned by the function definition() and the database row
  70. data \a $row. Each field will be fetch from the definition and then
  71. use that fieldname to fetch from the row and set the data.
  72. */
  73. function fill( $row )
  74. {
  75. if ( !is_array( $row ) )
  76. return;
  77. $def = $this->definition();
  78. $fields = $def["fields"];
  79. $intersectList = array_intersect_key( $fields,
  80. $row );
  81. foreach ( $intersectList as $key => $item )
  82. {
  83. if ( is_array( $item ) )
  84. {
  85. $item = $item['name'];
  86. }
  87. $this->$item = $row[$key];
  88. }
  89. foreach( array_diff_key( $fields, $intersectList ) as $item )
  90. {
  91. if ( is_array( $item ) )
  92. {
  93. $item = $item['name'];
  94. }
  95. $this->$item = null;
  96. }
  97. }
  98. /*!
  99. \private
  100. \static
  101. For the given array \a fields treats its keys (for associative array) or
  102. values (for non-associative array) as table fields names and replaces them
  103. with short names (aliases) found in \a fieldDefs.
  104. */
  105. static function replaceFieldsWithShortNames( $db, $fieldDefs, &$fields )
  106. {
  107. if ( !$db->useShortNames() || !$fields )
  108. return;
  109. $short_fields_names = array();
  110. foreach ( $fields as $key => $val )
  111. {
  112. if( is_numeric( $key ) ) // $fields is not an associative array
  113. {
  114. if ( array_key_exists( $val, $fieldDefs ) &&
  115. array_key_exists( 'short_name', $fieldDefs[$val] ) )
  116. {
  117. $short_fields_names[$key] = $fieldDefs[$val]['short_name'];
  118. }
  119. else
  120. $short_fields_names[$key] = $val;
  121. }
  122. else // $fields is an associative array
  123. {
  124. if ( array_key_exists( $key, $fieldDefs ) &&
  125. array_key_exists( 'short_name', $fieldDefs[$key] ) )
  126. {
  127. $newkey = $fieldDefs[$key]['short_name'];
  128. }
  129. else
  130. $newkey = $key;
  131. $short_fields_names[$newkey] = $val;
  132. }
  133. }
  134. $fields = $short_fields_names;
  135. }
  136. /*!
  137. Fetches the number of rows by using the object definition.
  138. Uses fetchObjectList for the actual SQL handling.
  139. See fetchObjectList() for a full description of the input parameters.
  140. */
  141. static function count( $def, $conds = null, $field = null )
  142. {
  143. if ( !isset( $field ) )
  144. {
  145. $field = '*';
  146. }
  147. $customFields = array( array( 'operation' => 'COUNT( ' . $field . ' )', 'name' => 'row_count' ) );
  148. $rows = eZPersistentObject::fetchObjectList( $def, array(), $conds, array(), null, false, false, $customFields );
  149. return $rows[0]['row_count'];
  150. }
  151. /*!
  152. Creates an SQL query out of the different parameters and returns an object with the result.
  153. If \a $asObject is true the returned item is an object otherwise a db row.
  154. Uses fetchObjectList for the actual SQL handling and just returns the first row item.
  155. See fetchObjectList() for a full description of the input parameters.
  156. */
  157. static function fetchObject( /*! The definition structure */
  158. $def,
  159. /*! If defined determines the fields which are extracted, if not all fields are fetched */
  160. $field_filters,
  161. /*! An array of conditions which determines which rows are fetched*/
  162. $conds,
  163. $asObject = true,
  164. /*! An array of elements to group by */
  165. $grouping = null,
  166. /*! An array of extra fields to fetch, each field may be a SQL operation */
  167. $custom_fields = null )
  168. {
  169. $rows = eZPersistentObject::fetchObjectList( $def, $field_filters, $conds,
  170. array(), null, $asObject,
  171. $grouping, $custom_fields );
  172. if ( $rows )
  173. return $rows[0];
  174. return null;
  175. }
  176. /*!
  177. Removes the object from the database, it will use the keys in the object
  178. definition to figure out which table row should be removed unless \a $conditions
  179. is defined as an array with fieldnames.
  180. It uses removeObject to do the real job and passes the object defintion,
  181. conditions and extra conditions \a $extraConditions to this function.
  182. \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  183. the calls within a db transaction; thus within db->begin and db->commit.
  184. */
  185. function remove( $conditions = null, $extraConditions = null )
  186. {
  187. $def = $this->definition();
  188. $keys = $def["keys"];
  189. if ( !is_array( $conditions ) )
  190. {
  191. $conditions = array();
  192. foreach ( $keys as $key )
  193. {
  194. $value = $this->attribute( $key );
  195. $conditions[$key] = $value;
  196. }
  197. }
  198. eZPersistentObject::removeObject( $def, $conditions, $extraConditions );
  199. }
  200. /*!
  201. Deletes the object from the table defined in \a $def with conditions \a $conditions
  202. and extra conditions \a $extraConditions. The extra conditions will either be
  203. appended to the existing conditions or overwrite existing fields.
  204. Uses conditionText() to create the condition SQL.
  205. \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  206. the calls within a db transaction; thus within db->begin and db->commit.
  207. */
  208. static function removeObject( $def, $conditions = null, $extraConditions = null )
  209. {
  210. $db = eZDB::instance();
  211. $table = $def["name"];
  212. if ( is_array( $extraConditions ) )
  213. {
  214. foreach ( $extraConditions as $key => $cond )
  215. {
  216. $conditions[$key] = $cond;
  217. }
  218. }
  219. /* substitute fields mentioned the conditions whith their
  220. short names (if any)
  221. */
  222. $fields = $def['fields'];
  223. eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $conditions );
  224. $cond_text = eZPersistentObject::conditionText( $conditions );
  225. $db->query( "DELETE FROM $table $cond_text" );
  226. }
  227. /*!
  228. Stores the object in the database, uses storeObject() to do the actual
  229. job and passes \a $fieldFilters to it.
  230. \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  231. the calls within a db transaction; thus within db->begin and db->commit.
  232. */
  233. function store( $fieldFilters = null )
  234. {
  235. eZPersistentObject::storeObject( $this, $fieldFilters );
  236. }
  237. /*!
  238. Makes sure data is stored if the data is considered dirty.
  239. \sa hasDirtyData
  240. \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  241. the calls within a db transaction; thus within db->begin and db->commit.
  242. */
  243. function sync( $fieldFilters = null )
  244. {
  245. if ( $this->hasDirtyData() )
  246. $this->store( $fieldFilters );
  247. }
  248. /*!
  249. \private
  250. Stores the data in \a $obj to database.
  251. \param fieldFilters If specified only certain fields will be stored.
  252. \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  253. the calls within a db transaction; thus within db->begin and db->commit.
  254. */
  255. static function storeObject( $obj, $fieldFilters = null )
  256. {
  257. $db = eZDB::instance();
  258. $useFieldFilters = ( isset( $fieldFilters ) && is_array( $fieldFilters ) && $fieldFilters );
  259. $def = $obj->definition();
  260. $fields = $def["fields"];
  261. $keys = $def["keys"];
  262. $table = $def["name"];
  263. $relations = isset( $def["relations"] ) ? $def["relations"] : null;
  264. $insert_object = false;
  265. $exclude_fields = array();
  266. foreach ( $keys as $key )
  267. {
  268. $value = $obj->attribute( $key );
  269. if ( $value === null )
  270. {
  271. $insert_object = true;
  272. $exclude_fields[] = $key;
  273. }
  274. }
  275. if ( $useFieldFilters )
  276. $insert_object = false;
  277. $use_fields = array_diff( array_keys( $fields ), $exclude_fields );
  278. // If we filter out some of the fields we need to intersect it with $use_fields
  279. if ( is_array( $fieldFilters ) )
  280. $use_fields = array_intersect( $use_fields, $fieldFilters );
  281. $doNotEscapeFields = array();
  282. $changedValueFields = array();
  283. $numericDataTypes = array( 'integer', 'float', 'double' );
  284. foreach ( $use_fields as $field_name )
  285. {
  286. $field_def = $fields[$field_name];
  287. $value = $obj->attribute( $field_name );
  288. if ( $value === null )
  289. {
  290. if ( ! is_array( $field_def ) )
  291. {
  292. $exclude_fields[] = $field_name;
  293. }
  294. else
  295. {
  296. if ( array_key_exists( 'default', $field_def ) &&
  297. ( $field_def['default'] !== null ||
  298. ( $field_name == 'data_int' &&
  299. array_key_exists( 'required', $field_def ) &&
  300. $field_def[ 'required' ] == false ) ) )
  301. {
  302. $obj->setAttribute( $field_name, $field_def[ 'default' ] );
  303. }
  304. else
  305. {
  306. //if ( in_array( $field_def['datatype'], $numericDataTypes )
  307. $exclude_fields[] = $field_name;
  308. }
  309. }
  310. }
  311. if ( strlen( $value ) == 0 &&
  312. is_array( $field_def ) &&
  313. in_array( $field_def['datatype'], $numericDataTypes ) &&
  314. array_key_exists( 'default', $field_def ) &&
  315. ( $field_def[ 'default' ] === null || is_numeric( $field_def[ 'default' ] ) ) )
  316. {
  317. $obj->setAttribute( $field_name, $field_def[ 'default' ] );
  318. }
  319. if ( $value !== null &&
  320. $field_def['datatype'] === 'string' &&
  321. array_key_exists( 'max_length', $field_def ) &&
  322. $field_def['max_length'] > 0 &&
  323. strlen( $value ) > $field_def['max_length'] )
  324. {
  325. $obj->setAttribute( $field_name, substr( $value, 0, $field_def['max_length'] ) );
  326. eZDebug::writeDebug( $value, "truncation of $field_name to max_length=". $field_def['max_length'] );
  327. }
  328. $bindDataTypes = array( 'text' );
  329. if ( $db->bindingType() != eZDBInterface::BINDING_NO &&
  330. strlen( $value ) > 2000 &&
  331. is_array( $field_def ) &&
  332. in_array( $field_def['datatype'], $bindDataTypes )
  333. )
  334. {
  335. $boundValue = $db->bindVariable( $value, $field_def );
  336. // $obj->setAttribute( $field_name, $value );
  337. $doNotEscapeFields[] = $field_name;
  338. $changedValueFields[$field_name] = $boundValue;
  339. }
  340. }
  341. $key_conds = array();
  342. foreach ( $keys as $key )
  343. {
  344. $value = $obj->attribute( $key );
  345. $key_conds[$key] = $value;
  346. }
  347. unset( $value );
  348. $important_keys = $keys;
  349. if ( is_array( $relations ) )
  350. {
  351. // $important_keys = array();
  352. foreach( $relations as $relation => $relation_data )
  353. {
  354. if ( !in_array( $relation, $keys ) )
  355. $important_keys[] = $relation;
  356. }
  357. }
  358. if ( count( $important_keys ) == 0 && !$useFieldFilters )
  359. {
  360. $insert_object = true;
  361. }
  362. else if ( !$insert_object )
  363. {
  364. $rows = eZPersistentObject::fetchObjectList( $def, $keys, $key_conds,
  365. array(), null, false,
  366. null, null );
  367. if ( count( $rows ) == 0 )
  368. {
  369. /* If we only want to update some fields in a record
  370. * and that records does not exist, then we should do nothing, only return.
  371. */
  372. if ( $useFieldFilters )
  373. return;
  374. $insert_object = true;
  375. }
  376. }
  377. if ( $insert_object )
  378. {
  379. // Note: When inserting we cannot hone the $fieldFilters parameters
  380. $use_fields = array_diff( array_keys( $fields ), $exclude_fields );
  381. $use_field_names = $use_fields;
  382. if ( $db->useShortNames() )
  383. {
  384. $use_short_field_names = $use_field_names;
  385. eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $use_short_field_names );
  386. $field_text = implode( ', ', $use_short_field_names );
  387. unset( $use_short_field_names );
  388. }
  389. else
  390. $field_text = implode( ', ', $use_field_names );
  391. $use_values_hash = array();
  392. $escapeFields = array_diff( $use_fields, $doNotEscapeFields );
  393. foreach ( $escapeFields as $key )
  394. {
  395. $value = $obj->attribute( $key );
  396. $field_def = $fields[$key];
  397. if ( $field_def['datatype'] == 'float' || $field_def['datatype'] == 'double' )
  398. {
  399. if ( $value === null )
  400. {
  401. $use_values_hash[$key] = 'NULL';
  402. }
  403. else
  404. {
  405. $use_values_hash[$key] = sprintf( '%F', $value );
  406. }
  407. }
  408. else if ( $field_def['datatype'] == 'int' || $field_def['datatype'] == 'integer' )
  409. {
  410. if ( $value === null )
  411. {
  412. $use_values_hash[$key] = 'NULL';
  413. }
  414. else
  415. {
  416. $use_values_hash[$key] = sprintf( '%d', $value );
  417. }
  418. }
  419. else
  420. {
  421. // Note: for more colherence, we might use NULL for sql strings if the php value is NULL and not an empty sring
  422. // but to keep compatibility with ez db, where most string columns are "not null default ''",
  423. // and code feeding us a php null value without meaning it, we do not.
  424. $use_values_hash[$key] = "'" . $db->escapeString( $value ) . "'";
  425. }
  426. }
  427. foreach ( $doNotEscapeFields as $key )
  428. {
  429. $use_values_hash[$key] = $changedValueFields[$key];
  430. }
  431. $use_values = array();
  432. foreach ( $use_field_names as $field )
  433. $use_values[] = $use_values_hash[$field];
  434. unset( $use_values_hash );
  435. $value_text = implode( ", ", $use_values );
  436. $sql = "INSERT INTO $table ($field_text) VALUES($value_text)";
  437. $db->query( $sql );
  438. if ( isset( $def["increment_key"] ) &&
  439. is_string( $def["increment_key"] ) &&
  440. !( $obj->attribute( $def["increment_key"] ) > 0 ) )
  441. {
  442. $inc = $def["increment_key"];
  443. $id = $db->lastSerialID( $table, $inc );
  444. if ( $id !== false )
  445. $obj->setAttribute( $inc, $id );
  446. }
  447. }
  448. else
  449. {
  450. $use_fields = array_diff( array_keys( $fields ), array_merge( $keys, $exclude_fields ) );
  451. if ( count( $use_fields ) > 0 )
  452. {
  453. // If we filter out some of the fields we need to intersect it with $use_fields
  454. if ( is_array( $fieldFilters ) )
  455. $use_fields = array_intersect( $use_fields, $fieldFilters );
  456. $use_field_names = array();
  457. foreach ( $use_fields as $key )
  458. {
  459. if ( $db->useShortNames() && is_array( $fields[$key] ) && array_key_exists( 'short_name', $fields[$key] ) && strlen( $fields[$key]['short_name'] ) > 0 )
  460. $use_field_names[$key] = $fields[$key]['short_name'];
  461. else
  462. $use_field_names[$key] = $key;
  463. }
  464. $field_text = "";
  465. $field_text_len = 0;
  466. $i = 0;
  467. foreach ( $use_fields as $key )
  468. {
  469. $value = $obj->attribute( $key );
  470. if ( $fields[$key]['datatype'] == 'float' || $fields[$key]['datatype'] == 'double' )
  471. {
  472. if ( $value === null )
  473. $field_text_entry = $use_field_names[$key] . '=NULL';
  474. else
  475. $field_text_entry = $use_field_names[$key] . "=" . sprintf( '%F', $value );
  476. }
  477. else if ($fields[$key]['datatype'] == 'int' || $fields[$key]['datatype'] == 'integer' )
  478. {
  479. if ( $value === null )
  480. $field_text_entry = $use_field_names[$key] . '=NULL';
  481. else
  482. $field_text_entry = $use_field_names[$key] . "=" . sprintf( '%d', $value );
  483. }
  484. else if ( in_array( $use_field_names[$key], $doNotEscapeFields ) )
  485. {
  486. $field_text_entry = $use_field_names[$key] . "=" . $changedValueFields[$key];
  487. }
  488. else
  489. {
  490. $field_text_entry = $use_field_names[$key] . "='" . $db->escapeString( $value ) . "'";
  491. }
  492. $field_text_len += strlen( $field_text_entry );
  493. $needNewline = false;
  494. if ( $field_text_len > 60 )
  495. {
  496. $needNewline = true;
  497. $field_text_len = 0;
  498. }
  499. if ( $i > 0 )
  500. $field_text .= "," . ($needNewline ? "\n " : ' ');
  501. $field_text .= $field_text_entry;
  502. ++$i;
  503. }
  504. $cond_text = eZPersistentObject::conditionText( $key_conds );
  505. $sql = "UPDATE $table SET $field_text$cond_text";
  506. $db->query( $sql );
  507. }
  508. }
  509. $obj->setHasDirtyData( false );
  510. }
  511. /*!
  512. Calls conditionTextByRow with an empty row and \a $conditions.
  513. */
  514. static function conditionText( $conditions )
  515. {
  516. return eZPersistentObject::conditionTextByRow( $conditions, null );
  517. }
  518. /*!
  519. Generates an SQL sentence from the conditions \a $conditions and row data \a $row.
  520. If \a $row is empty (null) it uses the condition data instead of row data.
  521. */
  522. static function conditionTextByRow( $conditions, $row )
  523. {
  524. $db = eZDB::instance();
  525. $where_text = "";
  526. if ( is_array( $conditions ) and
  527. count( $conditions ) > 0 )
  528. {
  529. $where_text = " WHERE ";
  530. $i = 0;
  531. foreach ( $conditions as $id => $cond )
  532. {
  533. if ( $i > 0 )
  534. $where_text .= " AND ";
  535. if ( is_array( $row ) )
  536. {
  537. $where_text .= $cond . "='" . $db->escapeString( $row[$cond] ) . "'";
  538. }
  539. else
  540. {
  541. if ( is_array( $cond ) )
  542. {
  543. if ( is_array( $cond[0] ) )
  544. {
  545. $where_text .= $id . ' IN ( ';
  546. $j = 0;
  547. foreach ( $cond[0] as $value )
  548. {
  549. if ( $j > 0 )
  550. $where_text .= ", ";
  551. $where_text .= "'" . $db->escapeString( $value ) . "'";
  552. ++$j;
  553. }
  554. $where_text .= ' ) ';
  555. }
  556. else if ( is_array( $cond[1] ) )
  557. {
  558. $range = $cond[1];
  559. $where_text .= "$id BETWEEN '" . $db->escapeString( $range[0] ) . "' AND '" . $db->escapeString( $range[1] ) . "'";
  560. }
  561. else
  562. {
  563. switch ( $cond[0] )
  564. {
  565. case '>=':
  566. case '<=':
  567. case '<':
  568. case '>':
  569. case '=':
  570. case '<>':
  571. case '!=':
  572. case 'like':
  573. {
  574. $where_text .= $db->escapeString( $id ) . " " . $cond[0] . " '" . $db->escapeString( $cond[1] ) . "'";
  575. } break;
  576. default:
  577. {
  578. eZDebug::writeError( "Conditional operator '$cond[0]' is not supported.", __METHOD__ );
  579. } break;
  580. }
  581. }
  582. }
  583. else
  584. $where_text .= $db->escapeString( $id ) . "='" . $db->escapeString( $cond ) . "'";
  585. }
  586. ++$i;
  587. }
  588. }
  589. return $where_text;
  590. }
  591. /*!
  592. Creates an SQL query out of the different parameters and returns an array with the result.
  593. If \a $asObject is true the array contains objects otherwise a db row.
  594. \param $def A definition array of all fields, table name and sorting
  595. \param $field_filters If defined determines the fields which are extracted (array of field names), if not all fields are fetched
  596. \param $conds \c null for no special condition or an associative array of fields to filter on.
  597. Syntax is \c FIELD => \c CONDITION, \c CONDITION can be one of:
  598. - Scalar value - Creates a condition where \c FIELD must match the value, e.g
  599. \code array( 'id' => 5 ) \endcode
  600. generates SQL
  601. \code id = 5 \endcode
  602. - Array with two scalar values - Element \c 0 is the match operator and element \c 1 is the scalar value
  603. \code array( 'priority' => array( '>', 5 ) ) \endcode
  604. generates SQL
  605. \code priority > 5 \endcode
  606. - Array with range - Element \c 1 is an array with start and stop of range in array
  607. \code array( 'type' => array( false, array( 1, 5 ) ) ) \endcode
  608. generates SQL
  609. \code type BETWEEN 1 AND 5 \endcode
  610. - Array with multiple elements - Element \c 0 is an array with scalar values
  611. \code array( 'id' => array( array( 1, 5, 7 ) ) ) \endcode
  612. generates SQL
  613. \code id IN ( 1, 5, 7 ) \endcode
  614. \param $sorts An associative array of sorting conditions, if set to \c false ignores settings in \a $def,
  615. if set to \c null uses settingss in \a $def.
  616. Syntax is \c FIELD => \c DIRECTION. \c DIRECTION must either be string \c 'asc'
  617. for ascending or \c 'desc' for descending.
  618. \param $limit An associative array with limitiations, can contain
  619. - offset - Numerical value defining the start offset for the fetch
  620. - length - Numerical value defining the max number of items to return
  621. \param $asObject If \c true then it will return an array with objects, objects are created from class defined in \a $def.
  622. If \c false it will just return the rows fetch from database.
  623. \param $grouping An array of fields to group by or \c null to use grouping in defintion \a $def.
  624. \param $custom_fields Array of \c FIELD elements to add to SQL, can be used to perform custom fetches, e.g counts.
  625. \c FIELD is an associative array containing:
  626. - operation - A text field which is included in the field list
  627. - name - If present it adds 'AS name' to the operation.
  628. \param $custom_tables Array of additional tables.
  629. \param $custom_conds String with sql conditions for 'WHERE' clause.
  630. A full example:
  631. \code
  632. $filter = array( 'id', 'name' );
  633. $conds = array( 'type' => 5,
  634. 'size' => array( false, array( 200, 500 ) ) );
  635. $sorts = array( 'name' => 'asc' );
  636. $limit = array( 'offset' => 50, 'length' => 10 );
  637. eZPersistentObject::fetchObjectList( $def, $filter, $conds, $sorts, $limit, true, false, null )
  638. \endcode
  639. Counting number of elements.
  640. \code
  641. $custom = array( array( 'operation' => 'count( id )',
  642. 'name' => 'count' ) );
  643. // Here $field_filters is set to an empty array, that way only count is used in fields
  644. $rows = eZPersistentObject::fetchObjectList( $def, array(), null, null, null, false, false, $custom );
  645. return $rows[0]['count'];
  646. \endcode
  647. Counting elements per type using grouping
  648. \code
  649. $custom = array( array( 'operation' => 'count( id )',
  650. 'name' => 'count' ) );
  651. $group = array( 'type' );
  652. $rows = eZPersistentObject::fetchObjectList( $def, array(), null, null, null, false, $group, $custom );
  653. return $rows[0]['count'];
  654. \endcode
  655. Example to fetch a result with custom conditions. The following example will fetch the attributes to
  656. the contentobject with id 1 and add the contentobject.name in each attribute row with the array key
  657. contentobject_name.
  658. \code
  659. $objectDef = eZContentObject::definition();
  660. $objectAttributeDef = eZContentObjectAttribute::definition();
  661. $fields = array();
  662. $conds = array( $objectDef['name'] . '.id' => 1 );
  663. $sorts = array( $objectAttributeDef['name'] . '.sort_key_string' => 'asc' );
  664. $limit = null;
  665. $asObject = false;
  666. $group = false;
  667. $customFields = array( $objectAttributeDef['name'] . '.*',
  668. array( 'operation' => $objectDef['name'] . '.name',
  669. 'name' => 'contentobject_name' ) );
  670. $customTables = array( $objectDef['name'] );
  671. $languageCode = 'eng-GB';
  672. $customConds = ' AND ' . $objectDef['name'] . '.current_version=' . $objectAttributeDef['name'] . '.version' .
  673. ' AND ' . $objectDef['name'] . '.id=' . $objectAttributeDef['name'] . '.contentobject_id' .
  674. ' AND ' . $objectAttributeDef['name'] . '.language_code=\'' . $languageCode . '\'';
  675. $rows = eZPersistentObject::fetchObjectList( $objectAttributeDef, $fields, $conds, $sorts, $limit, $asObject,
  676. $group, $customFields, $customTables, $customConds );
  677. \endcode
  678. */
  679. static function fetchObjectList( $def,
  680. $field_filters = null,
  681. $conds = null,
  682. $sorts = null,
  683. $limit = null,
  684. $asObject = true,
  685. $grouping = false,
  686. $custom_fields = null,
  687. $custom_tables = null,
  688. $custom_conds = null )
  689. {
  690. $db = eZDB::instance();
  691. $fields = $def["fields"];
  692. $tables = $def["name"];
  693. $class_name = $def["class_name"];
  694. if ( is_array( $custom_tables ) )
  695. {
  696. foreach( $custom_tables as $custom_table )
  697. $tables .= ', ' . $db->escapeString( $custom_table );
  698. }
  699. eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $conds );
  700. if ( is_array( $field_filters ) )
  701. $field_array = array_unique( array_intersect(
  702. $field_filters, array_keys( $fields ) ) );
  703. else
  704. $field_array = array_keys( $fields );
  705. if ( $custom_fields !== null and is_array( $custom_fields ) )
  706. {
  707. foreach( $custom_fields as $custom_field )
  708. {
  709. if ( is_array( $custom_field ) )
  710. {
  711. $custom_text = $custom_field["operation"];
  712. if ( isset( $custom_field["name"] ) )
  713. {
  714. $field_name = $custom_field["name"];
  715. $custom_text .= " AS $field_name";
  716. }
  717. }
  718. else
  719. {
  720. $custom_text = $custom_field;
  721. }
  722. $field_array[] = $custom_text;
  723. }
  724. }
  725. eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $field_array );
  726. $field_text = '';
  727. $i = 0;
  728. foreach ( $field_array as $field_item )
  729. {
  730. if ( ( $i % 7 ) == 0 and
  731. $i > 0 )
  732. $field_text .= ", ";
  733. else if ( $i > 0 )
  734. $field_text .= ', ';
  735. $field_text .= $field_item;
  736. ++$i;
  737. }
  738. $where_text = eZPersistentObject::conditionText( $conds );
  739. if ( $custom_conds )
  740. $where_text .= $custom_conds;
  741. $sort_text = "";
  742. if ( $sorts !== false and ( isset( $def["sort"] ) or is_array( $sorts ) ) )
  743. {
  744. $sort_list = array();
  745. if ( is_array( $sorts ) )
  746. {
  747. $sort_list = $sorts;
  748. }
  749. else if ( isset( $def['sort'] ) )
  750. {
  751. $sort_list = $def["sort"];
  752. }
  753. if ( count( $sort_list ) > 0 )
  754. {
  755. $sort_text = " ORDER BY ";
  756. $i = 0;
  757. foreach ( $sort_list as $sort_id => $sort_type )
  758. {
  759. if ( $i > 0 )
  760. $sort_text .= ", ";
  761. if ( $sort_type == "desc" )
  762. $sort_text .= "$sort_id DESC";
  763. else
  764. $sort_text .= "$sort_id ASC";
  765. ++$i;
  766. }
  767. }
  768. }
  769. $grouping_text = "";
  770. if ( isset( $def["grouping"] ) or ( is_array( $grouping ) and count( $grouping ) > 0 ) )
  771. {
  772. $grouping_list = $def["grouping"];
  773. if ( is_array( $grouping ) )
  774. $grouping_list = $grouping;
  775. if ( count( $grouping_list ) > 0 )
  776. {
  777. $grouping_text = " GROUP BY ";
  778. $i = 0;
  779. foreach ( $grouping_list as $grouping_id )
  780. {
  781. if ( $i > 0 )
  782. $grouping_text .= ", ";
  783. $grouping_text .= "$grouping_id";
  784. ++$i;
  785. }
  786. }
  787. }
  788. $db_params = array();
  789. if ( is_array( $limit ) )
  790. {
  791. if ( isset( $limit["offset"] ) )
  792. {
  793. $db_params["offset"] = $limit["offset"];
  794. }
  795. if ( isset( $limit['limit'] ) )
  796. {
  797. $db_params["limit"] = $limit["limit"];
  798. }
  799. else
  800. {
  801. $db_params["limit"] = $limit["length"];
  802. }
  803. }
  804. $sqlText = "SELECT $field_text
  805. FROM $tables" .
  806. $where_text .
  807. $grouping_text .
  808. $sort_text;
  809. $rows = $db->arrayQuery( $sqlText,
  810. $db_params );
  811. // Indicate that a DB error occured.
  812. if ( $rows === false )
  813. return null;
  814. $objectList = eZPersistentObject::handleRows( $rows, $class_name, $asObject );
  815. return $objectList;
  816. }
  817. /*!
  818. Creates PHP objects out of the database rows \a $rows.
  819. Each object is created from class \$ class_name and is passed
  820. as a row array as parameter.
  821. \param $asObject If \c true then objects will be created,
  822. if not it just returns \a $rows as it is.
  823. */
  824. static function handleRows( $rows, $class_name, $asObject )
  825. {
  826. if ( $asObject )
  827. {
  828. $objects = array();
  829. if ( is_array( $rows ) )
  830. {
  831. foreach ( $rows as $row )
  832. {
  833. $objects[] = new $class_name( $row );
  834. }
  835. }
  836. return $objects;
  837. }
  838. else
  839. return $rows;
  840. }
  841. /*!
  842. Sets row id \a $id2 to have the placement of row id \a $id1.
  843. \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  844. the calls within a db transaction; thus within db->begin and db->commit.
  845. */
  846. static function swapRow( $table, $keys, $order_id, $rows, $id1, $id2 )
  847. {
  848. $db = eZDB::instance();
  849. $text = $order_id . "='" . $db->escapeString( $rows[$id1][$order_id] ) . "' WHERE ";
  850. $i = 0;
  851. foreach ( $keys as $key )
  852. {
  853. if ( $i > 0 )
  854. $text .= " AND ";
  855. $text .= $key . "='" . $db->escapeString( $rows[$id2][$key] ) . "'";
  856. ++$i;
  857. }
  858. return "UPDATE $table SET $text";
  859. }
  860. /*!
  861. Returns an order value which can be used for new items in table, for instance placement.
  862. Uses \a $def, \a $orderField and \a $conditions to figure out the currently maximum order value
  863. and returns one that is larger.
  864. */
  865. static function newObjectOrder( $def, $orderField, $conditions )
  866. {
  867. $db = eZDB::instance();
  868. $table = $def["name"];
  869. $keys = $def["keys"];
  870. $cond_text = eZPersistentObject::conditionText( $conditions );
  871. $rows = $db->arrayQuery( "SELECT MAX($orderField) AS $orderField FROM $table $cond_text" );
  872. if ( count( $rows ) > 0 and isset( $rows[0][$orderField] ) )
  873. return $rows[0][$orderField] + 1;
  874. else
  875. return 1;
  876. }
  877. /*!
  878. Moves a row in a database table. \a $def is the object definition.
  879. Uses \a $orderField to determine the order of objects in a table, usually this
  880. is a placement of some kind. It uses this order field to figure out how move
  881. the row, the row is either swapped with another row which is either above or
  882. below according to whether \a $down is true or false, or it is swapped
  883. with the first item or the last item depending on whether this row is first or last.
  884. Uses \a $conditions to figure out unique rows.
  885. \sa swapRow
  886. \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  887. the calls within a db transaction; thus within db->begin and db->commit.
  888. */
  889. static function reorderObject( $def,
  890. /*! Associative array with one element, the key is the order id and values is order value. */
  891. $orderField,
  892. $conditions,
  893. $down = true )
  894. {
  895. $db = eZDB::instance();
  896. $table = $def["name"];
  897. $keys = $def["keys"];
  898. reset( $orderField );
  899. $order_id = key( $orderField );
  900. $order_val = $orderField[$order_id];
  901. if ( $down )
  902. {
  903. $order_operator = ">=";
  904. $order_type = "asc";
  905. $order_add = -1;
  906. }
  907. else
  908. {
  909. $order_operator = "<=";
  910. $order_type = "desc";
  911. $order_add = 1;
  912. }
  913. $fields = array_merge( $keys, array( $order_id ) );
  914. $rows = eZPersistentObject::fetchObjectList( $def,
  915. $fields,
  916. array_merge( $conditions,
  917. array( $order_id => array( $order_operator,
  918. $order_val ) ) ),
  919. array( $order_id => $order_type ),
  920. array( "length" => 2 ),
  921. false );
  922. if ( count( $rows ) == 2 )
  923. {
  924. $swapSQL1 = eZPersistentObject::swapRow( $table, $keys, $order_id, $rows, 1, 0 );
  925. $swapSQL2 = eZPersistentObject::swapRow( $table, $keys, $order_id, $rows, 0, 1 );
  926. $db->begin();
  927. $db->query( $swapSQL1 );
  928. $db->query( $swapSQL2 );
  929. $db->commit();
  930. }
  931. else
  932. {
  933. $tmp = eZPersistentObject::fetchObjectList( $def,
  934. $fields,
  935. $conditions,
  936. array( $order_id => $order_type ),
  937. array( "length" => 1 ),
  938. false );
  939. $where_text = eZPersistentObject::conditionTextByRow( $keys, $rows[0] );
  940. $db->query( "UPDATE $table SET $order_id='" . ( $tmp[0][$order_id] + $order_add ) .
  941. "'$where_text" );
  942. }
  943. }
  944. /*!
  945. \return the definition for the object, the default implementation
  946. is to return an empty array. It's upto each inheriting class
  947. to return a proper definition array.
  948. The definition array is an associative array consists of these keys:
  949. - fields - an associative array of fields which defines which database field (the key) is to fetched and how they map
  950. to object member variables (the value).
  951. - keys - an array of fields which is used for uniquely identifying the object in the table.
  952. - function_attributes - an associative array of attributes which maps to member functions, used for fetching data with functions.
  953. - set_functions - an associative array of attributes which maps to member functions, used for setting data with functions.
  954. - increment_key - the field which is incremented on table inserts.
  955. - class_name - the classname which is used for instantiating new objecs when fetching from the
  956. database.
  957. - sort - an associative array which defines the default sorting of lists, the key is the table field while the value
  958. is the sorting method which is either \c asc or \c desc.
  959. - name - the name of the database table
  960. Example:
  961. \code
  962. static function definition()
  963. {
  964. return array( "fields" => array( "id" => "ID",
  965. "version" => "Version",
  966. "name" => "Name" ),
  967. "keys" => array( "id", "version" ),
  968. "function_attributes" => array( "current" => "currentVersion",
  969. "class_name" => "className" ),
  970. "increment_key" => "id",
  971. "class_name" => "eZContentClass",
  972. "sort" => array( "id" => "asc" ),
  973. "name" => "ezcontentclass" );
  974. }
  975. \endcode
  976. */
  977. static function definition()
  978. {
  979. return array();
  980. }
  981. static function escapeArray( $array )
  982. {
  983. $db = eZDB::instance();
  984. $out = array();
  985. foreach( $array as $key => $value )
  986. {
  987. if ( is_array( $value ) )
  988. {
  989. $tmp = array();
  990. foreach( $value as $valueItem )
  991. {
  992. $tmp[] = $db->escapeString( $valueItem );
  993. }
  994. $out[$key] = $tmp;
  995. unset( $tmp );
  996. }
  997. else
  998. $out[$key] = $db->escapeString( $value );
  999. }
  1000. return $out;
  1001. }
  1002. /*!
  1003. \note Transaction unsafe. If you call several transaction unsafe methods you must enclose
  1004. the calls within a db transaction; thus within db->begin and db->commit.
  1005. */
  1006. static function updateObjectList( $parameters )
  1007. {
  1008. $db = eZDB::instance();
  1009. $def = $parameters['definition'];
  1010. $table = $def['name'];
  1011. $fields = $def['fields'];
  1012. $keys = $def['keys'];
  1013. $updateFields = $parameters['update_fields'];
  1014. $conditions = $parameters['conditions'];
  1015. $query = "UPDATE $table SET ";
  1016. $i = 0;
  1017. $valueBound = false;
  1018. foreach( $updateFields as $field => $value )
  1019. {
  1020. $fieldDef = $fields[ $field ];
  1021. $numericDataTypes = array( 'integer', 'float', 'double' );
  1022. if ( strlen( $value ) == 0 &&
  1023. is_array( $fieldDef ) &&
  1024. in_array( $fieldDef['datatype'], $numericDataTypes ) &&
  1025. array_key_exists( 'default', $fieldDef ) &&
  1026. $fieldDef[ 'default' ] !== null )
  1027. {
  1028. $value = $fieldDef[ 'default' ];
  1029. }
  1030. $bindDataTypes = array( 'text' );
  1031. if ( $db->bindingType() != eZDBInterface::BINDING_NO &&
  1032. strlen( $value ) > 2000 &&
  1033. is_array( $fieldDef ) &&
  1034. in_array( $fieldDef['datatype'], $bindDataTypes )
  1035. )
  1036. {
  1037. $value = $db->bindVariable( $value, $fieldDef );
  1038. $valueBound = true;
  1039. }
  1040. else
  1041. $valueBound = false;
  1042. if ( $i > 0 )
  1043. $query .= ', ';
  1044. if ( $valueBound )
  1045. $query .= $field . "=" . $value;
  1046. else
  1047. $query .= $field . "='" . $db->escapeString( $value ) . "'";
  1048. ++$i;
  1049. }
  1050. $query .= ' WHERE ';
  1051. $i = 0;
  1052. foreach( $conditions as $conditionKey => $condition )
  1053. {
  1054. if ( $i > 0 )
  1055. $query .= ' AND ';
  1056. if ( is_array( $condition ) )
  1057. {
  1058. $query .= $conditionKey . ' IN (';
  1059. $j = 0;
  1060. foreach( $condition as $conditionValue )
  1061. {
  1062. if ( $j > 0 )
  1063. $query .= ', ';
  1064. $query .= "'" . $db->escapeString( $conditionValue ) . "'";
  1065. ++$j;
  1066. }
  1067. $query .= ')';
  1068. }
  1069. else
  1070. $query .= $conditionKey . "='" . $db->escapeString( $condition ) . "'";
  1071. ++$i;
  1072. }
  1073. $db->query( $query );
  1074. }
  1075. /*!
  1076. \return the attributes for this object, taken from the definition fields and
  1077. function attributes.
  1078. */
  1079. function attributes()
  1080. {
  1081. $def = $this->definition();
  1082. $attrs = array_keys( $def["fields"] );
  1083. if ( isset( $def["function_attributes"] ) )
  1084. $attrs = array_unique( array_merge( $attrs, array_keys( $def["function_attributes"] ) ) );
  1085. if ( isset( $def["functions"] ) )
  1086. $attrs = array_unique( array_merge( $attrs, array_keys( $def["functions"] ) ) );
  1087. return $attrs;
  1088. }
  1089. /*!
  1090. \return true if the attribute \a $attr is part of the definition fields or function attributes.
  1091. */
  1092. function hasAttribute( $attr )
  1093. {
  1094. $def = $this->definition();
  1095. $has_attr = isset( $def["fields"][$attr] );
  1096. if ( !$has_attr and isset( $def["function_attributes"] ) )
  1097. $has_attr = isset( $def["function_attributes"][$attr] );
  1098. if ( !$has_attr and isset( $def["functions"] ) )
  1099. $has_attr = isset( $def["functions"][$attr] );
  1100. return $has_attr;
  1101. }
  1102. /*!
  1103. \return the attribute data for \a $attr, this is either returned from the member variables
  1104. or a member function depending on whether the definition field or function attributes matched.
  1105. */
  1106. function attribute( $attr, $noFunction = false )
  1107. {
  1108. $def = $this->definition();
  1109. $fields = $def["fields"];
  1110. $functions = isset( $def["functions"] ) ? $def["functions"] : null;
  1111. $attrFunctions = isset( $def["function_attributes"] ) ? $def["function_attributes"] : null;
  1112. if ( $noFunction === false and isset( $attrFunctions[$attr] ) )
  1113. {
  1114. $functionName = $attrFunctions[$attr];
  1115. $retVal = null;
  1116. if ( method_exists( $this, $functionName ) )
  1117. {
  1118. $retVal = $this->$functionName();
  1119. }
  1120. else
  1121. {
  1122. eZDebug::writeError( 'Could not find function : "' . get_class( $this ) . '::' . $functionName . '()".', __METHOD__ );
  1123. }
  1124. return $retVal;
  1125. }
  1126. else if ( isset( $fields[$attr] ) )
  1127. {
  1128. $attrName = $fields[$attr];
  1129. if ( is_array( $attrName ) )
  1130. {
  1131. $attrName = $attrName['name'];
  1132. }
  1133. return $this->$attrName;
  1134. }
  1135. else if ( isset( $functions[$attr] ) )
  1136. {
  1137. $functionName = $functions[$attr];
  1138. return $this->$functionName();
  1139. }
  1140. else
  1141. {
  1142. eZDebug::writeError( "Attribute '$attr' does not exist", $def['class_name'] . '::attribute' );
  1143. $attrValue = null;
  1144. return $attrValue;
  1145. }
  1146. }
  1147. /*!
  1148. Sets the attribute \a $attr to the value \a $val. The attribute must be present in the
  1149. objects definition fields or set functions.
  1150. */
  1151. function setAttribute( $attr, $val )
  1152. {
  1153. $def = $this->definition();
  1154. $fields = $def["fields"];
  1155. $functions = isset( $def["set_functions"] ) ? $def["set_functions"] : null;
  1156. if ( isset( $fields[$attr] ) )
  1157. {
  1158. $attrName = $fields[$attr];
  1159. if ( is_array( $attrName ) )
  1160. {
  1161. $attrName = $attrName['name'];
  1162. }
  1163. $oldValue = null;
  1164. if ( isset( $this->$attrName ) )
  1165. $oldValue = $this->$attrName;
  1166. $this->$attrName = $val;
  1167. if ( $oldValue === null ||
  1168. $oldValue !== $val )
  1169. $this->setHasDirtyData( true );
  1170. }
  1171. else if ( isset( $functions[$attr] ) )
  1172. {
  1173. $functionName =

Large files files are truncated, but you can click here to view the full file