|
eZ Publish
[trunk]
|
00001 <?php 00002 /** 00003 * File containing the eZContentObjectState class. 00004 * 00005 * @copyright Copyright (C) 1999-2012 eZ Systems AS. All rights reserved. 00006 * @license http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2 00007 * @version //autogentag// 00008 * @package kernel 00009 */ 00010 00011 /** 00012 * Class representing a content object state 00013 * 00014 * @version //autogentag// 00015 * @package kernel 00016 */ 00017 class eZContentObjectState extends eZPersistentObject 00018 { 00019 const MAX_IDENTIFIER_LENGTH = 45; 00020 00021 function __construct( $row = array() ) 00022 { 00023 $this->eZPersistentObject( $row ); 00024 } 00025 00026 static function definition() 00027 { 00028 static $definition = array( "fields" => array( "id" => array( "name" => "ID", 00029 "datatype" => "integer", 00030 "required" => true ), 00031 "group_id" => array( "name" => "GroupID", 00032 "datatype" => "integer", 00033 "required" => true, 00034 "foreign_class" => "eZContentObjectStateGroup", 00035 "foreign_attribute" => "id", 00036 "multiplicity" => "1..*" ), 00037 "identifier" => array( "name" => "Identifier", 00038 "datatype" => "string", 00039 "required" => true, 00040 "max_length" => self::MAX_IDENTIFIER_LENGTH ), 00041 "language_mask" => array( "name" => "LanguageMask", 00042 "datatype" => "integer", 00043 "default" => 0, 00044 "required" => true ), 00045 "default_language_id" => array( "name" => "DefaultLanguageID", 00046 "datatype" => "integer", 00047 "required" => true ), 00048 "priority" => array( "name" => "Order", 00049 "datatype" => "integer", 00050 "required" => true, 00051 "default" => 0 ) ), 00052 "keys" => array( "id" ), 00053 "function_attributes" => array( "current_translation" => "currentTranslation", 00054 "all_translations" => "allTranslations", 00055 "translations" => "translations", 00056 "languages" => "languages", 00057 "available_languages" => "availableLanguages", 00058 "default_language" => "defaultLanguage", 00059 "object_count" => "objectCount", 00060 "group" => "group" ), 00061 "increment_key" => "id", 00062 "class_name" => "eZContentObjectState", 00063 "sort" => array( "group_id" => "asc", "priority" => "asc" ), 00064 "name" => "ezcobj_state" ); 00065 return $definition; 00066 } 00067 00068 /** 00069 * Fetches a content object state by its numerical ID. 00070 * @param integer $id the numerical ID of the content object state 00071 * @return eZContentObjectState|boolean an instance of eZContentObjectState, or false if the requested state does not exist 00072 */ 00073 public static function fetchById( $id ) 00074 { 00075 $states = self::fetchByConditions( array( "ezcobj_state.id=$id" ), 1, 0 ); 00076 $state = count( $states ) > 0 ? $states[0] : false; 00077 return $state; 00078 } 00079 00080 /** 00081 * Fetches a content object state by its identifier 00082 * and group ID 00083 * 00084 * @param string $identifier the identifier of the content object state, which is unique per content object state group 00085 * @param integer $groupID the numerical ID of the content object state group 00086 * @return eZContentObjectState|boolean an instance of eZContentObjectState, or false if the requested state does not exist 00087 */ 00088 public static function fetchByIdentifier( $identifier, $groupID ) 00089 { 00090 $db = eZDB::instance(); 00091 $identifier = $db->escapeString( $identifier ); 00092 $states = self::fetchByConditions( array( "ezcobj_state.identifier='$identifier'", "ezcobj_state_group.id=$groupID" ), 1, 0 ); 00093 $state = count( $states ) > 0 ? $states[0] : false; 00094 return $state; 00095 } 00096 00097 /** 00098 * Fetches content object states by conditions. 00099 * 00100 * The content object states are fetched in the right language, depending on the list of prioritized languages 00101 * of the site access. 00102 * 00103 * @param $conditions 00104 * @param $limit 00105 * @param $offset 00106 * @return array 00107 */ 00108 private static function fetchByConditions( $conditions, $limit, $offset ) 00109 { 00110 $db = eZDB::instance(); 00111 00112 $defaultConditions = array( 00113 'ezcobj_state.group_id=ezcobj_state_group.id', 00114 'ezcobj_state_language.contentobject_state_id=ezcobj_state.id', 00115 eZContentLanguage::languagesSQLFilter( 'ezcobj_state' ), 00116 eZContentLanguage::sqlFilter( 'ezcobj_state_language', 'ezcobj_state' ) 00117 ); 00118 00119 $conditions = array_merge( $conditions, $defaultConditions ); 00120 00121 $conditionsSQL = implode( ' AND ', $conditions ); 00122 00123 $sql = "SELECT ezcobj_state.*, ezcobj_state_language.* " . 00124 "FROM ezcobj_state, ezcobj_state_group, ezcobj_state_language " . 00125 "WHERE $conditionsSQL " . 00126 "ORDER BY ezcobj_state.priority"; 00127 00128 $rows = $db->arrayQuery( $sql, array( 'limit' => $limit, 'offset' => $offset ) ); 00129 00130 $states = array(); 00131 foreach ( $rows as $row ) 00132 { 00133 $state = new eZContentObjectState( $row ); 00134 $stateLanguage = new eZContentObjectStateLanguage( $row ); 00135 $state->setLanguageObject( $stateLanguage ); 00136 $states[] = $state; 00137 } 00138 00139 return $states; 00140 } 00141 00142 /** 00143 * Fetches all content object states of a content object state group 00144 * 00145 * @param integer $groupID 00146 * @param integer $limit 00147 * @param integer $ofset 00148 * 00149 * @return array 00150 */ 00151 public static function fetchByGroup( $groupID, $limit = false, $offset = false ) 00152 { 00153 return self::fetchByConditions( array( "ezcobj_state_group.id=$groupID" ), $limit, $offset ); 00154 } 00155 00156 /** 00157 * @param eZContentObjectStateLanguage $stateLanguage 00158 */ 00159 private function setLanguageObject( eZContentObjectStateLanguage $stateLanguage ) 00160 { 00161 $this->LanguageObject = $stateLanguage; 00162 } 00163 00164 /** 00165 * Return the current translation of the content object state 00166 * 00167 * @return eZContentObjectStateLanguage 00168 */ 00169 public function currentTranslation() 00170 { 00171 return $this->LanguageObject; 00172 } 00173 00174 /** 00175 * Sets the current language 00176 * 00177 * @param string $locale the locale code 00178 * @return boolean true if the language was found and set, false if the language was not found 00179 */ 00180 public function setCurrentLanguage( $locale ) 00181 { 00182 $lang = eZContentLanguage::fetchByLocale( $locale ); 00183 $langID = $lang->attribute( 'id' ); 00184 foreach ( $this->translations() as $translation ) 00185 { 00186 if ( $translation->attribute( 'language_id' ) == $langID ) 00187 { 00188 $this->setLanguageObject( $translation ); 00189 return true; 00190 } 00191 } 00192 00193 return false; 00194 } 00195 00196 /** 00197 * 00198 * @return array an array of eZContentObjectStateLanguage objects, representing all possible 00199 * translations of this content object state 00200 */ 00201 public function allTranslations() 00202 { 00203 if ( !is_array( $this->AllTranslations ) ) 00204 { 00205 $allTranslations = array(); 00206 foreach ( $this->translations() as $translation ) 00207 { 00208 $languageID = $translation->attribute( 'language_id' ) & ~1; 00209 $allTranslations[$languageID] = $translation; 00210 } 00211 00212 $languages = eZContentLanguage::fetchList(); 00213 foreach ( $languages as $language ) 00214 { 00215 $languageID = $language->attribute( 'id' ); 00216 if ( !array_key_exists( $languageID, $allTranslations ) ) 00217 { 00218 $row = array( 'language_id' => $languageID ); 00219 if ( isset( $this->ID ) ) 00220 { 00221 $row['contentobject_state_id'] = $this->ID; 00222 } 00223 $allTranslations[$languageID] = new eZContentObjectStateLanguage( $row ); 00224 } 00225 } 00226 ksort( $allTranslations ); 00227 // array_values is needed here to reset keys, otherwise eZHTTPPersistence::fetch() won't work 00228 $this->AllTranslations = array_values( $allTranslations ); 00229 } 00230 return $this->AllTranslations; 00231 } 00232 00233 public function translationByLocale( $locale ) 00234 { 00235 $languageID = eZContentLanguage::idByLocale( $locale ); 00236 00237 if ( $languageID ) 00238 { 00239 $translations = $this->allTranslations(); 00240 foreach ( $translations as $translation ) 00241 { 00242 if ( $translation->realLanguageID() == $languageID ) 00243 { 00244 return $translation; 00245 } 00246 } 00247 } 00248 00249 return false; 00250 } 00251 00252 /** 00253 * 00254 * @return an array of eZContentObjectStateLanguage objects, representing all available 00255 * translations of this content object state 00256 */ 00257 public function translations() 00258 { 00259 if ( !isset( $this->ID ) ) 00260 { 00261 $this->Translations = array(); 00262 } 00263 else if ( !is_array( $this->Translations ) ) 00264 { 00265 $this->Translations = eZContentObjectStateLanguage::fetchByState( $this->ID ); 00266 } 00267 return $this->Translations; 00268 } 00269 00270 /** 00271 * Retrieves the languages this content object state is translated into 00272 * 00273 * @return array an array of eZContentLanguage instances 00274 */ 00275 public function languages() 00276 { 00277 return isset( $this->LanguageMask ) ? eZContentLanguage::prioritizedLanguagesByMask( $this->LanguageMask ) : array(); 00278 } 00279 00280 /** 00281 * 00282 * @return array the languages the state exists in, as an array with language code strings. 00283 */ 00284 public function availableLanguages() 00285 { 00286 $languages = array(); 00287 $languageObjects = $this->languages(); 00288 00289 foreach ( $languageObjects as $languageObject ) 00290 { 00291 $languages[] = $languageObject->attribute( 'locale' ); 00292 } 00293 00294 return $languages; 00295 } 00296 00297 /** 00298 * Stores the content object state and its translations. 00299 * 00300 * Before storing a content object state, you should use 00301 * {@link eZContentObjectState::isValid()} to check its validness. 00302 * 00303 * @param array $fieldFilters 00304 */ 00305 public function store( $fieldFilters = null ) 00306 { 00307 if ( $fieldFilters === null ) 00308 { 00309 $db = eZDB::instance(); 00310 00311 $db->begin(); 00312 00313 $languageMask = 1; 00314 // set language mask and always available bits 00315 foreach ( $this->AllTranslations() as $translation ) 00316 { 00317 if ( $translation->hasData() ) 00318 { 00319 $languageID = $translation->attribute( 'language_id' ); 00320 if ( empty( $this->DefaultLanguageID ) ) 00321 { 00322 $this->DefaultLanguageID = $languageID & ~1; 00323 } 00324 // if default language, set always available flag 00325 if ( $languageID & $this->DefaultLanguageID ) 00326 { 00327 $translation->setAttribute( 'language_id', $languageID | 1 ); 00328 } 00329 // otherwise, remove always available flag if it's set 00330 else if ( $languageID & 1 ) 00331 { 00332 $translation->setAttribute( 'language_id', $languageID & ~1 ); 00333 } 00334 00335 $languageMask = $languageMask | $languageID; 00336 } 00337 } 00338 00339 $assignToObjects = false; 00340 if ( !isset( $this->ID ) ) 00341 { 00342 $rows = $db->arrayQuery( "SELECT MAX(priority) AS max_priority FROM ezcobj_state WHERE group_id=" . $this->GroupID ); 00343 00344 if ( count( $rows ) > 0 && $rows[0]['max_priority'] !== null ) 00345 { 00346 $this->setAttribute( 'priority', $rows[0]['max_priority'] + 1 ); 00347 } 00348 else 00349 { 00350 // this is the first state created in the state group 00351 // make all content objects use this state 00352 $assignToObjects = true; 00353 } 00354 } 00355 00356 $this->setAttribute( 'language_mask', $languageMask ); 00357 00358 // store state 00359 eZPersistentObject::storeObject( $this, $fieldFilters ); 00360 00361 // store or remove translations 00362 foreach ( $this->AllTranslations as $translation ) 00363 { 00364 if ( !$translation->hasData() ) 00365 { 00366 // the name and description are empty 00367 // so the translation needs to be removed if it was stored before 00368 if ( $translation->attribute( 'contentobject_state_id' ) !== null ) 00369 { 00370 $translation->remove(); 00371 } 00372 } 00373 else 00374 { 00375 if ( $translation->attribute( 'contentobject_state_id' ) != $this->ID ) 00376 { 00377 $translation->setAttribute( 'contentobject_state_id', $this->ID ); 00378 } 00379 00380 $translation->store(); 00381 } 00382 } 00383 00384 if ( $assignToObjects ) 00385 { 00386 $stateID = $this->ID; 00387 $db->query( "INSERT INTO ezcobj_state_link (contentobject_id, contentobject_state_id) SELECT id, $stateID FROM ezcontentobject" ); 00388 } 00389 00390 $db->commit(); 00391 } 00392 else 00393 { 00394 eZPersistentObject::store( $fieldFilters ); 00395 } 00396 } 00397 00398 /** 00399 * 00400 * @return int the numerical ID of the default language 00401 */ 00402 public function defaultLanguage() 00403 { 00404 return eZContentLanguage::fetch( $this->DefaultLanguageID ); 00405 } 00406 00407 /** 00408 * Checks if all data is valid and can be stored to the database. 00409 * 00410 * @param array &$messages 00411 * @return boolean true when valid, false when not valid 00412 * @see eZContentObjectState::store() 00413 */ 00414 public function isValid( &$messages = array() ) 00415 { 00416 $isValid = true; 00417 // missing identifier 00418 if ( !isset( $this->Identifier ) || $this->Identifier == '' ) 00419 { 00420 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: input required' ); 00421 $isValid = false; 00422 } 00423 else 00424 { 00425 // make sure the identifier contains only valid characters 00426 $trans = eZCharTransform::instance(); 00427 $validIdentifier = $trans->transformByGroup( $this->Identifier, 'identifier' ); 00428 if ( strcmp( $validIdentifier, $this->Identifier ) != 0 ) 00429 { 00430 // invalid identifier 00431 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: invalid, it can only consist of characters in the range a-z, 0-9 and underscore.' ); 00432 $isValid = false; 00433 } 00434 else if ( strlen( $this->Identifier ) > self::MAX_IDENTIFIER_LENGTH ) 00435 { 00436 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: invalid, maximum %max characters allowed.', 00437 null, array( '%max' => self::MAX_IDENTIFIER_LENGTH ) ); 00438 $isValid = false; 00439 } 00440 else if ( isset( $this->GroupID ) ) 00441 { 00442 // check for existing identifier 00443 $existingState = self::fetchByIdentifier( $this->Identifier, $this->GroupID ); 00444 if ( $existingState && ( !isset( $this->ID ) || $existingState->attribute( 'id' ) !== $this->ID ) ) 00445 { 00446 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: a content object state group with this identifier already exists, please give another identifier' ); 00447 $isValid = false; 00448 } 00449 } 00450 } 00451 00452 $translationsWithData = 0; 00453 foreach ( $this->AllTranslations as $translation ) 00454 { 00455 if ( $translation->hasData() ) 00456 { 00457 $translationsWithData++; 00458 if ( !$translation->isValid( $messages ) ) 00459 { 00460 $isValid = false; 00461 } 00462 } 00463 else if ( ( $translation->attribute( 'language_id' ) & ~1 ) == $this->DefaultLanguageID ) 00464 { 00465 // if name nor description are set but translation is specified as main language 00466 $isValid = false; 00467 $messages[] = ezpI18n::tr( 'kernel/state/edit', '%language_name: this language is the default but neither name or description were provided for this language', null, array( '%language_name' => $translation->attribute( 'language' )->attribute( 'locale_object' )->attribute( 'intl_language_name' ) ) ); 00468 } 00469 } 00470 00471 if ( $translationsWithData == 0 ) 00472 { 00473 $isValid = false; 00474 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Translations: you need to add at least one localization' ); 00475 } 00476 else if ( empty( $this->DefaultLanguageID ) && $translationsWithData > 1 ) 00477 { 00478 $isValid = false; 00479 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Translations: there are multiple localizations but you did not specify which is the default one' ); 00480 } 00481 00482 return $isValid; 00483 } 00484 00485 public function group() 00486 { 00487 return eZContentObjectStateGroup::fetchById( $this->GroupID ); 00488 } 00489 00490 /** 00491 * Get the list of content object states that is used to create the object state limitation list in the policy/edit view 00492 * 00493 * @return array 00494 */ 00495 public static function limitationList() 00496 { 00497 $sql = "SELECT g.identifier group_identifier, s.identifier state_identifier, s.priority, s.id " . 00498 "FROM ezcobj_state s, ezcobj_state_group g " . 00499 "WHERE s.group_id=g.id " . 00500 "AND g.identifier NOT LIKE 'ez%' " . 00501 "ORDER BY g.identifier, s.priority"; 00502 $db = eZDB::instance(); 00503 $rows = $db->arrayQuery( $sql ); 00504 $limitationList = array(); 00505 foreach ( $rows as $row ) 00506 { 00507 $limitationList[] = array( 'name' => $row['group_identifier'] . '/' . $row['state_identifier'], 'id' => $row['id'] ); 00508 } 00509 00510 return $limitationList; 00511 } 00512 00513 /** 00514 * The defaults are cached in a static class variable, so subsequent calls to this method do not require 00515 * queries to the database each time. To clear this cache use {@link eZContentObjectState::cleanDefaultsCache()}. 00516 * 00517 * @return array an array of all default content object states 00518 */ 00519 public static function defaults() 00520 { 00521 if ( !is_array( self::$Defaults ) ) 00522 { 00523 self::$Defaults = eZPersistentObject::fetchObjectList( self::definition(), null, array( 'priority' => 0 ) ); 00524 } 00525 00526 return self::$Defaults; 00527 } 00528 00529 /** 00530 * Cleans the cache used by {@link eZContentObjectState::defaults()}. 00531 */ 00532 public static function cleanDefaultsCache() 00533 { 00534 self::$Defaults = null; 00535 } 00536 00537 /** 00538 * Fetches the HTTP persistent variables for this content object state and its localizations. 00539 * 00540 * "ContentObjectState" is used as base name for the persistent variables. 00541 * 00542 * @see eZHTTPPersistence 00543 */ 00544 public function fetchHTTPPersistentVariables() 00545 { 00546 $translations = $this->allTranslations(); 00547 00548 $http = eZHTTPTool::instance(); 00549 eZHTTPPersistence::fetch( 'ContentObjectState' , eZContentObjectState::definition(), $this, $http, false ); 00550 eZHTTPPersistence::fetch( 'ContentObjectState' , eZContentObjectStateLanguage::definition(), $translations, $http, true ); 00551 } 00552 00553 /** 00554 * Removes a content object state by its numerical ID 00555 * 00556 * This method should not be used directly, instead use {@link eZContentObjectStateGroup::removeStatesByID()}. 00557 * 00558 * @param integer $id the numerical ID of the content object state 00559 */ 00560 public static function removeByID( $id ) 00561 { 00562 $db = eZDB::instance(); 00563 $db->begin(); 00564 $db->query( "DELETE FROM ezcobj_state_link WHERE contentobject_state_id=$id" ); 00565 eZPersistentObject::removeObject( eZContentObjectStateLanguage::definition(), array( 'contentobject_state_id' => $id ) ); 00566 eZPersistentObject::removeObject( eZContentObjectState::definition(), array( 'id' => $id ) ); 00567 $db->commit(); 00568 } 00569 00570 /** 00571 * @return integer The count of objects that have this content object state 00572 */ 00573 public function objectCount() 00574 { 00575 $db = eZDB::instance(); 00576 $id = $this->ID; 00577 $result = $db->arrayQuery( "SELECT COUNT(contentobject_id) AS object_count FROM ezcobj_state_link WHERE contentobject_state_id=$id" ); 00578 return $result[0]['object_count']; 00579 } 00580 00581 private $LanguageObject; 00582 private $Translations; 00583 private $AllTranslations; 00584 private static $Defaults = null; 00585 } 00586 ?>