praiadeseselle/wire/modules/LanguageSupport/LanguageSupport.module

950 lines
29 KiB
Text
Raw Permalink Normal View History

2022-03-08 15:55:41 +01:00
<?php namespace ProcessWire;
/**
* Main multi-language support module
*
* This module is the front door to all the other language modules and files.
*
2022-11-05 18:32:48 +01:00
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
2022-03-08 15:55:41 +01:00
* https://processwire.com
*
* @property int $languagesPageID
* @property int $defaultLanguagePageID
* @property int $languageTranslatorPageID
* @property array $otherLanguagePageIDs Quick reference to non-default language IDs, for when needed before languages loaded
*
*/
class LanguageSupport extends WireData implements Module, ConfigurableModule {
/**
* Return information about the module
*
*/
static public function getModuleInfo() {
return array(
'title' => 'Languages Support',
'version' => 103,
'summary' => 'ProcessWire multi-language support.',
'author' => 'Ryan Cramer',
'autoload' => true,
'singular' => true,
'installs' => array(
'ProcessLanguage',
'ProcessLanguageTranslator',
),
'addFlag' => Modules::flagsNoUserConfig
2023-03-10 19:41:40 +01:00
);
2022-03-08 15:55:41 +01:00
}
/**
* Name of template used for language pages
*
*/
const languageTemplateName = 'language';
/**
* Name of field used to store the language page ref
*
*/
const languageFieldName = 'language';
/**
* This module can possibly be init'd before PW's Modules class fully loads, so we keep this to prevent double initialization
*
*/
protected $initialized = false;
/**
* Reference to the default language page
*
* @var Language|null
*
*/
protected $defaultLanguagePage = null;
/**
* Array of pages that were cached before this module was loaded.
*
* @var array
*
*/
protected $earlyCachedPages = array();
/**
* Instanceof LanguageSupportFields, if installed
*
* @var LanguageSupportFields|null
*
*/
protected $LanguageSupportFields = null;
/**
* Instanceof LanguageTabs, if installed
*
* @var LanguageTabs|null
*
*/
protected $languageTabs = null;
/**
* Construct and set our dynamic config vars
*
*/
public function __construct() {
2023-03-10 19:41:40 +01:00
parent::__construct();
2022-03-08 15:55:41 +01:00
$this->set('initialized', false);
// load other required classes
$dirname = dirname(__FILE__);
require_once($dirname . '/FieldtypeLanguageInterface.php');
require_once($dirname . '/Language.php');
require_once($dirname . '/Languages.php');
require_once($dirname . '/LanguageTranslator.php');
require_once($dirname . '/LanguagesValueInterface.php');
require_once($dirname . '/LanguagesPageFieldValue.php');
// set our config var placeholders
$this->set('languagesPageID', 0);
$this->set('defaultLanguagePageID', 0);
$this->set('languageTranslatorPageID', 0);
// quick reference to non-default language IDs, for when needed before languages loaded
$this->set('otherLanguagePageIDs', array());
}
/**
* Initialize the language support API vars
*
*/
public function init() {
2022-11-05 18:32:48 +01:00
$pages = $this->wire()->pages;
$user = $this->wire()->user;
$config = $this->wire()->config;
$templates = $this->wire()->templates;
$modules = $this->wire()->modules;
2022-03-08 15:55:41 +01:00
// document which pages were already cached at this point, as their values may need
// to be reloaded to account for language fields.
2023-03-10 19:41:40 +01:00
foreach($pages->getCache() as $id => $value) {
$this->earlyCachedPages[$id] = $value;
}
2022-03-08 15:55:41 +01:00
// prevent possible double init
if($this->initialized) return;
$this->initialized = true;
FieldtypePageTitle::$languageSupport = true;
$defaultLanguagePageID = $this->defaultLanguagePageID;
// create the $languages API var
2022-11-05 18:32:48 +01:00
$languageTemplate = $templates->get('language');
2022-03-08 15:55:41 +01:00
if(!$languageTemplate) return;
if(!$this->languagesPageID) {
// fallback if LanguageSupport config lost or not accessible for some reason
2022-11-05 18:32:48 +01:00
$this->languagesPageID = $pages->get("template=admin, name=languages");
2022-03-08 15:55:41 +01:00
}
// prevent fields like 'title' from autojoining until languages are fully loaded
2022-11-05 18:32:48 +01:00
$pages->setAutojoin(false);
/** @var Languages $languages */
2022-03-08 15:55:41 +01:00
$languages = $this->wire(new Languages($this->wire('wire'), $languageTemplate, $this->languagesPageID));
$_default = null; // just in case
// ensure all languages are loaded and get instantiated versions of system/default languages
$numOtherLanguages = 0;
foreach($languages as $language) {
if($language->id == $defaultLanguagePageID) {
$this->defaultLanguagePage = $language;
2022-11-05 18:32:48 +01:00
} else if($language->name === 'default') {
2022-03-08 15:55:41 +01:00
$_default = $language; // backup plan
} else {
$numOtherLanguages++;
PageProperties::$languageProperties["name$language->id"] = array('name', $language->id);
PageProperties::$languageProperties["status$language->id"] = array('status', $language->id);
}
}
if(!$this->defaultLanguagePage) {
if($_default) {
$this->defaultLanguagePage = $_default;
} else {
$this->defaultLanguagePage = $languages->getAll()->first();
}
}
2022-11-05 18:32:48 +01:00
2022-03-08 15:55:41 +01:00
$this->defaultLanguagePage->setIsDefaultLanguage();
$languages->setDefault($this->defaultLanguagePage);
// set $languages API variable
$this->wire('languages', $languages);
// identify the current language from the user, or set one if it's not already
2022-11-05 18:32:48 +01:00
if($user->language && $user->language->id) {
2022-03-08 15:55:41 +01:00
// $language = $this->user->language;
} else {
$language = $this->defaultLanguagePage;
2022-11-05 18:32:48 +01:00
$user->language = $language;
2022-03-08 15:55:41 +01:00
}
2022-11-05 18:32:48 +01:00
$config->dateFormat = $this->_('Y-m-d H:i:s'); // Sortable date format used in the admin
2022-03-08 15:55:41 +01:00
$locale = $this->_('C'); // Value to pass to PHP's setlocale(LC_ALL, 'value') function when initializing this language // Default is 'C'. Specify '0' to skip the setlocale() call (and carry on system default). Specify CSV string of locales to try multiple locales in order.
if($locale != '0') $languages->setLocale(LC_ALL, $locale);
// setup our hooks handled by this class
$this->addHookBefore('Inputfield::render', $this, 'hookInputfieldBeforeRender');
$this->addHookBefore('Inputfield::renderValue', $this, 'hookInputfieldBeforeRender');
$this->addHookAfter('Inputfield::render', $this, 'hookInputfieldAfterRender');
$this->addHookAfter('Inputfield::renderValue', $this, 'hookInputfieldAfterRender');
$this->addHookAfter('Inputfield::processInput', $this, 'hookInputfieldAfterProcessInput');
$this->addHookBefore('Inputfield::processInput', $this, 'hookInputfieldBeforeProcessInput');
$this->addHookAfter('Field::getInputfield', $this, 'hookFieldGetInputfield');
2023-03-10 19:41:40 +01:00
$pages->addHook('added', $this, 'hookPageAdded');
$pages->addHook('deleteReady', $this, 'hookPageDeleteReady');
2022-03-08 15:55:41 +01:00
$this->addHook('Page::setLanguageValue', $this, 'hookPageSetLanguageValue');
$this->addHook('Page::getLanguageValue', $this, 'hookPageGetLanguageValue');
2022-11-05 18:32:48 +01:00
if($modules->isInstalled('LanguageSupportFields')) {
$this->LanguageSupportFields = $modules->get('LanguageSupportFields');
2022-03-08 15:55:41 +01:00
$this->LanguageSupportFields->LS_init();
2022-11-05 18:32:48 +01:00
if($languages->getPageEditPermissions('none') && !$user->hasPermission('page-edit-lang-none')) {
2022-03-08 15:55:41 +01:00
$this->addHookBefore('InputfieldWrapper::renderInputfield', $this, 'hookInputfieldWrapperBeforeRenderInputfield');
}
}
if($numOtherLanguages && $numOtherLanguages != count($this->otherLanguagePageIDs)) {
$this->refreshLanguageIDs();
}
// restore autojoin state for pages
2022-11-05 18:32:48 +01:00
$pages->setAutojoin(true);
2022-03-08 15:55:41 +01:00
}
/**
* Called by ProcessWire when API is fully ready with known $page
*
*/
public function ready() {
2022-11-05 18:32:48 +01:00
$page = $this->wire()->page;
2022-03-08 15:55:41 +01:00
// styles used by our Inputfield hooks
2022-11-05 18:32:48 +01:00
if($page->template->name === 'admin') {
$config = $this->wire()->config;
$config->styles->add($config->urls('LanguageSupport') . "LanguageSupport.css");
$language = $this->wire()->user->language;
if($language) $config->js('LanguageSupport', array(
2022-03-08 15:55:41 +01:00
'language' => array(
'id' => $language->id,
'name' => $language->name,
'title' => (string) $language->title,
2022-11-05 18:32:48 +01:00
)
2022-03-08 15:55:41 +01:00
));
2022-11-05 18:32:48 +01:00
$modules = $this->wire()->modules;
if($modules->isInstalled('LanguageTabs')) {
$this->languageTabs = $modules->get('LanguageTabs');
2022-03-08 15:55:41 +01:00
}
}
// if languageSupportFields is here, then we have to deal with pages that loaded before this module did
if($this->LanguageSupportFields) {
// save the names of all fields that support languages
2022-11-05 18:32:48 +01:00
$fieldNames = $this->LanguageSupportFields->getMultilangFieldNames();
2022-03-08 15:55:41 +01:00
// unset the values from all the early cached pages since they didn't recognize languages
// this will force them to reload when accessed
2023-03-10 19:41:40 +01:00
foreach($this->earlyCachedPages as /* $id => */ $p) {
2022-03-08 15:55:41 +01:00
$t = $p->trackChanges();
if($t) $p->setTrackChanges(false);
foreach($fieldNames as $name) unset($p->$name);
if($t) $p->setTrackChanges(true);
}
}
2022-11-05 18:32:48 +01:00
2022-03-08 15:55:41 +01:00
// release this as we don't need it anymore
$this->earlyCachedPages = array();
if($this->LanguageSupportFields) $this->LanguageSupportFields->LS_ready();
}
/**
* Returns whether or not Inputfield is editable for current user in language context
*
* Takes the page-edit-lang-none permission into account
*
* @param Inputfield $inputfield
* @return bool
*
*/
protected function editableInputfield(Inputfield $inputfield) {
2023-03-10 19:41:40 +01:00
$page = $this->wire()->page;
2022-03-08 15:55:41 +01:00
$alwaysAllowInputfields = array(
'InputfieldWrapper',
'InputfieldPageName',
'InputfieldSubmit',
'InputfieldButton',
'InputfieldHidden',
);
// ignore this call if in ProcessProfile
2023-03-10 19:41:40 +01:00
if($page->process == 'ProcessProfile') return true;
if($page->process == 'ProcessLanguage') return true;
$user = $this->wire()->user;
$languages = $this->wire()->languages;
2022-03-08 15:55:41 +01:00
if($user->isSuperuser()) return true;
if($inputfield->getSetting('useLanguages')) return true;
if(!$this->LanguageSupportFields) return true;
2023-03-10 19:41:40 +01:00
$permissions = $languages->getPageEditPermissions();
2022-03-08 15:55:41 +01:00
if(!isset($permissions['none'])) return true;
2023-03-10 19:41:40 +01:00
if(!$this->wire()->process instanceof WirePageEditor) return true;
2022-03-08 15:55:41 +01:00
if($inputfield->name == 'delete_page') return true;
$allow = false;
foreach($alwaysAllowInputfields as $type) {
$type = __NAMESPACE__ . "\\$type";
if($inputfield instanceof $type) {
$allow = true;
break;
}
}
if($allow) return true;
if($inputfield->hasFieldtype && $this->LanguageSupportFields->isAlternateField($inputfield->name)) return true;
2023-03-10 19:41:40 +01:00
if($languages->editable('none')) return true;
2022-03-08 15:55:41 +01:00
return false;
}
/**
* Hook before Inputfield::render to set proper default language value
*
* Only applies to Inputfields that have: useLanguages == true
*
* @param HookEvent $event
*
*/
public function hookInputfieldBeforeRender(HookEvent $event) {
/** @var Inputfield $inputfield */
$inputfield = $event->object;
if(!$inputfield->useLanguages) return;
$user = $this->wire()->user;
$userLanguage = $user->language;
if(!$userLanguage) return;
// set 'value' attribute to default language values
if($userLanguage->id !== $this->defaultLanguagePageID) {
$t = $inputfield->trackChanges();
if($t) $inputfield->setTrackChanges(false);
$inputfield->attr('value', $inputfield->get('value' . $this->defaultLanguagePageID));
if($t) $inputfield->setTrackChanges(true);
}
}
/**
* Hook before InputfieldWrapper::renderInputfield
*
* Only applies to Inputfields that have: useLanguages == false.
* Applies only if page-edit-lang-none permission is installed.
*
* @param HookEvent $event
*
*/
public function hookInputfieldWrapperBeforeRenderInputfield(HookEvent $event) {
/** @var Inputfield $inputfield */
$inputfield = $event->arguments(0);
if($inputfield->getSetting('useLanguages')) return;
$renderValueMode = $event->arguments(1);
if(!$this->editableInputfield($inputfield) && !$renderValueMode) {
$event->return = '';
$event->replace = true;
}
}
/**
* Wrap the inputfield output with a language name label
*
* @param string $out Existing inputfield output
* @param string $id ID attribute to use
* @param Language $language
* @return string
*
*/
public function wrapInputfieldOutput($out, $id, Language $language) {
$label = (string) $language->title;
if(!strlen($label)) $label = $language->name;
$class = 'LanguageSupport';
$labelClass = 'LanguageSupportLabel detail';
2023-03-10 19:41:40 +01:00
if(!$this->wire()->languages->editable($language)) {
2022-03-08 15:55:41 +01:00
$labelClass .= ' LanguageNotEditable';
$class .= ' LanguageNotEditable';
$label = "<s>$label</s>";
$out =
"<p class='detail'>" .
sprintf($this->_('Changes to this field will not be saved because you do not have permission for language: %s.'),
$language->get('title|name')) .
"</p>" .
$out;
}
$out = "<div class='$class' id='langTab_$id' data-language='$language->id'>" .
"<label for='$id' class='$labelClass'>$label</label>" . $out .
"</div>";
2023-03-10 19:41:40 +01:00
2022-03-08 15:55:41 +01:00
return $out;
}
/**
* Hook into Inputfield::render to duplicate inputs for other languages
*
* Only applies to Inputfields that have: useLanguages == true
*
* @param HookEvent $event
*
*/
public function hookInputfieldAfterRender(HookEvent $event) {
static $numLanguages = null;
2023-03-10 19:41:40 +01:00
2022-03-08 15:55:41 +01:00
if(!$event->return) return; // if already empty, nothing to do
/** @var Inputfield $inputfield */
$inputfield = $event->object;
$name = $inputfield->attr('name');
$renderValueMode = $event->method == 'renderValue';
2023-03-10 19:41:40 +01:00
$languages = $this->wire()->languages;
2022-03-08 15:55:41 +01:00
if(is_null($numLanguages)) $numLanguages = $languages->count();
// provide an automatic translation for some system/default fields if they've not been overridden in the fields editor
2023-03-10 19:41:40 +01:00
if($name == 'language' && $inputfield->label == 'Language') {
$inputfield->label = $this->_('Language'); // Label for 'language' field in user profile
} else if($name == 'email' && $inputfield->label == 'E-Mail Address') {
$inputfield->label = $this->_('E-Mail Address'); // Label for 'email' field in user profile
} else if($name == 'title' && $inputfield->label == 'Title') {
$inputfield->label = $this->_('Title'); // Label for 'title' field used throughout ProcessWire
}
2022-03-08 15:55:41 +01:00
// check if this is a language alternate field (i.e. title_es or title)
if($this->LanguageSupportFields) {
$language = $this->LanguageSupportFields->isAlternateField($name);
if($language) {
$event->return = $this->wrapInputfieldOutput($event->return, $inputfield->attr('id'), $language);
return;
}
}
if(!$inputfield->getSetting('useLanguages') || $numLanguages < 2) return;
// keep originals to restore later (including $name, which we already got above)
$id = $inputfield->attr('id');
$value = $inputfield->attr('value');
$required = $inputfield->required;
$collapsed = $inputfield->collapsed;
$trackChanges = $inputfield->trackChanges();
$inputfield->setTrackChanges(false);
if($this->languageTabs) $this->languageTabs->resetTabs();
$out = '';
foreach($languages as $language) {
$languageID = (int) $language->id;
$languages->setLanguage($language);
if($language->isDefault) {
// default language
$newID = $id;
$o = $event->return;
$inputfield->attr('id', $newID);
} else {
// non-default language
$newID = $id . "__$languageID";
$newName = $name . "__$languageID";
$inputfield->attr('id', $newID);
$inputfield->attr('name', $newName);
$valueAttr = "value$languageID";
$inputfield->required = false;
$inputfield->setAttribute('value', $inputfield->$valueAttr);
$o = $renderValueMode ? $inputfield->___renderValue() : $inputfield->___render();
}
$languages->unsetLanguage();
if($collapsed == Inputfield::collapsedBlank && !$inputfield->isEmpty()) {
$inputfield->collapsed = Inputfield::collapsedNo;
}
$out .= $this->wrapInputfieldOutput($o, $newID, $language);
if($this->languageTabs) $this->languageTabs->addTab($inputfield, $language);
}
$inputfield->setAttribute('name', $name);
$inputfield->setAttribute('id', $id);
$inputfield->setAttribute('value', $value);
$inputfield->required = $required;
$inputfield->setTrackChanges($trackChanges);
if($this->languageTabs) {
$out = $this->languageTabs->renderTabs($inputfield, $out);
}
$event->return = $out;
}
/**
* Hook before Inputfield::processInput to process input for other languages (or prevent it)
*
* @param HookEvent $event
*
*/
public function hookInputfieldBeforeProcessInput(HookEvent $event) {
/** @var Inputfield $inputfield */
$inputfield = $event->object;
$replace = false;
if($inputfield->getSetting('useLanguages') || $inputfield->getSetting('hasLanguages')) {
// multi-language field
$this->hookInputfieldBeforeRender($event); // ensures default language values are populated
2023-03-10 19:41:40 +01:00
if(!$this->wire()->languages->editable($this->defaultLanguagePage)) $replace = true;
2022-03-08 15:55:41 +01:00
} else {
// not a native multi-language field, check if it's language alternate or not editable
if(!$this->editableInputfield($inputfield)) {
$replace = true;
} else if($inputfield->hasFieldtype && $this->LanguageSupportFields) {
$language = $this->LanguageSupportFields->isAlternateField($inputfield->name);
2023-03-10 19:41:40 +01:00
if($language && !$this->wire()->languages->editable($language)) $replace = true;
2022-03-08 15:55:41 +01:00
}
}
if($replace) {
// if field or language not editable, prevent processInput from running
$event->replace = true;
$event->return = $inputfield;
}
}
/**
* Hook into Inputfield::processInput to process input for other languages
*
* Only applies to Inputfields that have: useLanguages == true
*
* @param HookEvent $event
*
*/
public function hookInputfieldAfterProcessInput(HookEvent $event) {
/** @var Inputfield $inputfield */
$inputfield = $event->object;
if(!$inputfield->getSetting('useLanguages')) return;
2023-03-10 19:41:40 +01:00
2022-03-08 15:55:41 +01:00
$post = $event->arguments[0];
2023-03-10 19:41:40 +01:00
$languages = $this->wire()->languages;
2022-03-08 15:55:41 +01:00
// originals
$name = $inputfield->attr('name');
$id = $inputfield->attr('id');
$value = $inputfield->attr('value');
$required = $inputfield->required;
// process and set value for each language
foreach($languages as $language) {
// default language was already handled
if($language->isDefault()) continue;
// if language isn't editable, don't process it
if(!$languages->editable($language)) continue;
$languageID = (int) $language->id;
$newID = $id . "__$languageID";
$newName = $name . "__$languageID";
$inputfield->setTrackChanges(false);
$inputfield->attr('id', $newID);
$inputfield->attr('name', $newName);
// other language values not required, even if default language value is
$inputfield->required = false;
$valueAttr = "value$languageID";
$inputfield->attr('value', $inputfield->$valueAttr);
$inputfield->setTrackChanges(true);
$inputfield->___processInput($post);
$inputfield->set($valueAttr, $inputfield->attr('value'));
}
// restore originals
$inputfield->setTrackChanges(false);
$inputfield->setAttribute('name', $name);
$inputfield->setAttribute('id', $id);
$inputfield->setAttribute('value', $value);
$inputfield->required = $required;
$inputfield->setTrackChanges(true);
}
/**
* Hook into Field::getInputfield to change label/description to proper language
*
* @param HookEvent $event
*
*/
public function hookFieldGetInputfield(HookEvent $event) {
2023-03-10 19:41:40 +01:00
$language = $this->wire()->user->language;
2022-03-08 15:55:41 +01:00
if(!$language || !$language->id) return;
2023-03-10 19:41:40 +01:00
$field = $event->object; /** @var Field $field */
$page = $event->arguments[0]; /** @var Page $page */
$template = $page ? $page->template : null; /** @var Template|null $template */
$inputfield = $event->return; /** @var Inputfield $inputfield */
2022-03-08 15:55:41 +01:00
if(!$inputfield) return;
2023-03-10 19:41:40 +01:00
2022-03-08 15:55:41 +01:00
$translatable = array('label', 'description', 'notes');
2023-03-10 19:41:40 +01:00
if($inputfield->attr('placeholder') !== null && $this->wire()->process != 'ProcessField') {
2022-03-08 15:55:41 +01:00
$translatable[] = 'placeholder';
}
2023-03-10 19:41:40 +01:00
$languages = $template ? $template->getLanguages() : $this->wire()->languages;
2022-03-08 15:55:41 +01:00
$useLanguages = $template && $template->noLang ? false : true;
2023-03-10 19:41:40 +01:00
if(!$languages) $languages = $this->wire()->languages;
2022-03-08 15:55:41 +01:00
// populate language versions where available
foreach($translatable as $key) {
$langKey = $key . $language->id; // i.e. label1234
$value = $field->$langKey;
if(!$value) continue;
$inputfield->$key = $value;
}
// see if this fieldtype supports languages natively
if($field->type instanceof FieldtypeLanguageInterface && $useLanguages) {
// populate useLanguages in the inputfield so we can detect it elsehwere
$inputfield->set('useLanguages', true);
$value = $page->get($field->name);
// set values in this field specific to each language
foreach($languages as $language) {
$languageValue = '';
2023-03-10 19:41:40 +01:00
if($value instanceof LanguagesPageFieldValue) {
2022-03-08 15:55:41 +01:00
$languageValue = $value->getLanguageValue($language->id);
} else {
if($language->isDefault) $languageValue = $value;
}
$inputfield->set('value' . $language->id, $languageValue);
}
// following this hookInputfieldBeforeRender() completes the process after
// Fieldgroup::getPageInputfields() which sets the value attribute of Inputfields
}
$event->return = $inputfield;
}
/**
* Hook called when new language added
*
* @param HookEvent $event
*
*/
public function hookPageAdded(HookEvent $event) {
2023-03-10 19:41:40 +01:00
/** @var Page $page */
2022-03-08 15:55:41 +01:00
$page = $event->arguments[0];
if($page->template->name != self::languageTemplateName) return;
// trigger hook in $languages
$ids = $this->otherLanguagePageIDs;
$ids[] = $page->id;
$this->set('otherLanguagePageIDs', $ids);
2023-03-10 19:41:40 +01:00
$this->wire()->languages->added($page);
2022-03-08 15:55:41 +01:00
// save this as a known language page with module settings
// this is a shortcut used to identify language pages before the API is fully ready
2023-03-10 19:41:40 +01:00
$modules = $this->wire()->modules;
$configData = $modules->getModuleConfigData('LanguageSupport');
2022-03-08 15:55:41 +01:00
$configData['otherLanguagePageIDs'][] = $page->id;
2023-03-10 19:41:40 +01:00
$modules->saveModuleConfigData('LanguageSupport', $configData);
2022-03-08 15:55:41 +01:00
}
/**
* Hook called when language is deleted
*
* @param HookEvent $event
*
*/
public function hookPageDeleteReady(HookEvent $event) {
2023-03-10 19:41:40 +01:00
/** @var Page $page */
2022-03-08 15:55:41 +01:00
$page = $event->arguments[0];
if($page->template->name != self::languageTemplateName) return;
$language = $page;
// remove any language-specific values from any fields
2023-03-10 19:41:40 +01:00
foreach($this->wire()->fields as $field) {
/** @var Field $field */
2022-03-08 15:55:41 +01:00
$changed = false;
foreach(array('label', 'description', 'notes') as $name) {
$name = $name . $language->id;
if(!isset($field->$name)) continue;
$field->remove($name);
2023-03-10 19:41:40 +01:00
$this->message("Removed $language->name $name from field $field->name");
2022-03-08 15:55:41 +01:00
$changed = true;
}
if($changed) $field->save();
}
// remove template labels
2023-03-10 19:41:40 +01:00
foreach($this->wire()->templates as $template) {
/** @var Template $template */
2022-03-08 15:55:41 +01:00
$name = 'label' . $page->id;
if(isset($template->$name)) {
$template->remove($name);
$template->save();
2023-03-10 19:41:40 +01:00
$this->message("Removed $language->name label from template $template->name");
2022-03-08 15:55:41 +01:00
}
}
// trigger hook in $languages
2023-03-10 19:41:40 +01:00
$this->wire()->languages->deleted($page);
2022-03-08 15:55:41 +01:00
// update the other language module IDs to remove the uninstalled language
2023-03-10 19:41:40 +01:00
$modules = $this->wire()->modules;
$configData = $modules->getModuleConfigData('LanguageSupport');
2022-03-08 15:55:41 +01:00
$key = array_search($page->id, $configData['otherLanguagePageIDs']);
if($key !== false) {
unset($configData['otherLanguagePageIDs'][$key]);
2023-03-10 19:41:40 +01:00
$modules->saveModuleConfigData('LanguageSupport', $configData);
2022-03-08 15:55:41 +01:00
}
}
/**
* Adds a Page::setLanguageValue($language, $fieldName, $value) method
*
* Provides a common interface for setting all language values to a Page.
*
* This method exists in this class rather than one of the field-specific classes
* because it deals with both language fields and page names, and potentially
* other types of unknown types that implement LanguagesValueInterface.
*
* @param HookEvent $event
* @throws WireException
*
*/
public function hookPageSetLanguageValue(HookEvent $event) {
2023-03-10 19:41:40 +01:00
$page = $event->object; /** @var Page $page */
$language = $event->arguments(0); /** @var Language $language */
$field = $event->arguments(1); /** @var string|Field $field */
2022-03-08 15:55:41 +01:00
$value = $event->arguments(2);
2022-11-05 18:32:48 +01:00
$languages = $this->wire()->languages;
2023-03-10 19:41:40 +01:00
2022-03-08 15:55:41 +01:00
$event->return = $page;
if(!is_object($language)) {
if(ctype_digit("$language")) $language = (int) $language;
2022-11-05 18:32:48 +01:00
$language = $languages ? $languages->get($language) : null;
2022-03-08 15:55:41 +01:00
}
2023-03-10 19:41:40 +01:00
if(!$language instanceof Language) {
throw new WireException('Unknown language set to Page::setLanguageValue');
}
2022-03-08 15:55:41 +01:00
2022-11-05 18:32:48 +01:00
if($field === 'name' || $field === 'status') {
// set page name or status
if($languages && !$languages->hasPageNames()) {
throw new WireException("Please install LanguageSupportPageNames module before attempting to set multi-language names/paths/status/URLs.");
2022-03-08 15:55:41 +01:00
}
if($language->isDefault()) {
2022-11-05 18:32:48 +01:00
$page->set($field, $value);
2022-03-08 15:55:41 +01:00
} else {
2022-11-05 18:32:48 +01:00
$page->set("$field$language->id", $value);
2022-03-08 15:55:41 +01:00
}
} else {
if(is_object($field)) $field = $field->name;
$previousValue = $page->get($field);
2023-03-10 19:41:40 +01:00
if($previousValue instanceof LanguagesValueInterface) {
2022-03-08 15:55:41 +01:00
// utilize existing set methods available in LanguagesValueInterface (which might be slightly quicker than the else condition method
2023-03-10 19:41:40 +01:00
if($value instanceof LanguagesValueInterface) {
2022-03-08 15:55:41 +01:00
// if given a LanguagesPageFieldValue, then just set it to the page
$page->set($field, $value);
} else {
// otherwise use existing setLanguageValue method provided by LanguagesValueInterface
$previousValue->setLanguageValue($language->id, $value);
}
} else {
// temporarily set user's language to field language, set the field value, then set user's language back
// we don't know what exactly $field might be, whether custom field or some other field, but we'll set it anyway
2023-03-10 19:41:40 +01:00
$user = $this->wire()->user;
2022-03-08 15:55:41 +01:00
$userLanguage = $user->language->id != $language->id ? $user->language : null;
if($userLanguage) $user->language = $language;
$page->set($field, $value);
if($userLanguage) $user->language = $userLanguage;
}
}
}
/**
* Adds a Page::getLanguageValue($language, $fieldName) method
*
* Provides a common interface for getting all language values from a Page.
*
* This method exists in this class rather than one of the field-specific classes
* because it deals with both language fields and page names, and potentially
* other types of unknown types that implement LanguagesValueInterface.
*
* @param HookEvent $event
* @throws WireException
*
*/
public function hookPageGetLanguageValue(HookEvent $event) {
2023-03-10 19:41:40 +01:00
$page = $event->object; /** @var Page $page */
$language = $event->arguments(0); /** @var Language $language */
$field = $event->arguments(1); /** @var string|Field $field */
2022-03-08 15:55:41 +01:00
if(!is_object($language)) {
if(ctype_digit("$language")) $language = (int) $language;
2023-03-10 19:41:40 +01:00
$language = $this->wire()->languages->get($language);
2022-03-08 15:55:41 +01:00
}
2023-03-10 19:41:40 +01:00
if(!$language instanceof Language) {
throw new WireException('Unknown language sent to Page::getLanguageValue');
}
2022-03-08 15:55:41 +01:00
2023-03-10 19:41:40 +01:00
if($field === 'name') {
2022-03-08 15:55:41 +01:00
// get a page name
if($language->isDefault()) {
$value = $page->name;
} else {
$value = $page->get("name$language->id");
}
} else {
if(is_object($field)) $field = $field->name;
$value = $page->get($field);
2023-03-10 19:41:40 +01:00
if($value instanceof LanguagesValueInterface) {
2022-03-08 15:55:41 +01:00
$value = $value->getLanguageValue($language->id);
} else {
// temporarily set user's language to field language, get the field value, then set user's language back
2023-03-10 19:41:40 +01:00
$user = $this->wire()->user;
2022-03-08 15:55:41 +01:00
$userLanguage = $user->language->id != $language->id ? $user->language : null;
if($userLanguage) $user->language = $language;
$value = $page->get($field);
if($userLanguage) $user->language = $userLanguage;
}
}
$event->return = $value;
}
/**
* Module configuration screen
*
* @param array $data
* @return InputfieldWrapper
*
*/
public function getModuleConfigInputfields(array $data) {
if($data) { }
require(dirname(__FILE__) . '/LanguageSupportInstall.php');
/** @var LanguageSupportInstall $installer */
$installer = $this->wire(new LanguageSupportInstall());
return $installer->getModuleConfigInputfields();
}
/**
* Refresh the config stored value for $this->otherLanguagePageIDs
*
*/
public function refreshLanguageIDs() {
2023-03-10 19:41:40 +01:00
$languages = $this->wire()->languages;
$modules = $this->wire()->modules;
2022-03-08 15:55:41 +01:00
$this->message('Refreshing other language page IDs', Notice::debug);
2023-03-10 19:41:40 +01:00
if(!$languages) return;
2022-03-08 15:55:41 +01:00
$ids = array();
2023-03-10 19:41:40 +01:00
foreach($languages as $language) {
2022-03-08 15:55:41 +01:00
if($language->isDefault()) continue;
$ids[] = $language->id;
}
if($this->otherLanguagePageIDs != $ids) {
$this->set('otherLanguagePageIDs', $ids);
2023-03-10 19:41:40 +01:00
$configData = $modules->getModuleConfigData('LanguageSupport');
2022-03-08 15:55:41 +01:00
if($configData['otherLanguagePageIDs'] != $ids) {
$configData['otherLanguagePageIDs'] = $ids;
2023-03-10 19:41:40 +01:00
$modules->saveModuleConfigData('LanguageSupport', $configData);
2022-03-08 15:55:41 +01:00
}
}
}
/**
* Install or uninstall by loading the LanguageSupportInstall script
*
* @param bool $install
*
*/
protected function installer($install = true) {
require_once(dirname(__FILE__) . '/LanguageSupportInstall.php');
/** @var LanguageSupportInstall $installer */
$installer = $this->wire(new LanguageSupportInstall());
if($install) $installer->install();
else $installer->uninstall();
}
/**
* Get the LanguageTabs module instance, if it is installed, or null if not
*
* @return LanguageTabs|null
*
*/
public function getLanguageTabs() {
return $this->languageTabs;
}
/**
* Install the module
*
*/
public function ___install() {
$this->installer(true);
}
/**
* Uninstall the module
*
*/
public function ___uninstall() {
$this->installer(false);
}
}