diff --git a/CHANGELOG.md b/CHANGELOG.md index 196e26d63..28228927a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +* **Build 14x** (2014-08-xx) + - Components and variables can now be accessed in the page code section via `$this->foo` in addition to `$this['foo']`. + - AJAX handlers in the CMS can now invoke the page cycle without rendering the page using `$this->pageCycle()`. + +* **Build 139** (2014-08-18) + - List widget has been refactored to improve efficiency. + - Added new list column type `nameFrom` (take name from X attribute) as an alternative to `select`. + +* **Build 137** (2014-08-14) + - Lists now support Filters (see Backend > Lists docs). + - Numerous hard coded phrases converted to localized strings. + * **Build 132** (2014-08-03) - New system logging pages: Event log, Request log and Access log. diff --git a/modules/backend/assets/js/october.popup.js b/modules/backend/assets/js/october.popup.js index a6749fd0d..46cb5a0bf 100644 --- a/modules/backend/assets/js/october.popup.js +++ b/modules/backend/assets/js/october.popup.js @@ -1,6 +1,6 @@ /* * Ajax Popup plugin - * + * * Data attributes: * - data-control="popup" - enables the ajax popup plugin * - data-ajax="popup-content.htm" - ajax content to load @@ -99,14 +99,18 @@ this.$el.request(this.options.handler, { data: this.options.extraData, success: function(data, textStatus, jqXHR) { - self.setContent(data.result) - $(window).trigger('ajaxUpdateComplete', [this, data, textStatus, jqXHR]) - self.triggerEvent('popupComplete') + this.success(data, textStatus, jqXHR).done(function(){ + self.setContent(data.result) + $(window).trigger('ajaxUpdateComplete', [this, data, textStatus, jqXHR]) + self.triggerEvent('popupComplete') + }) }, error: function(jqXHR, textStatus, errorThrown) { - alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText) - self.hide() - self.triggerEvent('popupError') + this.error(jqXHR, textStatus, errorThrown).done(function(){ + alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText) + self.hide() + self.triggerEvent('popupError') + }) } }) @@ -168,7 +172,7 @@ this.$backdrop = null; } } - + Popup.prototype.setLoading = function(val) { if (!this.$backdrop) return; diff --git a/modules/backend/behaviors/FormController.php b/modules/backend/behaviors/FormController.php index b2c8e5cc8..0d5a8a9fb 100644 --- a/modules/backend/behaviors/FormController.php +++ b/modules/backend/behaviors/FormController.php @@ -154,7 +154,7 @@ class FormController extends ControllerBehavior $this->controller->formAfterSave($model); $this->controller->formAfterCreate($model); - Flash::success($this->getLang('create[flash-save]', 'backend::lang.form.create_success')); + Flash::success($this->getLang('create[flashSave]', 'backend::lang.form.create_success')); if ($redirect = $this->makeRedirect('create', $model)) return $redirect; @@ -207,7 +207,7 @@ class FormController extends ControllerBehavior $this->controller->formAfterSave($model); $this->controller->formAfterUpdate($model); - Flash::success($this->getLang('update[flash-save]', 'backend::lang.form.update_success')); + Flash::success($this->getLang('update[flashSave]', 'backend::lang.form.update_success')); if ($redirect = $this->makeRedirect('update', $model)) return $redirect; @@ -228,7 +228,7 @@ class FormController extends ControllerBehavior $this->controller->formAfterDelete($model); - Flash::success($this->getLang('update[flash-delete]', 'backend::lang.form.delete_success')); + Flash::success($this->getLang('update[flashDelete]', 'backend::lang.form.delete_success')); if ($redirect = $this->makeRedirect('delete', $model)) return $redirect; diff --git a/modules/backend/classes/Controller.php b/modules/backend/classes/Controller.php index bc64d5868..dee0402d9 100644 --- a/modules/backend/classes/Controller.php +++ b/modules/backend/classes/Controller.php @@ -247,7 +247,7 @@ class Controller extends Extendable /** * Invokes the current controller action without rendering a view, - * used by AJAX handler who may rely on the logic inside the action. + * used by AJAX handler that may rely on the logic inside the action. */ public function pageAction() { diff --git a/modules/backend/classes/ListColumn.php b/modules/backend/classes/ListColumn.php index ea9d14346..7cc7ff096 100644 --- a/modules/backend/classes/ListColumn.php +++ b/modules/backend/classes/ListColumn.php @@ -41,7 +41,14 @@ class ListColumn public $sortable = true; /** - * @var string Custom SQL for selecting this record value. + * @var string Model attribute to use for the display value, this will + * override any $sqlSelect definition. + */ + public $nameFrom; + + /** + * @var string Custom SQL for selecting this record display value, + * the @ symbol is replaced with the table name. */ public $sqlSelect; @@ -103,6 +110,7 @@ class ListColumn if (isset($config['searchable'])) $this->searchable = $config['searchable']; if (isset($config['sortable'])) $this->sortable = $config['sortable']; if (isset($config['invisible'])) $this->invisible = $config['invisible']; + if (isset($config['nameFrom'])) $this->nameFrom = $config['nameFrom']; if (isset($config['select'])) $this->sqlSelect = $config['select']; if (isset($config['relation'])) $this->relation = $config['relation']; if (isset($config['format'])) $this->format = $config['format']; diff --git a/modules/backend/controllers/EditorPreferences.php b/modules/backend/controllers/EditorPreferences.php index bc37ec44a..e73c695de 100644 --- a/modules/backend/controllers/EditorPreferences.php +++ b/modules/backend/controllers/EditorPreferences.php @@ -55,13 +55,13 @@ class EditorPreferences extends Controller $this->vars['language'] = 'css'; $this->vars['margin'] = 0; - $this->getClassExtension('Backend.Behaviors.FormController')->update(); + $this->asExtension('FormController')->update(); $this->pageTitle = Lang::get('backend::lang.editor.menu_label'); } public function index_onSave() { - return $this->getClassExtension('Backend.Behaviors.FormController')->update_onSave(); + return $this->asExtension('FormController')->update_onSave(); } public function formFindModelObject() diff --git a/modules/backend/controllers/Users.php b/modules/backend/controllers/Users.php index ac6b25f41..9c7cdd0c2 100644 --- a/modules/backend/controllers/Users.php +++ b/modules/backend/controllers/Users.php @@ -49,7 +49,7 @@ class Users extends Controller if ($context != 'myaccount' && $recordId == $this->user->id) return Redirect::to(Backend::url('backend/users/myaccount')); - return $this->getClassExtension('Backend.Behaviors.FormController')->update($recordId, $context); + return $this->asExtension('FormController')->update($recordId, $context); } /** @@ -68,7 +68,7 @@ class Users extends Controller */ public function myaccount_onSave() { - $result = $this->getClassExtension('Backend.Behaviors.FormController')->update_onSave($this->user->id, 'myaccount'); + $result = $this->asExtension('FormController')->update_onSave($this->user->id, 'myaccount'); /* * If the password or login name has been updated, reauthenticate the user diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index e6483cc11..9514aafb3 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -47,6 +47,7 @@ return [ 'menu_label' => 'Dashboard', 'widget_label' => 'Widget', 'widget_width' => 'Width', + 'full_width' => 'full width', 'add_widget' => 'Add widget', 'widget_inspector_title' => 'Widget configuration', 'widget_inspector_description' => 'Configure the report widget', @@ -119,15 +120,13 @@ return [ 'setup_title' => 'List Setup', 'setup_help' => 'Use checkboxes to select columns you want to see in the list. You can change position of columns by dragging them up or down.', 'records_per_page' => 'Records per page', - 'records_per_page_help' => 'Select the number of records per page to display. Please note that high number of records on a single page can reduce performance.', - 'apply_changes' => 'Apply changes', - 'cancel' => 'Cancel' + 'records_per_page_help' => 'Select the number of records per page to display. Please note that high number of records on a single page can reduce performance.' ], 'fileupload' => [ - 'attachment' => 'Attachment', - 'help' => 'Add a title and description for this attachment.', - 'title_label' => 'Title', - 'description_label' => 'Description' + 'attachment' => 'Attachment', + 'help' => 'Add a title and description for this attachment.', + 'title_label' => 'Title', + 'description_label' => 'Description' ], 'form' => [ 'create_title' => "New :name", @@ -163,6 +162,7 @@ return [ 'select' => 'Select', 'select_all' => 'all', 'select_none' => 'none', + 'select_placeholder' => 'please select', 'insert_row' => 'Insert Row', 'delete_row' => 'Delete Row' ], diff --git a/modules/backend/lang/it/lang.php b/modules/backend/lang/it/lang.php index b93286705..c339aae68 100644 --- a/modules/backend/lang/it/lang.php +++ b/modules/backend/lang/it/lang.php @@ -113,9 +113,7 @@ return [ 'setup_title' => 'Configura elenco', 'setup_help' => 'Utilizza le checkbox per selezionare le colonne che vuoi visualizzare nell\'elenco. Puoi cambiare la posizione delle colonne trascinandole verso l\'alto o il basso.', 'records_per_page' => 'Record per pagina', - 'records_per_page_help' => 'Seleziona il numero di record da visualizzare su ogni pagina. Ricorda che un numero elevato di record in una singola pagina può ridurre le prestazioni.', - 'apply_changes' => 'Applica modifiche', - 'cancel' => 'Annulla' + 'records_per_page_help' => 'Seleziona il numero di record da visualizzare su ogni pagina. Ricorda che un numero elevato di record in una singola pagina può ridurre le prestazioni.' ], 'form' => [ 'create_title' => "Nuovo :name", diff --git a/modules/backend/lang/nl/lang.php b/modules/backend/lang/nl/lang.php index 09f016964..c65889365 100644 --- a/modules/backend/lang/nl/lang.php +++ b/modules/backend/lang/nl/lang.php @@ -14,7 +14,7 @@ return [ 'access_denied' => [ 'label' => "Toegang geweigerd", 'help' => "Je hebt niet de benodigde rechten om deze pagina te bekijken.", - 'cms_link' => "Ga naar CMS backend", + 'cms_link' => "Ga naar CMS", ], ], 'partial' => [ @@ -25,18 +25,18 @@ return [ 'login' => 'Inloggen', 'reset' => 'Wissen', 'restore' => 'Herstellen', - 'login_placeholder' => 'gebruikersnaam', - 'password_placeholder' => 'wachtwoord', + 'login_placeholder' => 'Gebruikersnaam', + 'password_placeholder' => 'Wachtwoord', 'forgot_password' => "Wachtwoord vergeten?", - 'enter_email' => "Vul het e-mailadres in", - 'enter_login' => "Vul de gebruikersnaam in", - 'email_placeholder' => "e-mail", + 'enter_email' => "Vul jouw e-mailadres in", + 'enter_login' => "Vul jouw gebruikersnaam in", + 'email_placeholder' => "E-mailadres", 'enter_new_password' => "Vul een nieuw wachtwoord in", - 'password_reset' => "Herstel Wachtwoord", + 'password_reset' => "Herstel wachtwoord", 'restore_success' => "Een e-mail met instructies om het wachtwoord te herstellen is verzonden naar jouw e-mailadres.", 'restore_error' => "Een gebruiker met de gebruikersnaam ':login' is niet gevonden", - 'reset_success' => "Het wachtwoord is succesvol hersteld. Je kan nu inloggen", - 'reset_error' => "Ongeldige wachtwoordherstelinformatie aangeboden. Probeer overnieuw!", + 'reset_success' => "Het wachtwoord is succesvol hersteld. Je kunt nu inloggen", + 'reset_error' => "Ongeldige herstelinformatie aangeboden. Probeer het opnieuw!", 'reset_fail' => "Het is niet mogelijk het wachtwoord te herstellen!", 'apply' => 'Toepassen', 'cancel' => 'Annuleren', @@ -45,36 +45,59 @@ return [ ], 'dashboard' => [ 'menu_label' => 'Overzicht', + 'widget_label' => 'Widget', + 'widget_width' => 'Breedte', + 'full_width' => 'Volledige breedte', + 'add_widget' => 'Widget toevoegen', + 'widget_inspector_title' => 'Widget configuratie', + 'widget_inspector_description' => 'Configureer de rapportage widget', + 'widget_columns_label' => 'Breedte :columns', + 'widget_columns_description' => 'De widget breedte, een getal tussen 1 en 10.', + 'widget_columns_error' => 'Voer een getal tussen 1 en 10 in als widget breedte.', + 'columns' => '{1} kolom|[2,Inf] kolommen', + 'widget_new_row_label' => 'Forceer nieuwe rij', + 'widget_new_row_description' => 'Plaats de widget in een nieuwe rij.', + 'widget_title_label' => 'Widget titel', + 'widget_title_error' => 'Een widget title is verplicht.', + 'status' => [ + 'widget_title_default' => 'Systeem status', + 'online' => 'online', + 'update_available' => '{0} updates beschikbaar!|{1} update beschikbaar!|[2,Inf] updates beschikbaar!', + ] ], 'user' => [ 'name' => 'Beheerder', 'menu_label' => 'Beheerders', - 'list_title' => 'Beheer Beheerders', - 'new' => 'Nieuwe Beheerder', + 'menu_description' => 'Beheer beheerders, groepen en rechten.', + 'list_title' => 'Beheer beheerders', + 'new' => 'Nieuwe beheerder', 'login' => "Gebruikersnaam", 'first_name' => "Voornaam", 'last_name' => "Achternaam", - 'full_name' => "Volledige Naam", - 'email' => "E-mail", + 'full_name' => "Volledige naam", + 'email' => "E-mailadres", 'groups' => "Groepen", - 'groups_comment' => "Voeg de gebruiker eventueel toe aan gebruikersgroepen.", + 'groups_comment' => "Selecteer de groepen waar deze gebruiker bij hoort.", 'avatar' => "Avatar", 'password' => "Wachtwoord", - 'password_confirmation' => "Bevestig Wachtwoord", - 'superuser' => "Speciale Gebruiker", + 'password_confirmation' => "Bevestig wachtwoord", + 'superuser' => "Supergebruiker", 'superuser_comment' => "Vink deze optie aan om de gebruiker volledige rechten tot het systeem te geven.", 'send_invite' => 'Stuur uitnodiging per e-mail', 'send_invite_comment' => 'Vink deze optie aan om de gebruiker een uitnodiging per e-mail te sturen', 'delete_confirm' => 'Weet je zeker dat je deze beheerder wilt verwijderen?', - 'return' => 'Terug naar beheerdersoverzicht', + 'return' => 'Terug naar het beheerdersoverzicht', + 'allow' => 'Toestaat', + 'inherit' => 'Overerven', + 'deny' => 'Weigeren', 'group' => [ 'name' => 'Groep', 'name_field' => 'Naam', 'menu_label' => 'Groepen', - 'list_title' => 'Beheer Groepen', - 'new' => 'Nieuwe Beheerdersgroep', + 'list_title' => 'Beheer groepen', + 'new' => 'Nieuwe beheerdersgroep', 'delete_confirm' => 'Weet je zeker dat je deze beheerdersgroep wilt verwijderen?', - 'return' => 'Terug naar groepenoverzicht', + 'return' => 'Terug naar het groepenoverzicht', ], 'preferences' => [ 'not_authenticated' => 'Er is geen geauthenticeerde gebruiker om gegevens voor te laden of op te slaan.' @@ -85,14 +108,28 @@ return [ 'search_prompt' => 'Zoeken...', 'no_records' => 'Er zijn geen resultaten gevonden.', 'missing_model' => 'Geen model opgegeven voor het gedrag (behavior) van de lijst gebruikt in :class.', - 'missing_column' => 'Er zijn geen kolommen voor :columns opgegeven.', - 'missing_columns' => 'Er zijn geen kolommen opgegeven voor de lijst in :class.', + 'missing_column' => 'Er zijn geen kolomdefinities voor :columns.', + 'missing_columns' => 'De gebruikte lijst in :class heeft geen kolommen gedefineerd.', 'missing_definition' => "Het gedrag (behavior) van de lijst bevat geen kolom voor ':field'.", 'behavior_not_ready' => 'Gedrag (behavior) van de lijst is niet geladen. Controleer of makeLists() in de controller is aangeroepen.', - 'invalid_column_datetime' => "Column value ':column' is not a DateTime object, are you missing a \$dates reference in the Model?", + 'invalid_column_datetime' => "De waarde van kolom ':column' is geen DateTime object, mist er een \$dates referentie in het Model?", + 'pagination' => 'Getoonde resultaten: :from-:to van :total', + 'prev_page' => 'Vorige pagina', + 'next_page' => 'Volgende pagina', + 'loading' => 'Laden...', + 'setup_title' => 'Lijst instellingen', + 'setup_help' => 'Selecteer door middel van vinkjes de kolommen welke je in de lijst wilt zien. Je kunt de volgorde van kolommen veranderen door ze omhoog of omlaag te slepen.', + 'records_per_page' => 'Resultaten per pagina', + 'records_per_page_help' => 'Selecteer het aantal resultaten dat per pagina getoond moet worden. Let op: een hoog getal kan voor prestatieproblemen zorgen.' + ], + 'fileupload' => [ + 'attachment' => 'Bijlage', + 'help' => 'Voeg een titel en omschrijving toe aan deze bijlage.', + 'title_label' => 'Titel', + 'description_label' => 'Omschrijving' ], 'form' => [ - 'create_title' => "Maak :name", + 'create_title' => "Nieuwe :name", 'update_title' => "Bewerk :name", 'preview_title' => "Bekijk :name", 'create_success' => ':name is succesvol aangemaakt', @@ -113,6 +150,7 @@ return [ 'undefined_tab' => 'Overig', 'field_off' => 'Uit', 'field_on' => 'Aan', + 'add' => 'Toevoegen', 'apply' => 'Toepassen', 'cancel' => 'Annuleren', 'close' => 'Sluiten', @@ -120,14 +158,26 @@ return [ 'or' => 'of', 'confirm_tab_close' => 'Weet je zeker dat je dit tabblad wilt sluiten? Niet opgeslagen wijzigingen gaan verloren.', 'behavior_not_ready' => 'Gedrag (behavior) van het formulier is niet geladen. Controleer of initForm() in de controller is aangeroepen.', + 'preview_no_files_message' => 'Bestanden zijn niet geüploadet', + 'select' => 'Selecteer', + 'select_all' => 'alles', + 'select_none' => 'niets', + 'select_placeholder' => 'selecteer', + 'insert_row' => 'Rij invoegen', + 'delete_row' => 'Rij verwijderen' ], 'relation' => [ 'missing_definition' => "Het gedrag (behavior) van de relatie bevat geen kolom voor ':field'.", 'missing_model' => "Geen model opgegeven voor het gedrag (behavior) van relatie gebruikt in :class.", 'invalid_action_single' => "Deze actie kan niet worden uitgevoerd op een enkele (singular) relatie.", 'invalid_action_multi' => "Deze actie kan niet worden uitgevoerd op meerdere (multiple) relatie.", + 'help' => "Klik op een item om toe te voegen", + 'related_data' => "Gerelateerde :name data", 'add' => "Toevoegen", - 'add_name' => ":name Toevoegen", + 'add_selected' => "Selectie toevoegen", + 'add_a_new' => "Nieuwe :name toevoegen", + 'cancel' => "Annuleer", + 'add_name' => ":name toevoegen", 'create' => "Maken", 'create_name' => "Maak :name", 'update' => "Update", @@ -136,6 +186,7 @@ return [ 'remove_name' => "Verwijder :name", 'delete' => "Wissen", 'delete_name' => "Wis :name", + 'delete_confirm' => "Weet je het zeker?", ], 'model' => [ 'name' => "Model", @@ -143,25 +194,54 @@ return [ 'missing_id' => "Record ID van het model is niet opgegeven.", 'missing_relation' => "Model ':class' bevat geen definitie voor ':relation'.", 'invalid_class' => "Model :model gebruikt in :class is ongeldig. Het moet de \Model klasse erven (inherit).", - 'mass_assignment_failed' => "Mass assignment failed for Model attribute ':attribute'.", + 'mass_assignment_failed' => "Massa toewijzing voor Model attribute ':attribute' mislukt.", ], 'warnings' => [ 'tips' => 'Systeem configuratie tips', - 'tips_description' => 'Er zijn fouten opgetreden in uw systeem. Los deze problemen op om uw systeem goed te configureren.', + 'tips_description' => 'Er zijn problemen gevonden waar je aandacht aan moet besteden om uw systeem goed te configureren.', 'permissions' => 'De map :name of de submapen zijn niet schrijfbaar voor PHP. Zet de bijhorende rechten voor de webserver in deze map.', - 'extension' => 'De PHP extensie :name is niet geinstalleerd. Installeer deze versie en activeer de extensie.' + 'extension' => 'De PHP extensie :name is niet geïnstalleerd. Installeer deze bibliotheek en activeer de extensie.' ], 'editor' => [ 'menu_label' => 'Bewerker instellingen', - 'menu_description' => 'Beheer code bewerker instellingen.', - 'font_size' => 'Lettertype grootte', - 'tab_size' => 'Tab positie', + 'menu_description' => 'Beheer bewerker instellingen, zoals lettergrootte en kleurschema.', + 'font_size' => 'Lettergrootte', + 'tab_size' => 'Tab grootte', 'use_hard_tabs' => 'Inspringen met tabs', 'code_folding' => 'Code invouwing', 'word_wrap' => 'Tekstterugloop', 'highlight_active_line' => 'Markeer actieve lijnen', 'show_invisibles' => 'Toon verborgen karakters', - 'show_gutter' => 'Toon regelnummers', + 'show_gutter' => 'Toon "goot"', 'theme' => 'Kleurschema', ], -]; \ No newline at end of file + 'tooltips' => [ + 'preview_website' => 'Voorvertoning website' + ], + 'mysettings' => [ + 'menu_label' => 'Mijn instellingen', + 'menu_description' => 'Instellingen gerelateerd aan jouw beheeraccount', + ], + 'myaccount' => [ + 'menu_label' => 'Mijn account', + 'menu_description' => 'Werk accountinstellingen zoals naam, e-mailadres en wachtwoord bij.', + 'menu_keywords' => 'security login' + ], + 'backend_preferences' => [ + 'menu_label' => 'CMS voorkeuren', + 'menu_description' => 'Beheer taalvoorkeur en de weergave van het CMS.', + 'locale' => 'Taal', + 'locale_comment' => 'Selecteer jouw gewenste taal.', + ], + 'access_log' => [ + 'hint' => 'Dit logboek toont een lijst met succesvolle inlogpogingen door beheerders. Registraties blijven :days dagen bewaard..', + 'menu_label' => 'Toegangslogboek', + 'menu_description' => 'Bekijk een lijst met succesvolle inlogpogingen van gebruikers.', + 'created_at' => 'Datum & tijd', + 'login' => 'Gebruikersnaam', + 'ip_address' => 'IP-adres', + 'first_name' => 'Voornaam', + 'last_name' => 'Achternaam', + 'email' => 'E-mailadres', + ], +]; diff --git a/modules/backend/lang/ro/lang.php b/modules/backend/lang/ro/lang.php new file mode 100644 index 000000000..4e8440600 --- /dev/null +++ b/modules/backend/lang/ro/lang.php @@ -0,0 +1,227 @@ + [ + 'invalid_type' => 'Tipul campului folosit este invalid - :type.', + 'options_method_not_exists' => 'Clasa model :model trebuie sa defineasca o metoda :method() returnand optiuni pentru campul ":field".', + ], + 'widget' => [ + 'not_registered' => "Un nume de clasa de widget ':name' nu a fost inregistrat", + 'not_bound' => "Un widget cu numele de clasa ':name' nu a fost mapat la controller", + ], + 'page' => [ + 'untitled' => "Fara titlu", + 'access_denied' => [ + 'label' => "Acces restrictionat", + 'help' => "Nu aveti permisiuni pentru a vizualiza aceasta pagina.", + 'cms_link' => "Inapoi in panoul de administrare", + ], + ], + 'partial' => [ + 'not_found' => "Partialul ':name' nu a fost gasit.", + ], + 'account' => [ + 'sign_out' => 'Deconectare', + 'login' => 'Login', + 'reset' => 'Resetare', + 'restore' => 'Restaurare', + 'login_placeholder' => 'login', + 'password_placeholder' => 'password', + 'forgot_password' => "Ati uitat parola?", + 'enter_email' => "Introduceti email", + 'enter_login' => "Introduceti login", + 'email_placeholder' => "email", + 'enter_new_password' => "Introduceti o noua parola", + 'password_reset' => "Resetare parola", + 'restore_success' => "Un mesaj a fost trimis catre adresa de email cu instructiuni pentru resetarea parolei.", + 'restore_error' => "Utilizatorul ':login' nu a fost gasit in sistem.", + 'reset_success' => "Parola a fost resetata cu succes. Va puteti conecta.", + 'reset_error' => "Date invalide pentru resetarea parolei. Va rugam incercati din nou!", + 'reset_fail' => "Eroare la resetarea parolei!", + 'apply' => 'Aplicare', + 'cancel' => 'Anulare', + 'delete' => 'Stergere', + 'ok' => 'OK', + ], + 'dashboard' => [ + 'menu_label' => 'Dashboard', + 'widget_label' => 'Widget', + 'widget_width' => 'Latime', + 'add_widget' => 'Adauga widget', + 'widget_inspector_title' => 'Configurare widget', + 'widget_inspector_description' => 'Configurare raport widget', + 'widget_columns_label' => 'Latime :columns', + 'widget_columns_description' => 'Latime widget, un numar intre 1 si 10.', + 'widget_columns_error' => 'Va rugam sa introduceti latimea widget-ului ca un numar intre 1 si 10.', + 'columns' => '{1} coloana|[2,Inf] coloane', + 'widget_new_row_label' => 'Forteaza rand nou', + 'widget_new_row_description' => 'Amplasare widget pe un rand nou.', + 'widget_title_label' => 'Titlu widget', + 'widget_title_error' => 'Titlul widget-ului este necesar.', + 'status' => [ + 'widget_title_default' => 'Status sistem', + 'online' => 'online', + 'update_available' => '{0} actualizari disponibile!|{1} actualizare disponibila!|[2,Inf] actualizari disponibile!', + ] + ], + 'user' => [ + 'name' => 'Administrator', + 'menu_label' => 'Administratori', + 'menu_description' => 'Gestionare administratori, grupuri si permisiuni.', + 'list_title' => 'Gestionare Administratori', + 'new' => 'Administrator nou', + 'login' => "Login", + 'first_name' => "Prenume", + 'last_name' => "Nume", + 'full_name' => "Nume complet", + 'email' => "Email", + 'groups' => "Grupuri", + 'groups_comment' => "Specificati grupurile aferente acestei persoane.", + 'avatar' => "Avatar", + 'password' => "Parola", + 'password_confirmation' => "Confirmare Parola", + 'superuser' => "Super Utilizator", + 'superuser_comment' => "Bifati aceasta bifa pentru a permite acestei persoane sa aiba acces deplin.", + 'send_invite' => 'Trimitere invitatie prin email', + 'send_invite_comment' => 'Folositi aceasta bifa pentru a trimite o invitatie prin email catre utilizator', + 'delete_confirm' => 'Sunteti sigur(a) ca vreti sa stergeti acest administrator?', + 'return' => 'Intoarcere la lista de administratori', + 'group' => [ + 'name' => 'Grup', + 'name_field' => 'Nume', + 'menu_label' => 'Grupuri', + 'list_title' => 'Gestionare Grupuri', + 'new' => 'Grup Nou de Administratori', + 'delete_confirm' => 'Sunteti sigur(a) ca vreti sa stergeti acest grup de administratori?', + 'return' => 'Intoarcere la lista de grupuri', + ], + 'preferences' => [ + 'not_authenticated' => 'Nu exista niciun utilizator autentificat pentru care sa se incarce sau salveze preferinte.' + ] + ], + 'list' => [ + 'default_title' => 'Lista', + 'search_prompt' => 'Cautare...', + 'no_records' => 'Nu exista inregistari pentru aceasta fereastra.', + 'missing_model' => 'Lista folosita in clasa :class nu are un model definit.', + 'missing_column' => 'Nu exista denifitii pentru coloana :columns.', + 'missing_columns' => 'Lista folosita in clasa :class nu are coloane definite.', + 'missing_definition' => "Lista nu contine o coloana pentru campul ':field'.", + 'behavior_not_ready' => 'Setarile initiale ale listei nu au fost definite, verificati existenta functiei makeLists() in controller.', + 'invalid_column_datetime' => "Valoarea coloanei ':column' nu este un obiect de tip DateTime, verificati existenta unei referinte \$dates in Model?", + 'pagination' => 'Afisare inregistrari: :from-:to din :total', + 'setup_title' => 'Setare lista', + 'setup_help' => 'Folositi bife pentru a selecta coloanele pe care doriti sa le vedeti in lista. Puteti modifica pozitia coloanelor glisandu-le in sus sau in jos.', + 'records_per_page' => 'Inregistari pe pagina', + 'records_per_page_help' => 'Selectati numarul de inregistari care sa fie afisat pe pagina. Sa tineti cont de faptul ca un numar mare de inregistari pe o singura pagina poate sa reduca performanta.', + 'apply_changes' => 'Aplicare schimbari', + 'cancel' => 'Anulare' + ], + 'form' => [ + 'create_title' => "Nou :name", + 'update_title' => "Editare :name", + 'preview_title' => "Previzualizare :name", + 'create_success' => ':name a fost creat cu succes', + 'update_success' => ':name a fost actualizat cu succes', + 'delete_success' => ':name a fost sters cu succes', + 'missing_id' => "ID-ul inregistrarii formularului nu a fost specificat.", + 'missing_model' => 'Formularul folosit in clasa :class nu are un model definit.', + 'missing_definition' => "Formularul nu contine un camp pentru campul ':field'.", + 'not_found' => 'Inregistrarea formularului cu ID-ul :id nu a putut fi gasita.', + 'create' => 'Creare', + 'create_and_close' => 'Creare si inchidere', + 'creating' => 'Se creeaza...', + 'save' => 'Salvare', + 'save_and_close' => 'Salvare si inchidere', + 'saving' => 'Se salveaza...', + 'delete' => 'Stergere', + 'deleting' => 'Se sterge...', + 'undefined_tab' => 'Altele', + 'field_off' => 'Dezactivat', + 'field_on' => 'Activat', + 'add' => 'Adaugare', + 'apply' => 'Aplicare', + 'cancel' => 'Anulare', + 'close' => 'Inchidere', + 'ok' => 'OK', + 'or' => 'sau', + 'confirm_tab_close' => 'Sunteti sigur(a) ca doriti sa inchideti acest tab? Modificarile nesalvate vor fi pierdute.', + 'behavior_not_ready' => 'Setarile initiale ale formularului nu au fost definite, verificati existenta functiei initForm() in controller.', + 'preview_no_files_message' => 'Fisierele nu au fost incarcate', + 'select' => 'Selectare', + 'select_all' => 'toate', + 'select_none' => 'niciunul', + ], + 'relation' => [ + 'missing_definition' => "Relatia nu contine definitii pentru campul ':field'.", + 'missing_model' => "Relatia folosita in clasa :class nu are un model definit.", + 'invalid_action_single' => "Aceasta actiune nu poate fi realizata pentru o relatie singulara.", + 'invalid_action_multi' => "Aceasta actiune nu poate fi realizata pentru o relatie multipla.", + 'add' => "Adaugare", + 'add_name' => "Adaugare :name", + 'create' => "Creare", + 'create_name' => "Creare :name", + 'update' => "Actualizare", + 'update_name' => "Actualizare :name", + 'remove' => "Inlaturare", + 'remove_name' => "Inlaturare :name", + 'delete' => "Stergere", + 'delete_name' => "Stergere :name", + ], + 'model' => [ + 'name' => "Model", + 'not_found' => "Modelul ':class' cu ID-ul :id nu a putut fi gasit", + 'missing_id' => "Nu exista niciun ID specificat pentru care sa se realizeze cautarea inregistrarii modelului.", + 'missing_relation' => "Modelul ':class' nu contine o definitie pentru relatia ':relation'.", + 'invalid_class' => "Modelul :model folosit in clasa :class nu este valid, trebuie sa mosteneasca clasa \Model.", + 'mass_assignment_failed' => "Atribuirea in masa a esuat pentru atributul modelului ':attribute'.", + ], + 'warnings' => [ + 'tips' => 'Sfaturi pentru configurarea sistemului', + 'tips_description' => 'Exista anumite conditii care necesita atentie pentru a configura sistemul corect.', + 'permissions' => 'Directorul :name si subdirectoarele sale nu au permisiuni de scriere pentru PHP. Va rugam sa setati permisiunile corespunzatoare pentru acest director.', + 'extension' => 'Libraria PHP :name nu este instalata. Va rugam sa instalati aceasta librarie si apoi sa activati extensia.' + ], + 'editor' => [ + 'menu_label' => 'Preferinte Editor Cod', + 'menu_description' => 'Personalizati preferintele editorului de cod, preferinte precum dimensiunea fontului si culorile folosite.', + 'font_size' => 'Dimensiune font', + 'tab_size' => 'Lungime tab', + 'use_hard_tabs' => 'Indentare folosind tab-uri', + 'code_folding' => 'Code folding', + 'word_wrap' => 'Word wrap', + 'highlight_active_line' => 'Evidentiere linie activa', + 'show_invisibles' => 'Arata caractere invizibile', + 'show_gutter' => 'Afiseaza panou', + 'theme' => 'Schema culori', + ], + 'tooltips' => [ + 'preview_website' => 'Previzualizare site' + ], + 'mysettings' => [ + 'menu_label' => 'Setarile mele', + 'menu_description' => 'Setarile in legatura cu contul de administrare', + ], + 'myaccount' => [ + 'menu_label' => 'Contul meu', + 'menu_description' => 'Actualizati datele contului, precum nume, adresa de email si parola.', + 'menu_keywords' => 'securitate login' + ], + 'backend_preferences' => [ + 'menu_label' => 'Preferinte administrare', + 'menu_description' => 'Gestionati preferinte limba si setari aspect panou de administrare.', + 'locale' => 'Limba', + 'locale_comment' => 'Selectati limba dorita.', + ], + 'access_log' => [ + 'hint' => 'Acest jurnal afiseaza o lista de conectari reusite, realizate de catre administratori. Inregistrarile sunt pastrate pentru un numar total de :days zile.', + 'menu_label' => 'Jurnal acces', + 'menu_description' => 'Vizualizati o lista de conectari reusite, realizate de catre administratori.', + 'created_at' => 'Data & Ora', + 'login' => 'Login', + 'ip_address' => 'Adresa IP', + 'first_name' => 'Prenume', + 'last_name' => 'Nume', + 'email' => 'Email', + ], +]; diff --git a/modules/backend/lang/ru/lang.php b/modules/backend/lang/ru/lang.php index b5986ba3d..5af2ab8df 100644 --- a/modules/backend/lang/ru/lang.php +++ b/modules/backend/lang/ru/lang.php @@ -47,7 +47,23 @@ return [ 'menu_label' => 'Панель управления', 'widget_label' => 'Виджет', 'widget_width' => 'Ширина', + 'full_width' => 'полная ширина', 'add_widget' => 'Добавить виджет', + 'widget_inspector_title' => 'Конфигурации виджета', + 'widget_inspector_description' => 'Настройка отображения виджета', + 'widget_columns_label' => 'Ширина :columns', + 'widget_columns_description' => 'Ширина виджета может варьироваться от 1 до 10.', + 'widget_columns_error' => 'Пожалуйста, выберите ширину виджета.', + 'columns' => '{1} колонка|[2,4] колонки|[5,Inf] колонок', + 'widget_new_row_label' => 'Новая строка', + 'widget_new_row_description' => 'Поставить виджет с новой строки.', + 'widget_title_label' => 'Заголовок', + 'widget_title_error' => 'Заголовок виджета обязателен.', + 'status' => [ + 'widget_title_default' => 'Статус системы', + 'online' => 'Онлайн', + 'update_available' => '{0} нет новый обновлений!|{1} доступно новое обновление!|[2,Inf] доступны новые обновления!', + ] ], 'user' => [ 'name' => 'Администратора', @@ -71,6 +87,9 @@ return [ 'send_invite_comment' => 'Используйте эту опцию, чтобы отправить приглашение пользователю по электронной почте', 'delete_confirm' => 'Вы действительно хотите удалить этого администратора?', 'return' => 'Вернуться к списку администраторов', + 'allow' => 'Разрешить', + 'inherit' => 'Наследовать', + 'deny' => 'Запретить', 'group' => [ 'name' => 'Группы', 'name_field' => 'Название', @@ -94,6 +113,20 @@ return [ 'missing_definition' => "Поведение списка не содержит столбец для ':field'.", 'behavior_not_ready' => 'Поведение списка не было инициализировано, проверьте вызов makeLists() в вашем контроллере.', 'invalid_column_datetime' => "Значение столбца ':column' не является объектом DateTime. Отсутствует \$dates ссылка в модели?", + 'pagination' => 'Отображено записей: :from-:to из :total', + 'prev_page' => 'Предыдущая страница', + 'next_page' => 'Следующая страница', + 'loading' => 'Загрузка...', + 'setup_title' => 'Настройка списка', + 'setup_help' => 'Используйте флажки для выбора колонок, которые вы хотите видеть в списке. Вы можете изменить положение столбцов, перетаскивая их вверх или вниз.', + 'records_per_page' => 'Записей на странице', + 'records_per_page_help' => 'Выберите количество записей на странице для отображения. Обратите внимание, что большое количество записей на одной странице может привести к снижению производительности.' + ], + 'fileupload' => [ + 'attachment' => 'Приложение', + 'help' => 'Добавьте заголовок и описание для этого вложения.', + 'title_label' => 'Название', + 'description_label' => 'Описание' ], 'form' => [ 'create_title' => "Создание :name", @@ -129,14 +162,22 @@ return [ 'select' => 'Выбрать', 'select_all' => 'все', 'select_none' => 'ничего', + 'select_placeholder' => 'Пожалуйста, выберите', + 'insert_row' => 'Вставить строку', + 'delete_row' => 'Удалить строку' ], 'relation' => [ 'missing_definition' => "Поведение отношения не содержит определения для ':field'.", 'missing_model' => "Для поведения отношения, используемого в :class не определена модель.", 'invalid_action_single' => "Это действие не может быть выполнено для особого отношения.", 'invalid_action_multi' => "Это действие не может быть выполнено для множественных отношений.", + 'help' => "Нажмите на элемент, который нужно добавить", + 'related_data' => "Связанные :name данные", 'add' => "Добавить", - 'add_name' => "Добавление :name", + 'add_selected' => "Добавить выбранные", + 'add_a_new' => "Добавить новый :name", + 'cancel' => "Отмена", + 'add_name' => "Добавить :name", 'create' => "Создать", 'create_name' => "Создание :name", 'update' => "Update", @@ -145,6 +186,7 @@ return [ 'remove_name' => "Удаление :name", 'delete' => "Удалить", 'delete_name' => "Удаление :name", + 'delete_confirm' => "Вы уверены?", ], 'model' => [ 'name' => "Модель", diff --git a/modules/backend/models/BackendPreferences.php b/modules/backend/models/BackendPreferences.php index b365af49a..c7845c61f 100644 --- a/modules/backend/models/BackendPreferences.php +++ b/modules/backend/models/BackendPreferences.php @@ -38,6 +38,7 @@ class BackendPreferences extends Model 'de' => [Lang::get('system::lang.locale.de'), 'flag-de'], 'fr' => [Lang::get('system::lang.locale.fr'), 'flag-fr'], 'it' => [Lang::get('system::lang.locale.it'), 'flag-it'], + 'ro' => [Lang::get('system::lang.locale.ro'), 'flag-ro'], ]; // Sort the locales alphabetically diff --git a/modules/backend/widgets/Form.php b/modules/backend/widgets/Form.php index d8e2e1304..e3d973130 100644 --- a/modules/backend/widgets/Form.php +++ b/modules/backend/widgets/Form.php @@ -45,7 +45,7 @@ class Form extends WidgetBase /** * @var array Collection of all fields used in this form. */ - protected $allFields = []; + protected $fields = []; /** * @var array Collection of all form widgets used in this form. @@ -179,10 +179,10 @@ class Form extends WidgetBase public function renderField($field, $options = []) { if (is_string($field)) { - if (!isset($this->allFields[$field])) + if (!isset($this->fields[$field])) throw new ApplicationException(Lang::get('backend::lang.form.missing_definition', compact('field'))); - $field = $this->allFields[$field]; + $field = $this->fields[$field]; } if (!isset($options['useContainer'])) $options['useContainer'] = true; @@ -241,7 +241,7 @@ class Form extends WidgetBase $this->model->fill($data); $this->data = (object) array_merge((array) $this->data, (array) $data); - foreach ($this->allFields as $field) + foreach ($this->fields as $field) $field->value = $this->getFieldValue($field); return $data; @@ -274,10 +274,10 @@ class Form extends WidgetBase if (($updateFields = post('fields')) && is_array($updateFields)) { foreach ($updateFields as $field) { - if (!isset($this->allFields[$field])) + if (!isset($this->fields[$field])) continue; - $fieldObject = $this->allFields[$field]; + $fieldObject = $this->fields[$field]; $result['#' . $fieldObject->getId('group')] = $this->makePartial('field', ['field' => $fieldObject]); } } @@ -357,7 +357,7 @@ class Form extends WidgetBase /* * Bind all form widgets to controller */ - foreach ($this->allFields as $field) { + foreach ($this->fields as $field) { if ($field->type != 'widget') continue; @@ -413,7 +413,7 @@ class Form extends WidgetBase continue; } - $this->allFields[$name] = $fieldObj; + $this->fields[$name] = $fieldObj; switch (strtolower($addToArea)) { case 'primary': @@ -589,7 +589,7 @@ class Form extends WidgetBase */ public function getFields() { - return $this->allFields; + return $this->fields; } /** @@ -599,7 +599,7 @@ class Form extends WidgetBase */ public function getField($field) { - return $this->allFields[$field]; + return $this->fields[$field]; } /** @@ -621,10 +621,10 @@ class Form extends WidgetBase public function getFieldValue($field) { if (is_string($field)) { - if (!isset($this->allFields[$field])) + if (!isset($this->fields[$field])) throw new ApplicationException(Lang::get('backend::lang.form.missing_definition', compact('field'))); - $field = $this->allFields[$field]; + $field = $this->fields[$field]; } $columnName = $field->columnName; @@ -694,7 +694,7 @@ class Form extends WidgetBase * Boolean fields (checkbox, switch) won't be present value FALSE * Number fields should be converted to integers */ - foreach ($this->allFields as $field) { + foreach ($this->fields as $field) { if (!in_array($field->type, ['switch', 'checkbox', 'number'])) continue; diff --git a/modules/backend/widgets/Lists.php b/modules/backend/widgets/Lists.php index a0cce838b..c49b08648 100644 --- a/modules/backend/widgets/Lists.php +++ b/modules/backend/widgets/Lists.php @@ -46,7 +46,7 @@ class Lists extends WidgetBase protected $visibleColumns; /** - * @var array All available columns. + * @var array Collection of all list columns used in this list. */ protected $columns; @@ -265,9 +265,10 @@ class Lists extends WidgetBase protected function prepareModel() { $query = $this->model->newQuery(); - $selects = [$this->model->getTable().'.*']; - $tables = ['base'=>$this->model->getTable()]; + $primaryTable = $this->model->getTable(); + $selects = [$primaryTable.'.*']; $joins = []; + $withs = []; /* * Extensibility @@ -276,70 +277,134 @@ class Lists extends WidgetBase $this->fireEvent('list.extendQueryBefore', [$query]); /* - * Related custom selects, must come first + * Prepare searchable column names */ - foreach ($this->getVisibleListColumns() as $column) { - if (!isset($column->relation) || !isset($column->sqlSelect)) - continue; + $primarySearchable = []; + $relationSearchable = []; - if (!$this->model->hasRelation($column->relation)) - throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', ['class'=>get_class($this->model), 'relation'=>$column->relation])); + $columnsToSearch = []; + if (!empty($this->searchTerm) && ($searchableColumns = $this->getSearchableColumns())) { + foreach ($searchableColumns as $column) { + /* + * Related + */ + if ($this->isColumnRelated($column)) { + $table = $this->model->makeRelation($column->relation)->getTable(); + $columnName = isset($column->sqlSelect) + ? DbDongle::raw($this->parseTableName($column->sqlSelect, $table)) + : $table . '.' . $column->nameFrom; - $alias = Db::getQueryGrammar()->wrap($column->columnName); - $table = $this->model->makeRelation($column->relation)->getTable(); - $relationType = $this->model->getRelationType($column->relation); - $sqlSelect = $this->parseTableName($column->sqlSelect, $table); + $relationSearchable[$column->relation][] = $columnName; + } + /* + * Primary + */ + else { + $columnName = isset($column->sqlSelect) + ? DbDongle::raw($this->parseTableName($column->sqlSelect, $primaryTable)) + : $primaryTable . '.' . $column->columnName; - if (in_array($relationType, ['hasMany', 'belongsToMany', 'morphToMany', 'morphedByMany', 'morphMany', 'attachMany', 'hasManyThrough'])) - $selects[] = DbDongle::raw("group_concat(" . $sqlSelect . " separator ', ') as ". $alias); - else - $selects[] = DbDongle::raw($sqlSelect . ' as '. $alias); - - $joins[] = $column->relation; - $tables[$column->relation] = $table; + $primarySearchable[] = $columnName; + } + } } - if ($joins) - $query->joinWith(array_unique($joins), false); + /* + * Prepare related eager loads (withs) and custom selects (joins) + */ + foreach ($this->getVisibleListColumns() as $column) { + + if (!$this->isColumnRelated($column) || (!isset($column->sqlSelect) && !isset($column->nameFrom))) + continue; + + if (isset($column->nameFrom)) + $withs[] = $column->relation; + + $joins[] = $column->relation; + } + + /* + * Include any relation constraints + */ + if ($joins) { + foreach (array_unique($joins) as $join) { + /* + * Apply a supplied search term for relation columns and + * constrain the query only if there is something to search for + */ + $columnsToSearch = array_get($relationSearchable, $join, []); + + if (count($columnsToSearch) > 0) { + $query->whereHas($join, function($_query) use ($columnsToSearch) { + $_query->searchWhere($this->searchTerm, $columnsToSearch); + }); + } + } + } + + /* + * Add eager loads to the query + */ + if ($withs) { + $query->with(array_unique($withs)); + } /* * Custom select queries */ foreach ($this->getVisibleListColumns() as $column) { - if (!isset($column->sqlSelect) || isset($column->relation)) + if (!isset($column->sqlSelect)) continue; $alias = Db::getQueryGrammar()->wrap($column->columnName); - $sqlSelect = $this->parseTableName($column->sqlSelect, $tables['base']); - $selects[] = DbDongle::raw($sqlSelect . ' as '. $alias); + + /* + * Relation column + */ + if (isset($column->relation)) { + $table = $this->model->makeRelation($column->relation)->getTable(); + $relationType = $this->model->getRelationType($column->relation); + $sqlSelect = $this->parseTableName($column->sqlSelect, $table); + + /* + * Manipulate a count query for the sub query + */ + $relationObj = $this->model->{$column->relation}(); + $countQuery = $relationObj->getRelationCountQuery($relationObj->getRelated()->newQuery(), $query); + + $joinSql = $this->isColumnRelated($column, true) + ? DbDongle::raw("group_concat(" . $sqlSelect . " separator ', ')") + : DbDongle::raw($sqlSelect); + + $joinSql = $countQuery->select($joinSql)->toSql(); + + $selects[] = Db::raw("(".$joinSql.") as ".$alias); + } + /* + * Primary column + */ + else { + $sqlSelect = $this->parseTableName($column->sqlSelect, $primaryTable); + $selects[] = DbDongle::raw($sqlSelect . ' as '. $alias); + } } /* - * Handle a supplied search term + * Apply a supplied search term for primary columns */ - if (!empty($this->searchTerm) && ($searchableColumns = $this->getSearchableColumns())) { - $query->orWhere(function($innerQuery) use ($searchableColumns, $tables) { - $columnsToSearch = []; - foreach ($searchableColumns as $column) { - - if (isset($column->sqlSelect)) { - $table = (isset($column->relation)) ? $tables[$column->relation] : 'base'; - $columnName = DbDongle::raw($this->parseTableName($column->sqlSelect, $table)); - } - else - $columnName = $tables['base'] . '.' . $column->columnName; - - $columnsToSearch[] = $columnName; - } - - $innerQuery->searchWhere($this->searchTerm, $columnsToSearch); + if (count($primarySearchable) > 0) { + $query->orWhere(function($innerQuery) use ($primarySearchable) { + $innerQuery->searchWhere($this->searchTerm, $primarySearchable); }); } /* - * Handle sorting + * Apply sorting */ if ($sortColumn = $this->getSortColumn()) { + if (($column = array_get($this->columns, $sortColumn)) && $column->sqlSelect) + $sortColumn = $column->sqlSelect; + $query->orderBy($sortColumn, $this->sortDirection); } @@ -350,15 +415,17 @@ class Lists extends WidgetBase $callback($query); } + /* + * Add custom selects + */ + $query->select($selects); + /* * Extensibility */ Event::fire('backend.list.extendQuery', [$this, $query]); $this->fireEvent('list.extendQuery', [$query]); - // Grouping due to the joinWith() call - $query->select($selects); - $query->groupBy($this->model->getQualifiedKeyName()); return $query; } @@ -414,12 +481,31 @@ class Lists extends WidgetBase return Html::attributes(['onclick' => $recordOnClick]); } + /** + * Get all the registered columns for the instance. + * @return array + */ + public function getColumns() + { + return $this->columns ?: $this->defineListColumns(); + } + + /** + * Get a specified column object + * @param string $column + * @return mixed + */ + public function getColumn($column) + { + return $this->columns[$column]; + } + /** * Returns the list columns that are visible by list settings or default */ protected function getVisibleListColumns() { - $definitions = $this->getListColumns(); + $definitions = $this->defineListColumns(); $columns = []; /* @@ -457,7 +543,7 @@ class Lists extends WidgetBase /** * Builds an array of list columns with keys as the column name and values as a ListColumn object. */ - protected function getListColumns() + protected function defineListColumns() { if (!isset($this->config->columns) || !is_array($this->config->columns) || !count($this->config->columns)) throw new ApplicationException(Lang::get('backend::lang.list.missing_columns', ['class'=>get_class($this->controller)])); @@ -555,16 +641,33 @@ class Lists extends WidgetBase */ public function getColumnValue($record, $column) { + + $columnName = $column->columnName; + /* - * If the column is a relation, it will be a custom select, + * Handle taking name from model attribute. + */ + if ($column->nameFrom) { + if (!array_key_exists($columnName, $record->getRelations())) + $value = null; + elseif ($this->isColumnRelated($column, true)) + $value = implode(', ', $record->{$columnName}->lists($column->nameFrom)); + elseif ($this->isColumnRelated($column)) + $value = $record->{$columnName}->{$column->nameFrom}; + else + $value = $record->{$column->nameFrom}; + } + /* + * Otherwise, if the column is a relation, it will be a custom select, * so prevent the Model from attempting to load the relation * if the value is NULL. */ - $columnName = $column->columnName; - if ($record->hasRelation($columnName) && array_key_exists($columnName, $record->attributes)) - $value = $record->attributes[$columnName]; - else - $value = $record->{$columnName}; + else { + if ($record->hasRelation($columnName) && array_key_exists($columnName, $record->attributes)) + $value = $record->attributes[$columnName]; + else + $value = $record->{$columnName}; + } if (method_exists($this, 'eval'. studly_case($column->type) .'TypeValue')) $value = $this->{'eval'. studly_case($column->type) .'TypeValue'}($record, $column, $value); @@ -613,6 +716,9 @@ class Lists extends WidgetBase { return $this->controller->makePartial($column->path ?: $column->columnName, [ 'listColumn' => $column, + 'listRecord' => $record, + 'listValue' => $value, + 'column' => $column, 'record' => $record, 'value' => $value ]); @@ -738,7 +844,7 @@ class Lists extends WidgetBase */ protected function getSearchableColumns() { - $columns = $this->columns ?: $this->getListColumns(); + $columns = $this->getColumns(); $searchable = []; foreach ($columns as $column) { @@ -848,7 +954,7 @@ class Lists extends WidgetBase if ($this->sortableColumns !== null) return $this->sortableColumns; - $columns = $this->columns ?: $this->getListColumns(); + $columns = $this->getColumns(); $sortable = []; foreach ($columns as $column) { @@ -912,12 +1018,12 @@ class Lists extends WidgetBase /* * Force all columns invisible */ - $allColumns = $this->getListColumns(); - foreach ($allColumns as $column) { + $columns = $this->defineListColumns(); + foreach ($columns as $column) { $column->invisible = true; } - return array_merge($allColumns, $this->getVisibleListColumns()); + return array_merge($columns, $this->getVisibleListColumns()); } // @@ -962,4 +1068,38 @@ class Lists extends WidgetBase return $this->onRefresh(); } + // + // Helpers + // + + /** + * Check if column refers to a relation of the model + * @param ListColumn $column List column object + * @param boolean $multi If set, returns true only if the relation is a "multiple relation type" + * @return boolean + */ + protected function isColumnRelated($column, $multi = false) + { + if (!isset($column->relation)) + return false; + + if (!$this->model->hasRelation($column->relation)) + throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', ['class'=>get_class($this->model), 'relation'=>$column->relation])); + + if (!$multi) + return true; + + $relationType = $this->model->getRelationType($column->relation); + + return in_array($relationType, [ + 'hasMany', + 'belongsToMany', + 'morphToMany', + 'morphedByMany', + 'morphMany', + 'attachMany', + 'hasManyThrough' + ]); + } + } \ No newline at end of file diff --git a/modules/backend/widgets/ReportContainer.php b/modules/backend/widgets/ReportContainer.php index 623649777..161048c88 100644 --- a/modules/backend/widgets/ReportContainer.php +++ b/modules/backend/widgets/ReportContainer.php @@ -117,7 +117,7 @@ class ReportContainer extends WidgetBase { $sizes = []; for ($i = 1; $i <= 10; $i++) - $sizes[$i] = $i < 10 ? $i : $i.' (full width)'; + $sizes[$i] = $i < 10 ? $i : $i.' (' . Lang::get('backend::lang.dashboard.full_width') . ')'; $this->vars['sizes'] = $sizes; $this->vars['widgets'] = WidgetManager::instance()->listReportWidgets(); diff --git a/modules/backend/widgets/form/partials/_field_partial.htm b/modules/backend/widgets/form/partials/_field_partial.htm index cfa8e9d87..960bd0c4b 100644 --- a/modules/backend/widgets/form/partials/_field_partial.htm +++ b/modules/backend/widgets/form/partials/_field_partial.htm @@ -1,2 +1,9 @@ -controller->makePartial($field->path ?: $field->columnName) ?> \ No newline at end of file +controller->makePartial($field->path ?: $field->columnName, [ + 'formModel' => $formModel, + 'formField' => $field, + 'formValue' => $field->value, + 'model' => $formModel, + 'field' => $field, + 'value' => $field->value +]) ?> \ No newline at end of file diff --git a/modules/backend/widgets/lists/partials/_setup_form.htm b/modules/backend/widgets/lists/partials/_setup_form.htm index 0ae7b83eb..3d51f8d21 100644 --- a/modules/backend/widgets/lists/partials/_setup_form.htm +++ b/modules/backend/widgets/lists/partials/_setup_form.htm @@ -54,13 +54,13 @@ data-request="getEventHandler('onApplySetup') ?>" data-dismiss="popup" data-stripe-load-indicator> - + \ No newline at end of file diff --git a/modules/backend/widgets/reportcontainer/partials/_new_widget_popup.htm b/modules/backend/widgets/reportcontainer/partials/_new_widget_popup.htm index 6121d674d..e27c7410b 100644 --- a/modules/backend/widgets/reportcontainer/partials/_new_widget_popup.htm +++ b/modules/backend/widgets/reportcontainer/partials/_new_widget_popup.htm @@ -10,7 +10,7 @@