Source of file Locale.php
Size: 20,190 Bytes - Last Modified: 2021-12-23T10:53:31+00:00
/var/www/docs.ssmods.com/process/src/src/Model/Locale.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 | <?php namespace TractorCow\Fluent\Model; use SilverStripe\Control\Controller; use SilverStripe\Control\Director; use SilverStripe\Core\Config\Config; use SilverStripe\Forms\CheckboxField; use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\GridField\GridField; use SilverStripe\Forms\GridField\GridFieldButtonRow; use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\GridField\GridFieldDeleteAction; use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\TabSet; use SilverStripe\Forms\TextField; use SilverStripe\i18n\i18n; use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; use SilverStripe\ORM\HasManyList; use SilverStripe\ORM\ManyManyList; use SilverStripe\Security\Member; use SilverStripe\Security\Permission; use SilverStripe\Security\PermissionProvider; use Symbiote\GridFieldExtensions\GridFieldAddNewInlineButton; use Symbiote\GridFieldExtensions\GridFieldEditableColumns; use Symbiote\GridFieldExtensions\GridFieldOrderableRows; use TractorCow\Fluent\Control\LocaleAdmin; use TractorCow\Fluent\Extension\FluentDirectorExtension; use TractorCow\Fluent\Extension\Traits\FluentObjectTrait; use TractorCow\Fluent\State\FluentState; /** * @property string $Title * @property string $Locale * @property string $URLSegment * @property bool $IsGlobalDefault * @property int $DomainID * @property bool $UseDefaultCode * @method HasManyList|FallbackLocale[] FallbackLocales() * @method ManyManyList|Locale[] Fallbacks() * @method Domain Domain() Raw SQL Domain (unfiltered by domain mode) */ class Locale extends DataObject implements PermissionProvider { use CachableModel; /** * Code for accessing cross-locale actions */ const CMS_ACCESS_MULTI_LOCALE = 'CMS_ACCESS_Fluent_Actions_MultiLocale'; /** * Prefix for per-locale permission code. * * Note that this is not a permission code in itself, and must always be * joined with a locale. */ const CMS_ACCESS_FLUENT_LOCALE = "CMS_ACCESS_Fluent_Locale_"; private static $table_name = 'Fluent_Locale'; private static $singular_name = 'Locale'; private static $plural_name = 'Locales'; /** * hreflang for default landing pages. * Note: PHP's ext-intl doesn't support this code, so only use it * in templates. */ const X_DEFAULT = 'x-default'; private static $summary_fields = [ 'Title' => 'Title', 'Locale' => 'Locale', 'URLSegment' => 'URL', 'IsGlobalDefault' => 'Global Default', 'Domain.Domain' => 'Domain', ]; /** * @config * @var array */ private static $db = [ 'Title' => 'Varchar(100)', 'Locale' => 'Varchar(10)', 'URLSegment' => 'Varchar(100)', 'IsGlobalDefault' => 'Boolean', 'UseDefaultCode' => 'Boolean', 'Sort' => 'Int', ]; private static $default_sort = '"Fluent_Locale"."Sort" ASC, "Fluent_Locale"."Locale" ASC'; /** * @config * @var array */ private static $has_one = [ 'Domain' => Domain::class, ]; private static $has_many = [ 'FallbackLocales' => FallbackLocale::class . '.Parent', ]; private static $many_many = [ 'Fallbacks' => [ 'through' => FallbackLocale::class, 'from' => 'Parent', 'to' => 'Locale', ], ]; /** * @var ArrayList */ protected $chain = null; /** * @var Locale[] */ protected static $locales_by_title; /** * Get internal title for this locale * * @return string */ public function getTitle() { $title = $this->getField('Title'); if ($title) { return $title; } return $this->getDefaultTitle(); } /** * Long title (including locale code) * * @return string */ public function getLongTitle() { return "{$this->Title} ({$this->Locale})"; } /** * @return string */ protected function getDefaultTitle() { // Get default name from locale return i18n::getData()->localeName($this->getLocale()); } /** * Locale code for this object * * @return string */ public function getLocale() { $locale = $this->getField('Locale'); if ($locale) { return $locale; } return $this->getDefaultLocale(); } /** * Default locale for * * @return string */ public function getDefaultLocale() { return i18n::config()->get('default_locale'); } /** * Get the locale's country part * * @return string e.g. "NZ" for "en_NZ" */ public function getLocaleSuffix() { $bits = explode('_', $this->Locale); return array_pop($bits); } /** * Returns the label to display for Fluent badges in the CMS. By default this is the * locale's URLSegment as set in /admin/locales, but can be configured with extensions. * * For example, you may want to display the full locale badge: * <code> * public function updateBadgeLabel(&$badgeLabel) * { * $badgeLabel = $this->owner->Locale; * } * </code> * * @return string */ public function getBadgeLabel() { $badgeLabel = $this->getURLSegment(); $this->extend('updateBadgeLabel', $badgeLabel); return (string)$badgeLabel; } /** * RFC 1766 hreflang * * @return string */ public function getHrefLang() { if ($this->UseDefaultCode) { return self::X_DEFAULT; } return strtolower(i18n::convert_rfc1766($this->Locale)); } /** * Get URLSegment for this locale * * @return string */ public function getURLSegment() { $segment = $this->getField('URLSegment'); if ($segment) { return $segment; } // Default to locale return $this->getLocale(); } public function getCMSFields() { $fields = FieldList::create(TabSet::create('Root')); // Main tab $fields->addFieldsToTab( 'Root.Main', [ DropdownField::create( 'Locale', _t(__CLASS__ . '.LOCALE', 'Locale'), i18n::getData()->getLocales() ), TextField::create( 'Title', _t(__CLASS__ . '.LOCALE_TITLE', 'Title') )->setAttribute('placeholder', $this->getDefaultTitle()), TextField::create( 'URLSegment', _t(__CLASS__ . '.LOCALE_URL', 'URL Segment') )->setAttribute('placeholder', $this->Locale), $globalDefault = CheckboxField::create( 'IsGlobalDefault', _t(__CLASS__ . '.IS_DEFAULT', 'This is the global default locale') ) ->setAttribute('data-hides', 'ParentDefaultID') ->setDescription(_t( __CLASS__ . '.IS_DEFAULT_DESCRIPTION', 'Note: Per-domain specific locale can be assigned on the Locales tab' . ' and will override this value for specific domains.' )), CheckboxField::create( 'UseDefaultCode', _t(__CLASS__ . '.USE_X_DEFAULT', 'Use {code} as SEO language code (treat as global)', ['code' => self::X_DEFAULT]) ) ->setDescription(_t( __CLASS__ . '.USE_X_DEFAULT_DESCRIPTION', 'Use of this code indicates to search engines that this is a non-localised global landing page' )), DropdownField::create( 'DomainID', _t(__CLASS__ . '.DOMAIN', 'Domain'), Domain::get()->map('ID', 'Domain') )->setEmptyString(_t(__CLASS__ . '.DEFAULT_NONE', '(none)')) ] ); if ($this->exists()) { $config = GridFieldConfig::create() ->addComponents( new GridFieldButtonRow(), new GridFieldAddNewInlineButton(), new GridFieldOrderableRows('Sort'), $editable = new GridFieldEditableColumns(), new GridFieldDeleteAction() ); $editable->setDisplayFields([ 'LocaleID' => function () { return DropdownField::create( 'LocaleID', _t(__CLASS__ . '.LOCALE', 'Locale'), Locale::getCached()->exclude('Locale', $this->Locale)->map('ID', 'Title') ); } ]); // Add default selection $defaultField = GridField::create( 'FallbackLocales', _t(__CLASS__ . '.FALLBACKS', 'Fallback Locales'), $this->FallbackLocales(), $config ); $fields->addFieldToTab('Root.Fallbacks', $defaultField); } else { $fields->addFieldToTab( 'Root.Fallbacks', LiteralField::create( 'UnsavedNotice', '<p>' . _t(__CLASS__ . '.UnsavedNotice', "You can add fallbacks once you've saved the locale.") ) ); // If this is the first locale, it should be checked by default if (static::getCached()->count() === 0) { $globalDefault->setValue(true); } } $this->extend('updateCMSFields', $fields); return $fields; } /** * Get default locale * * @param string|null|true $domain If provided, the default locale for the given domain will be returned. * If true, then the current state domain will be used (if in domain mode). * @return Locale */ public static function getDefault($domain = null) { // Get by domain if it exists and has a default $domainObject = Domain::getByDomain($domain); if ($domainObject) { $default = $domainObject->DefaultLocale(); if ($default) { return $default; } } // Get explicit or implicit default $locales = static::getLocales(); return $locales->filter('IsGlobalDefault', 1)->first() ?: $locales->first(); } /** * Get current locale object * * @return Locale */ public static function getCurrentLocale() { $locale = FluentState::singleton()->getLocale(); return static::getByLocale($locale); } /** * Get object by locale code. * * @param string|Locale $locale * @return Locale */ public static function getByLocale($locale) { if (!$locale) { return null; } if ($locale instanceof Locale) { return $locale; } if (!static::$locales_by_title) { static::$locales_by_title = []; foreach (Locale::getCached() as $localeObj) { static::$locales_by_title[$localeObj->Locale] = $localeObj; } } // Get filtered locale return isset(static::$locales_by_title[$locale]) ? static::$locales_by_title[$locale] : null; } /** * Returns whether the given locale matches the current Locale object * * @param string $locale E.g. en_NZ, en-NZ, en-nz-1990 * @return bool */ public function isLocale($locale) { return stripos(i18n::convert_rfc1766($locale), i18n::convert_rfc1766($this->Locale)) === 0; } /** * Check if this is the default (non-global). * Use IsGlobalDefault check if global default otherwise. * * @return bool */ public function getIsDefault() { // Get default for own domain $default = static::getDefault($this->getDomain()); // Compare best default with current locale return $default && ((int)$default->ID === (int)$this->ID); } /** * Get domain if in domain mode * * @return Domain|null Domain found, or null if not in domain mode (or no domain) */ public function getDomain() { if (FluentState::singleton()->getIsDomainMode() && $this->DomainID) { return Domain::getCached()->byID($this->DomainID); } return null; } /** * Determine if this locale is the sole locale on its domain, * or globally if domain mode is disabled * * @return bool */ public function getIsOnlyLocale() { // Get locales filtered by same domain (in domain mode) $locales = $this->getSiblingLocales(); return $locales->count() < 2; } /** * Get available locales * * @param string|null|true $domain If provided, locales for the given domain will be returned. * If true, then the current state domain will be used (if in domain mode). * @return ArrayList|Locale[] */ public static function getLocales($domain = null) { // Optionally filter by domain $domainObj = Domain::getByDomain($domain); if ($domainObj) { return $domainObj->getLocales(); } return Locale::getCached(); } public function onAfterWrite() { parent::onAfterWrite(); // If this is the default locale, remove default from other locales if ($this->IsGlobalDefault) { $table = $this->baseTable(); DB::prepared_query( "UPDATE \"{$table}\" SET \"IsGlobalDefault\" = 0 WHERE \"ID\" != ?", [$this->ID] ); } } /** * Get chain of all locales that should be preferred when this locale is current * * @return ArrayList */ public function getChain() { if ($this->chain) { return $this->chain; } $this->chain = ArrayList::create(); // Push the current locale as the first fallback. $this->chain->push($this); // Get the current locale and sort them by "Sort" field. $fallbacks = $this->FallbackLocales()->sort('Sort'); foreach ($fallbacks as $fallback) { $this->chain->push($fallback->Locale()); } return $this->chain; } /** * Fetch a native language string from the {@link i18n} class via the current locale code in the format "XX_xx". In * the event a match cannot be found in any framework resource, an empty string is returned. * * @return string The native language string for the current locale e.g. "português (Brazil)" */ public function getNativeName() { $locales = i18n::getData(); // Attempts to fetch the native language string via the `i18n::$common_languages` array if ($native = $locales->languageName($locales->langFromLocale($this->Locale))) { return $native; } return ''; } /** * Determine the base URL within the current locale * * @return string */ public function getBaseURL() { $base = Director::baseURL(); // Prepend hostname for domain mode $domain = $this->getDomain(); if ($domain) { $base = Controller::join_links($domain->Link(), $base); } // Determine if base suffix should be appended $append = true; if ($this->getIsDefault()) { // Apply config $append = !(bool)Config::inst()->get(FluentDirectorExtension::class, 'disable_default_prefix'); } if ($append) { // Append locale url segment $base = Controller::join_links($base, $this->getURLSegment(), '/'); } return $base; } /** * Get other locales that appear alongside this (including self) * * @return ArrayList */ public function getSiblingLocales() { $domain = $this->getDomain(); $locales = $domain ? $domain->getLocales() : Locale::getCached(); return $locales; } /** * Get details for the current object in this locale. * * @return null|RecordLocale * @see FluentObjectTrait::LinkedLocales() */ public function RecordLocale() { $recordID = $this->getSourceQueryParam('FluentObjectID'); $recordClass = $this->getSourceQueryParam('FluentObjectClass'); if (!$recordID || !$recordClass) { return null; } $record = DataObject::get($recordClass)->byID($recordID); if ($record) { return RecordLocale::create($record, $this); } return null; } /** * Get permission code to enable access in this locale * * @return string */ public function getLocaleEditPermission() { $prefix = self::CMS_ACCESS_FLUENT_LOCALE; return "{$prefix}{$this->Locale}"; } public function providePermissions() { $category = _t(__CLASS__ . '.PERMISSION', 'Localisation'); $permissions = [ // @todo - Actually implement this check on those actions self::CMS_ACCESS_MULTI_LOCALE => [ 'name' => _t( __CLASS__ . '.MULTI_LOCALE', 'Access to multi-locale actions (E.g. save in all locales)' ), 'category' => $category, ], ]; foreach (Locale::getCached() as $locale) { $permissions[$locale->getLocaleEditPermission()] = [ 'name' => _t( __CLASS__ . '.EDIT_LOCALE', 'Access "{title}" ({locale})', [ 'title' => $locale->Title, 'locale' => $locale->Locale, ] ), 'category' => $category, ]; } return $permissions; } /** * @param Member $member * @return boolean */ public function canView($member = null) { $extended = $this->extendedCan(__FUNCTION__, $member); if ($extended !== null) { return $extended; } return Permission::check('CMS_ACCESS', 'any', $member); } /** * @param Member $member * @return boolean */ public function canEdit($member = null) { $extended = $this->extendedCan(__FUNCTION__, $member); if ($extended !== null) { return $extended; } // Access locale admin permission return LocaleAdmin::singleton()->canView($member); } /** * @param Member $member * @return boolean */ public function canDelete($member = null) { $extended = $this->extendedCan(__FUNCTION__, $member); if ($extended !== null) { return $extended; } // Access locale admin permission return LocaleAdmin::singleton()->canView($member); } /** * @param Member $member * @param array $context Additional context-specific data which might * affect whether (or where) this object could be created. * @return boolean */ public function canCreate($member = null, $context = []) { $extended = $this->extendedCan(__FUNCTION__, $member, $context); if ($extended !== null) { return $extended; } // Access locale admin permission return LocaleAdmin::singleton()->canView($member); } } |