|
eZ Publish
[trunk]
|
00001 <?php 00002 /** 00003 * File containing the eZContentObjectStateGroup 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 respresenting a content object state group 00013 * 00014 * @version //autogentag// 00015 * @package kernel 00016 */ 00017 class eZContentObjectStateGroup extends eZPersistentObject 00018 { 00019 const MAX_IDENTIFIER_LENGTH = 45; 00020 00021 /** 00022 * flag which specifies if it is allowed to create, update or delete internal state groups and their states 00023 * 00024 * @var boolean 00025 */ 00026 static $allowInternalCUD = false; 00027 00028 function __construct( $row = array() ) 00029 { 00030 $this->eZPersistentObject( $row ); 00031 } 00032 00033 public static function definition() 00034 { 00035 static $definition = array( "fields" => array( "id" => array( "name" => "ID", 00036 "datatype" => "integer", 00037 "required" => true ), 00038 "identifier" => array( "name" => "Identifier", 00039 "datatype" => "string", 00040 "required" => true, 00041 "max_length" => self::MAX_IDENTIFIER_LENGTH ), 00042 "language_mask" => array( "name" => "LanguageMask", 00043 "datatype" => "integer", 00044 "default" => 0, 00045 "required" => true ), 00046 "default_language_id" => array( "name" => "DefaultLanguageID", 00047 "datatype" => "integer", 00048 "required" => true ) ), 00049 "keys" => array( "id" ), 00050 "function_attributes" => array( "current_translation" => "currentTranslation", 00051 "all_translations" => "allTranslations", 00052 "translations" => "translations", 00053 "languages" => "languages", 00054 "available_languages" => "availableLanguages", 00055 "default_language" => "defaultLanguage", 00056 "states" => "states", 00057 "is_internal" => "isInternal" ), 00058 "increment_key" => "id", 00059 "class_name" => "eZContentObjectStateGroup", 00060 "sort" => array( "identifier" => "asc" ), 00061 "name" => "ezcobj_state_group" ); 00062 return $definition; 00063 } 00064 00065 /** 00066 * Fetches a content object state group by its numerical ID 00067 * 00068 * @param integer $id 00069 * @return eZContentObjectStateGroup|boolean 00070 */ 00071 public static function fetchById( $id ) 00072 { 00073 $stateGroups = self::fetchByConditions( array( "ezcobj_state_group.id=$id" ), 1, 0 ); 00074 $stateGroup = count( $stateGroups ) > 0 ? $stateGroups[0] : false; 00075 return $stateGroup; 00076 } 00077 00078 /** 00079 * Fetches a content object state group by its identifier 00080 * 00081 * @param string $identifier 00082 * @return eZContentObjectStateGroup|boolean 00083 */ 00084 public static function fetchByIdentifier( $identifier ) 00085 { 00086 $db = eZDB::instance(); 00087 $identifier = $db->escapeString( $identifier ); 00088 $stateGroups = self::fetchByConditions( array( "ezcobj_state_group.identifier='$identifier'" ), 1, 0 ); 00089 $stateGroup = count( $stateGroups ) > 0 ? $stateGroups[0] : false; 00090 return $stateGroup; 00091 } 00092 00093 /** 00094 * Fetches content object state groups by certain conditions 00095 * 00096 * @param array $conditions 00097 * @param integer $limit 00098 * @param integer $offset 00099 * @return array 00100 */ 00101 private static function fetchByConditions( $conditions, $limit, $offset ) 00102 { 00103 $db = eZDB::instance(); 00104 00105 $defaultConditions = array( 00106 'ezcobj_state_group_language.contentobject_state_group_id=ezcobj_state_group.id', 00107 eZContentLanguage::languagesSQLFilter( 'ezcobj_state_group' ), 00108 eZContentLanguage::sqlFilter( 'ezcobj_state_group_language', 'ezcobj_state_group' ) 00109 ); 00110 00111 $conditions = array_merge( $conditions, $defaultConditions ); 00112 00113 $conditionsSQL = implode( ' AND ', $conditions ); 00114 00115 $sql = "SELECT * " . 00116 "FROM ezcobj_state_group, ezcobj_state_group_language ". 00117 "WHERE $conditionsSQL"; 00118 00119 $rows = $db->arrayQuery( $sql, array( 'limit' => $limit, 'offset' => $offset ) ); 00120 00121 $stateGroups = array(); 00122 foreach ( $rows as $row ) 00123 { 00124 $stateGroup = new eZContentObjectStateGroup( $row ); 00125 $stateGroupLanguage = new eZContentObjectStateGroupLanguage( $row ); 00126 $stateGroup->setLanguageObject( $stateGroupLanguage ); 00127 $stateGroups[] = $stateGroup; 00128 } 00129 00130 return $stateGroups; 00131 } 00132 00133 /** 00134 * 00135 * 00136 * @param int $limit 00137 * @param int $offset 00138 * @return array 00139 */ 00140 public static function fetchByOffset( $limit, $offset ) 00141 { 00142 return self::fetchByConditions( array(), $limit, $offset ); 00143 } 00144 00145 /** 00146 * 00147 * 00148 * @param eZContentObjectStateGroupLanguage $stateGroupLanguage 00149 */ 00150 private function setLanguageObject( eZContentObjectStateGroupLanguage $stateGroupLanguage ) 00151 { 00152 $this->LanguageObject = $stateGroupLanguage; 00153 } 00154 00155 /** 00156 * 00157 * 00158 * @return eZContentObjectStateGroupLanguage 00159 */ 00160 public function currentTranslation() 00161 { 00162 return $this->LanguageObject; 00163 } 00164 00165 /** 00166 * 00167 * 00168 * @param string $locale 00169 * @return boolean 00170 */ 00171 public function setCurrentLanguage( $locale ) 00172 { 00173 $lang = eZContentLanguage::fetchByLocale( $locale ); 00174 $langID = $lang->attribute( 'id' ); 00175 foreach ( $this->translations() as $translation ) 00176 { 00177 if ( $translation->attribute( 'language_id' ) == $langID ) 00178 { 00179 $this->setLanguageObject( $translation ); 00180 return true; 00181 } 00182 } 00183 00184 return false; 00185 } 00186 00187 /** 00188 * 00189 * 00190 * @return array 00191 */ 00192 public function allTranslations() 00193 { 00194 if ( !is_array( $this->AllTranslations ) ) 00195 { 00196 $allTranslations = array(); 00197 foreach ( $this->translations() as $translation ) 00198 { 00199 $languageID = $translation->attribute( 'language_id' ) & ~1; 00200 $allTranslations[$languageID] = $translation; 00201 } 00202 00203 $languages = eZContentLanguage::fetchList(); 00204 foreach ( $languages as $language ) 00205 { 00206 $languageID = $language->attribute( 'id' ); 00207 00208 if ( !array_key_exists( $languageID, $allTranslations ) ) 00209 { 00210 $row = array( 'language_id' => $languageID ); 00211 if ( isset( $this->ID ) ) 00212 { 00213 $row['contentobject_state_group_id'] = $this->ID; 00214 } 00215 $allTranslations[$languageID] = new eZContentObjectStateGroupLanguage( $row ); 00216 } 00217 } 00218 ksort( $allTranslations ); 00219 // array_values is needed here to reset keys, otherwise eZHTTPPersistence::fetch() won't work 00220 $this->AllTranslations = array_values( $allTranslations ); 00221 } 00222 return $this->AllTranslations; 00223 } 00224 00225 public function translationByLocale( $locale ) 00226 { 00227 $languageID = eZContentLanguage::idByLocale( $locale ); 00228 00229 if ( $languageID ) 00230 { 00231 $translations = $this->allTranslations(); 00232 foreach ( $translations as $translation ) 00233 { 00234 if ( $translation->realLanguageID() == $languageID ) 00235 { 00236 return $translation; 00237 } 00238 } 00239 } 00240 00241 return false; 00242 } 00243 00244 /** 00245 * 00246 * 00247 * @return array 00248 */ 00249 public function translations() 00250 { 00251 if ( !isset( $this->ID ) ) 00252 { 00253 $this->Translations = array(); 00254 } 00255 else if ( !is_array( $this->Translations ) ) 00256 { 00257 $this->Translations = eZContentObjectStateGroupLanguage::fetchByGroup( $this->ID ); 00258 } 00259 return $this->Translations; 00260 } 00261 00262 /** 00263 * Get the languages the state group exists in. 00264 * 00265 * @return array an array of eZContentLanguage instances 00266 */ 00267 public function languages() 00268 { 00269 return isset( $this->LanguageMask ) ? eZContentLanguage::prioritizedLanguagesByMask( $this->LanguageMask ) : array(); 00270 } 00271 00272 /** 00273 * Get the languages the state group exists in. 00274 * 00275 * @return array an array of language code strings. 00276 */ 00277 public function availableLanguages() 00278 { 00279 $languages = array(); 00280 $languageObjects = $this->languages(); 00281 00282 foreach ( $languageObjects as $languageObject ) 00283 { 00284 $languages[] = $languageObject->attribute( 'locale' ); 00285 } 00286 00287 return $languages; 00288 } 00289 00290 /** 00291 * Stores the content object state group and its translations. 00292 * 00293 * Before storing a content object state group, you should use 00294 * {@link eZContentObjectStateGroup::isValid()} to check its validness. 00295 * 00296 * @param array $fieldFilters 00297 */ 00298 public function store( $fieldFilters = null ) 00299 { 00300 $db = eZDB::instance(); 00301 00302 $db->begin(); 00303 00304 $languageMask = 1; 00305 // set language mask and always available bits 00306 foreach ( $this->AllTranslations as $translation ) 00307 { 00308 if ( $translation->hasData() ) 00309 { 00310 $languageID = $translation->attribute( 'language_id' ); 00311 if ( empty( $this->DefaultLanguageID ) ) 00312 { 00313 $this->DefaultLanguageID = $languageID & ~1; 00314 } 00315 // if default language, set always available flag 00316 if ( $languageID & $this->DefaultLanguageID ) 00317 { 00318 $translation->setAttribute( 'language_id', $languageID | 1 ); 00319 } 00320 // otherwise, remove always available flag if it's set 00321 else if ( $languageID & 1 ) 00322 { 00323 $translation->setAttribute( 'language_id', $languageID & ~1 ); 00324 } 00325 00326 $languageMask = $languageMask | $languageID; 00327 } 00328 } 00329 $this->setAttribute( 'language_mask', $languageMask ); 00330 00331 // store state group 00332 eZPersistentObject::storeObject( $this, $fieldFilters ); 00333 00334 // store or remove translations 00335 foreach ( $this->AllTranslations as $translation ) 00336 { 00337 if ( !$translation->hasData() ) 00338 { 00339 // the name and description are empty 00340 // so the translation needs to be removed if it was stored before 00341 if ( $translation->attribute( 'contentobject_state_group_id' ) !== null ) 00342 { 00343 $translation->remove(); 00344 } 00345 } 00346 else 00347 { 00348 if ( $translation->attribute( 'contentobject_state_group_id' ) != $this->ID ) 00349 { 00350 $translation->setAttribute( 'contentobject_state_group_id', $this->ID ); 00351 } 00352 00353 $translation->store(); 00354 } 00355 } 00356 00357 eZExpiryHandler::registerShutdownFunction(); 00358 $handler = eZExpiryHandler::instance(); 00359 $handler->setTimestamp( 'state-limitations', time() ); 00360 00361 $db->commit(); 00362 } 00363 00364 /** 00365 * 00366 * 00367 * @return eZContentLanguage 00368 */ 00369 public function defaultLanguage() 00370 { 00371 return eZContentLanguage::fetch( $this->DefaultLanguageID ); 00372 } 00373 00374 /** 00375 * Checks if all data is valid and can be stored to the database. 00376 * 00377 * @param array &$messages 00378 * @return boolean true when valid, false when not valid 00379 * @see eZContentObjectStateGroup::store() 00380 */ 00381 public function isValid( &$messages = array() ) 00382 { 00383 $isValid = true; 00384 // missing identifier 00385 if ( !isset( $this->Identifier ) || $this->Identifier == '' ) 00386 { 00387 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: input required' ); 00388 $isValid = false; 00389 } 00390 else 00391 { 00392 // make sure the identifier contains only valid characters 00393 $trans = eZCharTransform::instance(); 00394 $validIdentifier = $trans->transformByGroup( $this->Identifier, 'identifier' ); 00395 if ( strcmp( $validIdentifier, $this->Identifier ) != 0 ) 00396 { 00397 // invalid identifier 00398 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: invalid, it can only consist of characters in the range a-z, 0-9 and underscore.' ); 00399 $isValid = false; 00400 } 00401 else if ( !self::$allowInternalCUD && strncmp( $this->Identifier, 'ez', 2 ) === 0 ) 00402 { 00403 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: identifiers starting with "ez" are reserved.' ); 00404 $isValid = false; 00405 } 00406 else if ( strlen( $this->Identifier ) > self::MAX_IDENTIFIER_LENGTH ) 00407 { 00408 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: invalid, maximum %max characters allowed.', 00409 null, array( '%max' => self::MAX_IDENTIFIER_LENGTH ) ); 00410 $isValid = false; 00411 } 00412 else 00413 { 00414 // check for existing identifier 00415 $existingGroup = self::fetchByIdentifier( $this->Identifier ); 00416 if ( $existingGroup && ( !isset( $this->ID ) || $existingGroup->attribute( 'id' ) !== $this->ID ) ) 00417 { 00418 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Identifier: a content object state group with this identifier already exists, please give another identifier' ); 00419 $isValid = false; 00420 } 00421 } 00422 } 00423 00424 $translationsWithData = 0; 00425 foreach ( $this->AllTranslations as $translation ) 00426 { 00427 if ( $translation->hasData() ) 00428 { 00429 $translationsWithData++; 00430 if ( !$translation->isValid( $messages ) ) 00431 { 00432 $isValid = false; 00433 } 00434 } 00435 else if ( ( $translation->attribute( 'language_id' ) & ~1 ) == $this->DefaultLanguageID ) 00436 { 00437 // if name nor description are set but translation is specified as main language 00438 $isValid = false; 00439 $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' ) ) ); 00440 } 00441 } 00442 00443 if ( $translationsWithData == 0 ) 00444 { 00445 $isValid = false; 00446 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Translations: you need to add at least one localization' ); 00447 } 00448 else if ( empty( $this->DefaultLanguageID ) && $translationsWithData > 1 ) 00449 { 00450 $isValid = false; 00451 $messages[] = ezpI18n::tr( 'kernel/state/edit', 'Translations: there are multiple localizations but you did not specify which is the default one' ); 00452 } 00453 00454 return $isValid; 00455 } 00456 00457 /** 00458 * 00459 * 00460 * @param boolean $refreshMemberVariable 00461 * @return array 00462 */ 00463 public function states( $refreshMemberVariable = false ) 00464 { 00465 if ( !isset( $this->ID ) ) 00466 { 00467 return array(); 00468 } 00469 else if ( !is_array( $this->States ) || $refreshMemberVariable ) 00470 { 00471 $this->States = eZContentObjectState::fetchByGroup( $this->ID ); 00472 } 00473 00474 return $this->States; 00475 } 00476 00477 /** 00478 * Fetches the HTTP persistent variables for this content object state group and its localizations. 00479 * 00480 * "ContentObjectStateGroup" is used as base name for the persistent variables. 00481 * 00482 * @see eZHTTPPersistence 00483 */ 00484 public function fetchHTTPPersistentVariables() 00485 { 00486 $translations = $this->allTranslations(); 00487 00488 $http = eZHTTPTool::instance(); 00489 eZHTTPPersistence::fetch( 'ContentObjectStateGroup' , eZContentObjectStateGroup::definition(), $this, $http, false ); 00490 eZHTTPPersistence::fetch( 'ContentObjectStateGroup' , eZContentObjectStateGroupLanguage::definition(), $translations, $http, true ); 00491 } 00492 00493 /** 00494 * 00495 * 00496 * @param integer $id 00497 */ 00498 public static function removeByID( $id ) 00499 { 00500 $db = eZDB::instance(); 00501 $db->begin(); 00502 $states = eZContentObjectState::fetchByGroup( $id ); 00503 foreach ( $states as $state ) 00504 { 00505 eZContentObjectState::removeByID( $state->attribute( 'id' ) ); 00506 } 00507 eZPersistentObject::removeObject( eZContentObjectStateGroupLanguage::definition(), array( 'contentobject_state_group_id' => $id ) ); 00508 eZPersistentObject::removeObject( eZContentObjectStateGroup::definition(), array( 'id' => $id ) ); 00509 $db->commit(); 00510 } 00511 00512 /** 00513 * 00514 * 00515 * @param array $idList 00516 */ 00517 public function removeStatesByID( $idList ) 00518 { 00519 $newDefaultStateID = null; 00520 $removeIDList = array(); 00521 00522 $db = eZDB::instance(); 00523 $db->begin(); 00524 00525 $states = $this->states(); 00526 00527 foreach ( $states as $state ) 00528 { 00529 $stateID = $state->attribute( 'id' ); 00530 if ( in_array( $stateID, $idList ) ) 00531 { 00532 $removeIDList[] = $stateID; 00533 } 00534 else if ( $newDefaultStateID === null ) 00535 { 00536 $newDefaultStateID = $stateID; 00537 } 00538 } 00539 00540 $removeIDListCount = count( $removeIDList ); 00541 if ( $removeIDListCount > 0 ) 00542 { 00543 if ( $newDefaultStateID ) 00544 { 00545 $contentObjectStateIDCondition = $removeIDListCount > 1 ? $db->generateSQLINStatement( $removeIDList, 'contentobject_state_id' ) : 00546 "contentobject_state_id=$removeIDList[0]"; 00547 $db->query( "UPDATE ezcobj_state_link 00548 SET contentobject_state_id=$newDefaultStateID 00549 WHERE $contentObjectStateIDCondition" ); 00550 eZContentObjectState::cleanDefaultsCache(); 00551 } 00552 00553 foreach ( $removeIDList as $id ) 00554 { 00555 eZContentObjectState::removeByID( $id ); 00556 } 00557 00558 // re-order remaining states in the same group 00559 $states = $this->states( true ); 00560 $i = 0; 00561 foreach ( $states as $state ) 00562 { 00563 $state->setAttribute( 'priority', $i ); 00564 $state->sync( array( 'priority' ) ); 00565 $i++; 00566 } 00567 } 00568 $db->commit(); 00569 } 00570 00571 /** 00572 * 00573 * 00574 * @param array $stateIDList 00575 * @return boolean 00576 */ 00577 public function reorderStates( $stateIDList ) 00578 { 00579 $stateIDList = array_values( $stateIDList ); 00580 00581 $states = $this->states(); 00582 00583 $currentStateIDList = array(); 00584 foreach ( $states as $state ) 00585 { 00586 $stateID = $state->attribute( 'id' ); 00587 if ( !in_array( $stateID, $stateIDList ) ) 00588 { 00589 return false; 00590 } 00591 00592 // need to convert to int here, otherwise comparing arrays with === won't work 00593 $currentStateIDList[] = (int)$stateID; 00594 } 00595 00596 if ( $stateIDList === $currentStateIDList ) 00597 { 00598 // order didn't change at all 00599 return true; 00600 } 00601 00602 $db = eZDB::instance(); 00603 $db->begin(); 00604 foreach ( $stateIDList as $i => $updateID ) 00605 { 00606 if ( $currentStateIDList[$i] != $updateID ) 00607 { 00608 $db->query( "UPDATE ezcobj_state SET priority=$i WHERE id=$updateID" ); 00609 } 00610 } 00611 $db->commit(); 00612 00613 // re-order states in the same group 00614 $this->states( true ); 00615 00616 return true; 00617 } 00618 00619 /** 00620 * Creates a new content object state in this content object state group 00621 * 00622 * @param string $identifier identifier for the new state group 00623 * @return eZContentObjectState the new content object state 00624 */ 00625 public function newState( $identifier = null ) 00626 { 00627 return new eZContentObjectState( array( 'group_id' => $this->ID, 00628 'identifier' => $identifier ) ); 00629 } 00630 00631 public function isInternal() 00632 { 00633 return ( $this->ID && strncmp( $this->Identifier, 'ez', 2 ) === 0 ); 00634 } 00635 00636 public function stateByIdentifier( $stateIdentifier ) 00637 { 00638 return eZContentObjectState::fetchByIdentifier( $stateIdentifier, $this->attribute( 'id' ) ); 00639 } 00640 00641 /** 00642 * Returns an array of limitations useable by the policy system 00643 * 00644 * @return array 00645 */ 00646 public static function limitations() 00647 { 00648 static $limitations; 00649 00650 if ( $limitations === null ) 00651 { 00652 $db = eZDB::instance(); 00653 $dbName = md5( $db->DB ); 00654 00655 $cacheDir = eZSys::cacheDirectory(); 00656 $phpCache = new eZPHPCreator( $cacheDir, 00657 'statelimitations_' . $dbName . '.php', 00658 '', 00659 array( 'clustering' => 'statelimitations' ) ); 00660 00661 $handler = eZExpiryHandler::instance(); 00662 $storedTimeStamp = $handler->hasTimestamp( 'state-limitations' ) ? $handler->timestamp( 'state-limitations' ) : false; 00663 $expiryTime = $storedTimeStamp !== false ? $storedTimeStamp : 0; 00664 00665 if ( $phpCache->canRestore( $expiryTime ) ) 00666 { 00667 $var = $phpCache->restore( array( 'state_limitations' => 'state_limitations' ) ); 00668 $limitations = $var['state_limitations']; 00669 } 00670 else 00671 { 00672 $limitations = array(); 00673 00674 $groups = self::fetchByConditions( array( "identifier NOT LIKE 'ez%'" ), false, false ); 00675 00676 foreach ( $groups as $group ) 00677 { 00678 $name = 'StateGroup_' . $group->attribute( 'identifier' ); 00679 $limitations[$name] = array( 00680 'name' => $name, 00681 'values' => array(), 00682 'class' => __CLASS__, 00683 'function' => 'limitationValues', 00684 'parameter' => array( $group->attribute( 'id' ) ) 00685 ); 00686 } 00687 00688 $phpCache->addVariable( 'state_limitations', $limitations ); 00689 $phpCache->store(); 00690 } 00691 00692 if ( $storedTimeStamp === false ) 00693 { 00694 eZExpiryHandler::registerShutdownFunction(); 00695 $handler->setTimestamp( 'state-limitations', time() ); 00696 } 00697 } 00698 00699 return $limitations; 00700 } 00701 00702 /** 00703 * Returns an array of limitation values useable by the policy system 00704 * 00705 * @param integer $groupID 00706 * @return array 00707 */ 00708 public static function limitationValues( $groupID ) 00709 { 00710 $group = self::fetchById( $groupID ); 00711 $states = array(); 00712 00713 if ( $group !== false ) 00714 $states = $group->attribute( 'states' ); 00715 00716 $limitationValues = array(); 00717 foreach ( $states as $state ) 00718 { 00719 $limitationValues[] = array( 'name' => $state->attribute( 'current_translation' )->attribute( 'name' ), 00720 'id' => $state->attribute( 'id' ) ); 00721 } 00722 00723 return $limitationValues; 00724 } 00725 00726 private $LanguageObject; 00727 private $Translations; 00728 private $AllTranslations; 00729 private $States; 00730 } 00731 ?>