eZ Publish  [trunk]
ezcontentobjectstategroup.php
Go to the documentation of this file.
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 ?>