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