- Fixed #16160: Call to undefined function ezi18n()
[tinyz:tinyz.git] / kernel / private / classes / ezcontentobjectstate.php
1 <?php
2 /**
3  * File containing the eZContentObjectState class.
4  *
5  * @copyright Copyright (C) 1999-2010 eZ Systems AS. All rights reserved.
6  * @license http://ez.no/licenses/gnu_gpl GNU GPL v2
7  * @version //autogentag//
8  * @package kernel
9  */
10
11 /**
12  * Class representing a content object state
13  *
14  * @version //autogentag//
15  * @package kernel
16  */
17 class eZContentObjectState extends eZPersistentObject
18 {
19     const MAX_IDENTIFIER_LENGTH = 45;
20
21     function __construct( $row = array() )
22     {
23         $this->eZPersistentObject( $row );
24     }
25
26     static function definition()
27     {
28         static $definition = array( "fields" => array( "id" => array( "name" => "ID",
29                                                         "datatype" => "integer",
30                                                         "required" => true ),
31                                          "group_id" => array( "name" => "GroupID",
32                                                               "datatype" => "integer",
33                                                               "required" => true ),
34                                          "identifier" => array( "name" => "Identifier",
35                                                                 "datatype" => "string",
36                                                                 "required" => true,
37                                                                 "max_length" => self::MAX_IDENTIFIER_LENGTH ),
38                                          "language_mask" => array( "name" => "LanguageMask",
39                                                                    "datatype" => "integer",
40                                                                    "default" => 0,
41                                                                    "required" => true ),
42                                          "default_language_id" => array( "name" => "DefaultLanguageID",
43                                                                          "datatype" => "integer",
44                                                                          "required" => true ),
45                                          "priority" => array( "name" => "Order",
46                                                               "datatype" => "integer",
47                                                               "required" => true,
48                                                               "default" => 0 ) ),
49                       "keys" => array( "id" ),
50                       "function_attributes" => array( "current_translation" => "currentTranslation",
51                                                       "all_translations" => "allTranslations",
52                                                       "translations" => "translations",
53                                                       "languages" => "languages",
54                                                       "available_languages" => "availableLanguages",
55                                                       "default_language" => "defaultLanguage",
56                                                       "object_count" => "objectCount",
57                                                       "group" => "group" ),
58                       "increment_key" => "id",
59                       "class_name" => "eZContentObjectState",
60                       "sort" => array( "group_id" => "asc", "priority" => "asc" ),
61                       "name" => "ezcobj_state" );
62         return $definition;
63     }
64
65     /**
66      * Fetches a content object state by its numerical ID.
67      * @param integer $id the numerical ID of the content object state
68      * @return eZContentObjectState|boolean an instance of eZContentObjectState, or false if the requested state does not exist
69      */
70     public static function fetchById( $id )
71     {
72         $states = self::fetchByConditions( array( "ezcobj_state.id=$id" ), 1, 0 );
73         $state = count( $states ) > 0 ? $states[0] : false;
74         return $state;
75     }
76
77     /**
78      * Fetches a content object state by its identifier
79      * and group ID
80      *
81      * @param string $identifier the identifier of the content object state, which is unique per content object state group
82      * @param integer $groupID the numerical ID of the content object state group
83      * @return eZContentObjectState|boolean an instance of eZContentObjectState, or false if the requested state does not exist
84      */
85     public static function fetchByIdentifier( $identifier, $groupID )
86     {
87         $db = eZDB::instance();
88         $identifier = $db->escapeString( $identifier );
89         $states = self::fetchByConditions( array( "ezcobj_state.identifier='$identifier'", "ezcobj_state_group.id=$groupID" ), 1, 0 );
90         $state = count( $states ) > 0 ? $states[0] : false;
91         return $state;
92     }
93
94     /**
95      * Fetches content object states by conditions.
96      *
97      * The content object states are fetched in the right language, depending on the list of prioritized languages
98      * of the site access.
99      *
100      * @param $conditions
101      * @param $limit
102      * @param $offset
103      * @return array
104      */
105     private static function fetchByConditions( $conditions, $limit, $offset )
106     {
107         $db = eZDB::instance();
108
109         $defaultConditions = array(
110             'ezcobj_state.group_id=ezcobj_state_group.id',
111             'ezcobj_state_language.contentobject_state_id=ezcobj_state.id',
112             eZContentLanguage::languagesSQLFilter( 'ezcobj_state' ),
113             eZContentLanguage::sqlFilter( 'ezcobj_state_language', 'ezcobj_state' )
114         );
115
116         $conditions = array_merge( $conditions, $defaultConditions );
117
118         $conditionsSQL = implode( ' AND ', $conditions );
119
120         $sql = "SELECT ezcobj_state.*, ezcobj_state_language.* \r\n" .
121                "FROM ezcobj_state, ezcobj_state_group, ezcobj_state_language \r\n" .
122                "WHERE $conditionsSQL \r\n" .
123                "ORDER BY ezcobj_state.priority";
124
125         $rows = $db->arrayQuery( $sql, array( 'limit' => $limit, 'offset' => $offset ) );
126
127         $states = array();
128         foreach ( $rows as $row )
129         {
130             $state = new eZContentObjectState( $row );
131             $stateLanguage = new eZContentObjectStateLanguage( $row );
132             $state->setLanguageObject( $stateLanguage );
133             $states[] = $state;
134         }
135
136         return $states;
137     }
138
139     /**
140      * Fetches all content object states of a content object state group
141      *
142      * @param integer $groupID
143      * @param integer $limit
144      * @param integer $ofset
145      *
146      * @return array
147      */
148     public static function fetchByGroup( $groupID, $limit = false, $offset = false )
149     {
150         return self::fetchByConditions( array( "ezcobj_state_group.id=$groupID" ), $limit, $offset );
151     }
152
153     /**
154      * @param eZContentObjectStateLanguage $stateLanguage
155      */
156     private function setLanguageObject( eZContentObjectStateLanguage $stateLanguage )
157     {
158         $this->LanguageObject = $stateLanguage;
159     }
160
161     /**
162      * Return the current translation of the content object state
163      *
164      * @return eZContentObjectStateLanguage
165      */
166     public function currentTranslation()
167     {
168         return $this->LanguageObject;
169     }
170
171     /**
172      * Sets the current language
173      *
174      * @param string $locale the locale code
175      * @return boolean true if the language was found and set, false if the language was not found
176      */
177     public function setCurrentLanguage( $locale )
178     {
179         $lang = eZContentLanguage::fetchByLocale( $locale );
180         $langID = $lang->attribute( 'id' );
181         foreach ( $this->translations() as $translation )
182         {
183             if ( $translation->attribute( 'language_id' ) == $langID )
184             {
185                 $this->setLanguageObject( $translation );
186                 return true;
187             }
188         }
189
190         return false;
191     }
192
193     /**
194      *
195      * @return array an array of eZContentObjectStateLanguage objects, representing all possible
196      *         translations of this content object state
197      */
198     public function allTranslations()
199     {
200         if ( !is_array( $this->AllTranslations ) )
201         {
202             $allTranslations = array();
203             foreach ( $this->translations() as $translation )
204             {
205                 $languageID = $translation->attribute( 'language_id' ) & ~1;
206                 $allTranslations[$languageID] = $translation;
207             }
208
209             $languages = eZContentLanguage::fetchList();
210             foreach ( $languages as $language )
211             {
212                 $languageID = $language->attribute( 'id' );
213                 if ( !array_key_exists( $languageID, $allTranslations ) )
214                 {
215                     $row = array( 'language_id' => $languageID );
216                     if ( isset( $this->ID ) )
217                     {
218                         $row['contentobject_state_id'] = $this->ID;
219                     }
220                     $allTranslations[$languageID] = new eZContentObjectStateLanguage( $row );
221                 }
222             }
223             ksort( $allTranslations );
224             // array_values is needed here to reset keys, otherwise eZHTTPPersistence::fetch() won't work
225             $this->AllTranslations = array_values( $allTranslations );
226         }
227         return $this->AllTranslations;
228     }
229
230     public function translationByLocale( $locale )
231     {
232         $languageID = eZContentLanguage::idByLocale( $locale );
233
234         if ( $languageID )
235         {
236             $translations = $this->allTranslations();
237             foreach ( $translations as $translation )
238             {
239                 if ( $translation->realLanguageID() == $languageID )
240                 {
241                     return $translation;
242                 }
243             }
244         }
245
246         return false;
247     }
248
249     /**
250      *
251      * @return an array of eZContentObjectStateLanguage objects, representing all available
252      *         translations of this content object state
253      */
254     public function translations()
255     {
256         if ( !isset( $this->ID ) )
257         {
258             $this->Translations = array();
259         }
260         else if ( !is_array( $this->Translations ) )
261         {
262             $this->Translations = eZContentObjectStateLanguage::fetchByState( $this->ID );
263         }
264         return $this->Translations;
265     }
266
267     /**
268      * Retrieves the languages this content object state is translated into
269      *
270      * @return array an array of eZContentLanguage instances
271      */
272     public function languages()
273     {
274         return isset( $this->LanguageMask ) ? eZContentLanguage::prioritizedLanguagesByMask( $this->LanguageMask ) : array();
275     }
276
277     /**
278      *
279      * @return array the languages the state exists in, as an array with language code strings.
280      */
281     public function availableLanguages()
282     {
283         $languages = array();
284         $languageObjects = $this->languages();
285
286         foreach ( $languageObjects as $languageObject )
287         {
288             $languages[] = $languageObject->attribute( 'locale' );
289         }
290
291         return $languages;
292     }
293
294     /**
295      * Stores the content object state and its translations.
296      *
297      * Before storing a content object state, you should use
298      * {@link eZContentObjectState::isValid()} to check its validness.
299      *
300      * @param array $fieldFilters
301      */
302     public function store( $fieldFilters = null )
303     {
304         if ( $fieldFilters === null )
305         {
306             $db = eZDB::instance();
307
308             $db->begin();
309
310             $languageMask = 1;
311             // set language mask and always available bits
312             foreach ( $this->AllTranslations() as $translation )
313             {
314                 if ( $translation->hasData() )
315                 {
316                     $languageID = $translation->attribute( 'language_id' );
317                     if ( empty( $this->DefaultLanguageID ) )
318                     {
319                         $this->DefaultLanguageID = $languageID & ~1;
320                     }
321                     // if default language, set always available flag
322                     if ( $languageID & $this->DefaultLanguageID )
323                     {
324                         $translation->setAttribute( 'language_id', $languageID | 1 );
325                     }
326                     // otherwise, remove always available flag if it's set
327                     else if ( $languageID & 1 )
328                     {
329                         $translation->setAttribute( 'language_id',  $languageID & ~1 );
330                     }
331
332                     $languageMask = $languageMask | $languageID;
333                 }
334             }
335
336             $assignToObjects = false;
337             if ( !isset( $this->ID ) )
338             {
339                 $rows = $db->arrayQuery( "SELECT MAX(priority) AS max_priority FROM ezcobj_state WHERE group_id=" . $this->GroupID );
340
341                 if ( count( $rows ) > 0 && $rows[0]['max_priority'] !== null )
342                 {
343                     $this->setAttribute( 'priority', $rows[0]['max_priority'] + 1 );
344                 }
345                 else
346                 {
347                     // this is the first state created in the state group
348                     // make all content objects use this state
349                     $assignToObjects = true;
350                 }
351             }
352
353             $this->setAttribute( 'language_mask', $languageMask );
354
355             // store state
356             eZPersistentObject::storeObject( $this, $fieldFilters );
357
358             // store or remove translations
359             foreach ( $this->AllTranslations as $translation )
360             {
361                 if ( !$translation->hasData() )
362                 {
363                     // the name and description are empty
364                     // so the translation needs to be removed if it was stored before
365                     if ( $translation->attribute( 'contentobject_state_id' ) !== null )
366                     {
367                         $translation->remove();
368                     }
369                 }
370                 else
371                 {
372                     if ( $translation->attribute( 'contentobject_state_id' ) != $this->ID )
373                     {
374                         $translation->setAttribute( 'contentobject_state_id', $this->ID );
375                     }
376
377                     $translation->store();
378                 }
379             }
380
381             if ( $assignToObjects )
382             {
383                 $stateID = $this->ID;
384                 $db->query( "INSERT INTO ezcobj_state_link (contentobject_id, contentobject_state_id) SELECT id, $stateID FROM ezcontentobject" );
385             }
386
387             $db->commit();
388         }
389         else
390         {
391             eZPersistentObject::store( $fieldFilters );
392         }
393     }
394
395     /**
396      *
397      * @return int the numerical ID of the default language
398      */
399     public function defaultLanguage()
400     {
401         return eZContentLanguage::fetch( $this->DefaultLanguageID );
402     }
403
404     /**
405      * Checks if all data is valid and can be stored to the database.
406      *
407      * @param array &$messages
408      * @return boolean true when valid, false when not valid
409      * @see eZContentObjectState::store()
410      */
411     public function isValid( &$messages = array() )
412     {
413         $isValid = true;
414         // missing identifier
415         if ( !isset( $this->Identifier ) || $this->Identifier == '' )
416         {
417             $messages[] = ezpI18n::translate( 'kernel/state/edit', 'Identifier: input required' );
418             $isValid = false;
419         }
420         else
421         {
422             // make sure the identifier contains only valid characters
423             $trans = eZCharTransform::instance();
424             $validIdentifier = $trans->transformByGroup( $this->Identifier, 'identifier' );
425             if ( strcmp( $validIdentifier, $this->Identifier ) != 0 )
426             {
427                 // invalid identifier
428                 $messages[] = ezpI18n::translate( 'kernel/state/edit', 'Identifier: invalid, it can only consist of characters in the range a-z, 0-9 and underscore.' );
429                 $isValid = false;
430             }
431             else if ( strlen( $this->Identifier ) > self::MAX_IDENTIFIER_LENGTH )
432             {
433                 $messages[] = ezpI18n::translate( 'kernel/state/edit', 'Identifier: invalid, maximum %max characters allowed.',
434                                       null, array( '%max' => self::MAX_IDENTIFIER_LENGTH ) );
435                 $isValid = false;
436             }
437             else if ( isset( $this->GroupID ) )
438             {
439                 // check for existing identifier
440                 $existingState = self::fetchByIdentifier( $this->Identifier, $this->GroupID );
441                 if ( $existingState && ( !isset( $this->ID ) || $existingState->attribute( 'id' ) !== $this->ID ) )
442                 {
443                     $messages[] = ezpI18n::translate( 'kernel/state/edit', 'Identifier: a content object state group with this identifier already exists, please give another identifier' );
444                     $isValid = false;
445                 }
446             }
447         }
448
449         $translationsWithData = 0;
450         foreach ( $this->AllTranslations as $translation )
451         {
452             if ( $translation->hasData() )
453             {
454                 $translationsWithData++;
455                 if ( !$translation->isValid( $messages ) )
456                 {
457                     $isValid = false;
458                 }
459             }
460             else if ( ( $translation->attribute( 'language_id' ) & ~1 ) == $this->DefaultLanguageID )
461             {
462                 // if name nor description are set but translation is specified as main language
463                 $isValid = false;
464                 $messages[] = ezpI18n::translate( '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' ) ) );
465             }
466         }
467
468         if ( $translationsWithData == 0 )
469         {
470             $isValid = false;
471             $messages[] =  ezpI18n::translate( 'kernel/state/edit', 'Translations: you need to add at least one localization' );
472         }
473         else if ( empty( $this->DefaultLanguageID ) && $translationsWithData > 1 )
474         {
475             $isValid = false;
476             $messages[] =  ezpI18n::translate( 'kernel/state/edit', 'Translations: there are multiple localizations but you did not specify which is the default one' );
477         }
478
479         return $isValid;
480     }
481
482     public function group()
483     {
484         return eZContentObjectStateGroup::fetchById( $this->GroupID );
485     }
486
487     /**
488      * Get the list of content object states that is used to create the object state limitation list in the policy/edit view
489      *
490      * @return array
491      */
492     public static function limitationList()
493     {
494         $sql = "SELECT g.identifier group_identifier, s.identifier state_identifier, s.priority, s.id \r\n" .
495                "FROM ezcobj_state s, ezcobj_state_group g \r\n" .
496                "WHERE s.group_id=g.id \r\n" .
497                "AND g.identifier NOT LIKE 'ez%' \r\n" .
498                "ORDER BY g.identifier, s.priority";
499         $db = eZDB::instance();
500         $rows = $db->arrayQuery( $sql );
501         $limitationList = array();
502         foreach ( $rows as $row )
503         {
504             $limitationList[] = array( 'name' => $row['group_identifier'] . '/' . $row['state_identifier'], 'id' => $row['id'] );
505         }
506
507         return $limitationList;
508     }
509
510     /**
511      * The defaults are cached in a static class variable, so subsequent calls to this method do not require
512      * queries to the database each time. To clear this cache use {@link eZContentObjectState::cleanDefaultsCache()}.
513      *
514      * @return array an array of all default content object states
515      */
516     public static function defaults()
517     {
518         if ( !is_array( self::$Defaults ) )
519         {
520             self::$Defaults = eZPersistentObject::fetchObjectList( self::definition(), null, array( 'priority' => 0 ) );
521         }
522
523         return self::$Defaults;
524     }
525
526     /**
527      * Cleans the cache used by {@link eZContentObjectState::defaults()}.
528      */
529     public static function cleanDefaultsCache()
530     {
531         self::$Defaults = null;
532     }
533
534     /**
535      * Fetches the HTTP persistent variables for this content object state and its localizations.
536      *
537      * "ContentObjectState" is used as base name for the persistent variables.
538      *
539      * @see eZHTTPPersistence
540      */
541     public function fetchHTTPPersistentVariables()
542     {
543         $translations = $this->allTranslations();
544
545         $http = eZHTTPTool::instance();
546         eZHTTPPersistence::fetch( 'ContentObjectState' , eZContentObjectState::definition(), $this, $http, false );
547         eZHTTPPersistence::fetch( 'ContentObjectState' , eZContentObjectStateLanguage::definition(), $translations, $http, true );
548     }
549
550     /**
551      * Removes a content object state by its numerical ID
552      *
553      * This method should not be used directly, instead use {@link eZContentObjectStateGroup::removeStatesByID()}.
554      *
555      * @param integer $id the numerical ID of the content object state
556      */
557     public static function removeByID( $id )
558     {
559         $db = eZDB::instance();
560         $db->begin();
561         $db->query( "DELETE FROM ezcobj_state_link WHERE contentobject_state_id=$id" );
562         eZPersistentObject::removeObject( eZContentObjectStateLanguage::definition(), array( 'contentobject_state_id' => $id ) );
563         eZPersistentObject::removeObject( eZContentObjectState::definition(), array( 'id' => $id ) );
564         $db->commit();
565     }
566
567     /**
568      * @return integer The count of objects that have this content object state
569      */
570     public function objectCount()
571     {
572         $db = eZDB::instance();
573         $id = $this->ID;
574         $result = $db->arrayQuery( "SELECT COUNT(contentobject_id) AS object_count FROM ezcobj_state_link WHERE contentobject_state_id=$id" );
575         return $result[0]['object_count'];
576     }
577
578     private $LanguageObject;
579     private $Translations;
580     private $AllTranslations;
581     private static $Defaults = null;
582 }
583 ?>