diff --git a/docs/en/04_Changelogs/rc/4.3.0-rc1.md b/docs/en/04_Changelogs/rc/4.3.0-rc1.md new file mode 100644 index 000000000..7584cc59b --- /dev/null +++ b/docs/en/04_Changelogs/rc/4.3.0-rc1.md @@ -0,0 +1,13 @@ +# 4.3.0-rc1 + + + +## Change Log + +### Features and Enhancements + + * 2018-07-25 [79a5ea3](https://github.com/silverstripe/recipe-cms/commit/79a5ea3dace336558ef4d5be4a86cc1a0e84badc) Add versioned-admin (Luke Edwards) + +### Bugfixes + + * 2018-06-15 [5e4ad34](https://github.com/silverstripe/silverstripe-installer/commit/5e4ad341622565cc998bd8537ad3ec7a6a6a7913) Fix incorrect base recipe dependency (Damian Mooyman) diff --git a/lang/ar.yml b/lang/ar.yml index 19381589f..518bf1344 100644 --- a/lang/ar.yml +++ b/lang/ar.yml @@ -150,7 +150,4 @@ ar: LOGIN: دخول LOSTPASSWORDHEADER: 'كلمة مرور مفقودة' NOTEPAGESECURED: 'هذه الصفحة محمية بكلمة مرور ، أدخل بيانات دخولك بالأسفل ليتم السماح لك بالوصول للصفحة' - NOTERESETLINKINVALID: "

رابط إعادة تعيين كلمة المرور غير صحيح أو نفذت صلاحيته.

\n

\nيمكنك طلب رابط جديد <\"{a href=\"{link1\"> هنا \n أو تغيير كلمة المرور الخاصة بك بعد <\"{a href=\"{link2\"> تسجيل دخولك.\n

" NOTERESETPASSWORD: 'أدخل بريدك الإلكتروني و سيتم إرسال رابط إعادة تهيئة كلمة المرور ' - PASSWORDSENTHEADER: 'رابط استعادة كلمة المرور تم إرساله إلى ''{بريدك}''' - PASSWORDSENTTEXT: 'شكرا لك! تم إرسال رابط إعادة تعيين إلى ''{بريدك}''، بشرط وجود حساب قائم بالنسبة لعنوان هذا البريد الإلكتروني .' diff --git a/lang/bg.yml b/lang/bg.yml index 3e1eba6a0..241841f1e 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -313,7 +313,4 @@ bg: LOGOUT: Изход LOSTPASSWORDHEADER: 'Забравена парола' NOTEPAGESECURED: 'Тази страница е защитена. Въведете вашите данни по-долу, за да продължите.' - NOTERESETLINKINVALID: '

Връзката за нулиране на парола не е вярна или е просрочена.

Можете да заявите нова тук или да промените паролата си след като влезете.

' NOTERESETPASSWORD: 'Въведете вашият email адрес и ще ви изпратим линк, с който ще можете да смените паролата си' - PASSWORDSENTHEADER: 'Връзка за нулиране на парола беше изпратена на ''{email}''' - PASSWORDSENTTEXT: 'Благодарим ви! Връзка за нулиране на паролата беше изпратен на ''{email}'', ако съществува акаунт с този имейл адрес.' diff --git a/lang/cs.yml b/lang/cs.yml index 4a3cae3ab..759f0268e 100644 --- a/lang/cs.yml +++ b/lang/cs.yml @@ -194,7 +194,4 @@ cs: LOGIN: Přihlásit LOSTPASSWORDHEADER: 'Zapomenuté heslo' NOTEPAGESECURED: 'Tato stránka je zabezpečená. Vložte své přihlašovací údaje a my Vám zároveň pošleme práva.' - NOTERESETLINKINVALID: '

Odkaz na resetování hesla není platný nebo je prošlý.

Můžete požádat o nový zde nebo změňte své heslo až se přihlásíte.

' NOTERESETPASSWORD: 'Zadejte svou e-mailovou adresu a bude vám zaslán nulovací odkaz pro Vaše heslo' - PASSWORDSENTHEADER: 'Odkaz na resetování hesla byl odeslán na ''{email}''' - PASSWORDSENTTEXT: 'Děkujeme! Resetovací odkaz byl odeslán na ''{email}'', pokud účet existuje pro tuto emailovou adresu.' diff --git a/lang/da.yml b/lang/da.yml index 8dc512b23..3fe62969a 100644 --- a/lang/da.yml +++ b/lang/da.yml @@ -1,5 +1,328 @@ da: + SilverStripe\Admin\LeftAndMain: + VersionUnknown: ukendt + SilverStripe\AssetAdmin\Forms\UploadField: + Dimensions: Dimensioner + EDIT: Rediger + EDITINFO: 'Rediger denne fil' + REMOVE: Fjern + SilverStripe\Control\ChangePasswordEmail_ss: + CHANGEPASSWORDFOREMAIL: 'Koden for kontoen med email addressen {email} er ændret. Hvis du ikke har skiftet din kode, så skift venligst din kode ved at klikke på linket herunder' + CHANGEPASSWORDTEXT1: 'Du skiftede dit kodeord for' + CHANGEPASSWORDTEXT3: 'Skift kodeord' + HELLO: Hej + SilverStripe\Control\Email\ForgotPasswordEmail_ss: + HELLO: Hej + TEXT1: 'Her er din' + TEXT2: 'link til at nulstille dit kodeord' + TEXT3: for + SilverStripe\Control\RequestProcessor: + INVALID_REQUEST: 'Ugyldig forespørgsel' + REQUEST_ABORTED: 'Forespørgsel annulleret' + SilverStripe\Core\Manifest\VersionProvider: + VERSIONUNKNOWN: Ukendt + SilverStripe\Forms\CheckboxField: + NOANSWER: Nej + YESANSWER: Ja + SilverStripe\Forms\CheckboxSetField_ss: + NOOPTIONSAVAILABLE: 'Ingen tilgængelige muligheder' + SilverStripe\Forms\ConfirmedPasswordField: + ATLEAST: 'Kodeord skal være mindst {min} tegn lang.' + BETWEEN: 'Kodeord skal være {min} til {max} karakterer lang.' + CURRENT_PASSWORD_ERROR: 'Det nuværende kodeord du har indtastet er ikke korrekt.' + CURRENT_PASSWORD_MISSING: 'Du skal indtaste dit nuværende kodeord.' + LOGGED_IN_ERROR: 'Du skal være logget ind for at skifte dit kodeord.' + MAXIMUM: 'Kodeord må maks være {max} tegn lang' + SHOWONCLICKTITLE: 'Skift kodeord' + SilverStripe\Forms\CurrencyField: + CURRENCYSYMBOL: DKK + SilverStripe\Forms\DateField: + VALIDDATEFORMAT2: 'Indtats venligst et gyldigt datoformat ({format})' + VALIDDATEMAXDATE: 'Din dato skal være ældre end eller matche den maksimalt tilladte dato ({date})' + VALIDDATEMINDATE: 'Din dato skal være yngre end eller matche den minimum tilladte dato ({date})' + SilverStripe\Forms\DatetimeField: + VALIDDATEMAXDATETIME: 'Din dato skal være ældre end eller matche den maksimalt tilladte dato ({datetime})' + VALIDDATETIMEFORMAT: 'Indtats venligst et gyldigt dato- og tidsformat ({format})' + VALIDDATETIMEMINDATE: 'Din dato skal være yngre end eller matche den minimum tilladte dato og tid ({datetime})' + SilverStripe\Forms\DropdownField: + CHOOSE: (Vælg) + CHOOSE_MODEL: '(Vælg {name})' + SOURCE_VALIDATION: 'Venligst vælg en eksisterende værdi fra listen. {value} er ikke en tilladt mulighed' + SilverStripe\Forms\EmailField: + VALIDATION: 'Indtast venligst en emailadresse' + SilverStripe\Forms\FileUploadReceiver: + FIELDNOTSET: 'Fil information ikke fundet' + SilverStripe\Forms\Form: + BAD_METHOD: 'Denne form kræver en {method} indsendelse' + CSRF_EXPIRED_MESSAGE: 'Din session er udløbet. Venligst gensend formularen.' + CSRF_FAILED_MESSAGE: 'Det ser ud til der har været et teknisk problem. Klik venligst på tilbageknappen, tryk opdater i din browser og prøv igen.' + VALIDATIONPASSWORDSDONTMATCH: 'Kodeordene er ikke identiske' + VALIDATIONPASSWORDSNOTEMPTY: 'Kodeord kan ikke være tomme' + VALIDATIONSTRONGPASSWORD: 'Kodeord skal mindst have et tal og et alfanumerisk tegn' + VALIDATOR: Validering + VALIDCURRENCY: 'Indtast venligst en gyldig valuta' + SilverStripe\Forms\FormField: + EXAMPLE: 'f.eks. {format}' + NONE: ingen + SilverStripe\Forms\FormScaffolder: + TABMAIN: Primær SilverStripe\Forms\GridField\GridField: - Filter: Filter + Add: 'Tilføj {name}' + CSVEXPORT: 'Eksporter til CSV' + CSVIMPORT: 'Importer CSV' + Filter: Filtrer + FilterBy: 'Filtrer på' + Find: Find + LinkExisting: 'Link eksisterende' + NewRecord: 'Ny {type}' + NoItemsFound: 'Ingen elementer fundet' + PRINTEDAT: 'Printet d.' + PRINTEDBY: 'Printet af' + PlaceHolder: 'Find {type}' + PlaceHolderWithLabels: 'Find {type} på {name}' + Print: Print + RelationSearch: Relationssøgning + ResetFilter: Nulstil + SilverStripe\Forms\GridField\GridFieldDeleteAction: + Delete: Slet + DeletePermissionsFailure: 'Ingen slette rettigheder' + EditPermissionsFailure: 'Ingen rettighed til at fjerne emnet' + UnlinkRelation: Fjern + SilverStripe\Forms\GridField\GridFieldDetailForm: + CancelBtn: Annuller + Create: Opret + Delete: Slet + DeletePermissionsFailure: 'Ingen slette rettigheder' + Deleted: 'Slet {type} {name}' + Save: Gem + SilverStripe\Forms\GridField\GridFieldEditButton: + EDIT: Rediger + SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: + UnlinkSelfFailure: 'Kan ikke fjerne dig selv fra denne gruppe, du vil miste administrator rettigheder' + SilverStripe\Forms\GridField\GridFieldPaginator: + OF: af + Page: Side + View: Vis + SilverStripe\Forms\MoneyField: + FIELDLABELAMOUNT: Beløb + FIELDLABELCURRENCY: Valuta + INVALID_CURRENCY: 'Valuta {currency} er ikke i listen over tilladte valutaer' + SilverStripe\Forms\MultiSelectField: + SOURCE_VALIDATION: 'Vælg venligst eksisterende værdier fra listen. Ugyldig mulighed(er) {value} valgt' + SilverStripe\Forms\NullableField: + IsNullLabel: 'Er Null' + SilverStripe\Forms\NumericField: + VALIDATION: '''{value}'' er ikke et tal, kun tal accepteres i dette felt' + SilverStripe\Forms\TimeField: + VALIDATEFORMAT: 'Indtats venligst et gyldigt tidsformat ({format})' + SilverStripe\ORM\DataObject: + PLURALNAME: Dataobjekter + PLURALS: + one: 'Et dataobjekt' + other: '{count} dataobjekter' + SINGULARNAME: Dataobjekt + SilverStripe\ORM\FieldType\DBBoolean: + ANY: Enhver + NOANSWER: Nej + YESANSWER: Ja + SilverStripe\ORM\FieldType\DBDate: + DAYS_SHORT_PLURALS: + one: '{count} dag' + other: '{count} dage' + HOURS_SHORT_PLURALS: + one: '{count} time' + other: '{count} timer' + LessThanMinuteAgo: 'mindre end et minut' + MINUTES_SHORT_PLURALS: + one: '{count} minut' + other: '{count} minutter' + MONTHS_SHORT_PLURALS: + one: '{count} måned' + other: '{count} måneder' + SECONDS_SHORT_PLURALS: + one: '{count} sekund' + other: '{count} sekunder' + TIMEDIFFAGO: '{difference} siden' + TIMEDIFFIN: 'i {difference}' + YEARS_SHORT_PLURALS: + one: '{count} år' + other: '{count} år' + SilverStripe\ORM\FieldType\DBEnum: + ANY: Enhver + SilverStripe\ORM\FieldType\DBForeignKey: + DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'For mange relaterede objekter; fallback felt i brug' + SilverStripe\ORM\Hierarchy: + LIMITED_TITLE: 'For mange underelementer ({count})' + SilverStripe\ORM\Hierarchy\Hierarchy: + InfiniteLoopNotAllowed: 'Uendeligt løkke fundet i "{type}" hierarkiet. Ændre venligst det overliggende element for at løse dette' + LIMITED_TITLE: 'For mange underelementer ({count})' + SilverStripe\ORM\ValidationException: + DEFAULT_ERROR: Valideringsfejl + SilverStripe\Security\BasicAuth: + ENTERINFO: 'Indtast venligst et brugernavn og kodeord.' + ERRORNOTADMIN: 'Den bruger er ikke en administrator.' + ERRORNOTREC: 'Brugernavn / kodeord kunne ikke genkendes' + SilverStripe\Security\CMSMemberLoginForm: + PASSWORDEXPIRED: '

Dit kodeord er udløbet. Vælg venligst et nyt.

' + SilverStripe\Security\CMSSecurity: + INVALIDUSER: '

Ugyldig bruger. Log venligst ind igen her for at fortsætte.

' + LOGIN_MESSAGE: '

Din session er løbet ud pga. inaktivitet

' + LOGIN_TITLE: 'Log ind igen, for at fortsætte hvor du slap.' + SUCCESS: Succes + SUCCESSCONTENT: '

Logget ind. Hvis du ikke automatisk viderestilles så klik her

' + SUCCESS_TITLE: 'Logget ind med sucess' + SilverStripe\Security\DefaultAdminService: + DefaultAdminFirstname: 'Standard admin' + SilverStripe\Security\Group: + AddRole: 'Tilføj en rolle for denne gruppe' + Code: 'Gruppe kode' + DefaultGroupTitleAdministrators: Administratorer + DefaultGroupTitleContentAuthors: Indholdsforfattere + Description: Beskrivelse + GROUPNAME: Gruppenavn + GroupReminder: 'Hvis du vælger en overliggende gruppe, får denne gruppe alle dens roller' + HierarchyPermsError: 'Kan ikke tildele overliggende gruppe "{group}" med fortrinsrettigheder (kræver ADMIN adgang)' + Locked: 'Låst?' + MEMBERS: Brugere + NEWGROUP: 'Ny gruppe' + NoRoles: 'Ingen roller fundet' + PERMISSIONS: Rettigheder + PLURALNAME: Grupper + PLURALS: + one: 'En gruppe' + other: '{count} grupper' + Parent: 'Overliggende gruppe' + ROLES: Roller + ROLESDESCRIPTION: 'Roller er et prædefineret sæt af rettigheder, som kan tildeles grupper.
De bliver nedarvet fra en overliggende grupper hvis krævet.' + RolesAddEditLink: 'Administrer roller' + SINGULARNAME: Gruppe + Sort: Sortering + has_many_Permissions: Rettigheder + many_many_Members: Brugere + SilverStripe\Security\LoginAttempt: + Email: 'Email adresse' + EmailHashed: 'Email adresse (hashed)' + IP: 'IP addresse' + PLURALNAME: Loginforsøg + PLURALS: + one: 'Et loginforsøg' + other: '{count} loginforsøg' + SINGULARNAME: 'Login forsøg' + Status: Status + SilverStripe\Security\Member: + ADDGROUP: 'Tilføj gruppe' + BUTTONCHANGEPASSWORD: 'Skift kodeord' + BUTTONLOGIN: 'Log ind' + BUTTONLOGINOTHER: 'Log ind med en anden bruger' + BUTTONLOGOUT: 'Log ud' + BUTTONLOSTPASSWORD: 'Jeg har glemt mit kodeord' + CONFIRMNEWPASSWORD: 'Bekræft nyt kodeord' + CONFIRMPASSWORD: 'Bekræft kodeord' + CURRENT_PASSWORD: 'Nuværende kodeord' + EDIT_PASSWORD: 'Nyt kodeord' + EMAIL: Email + EMPTYNEWPASSWORD: 'Det nye kodeord kan ikke være tom, prøv venligst igen' + ENTEREMAIL: 'Indtast venligst en email adresse for at få et nulstillingslink.' + ERRORLOCKEDOUT2: 'Din konto er blevet midlertidigt deaktiveret pga. for mange fejlslagne loginforsøg. Forsøg venligst igen om {count} minutter.' + ERRORNEWPASSWORD: 'Du har indtastet dit nye kodeord forskelligt, forsøg igen' + ERRORPASSWORDNOTMATCH: 'Dit nuværende kodeord matcher ikke, forsøg venligst igen' + ERRORWRONGCRED: 'De indtastede værdier ser ikke ud til at være korrekte. Forsøg venligst igen.' + FIRSTNAME: Fornavn + INTERFACELANG: 'Sprog i brugerfladen' + KEEPMESIGNEDIN: 'Hold mig logget ind' + LOGGEDINAS: 'Du er logget ind som {name}.' + NEWPASSWORD: 'Nyt kodeord' + PASSWORD: Kodeord + PASSWORDEXPIRED: 'Dit kodeord er udløbet. Vælg venligst et nyt.' + PLURALNAME: Brugere + PLURALS: + one: 'En bruger' + other: '{count} brugere' + REMEMBERME: 'Husk mig til næste gang? (i {count} dage på denne enhed)' + SINGULARNAME: Bruger + SUBJECTPASSWORDCHANGED: 'Dit kodeord er blevet ændret' + SUBJECTPASSWORDRESET: 'Link til at nulstille dit kodeord' + SURNAME: Efternavn + VALIDATIONADMINLOSTACCESS: 'Kan ikke fjerne alle admin grupper fra din profil' + ValidationIdentifierFailed: 'Kan ikke overskrive eksisterende bruger #{id} med identisk identifikator ({name} = {value}))' + WELCOMEBACK: 'Velkommen tilbage, {firstname}' + YOUROLDPASSWORD: 'Dit gamle kodeord' + belongs_many_many_Groups: Grupper + db_Locale: 'Sprog i brugerfladen' + db_LockedOutUntil: 'Låst ude indtil' + db_Password: Kodeord + db_PasswordExpiry: Kodeordsudløbsdato + SilverStripe\Security\MemberAuthenticator\CMSMemberLoginForm: + AUTHENTICATORNAME: 'CMS bruger loginform' + BUTTONFORGOTPASSWORD: 'Glemt kodeord' + BUTTONLOGIN: 'Log mig ind igen' + BUTTONLOGOUT: 'Log ud' + SilverStripe\Security\MemberAuthenticator\MemberAuthenticator: + ERRORWRONGCRED: 'De indtastede værdier ser ikke ud til at være korrekte. Forsøg venligst igen.' + NoPassword: 'Der er ikke en kode på denne bruger.' + SilverStripe\Security\MemberAuthenticator\MemberLoginForm: + AUTHENTICATORNAME: 'Email og kodeord' + SilverStripe\Security\MemberPassword: + PLURALNAME: 'Bruger kodeord' + PLURALS: + one: 'Et bruger kodeord' + other: '{count} bruger kodeord' + SINGULARNAME: 'Bruger kodeord' + SilverStripe\Security\PasswordValidator: + LOWCHARSTRENGTH: 'Forøg venligst kodeordets styrke, ved at tilføje nogle af følgende tegn: {chars}' + PREVPASSWORD: 'Du har tidligere brugt dette kodeord, vælg venligst et nyt kodeord' + TOOSHORT: 'Kodeordet er for kort, det skal mindst være {minimum} eller flere tegn langt' SilverStripe\Security\Permission: + AdminGroup: Administrator + CMS_ACCESS_CATEGORY: 'CMS Adgang' CONTENT_CATEGORY: Indholdsrettigheder + FULLADMINRIGHTS: 'Fuld administrator rettighed' + FULLADMINRIGHTS_HELP: 'Indebærer og overskriver alle andre tildelte rettigheder.' + PERMISSIONS_CATEGORY: 'Roller og adgangsrettigheder' + PLURALNAME: Rettigheder + PLURALS: + one: 'En rettighed' + other: '{count} rettigheder' + SINGULARNAME: Rettighed + UserPermissionsIntro: 'Tildeling af grupper til denne bruger, ændrer de rettigheder brugeren har. Se gruppe området for rettigheds detaljer på de individuelle grupper.' + SilverStripe\Security\PermissionCheckboxSetField: + AssignedTo: 'tildelt til "{title}"' + FromGroup: 'nedarvet fra gruppen "{title}"' + FromRole: 'nedarvet fra rollen "{title}"' + FromRoleOnGroup: 'nedarvet fra rollen "{roletitle}" på gruppen "{grouptitle}"' + SilverStripe\Security\PermissionRole: + OnlyAdminCanApply: 'Kun administratorer kan tilføje' + PLURALNAME: Roller + PLURALS: + one: 'En rolle' + other: '{count} roller' + SINGULARNAME: Rolle + Title: Titel + SilverStripe\Security\PermissionRoleCode: + PLURALNAME: 'Rettigheds rolle koder' + PLURALS: + one: 'En rettigheds rolle kode' + other: '{count} rettigheds rolle koder' + PermsError: 'Kan ikke tildele koden "{code}" med fortrinsrettigheder (kræver ADMIN adgang)' + SINGULARNAME: 'Rettighed rolle kode' + SilverStripe\Security\RememberLoginHash: + PLURALNAME: 'Login hashes' + PLURALS: + one: 'Et login hash' + other: '{count} Login Hashes' + SINGULARNAME: 'Login hash' + SilverStripe\Security\Security: + ALREADYLOGGEDIN: 'Du har ikke adgang til denne side. Hvis du har en anden bruger der har adgang til denne side, kan du logge ind med denne herunder.' + BUTTONSEND: 'Send mig linket til at nulstille kodeordet' + CHANGEPASSWORDBELOW: 'Du kan ændre dit kodeord herunder.' + CHANGEPASSWORDHEADER: 'Skift dit kodeord' + CONFIRMLOGOUT: 'Klik venligst på knappen herunder, for at bekræfte at du vil logge ud.' + ENTERNEWPASSWORD: 'Indtast venligst et nyt kodeord.' + ERRORPASSWORDPERMISSION: 'Du skal være logget ind, for at kunne ændre dit kodeord!' + LOGIN: 'Log ind' + LOGOUT: 'Log ud' + LOSTPASSWORDHEADER: 'Glemt kodeord' + NOTEPAGESECURED: 'Denne side er beskyttet. Indtast dine loginoplysninger herunder for at få adgang.' + NOTERESETPASSWORD: 'Indtast din email adresse, så sender vi dig et link som du kan nulstille dit kodeord med' + PASSWORDRESETSENTHEADER: 'link til at nulstille kodeord afsendt' + PASSWORDRESETSENTTEXT: 'Tak for det. Et link til at nulstille dit kodeord er afsendt, hvis der findes en bruger med denne email adresse.' diff --git a/lang/de.yml b/lang/de.yml index 1e9f16e5e..77e2f1584 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -190,7 +190,4 @@ de: LOGIN: Anmelden LOSTPASSWORDHEADER: 'Passwort vergessen' NOTEPAGESECURED: 'Diese Seite ist geschützt. Bitte melden Sie sich an und Sie werden sofort weitergeleitet.' - NOTERESETLINKINVALID: '

Der Link zum Zurücksetzen des Passworts ist entweder nicht korrekt oder abgelaufen

Sie können einen neuen Link anfordern oder Ihr Passwort nach dem einloggen ändern.

' NOTERESETPASSWORD: 'Geben Sie Ihre E-Mail-Adresse ein und wir werden Ihnen einen Link zuschicken, mit dem Sie Ihr Passwort zurücksetzen können.' - PASSWORDSENTHEADER: 'Der Link zum Zurücksetzen des Passworts wurde an ''{email}'' gesendet' - PASSWORDSENTTEXT: 'Vielen Dank! Wenn ein Account zu der E-Mail Adresse ''{email}'' existiert, wurde eine E-Mail mit dem Link zum Zurücksetzen des Passworts verschickt.' diff --git a/lang/en.yml b/lang/en.yml index a81c56613..42a437d01 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -78,6 +78,7 @@ en: LinkExisting: 'Link Existing' NewRecord: 'New {type}' NoItemsFound: 'No items found' + OpenFilter: 'Open search and filter' PRINTEDAT: 'Printed at' PRINTEDBY: 'Printed by' PlaceHolder: 'Find {type}' @@ -85,10 +86,6 @@ en: Print: Print RelationSearch: 'Relation search' ResetFilter: Reset - OpenFilter: 'Open search and filter' - SilverStripe\Forms\GridField\GridFieldFilterHeader: - Search: 'Search "{name}"' - SearchFormFaliure: 'No search form could be generated' SilverStripe\Forms\GridField\GridFieldDeleteAction: Delete: Delete DeletePermissionsFailure: 'No delete permissions' @@ -103,6 +100,9 @@ en: Save: Save SilverStripe\Forms\GridField\GridFieldEditButton: EDIT: Edit + SilverStripe\Forms\GridField\GridFieldFilterHeader: + Search: 'Search "{name}"' + SearchFormFaliure: 'No search form could be generated' SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: UnlinkSelfFailure: 'Cannot remove yourself from this group, you will lose admin rights' SilverStripe\Forms\GridField\GridFieldPaginator: diff --git a/lang/eo.yml b/lang/eo.yml index 59806b953..fb6b74ca9 100644 --- a/lang/eo.yml +++ b/lang/eo.yml @@ -95,6 +95,8 @@ eo: DeletePermissionsFailure: 'Mankas permeso forigi' Deleted: 'Forigita {type} {name}' Save: Konservi + SilverStripe\Forms\GridField\GridFieldEditButton: + EDIT: Redakti SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: UnlinkSelfFailure: 'Ne povas forigi vin el ĉi tiu grupo; vi perdus administrajn rajtojn' SilverStripe\Forms\GridField\GridFieldPaginator: @@ -147,6 +149,8 @@ eo: other: '{count} jaroj' SilverStripe\ORM\FieldType\DBEnum: ANY: Ajna + SilverStripe\ORM\FieldType\DBForeignKey: + DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'Tro multaj objektoj; retropaŝa kampo uzata' SilverStripe\ORM\Hierarchy: LIMITED_TITLE: 'Tro da idoj ({count})' SilverStripe\ORM\Hierarchy\Hierarchy: @@ -319,7 +323,6 @@ eo: LOGOUT: Elsaluti LOSTPASSWORDHEADER: 'Perdis pasvorton' NOTEPAGESECURED: 'Tiu paĝo estas sekurigita. Enigu viajn akreditaĵojn sube kaj vi aliros pluen.' - NOTERESETLINKINVALID: '

La pasvorta reagorda ligilo estas malvalida aŭ finiĝis.

Vi povas peti novan ĉi tie aŭ ŝanĝi vian pasvorton post vi ensalutis.

' NOTERESETPASSWORD: 'Enigu vian retpoŝtan adreson kaj ni sendos al vi ligilon per kiu vi povas reagordi vian pasvorton' - PASSWORDSENTHEADER: 'Pasvorta reagorda ligilo sendiĝis al ''{email}''' - PASSWORDSENTTEXT: 'Dankon! Reagordita ligilo sendiĝis al ''{email}'', kondiĉe ke konto ekzistas por tiu retadreso.' + PASSWORDRESETSENTHEADER: 'Pasvorta reagorda ligilo sendiĝis' + PASSWORDRESETSENTTEXT: 'Dankon. Reagorda ligilo sendiĝis, kondiĉe ke konto ekzistas por ĉi tiu retadreso.' diff --git a/lang/es.yml b/lang/es.yml index 0009c7a4a..debd4b4a7 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -249,7 +249,4 @@ es: LOGIN: Entrar LOSTPASSWORDHEADER: '¿Contraseña Perdida?' NOTEPAGESECURED: 'Esa página está protegida. Introduzca sus datos de acreditación a continuación y lo enviaremos a ella en un momento.' - NOTERESETLINKINVALID: '

El enlace para restablecer la contraseña es inválido o ha expirado.

Usted puede solicitar uno nuevo aqui o cambiar su contraseña después de que se haya conectado.

' NOTERESETPASSWORD: 'Introduzca su dirección de e-mail, y le enviaremos un enlace, con el cual podrá restaurar su contraseña' - PASSWORDSENTHEADER: 'Un enlace para restablecer la contraseña ha sido enviado a ''{email}''' - PASSWORDSENTTEXT: 'Gracias! Un enlace para restablecer la contraseña ha sido enviado a ''{email}'', siempre que una cuenta exista para la dirección de email indicada.' diff --git a/lang/et_EE.yml b/lang/et_EE.yml index 63fc4cd5d..a8fb3b686 100644 --- a/lang/et_EE.yml +++ b/lang/et_EE.yml @@ -139,7 +139,4 @@ et_EE: ERRORPASSWORDPERMISSION: 'Pead olema sisseloginud, et parooli muuta!' LOGIN: 'Logi sisse' NOTEPAGESECURED: 'See leht on turvatud. Sisesta enda andmed allpool ja me saadame sind otse edasi' - NOTERESETLINKINVALID: '

Parooli lähtestamise link on kehtetu või aegunud.

Saate taotleda uut linki siin või muuta parooli pärast sisselogimist.

' NOTERESETPASSWORD: 'Sisesta oma email ja me saadame sulle lingi kus saad oma parooli tühistada.' - PASSWORDSENTHEADER: 'Parooli lähtestamise link saadeti aadressile ''{email}''' - PASSWORDSENTTEXT: 'Aitäh! Lähtestamislink saadeti aadressile ''{email}'' eeldusel, et selle e-posti aadressiga seotud konto on olemas.' diff --git a/lang/fa_IR.yml b/lang/fa_IR.yml index b292b25d7..b9aa0ebea 100644 --- a/lang/fa_IR.yml +++ b/lang/fa_IR.yml @@ -168,4 +168,3 @@ fa_IR: ERRORPASSWORDPERMISSION: 'جهت تغییر گذرواژه خود باید وارد شده باشید!' LOGIN: ورود LOSTPASSWORDHEADER: 'فراموشی گذرواژه' - PASSWORDSENTHEADER: 'لینک ازنوسازی گذرواژه به ''{email}'' ارسال شد' diff --git a/lang/fi.yml b/lang/fi.yml index 364762d3a..b19807225 100644 --- a/lang/fi.yml +++ b/lang/fi.yml @@ -76,6 +76,7 @@ fi: LinkExisting: 'Linkitä olemassaoleva' NewRecord: 'Uusi {type}' NoItemsFound: 'Ei kohteita' + OpenFilter: 'Avaa haku ja suodatus' PRINTEDAT: Tulostettu PRINTEDBY: Tulostaja PlaceHolder: 'Etsi {type}' @@ -95,12 +96,19 @@ fi: DeletePermissionsFailure: 'Ei oikeuksia poistamiseen' Deleted: 'Poistettiin {type} {name}' Save: Tallenna + SilverStripe\Forms\GridField\GridFieldEditButton: + EDIT: Muokkaa + SilverStripe\Forms\GridField\GridFieldFilterHeader: + Search: 'Haku "{name}"' + SearchFormFaliure: 'Hakulomaketta ei pystytty luomaan.' SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: UnlinkSelfFailure: 'Et voi siirtää itseäsi pois tästä ryhmästä: menettäisit pääkäyttäjän oikeudet' SilverStripe\Forms\GridField\GridFieldPaginator: OF: / Page: Sivu View: Näytä + SilverStripe\Forms\GridField\GridFieldViewButton: + VIEW: Avaa SilverStripe\Forms\MoneyField: FIELDLABELAMOUNT: Määrä FIELDLABELCURRENCY: Valuutta @@ -147,6 +155,8 @@ fi: other: '{count} vuotta' SilverStripe\ORM\FieldType\DBEnum: ANY: Yhtään + SilverStripe\ORM\FieldType\DBForeignKey: + DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'Liian monta samaan liittyvää objektia: oletuskenttä käytössä' SilverStripe\ORM\Hierarchy: LIMITED_TITLE: 'Liian monta lapsiobjektia ({count}}' SilverStripe\ORM\Hierarchy\Hierarchy: @@ -197,6 +207,7 @@ fi: many_many_Members: Jäsenet SilverStripe\Security\LoginAttempt: Email: Sähköpostiosoite + EmailHashed: 'Sähköpostiosoite (tiivistetty)' IP: IP-osoite PLURALNAME: Kirjautumisyritykset PLURALS: @@ -255,6 +266,8 @@ fi: SilverStripe\Security\MemberAuthenticator\MemberAuthenticator: ERRORWRONGCRED: 'Antamasi tiedot eivät näytä oikeilta. Yritä uudelleen.' NoPassword: 'Tällä käyttäjällä ei ole salasanaa' + SilverStripe\Security\MemberAuthenticator\MemberLoginForm: + AUTHENTICATORNAME: 'Sähköpostiosoite & salasana' SilverStripe\Security\MemberPassword: PLURALNAME: 'Käyttäjän salasanat' PLURALS: @@ -318,5 +331,5 @@ fi: NOTEPAGESECURED: 'Tämä sivu on suojattu. Syötä tunnistetietosi alle niin pääset eteenpäin.' NOTERESETLINKINVALID: '

Salasanan palautuslinkki on virheellinen tai vanhentunut.

Voit pyytää uuden napsauttamalla tästä tai vaihtaa salasanasi kirjautumisen jälkeen.

' NOTERESETPASSWORD: 'Syötä sähköpostiosoitteesi ja lähetämme sinulle linkin, jonka avulla saat palautettua salasanasi' - PASSWORDSENTHEADER: 'Salasanan palautuslinkki lähetettiin osoitteeseen ''{email}''' - PASSWORDSENTTEXT: 'Kiitos! Salasanan palautuslinkki lähetettiin osoitteeseen ''{email}'', joka on liitettynä tähän käyttäjätiliin.' + PASSWORDRESETSENTHEADER: 'Salasanan palautuslinkki lähetetty' + PASSWORDRESETSENTTEXT: 'Kiitos, palautuslinkki on lähetetty käyttäjätilille asetettuun sähköpostiosoitteeseen.' diff --git a/lang/fr.yml b/lang/fr.yml index 5a18188da..450cfdb87 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -84,7 +84,6 @@ fr: RelationSearch: 'Rechercher relations' ResetFilter: Réinitialiser SilverStripe\Forms\GridField\GridFieldDeleteAction: - DELETE_DESCRIPTION: Supprimer Delete: Supprimer DeletePermissionsFailure: 'Vous n’avez pas les autorisations pour supprimer' EditPermissionsFailure: 'Pas de permissions pour délier l''enregistrement' @@ -96,8 +95,6 @@ fr: DeletePermissionsFailure: 'Vous n’avez pas les autorisations pour supprimer' Deleted: '{type} {name} supprimés' Save: Enregistrer - SilverStripe\Forms\GridField\GridFieldEditButton_ss: - EDIT: Éditer SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: UnlinkSelfFailure: 'Impossible de retirer votre propre profil de ce groupe, vous perdriez vos droits d''administration' SilverStripe\Forms\GridField\GridFieldPaginator: @@ -322,7 +319,4 @@ fr: LOGOUT: 'Se déconnecter' LOSTPASSWORDHEADER: 'Mot de passe oublié' NOTEPAGESECURED: 'Cette page est sécurisée. Entrez vos identifiants ci-dessous et vous pourrez y avoir accès.' - NOTERESETLINKINVALID: '

Le lien de réinitialisation du mot de passe n’est pas valide ou a expiré.

Vous pouvez en demander un nouveau en suivant ce lien ou changer de mot de passe après connexion.

' NOTERESETPASSWORD: 'Entrez votre adresse email et nous vous enverrons un lien pour modifier votre mot de passe' - PASSWORDSENTHEADER: "Lien de réinitialisation de mot de passe envoyé à «\_{email}\_»" - PASSWORDSENTTEXT: "Merci\_! Un lien de réinitialisation vient d’être envoyé à «\_{email}\_», à condition que cette adresse existe." diff --git a/lang/id.yml b/lang/id.yml index d17eac338..c9c06bbff 100644 --- a/lang/id.yml +++ b/lang/id.yml @@ -167,7 +167,4 @@ id: LOGIN: Masuk LOSTPASSWORDHEADER: 'Kata Kunci yang Terlupa' NOTEPAGESECURED: 'Laman ini diamankan. Isikan data berikut untuk dikirimkan hak akses Anda.' - NOTERESETLINKINVALID: '

Tautan penggantian kata kunci tidak valid atau sudah kadaluarsa.

Anda dapat meminta yang baru di sini atau mengganti kata kunci setelah Anda masuk.

' NOTERESETPASSWORD: 'Isikan alamat email Anda untuk mendapatkan tautan penggantian kata kunci' - PASSWORDSENTHEADER: 'Tautan penggantian kata kunci dikirimkan ke ''{email}''' - PASSWORDSENTTEXT: 'Terimakasih! Tautan reset telah dikirim ke ''{email}'', berisi informasi akun untuk alamat email ini.' diff --git a/lang/id_ID.yml b/lang/id_ID.yml index 22ebcb2e5..11d1f2bb3 100644 --- a/lang/id_ID.yml +++ b/lang/id_ID.yml @@ -166,7 +166,4 @@ id_ID: LOGIN: Masuk LOSTPASSWORDHEADER: 'Kata Kunci yang Terlupa' NOTEPAGESECURED: 'Laman ini diamankan. Isikan data berikut untuk dikirimkan hak akses Anda.' - NOTERESETLINKINVALID: '

Tautan penggantian kata kunci tidak valid atau sudah kadaluarsa.

Anda dapat meminta yang baru di sini atau mengganti kata kunci setelah Anda masuk.

' NOTERESETPASSWORD: 'Isikan alamat email Anda untuk mendapatkan tautan penggantian kata kunci' - PASSWORDSENTHEADER: 'Tautan penggantian kata kunci dikirimkan ke ''{email}''' - PASSWORDSENTTEXT: 'Terimakasih! Tautan reset telah dikirim ke ''{email}'', berisi informasi akun untuk alamat email ini.' diff --git a/lang/it.yml b/lang/it.yml index 05bf5dc83..4089faf9b 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -95,6 +95,10 @@ it: DeletePermissionsFailure: 'Non hai i permessi per eliminare' Deleted: 'Eliminato {type} {name}' Save: Salva + SilverStripe\Forms\GridField\GridFieldEditButton: + EDIT: Modifica + SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: + UnlinkSelfFailure: 'Non è possibile rimuovere te stesso da questo gruppo, perderesti i diritti di admin' SilverStripe\Forms\GridField\GridFieldPaginator: OF: di Page: Pagina @@ -145,6 +149,8 @@ it: other: '{count} anni' SilverStripe\ORM\FieldType\DBEnum: ANY: Qualsiasi + SilverStripe\ORM\FieldType\DBForeignKey: + DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'Troppi oggetti correlati; campo di fallback in uso' SilverStripe\ORM\Hierarchy: LIMITED_TITLE: 'Troppi figli ({count})' SilverStripe\ORM\Hierarchy\Hierarchy: @@ -195,6 +201,7 @@ it: many_many_Members: Membri SilverStripe\Security\LoginAttempt: Email: 'Indirizzo e-mail' + EmailHashed: 'Indirizzo email (hash)' IP: 'Indirizzo IP' PLURALNAME: 'Tentativi d''accesso' PLURALS: @@ -236,6 +243,7 @@ it: SUBJECTPASSWORDCHANGED: 'La tua password è stata cambiata' SUBJECTPASSWORDRESET: 'Link per azzerare la tua password' SURNAME: Cognome + VALIDATIONADMINLOSTACCESS: 'Non è possibile rimuovere tutti i gruppi admin dal tuo profilo' ValidationIdentifierFailed: 'Non posso sovrascrivere l''utente esistente #{id} con identificatore identico ({name} = {value}))' WELCOMEBACK: 'Bentornato, {firstname}' YOUROLDPASSWORD: 'La tua vecchia password' @@ -252,6 +260,8 @@ it: SilverStripe\Security\MemberAuthenticator\MemberAuthenticator: ERRORWRONGCRED: 'I dettagli forniti non sembrano corretti. Per favore riprovare.' NoPassword: 'Manca la password per questo utente.' + SilverStripe\Security\MemberAuthenticator\MemberLoginForm: + AUTHENTICATORNAME: 'E-mail & Password' SilverStripe\Security\MemberPassword: PLURALNAME: 'Password utenti' PLURALS: @@ -313,7 +323,6 @@ it: LOGOUT: Scollegati LOSTPASSWORDHEADER: 'Password smarrita' NOTEPAGESECURED: 'La pagina è protetta. Inserisci le credenziali qui sotto per poter andare avanti.' - NOTERESETLINKINVALID: '

Il link per azzerare la password non è valido o è scaduto.

Puoi richiederne uno nuovo qui o cambiare la tua password dopo che ti sei connesso.

' NOTERESETPASSWORD: 'Inserisci il tuo indirizzo e-mail e ti verrà inviato un link per poter azzerare la tua password.' - PASSWORDSENTHEADER: 'Link per azzeramento della password inviato a ''{email}''' - PASSWORDSENTTEXT: 'Grazie! Un link di azzeramento è stato inviato a ''{email}'', fornito un account esistente per questo indirizzo e-mail.' + PASSWORDRESETSENTHEADER: 'Link di azzeramento password inviato' + PASSWORDRESETSENTTEXT: 'Grazie! Un link di azzeramento è stato inviato, supponendo un account esista a quell''indirizzo e-mail.' diff --git a/lang/ja.yml b/lang/ja.yml index b2f92eb1e..433a832e2 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -146,7 +146,4 @@ ja: ERRORPASSWORDPERMISSION: パスワードを変更する為に、ログインしなければなりません! LOGIN: ログイン NOTEPAGESECURED: このページはセキュリティで保護されております証明書キーを下記に入力してください。こちらからすぐに送信します - NOTERESETLINKINVALID: '

パスワードのリセットリンクは有効でないか期限切れです。

新しいパスワードを要求することができます ここ もしくはパスワードを変更することができます ログインした後 .

' NOTERESETPASSWORD: メールアドレスを入力してください、パスワードをリセットするURLを送信致します - PASSWORDSENTHEADER: 'パスワードリセットリンクは ''{email}'' に送信されました' - PASSWORDSENTTEXT: 'ありがとうございました! リセットリンクは、''{email}'' に、このアカウントが存在することを前提として送信されました。' diff --git a/lang/lt.yml b/lang/lt.yml index abdbaea25..3ca7adbff 100644 --- a/lang/lt.yml +++ b/lang/lt.yml @@ -167,7 +167,4 @@ lt: LOGIN: Prisijungti LOSTPASSWORDHEADER: 'Slaptažodžio atstatymas' NOTEPAGESECURED: 'Šis puslapis yra apsaugotas. Įveskite savo duomenis į žemiau esančius laukelius.' - NOTERESETLINKINVALID: '

Neteisinga arba negaliojanti slaptažodžio atstatymo nuoroda.

Galite atsisiųsti naują čia arba pasikeisti slaptažodį po to, kai prisijungsite.

' NOTERESETPASSWORD: 'Įveskite savo e. pašto adresą ir atsiųsime slaptažodžio atstatymui skirtą nuorodą' - PASSWORDSENTHEADER: 'Slaptažodžio atstatymo nuoroda nusiųsta į ''{email}''' - PASSWORDSENTTEXT: 'Atstatymo nuoroda nusiųsta į ''{email}''' diff --git a/lang/mi.yml b/lang/mi.yml index 7a2601ed5..e9fe2308e 100644 --- a/lang/mi.yml +++ b/lang/mi.yml @@ -149,7 +149,4 @@ mi: LOGIN: Takiuru LOSTPASSWORDHEADER: 'Kupuhipa Ngaro' NOTEPAGESECURED: 'Kua ngita tēnā whārangi. Tāurua ō taipitoptio tuakiri ki raro, ā, mā mātou koe e tuku kia haere tonu.' - NOTERESETLINKINVALID: '

He muhu, kua mōnehu rānei te hono tautuhi kupuhipa anō.

Ka taea te tono i te mea hōui konei ka huri rānei i tō kupuhipa ā muri i tōtakiuru.

' NOTERESETPASSWORD: 'Tāurua tō wāhitau īmēra, mā mātou e tuku tētahi hono ki a koe e taea ai te tautuhi anō i tō kupuhipa' - PASSWORDSENTHEADER: 'I tukuna he hono tautuhi kupuhipa anō ki ''{email}''' - PASSWORDSENTTEXT: 'Kia ora! Kua tukuna he hono tautuhi anō ki ''{email}'',engari rā kei te tīariari he pūkete mō taua wāhitau īmēra.' diff --git a/lang/nb.yml b/lang/nb.yml index 91dd9f758..f1dc4c74d 100644 --- a/lang/nb.yml +++ b/lang/nb.yml @@ -152,7 +152,4 @@ nb: LOGIN: 'Logg inn' LOSTPASSWORDHEADER: 'Mistet passord' NOTEPAGESECURED: 'Den siden er sikret. Skriv inn gyldig innloggingsinfo så kommer du inn.' - NOTERESETLINKINVALID: '

Lenken for å nullstille passordet er ugyldig eller utgått.

Du kan kreve en ny her eller endre passordet etter at du har logget inn.

' NOTERESETPASSWORD: 'Skriv inn epostadressen din og vi vil sende deg en lenke som nullstiller passordet.' - PASSWORDSENTHEADER: 'Lenke for nullstilling av passord ble sendt til ''{email}''' - PASSWORDSENTTEXT: 'Takk! En lenke for å lage nytt passord er sendt til ''{email}'', forutsatt at det eksisterer en konto for denne epostadressen.' diff --git a/lang/nl.yml b/lang/nl.yml index 64f191213..f6de04299 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -1,4 +1,26 @@ nl: + SilverStripe\Admin\LeftAndMain: + VersionUnknown: onbekend + SilverStripe\AssetAdmin\Forms\UploadField: + Dimensions: Afmetingen + EDIT: Bewerken + EDITINFO: 'Bewerk dit bestand' + REMOVE: Verwijder + SilverStripe\Control\ChangePasswordEmail_ss: + CHANGEPASSWORDFOREMAIL: 'Het wachtwoord voor het account met e-mailadres {email} is aangepast. Indien u uw wachtwoord niet heeft aangepast kunt u dat doen met onderstaande link.' + CHANGEPASSWORDTEXT1: 'U heeft het wachtwoord veranderd voor' + CHANGEPASSWORDTEXT3: 'Wachtwoord veranderen' + HELLO: Hallo + SilverStripe\Control\Email\ForgotPasswordEmail_ss: + HELLO: Hallo + TEXT1: 'Hier is uw' + TEXT2: 'link om uw wachtwoord opnieuw aan te maken' + TEXT3: voor + SilverStripe\Control\RequestProcessor: + INVALID_REQUEST: 'Fout bij verwerken' + REQUEST_ABORTED: 'Fout bij verwerken (geannuleerd)' + SilverStripe\Core\Manifest\VersionProvider: + VERSIONUNKNOWN: Onbekend SilverStripe\Forms\CheckboxField: NOANSWER: Nee YESANSWER: Ja @@ -8,6 +30,8 @@ nl: ATLEAST: 'Een wachtwoord moet tenminste {min} karakters hebben.' BETWEEN: 'Een wachtwoord moet tussen de {min} en {max} karakters hebben' CURRENT_PASSWORD_ERROR: 'Het wachtwoord dat u heeft ingevoerd is niet juist.' + CURRENT_PASSWORD_MISSING: 'Voer uw huidige wachtwoord in.' + LOGGED_IN_ERROR: 'U moet ingelogd zijn om uw wachtwoord te kunnen veranderen!' MAXIMUM: 'Een wachtwoord mag maximaal {max} karakters hebben.' SHOWONCLICKTITLE: 'Verander wachtwoord' SilverStripe\Forms\CurrencyField: @@ -16,12 +40,20 @@ nl: VALIDDATEFORMAT2: 'Vul een geldig datumformaat in ({format})' VALIDDATEMAXDATE: 'De datum moet ouder of gelijk zijn aan de maximale datum ({date})' VALIDDATEMINDATE: 'De datum moet nieuwer of gelijk zijn aan de minimale datum ({date})' + SilverStripe\Forms\DatetimeField: + VALIDDATEMAXDATETIME: 'De datum moet ouder of gelijk zijn aan de maximale datum ({datetime})' + VALIDDATETIMEFORMAT: 'Vul een geldige datum in ({format})' + VALIDDATETIMEMINDATE: 'De datum moet nieuwer of gelijk zijn aan de minimale datum ({datetime})' SilverStripe\Forms\DropdownField: CHOOSE: (Kies) + CHOOSE_MODEL: '(Selecteer {name})' SOURCE_VALIDATION: 'Selecteer een optie uit de lijst. {value} is geen geldige keuze.' SilverStripe\Forms\EmailField: VALIDATION: 'Gelieve een e-mailadres in te voeren.' + SilverStripe\Forms\FileUploadReceiver: + FIELDNOTSET: 'Bestandsinformatie niet gevonden' SilverStripe\Forms\Form: + BAD_METHOD: 'Dit formulier moet middels {method} verzonden worden' CSRF_EXPIRED_MESSAGE: 'Uw sessie is verlopen. Verzend het formulier opnieuw.' CSRF_FAILED_MESSAGE: 'Er lijkt een technisch probleem te zijn. Klik op de knop terug, vernieuw uw browser, en probeer het opnieuw.' VALIDATIONPASSWORDSDONTMATCH: 'Wachtwoorden komen niet overeen' @@ -30,7 +62,10 @@ nl: VALIDATOR: Validator VALIDCURRENCY: 'Vul een geldige munteenheid in' SilverStripe\Forms\FormField: + EXAMPLE: 'bijv. {format}' NONE: geen + SilverStripe\Forms\FormScaffolder: + TABMAIN: Hoofdgedeelte SilverStripe\Forms\GridField\GridField: Add: '{name} toevoegen' CSVEXPORT: 'Exporteer naar CSV' @@ -41,6 +76,7 @@ nl: LinkExisting: 'Koppel een bestaand item' NewRecord: 'Nieuw {type}' NoItemsFound: 'Geen items gevonden.' + OpenFilter: 'Zoeken en filteren openen' PRINTEDAT: 'Geprint op' PRINTEDBY: 'Geprint door' PlaceHolder: 'Zoek {type}' @@ -60,27 +96,72 @@ nl: DeletePermissionsFailure: 'Onvoldoende rechten om te verwijderen' Deleted: '{type} {name} verwijderd' Save: Opslaan + SilverStripe\Forms\GridField\GridFieldEditButton: + EDIT: Bewerken + SilverStripe\Forms\GridField\GridFieldFilterHeader: + Search: 'Zoek naar "{name}"' + SearchFormFaliure: 'Er kon geen zoekformulier worden aangemaakt' + SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: + UnlinkSelfFailure: 'U kunt uzelf niet verwijderen van deze groep, omdat u dan geen admin-rechten meer heeft.' + SilverStripe\Forms\GridField\GridFieldPaginator: + OF: van + Page: Pagina + View: Bekijk + SilverStripe\Forms\GridField\GridFieldViewButton: + VIEW: Bekijk SilverStripe\Forms\MoneyField: FIELDLABELAMOUNT: Aantal FIELDLABELCURRENCY: Munteenheid + INVALID_CURRENCY: 'Valuta {currency} is niet toegestaan' + SilverStripe\Forms\MultiSelectField: + SOURCE_VALIDATION: 'Selecteer een optie uit de lijst. {value} is geen geldige keuze.' SilverStripe\Forms\NullableField: IsNullLabel: 'Is null' SilverStripe\Forms\NumericField: VALIDATION: '''{value}'' is geen getal, enkel getallen worden door dit veld geaccepteerd' SilverStripe\Forms\TimeField: VALIDATEFORMAT: 'Vul een geldig datumformaat in ({format})' + SilverStripe\ORM\DataObject: + PLURALNAME: 'Data objecten' + PLURALS: + one: 'Data object' + other: '{count} Data objecten' + SINGULARNAME: 'Data object' SilverStripe\ORM\FieldType\DBBoolean: ANY: Elke NOANSWER: Nee YESANSWER: Ja SilverStripe\ORM\FieldType\DBDate: + DAYS_SHORT_PLURALS: + one: '{count} dag' + other: '{count} dagen' + HOURS_SHORT_PLURALS: + one: '{count} uur' + other: '{count} uren' LessThanMinuteAgo: 'minder dan één minuut' + MINUTES_SHORT_PLURALS: + one: '{count} minuut' + other: '{count} minuten' + MONTHS_SHORT_PLURALS: + one: '{count} maand' + other: '{count} maanden' + SECONDS_SHORT_PLURALS: + one: '{count} seconde' + other: '{count} seconden' TIMEDIFFAGO: '{difference} geleden' TIMEDIFFIN: 'in {difference}' + YEARS_SHORT_PLURALS: + one: '{count} jaar' + other: '{count} jaren' SilverStripe\ORM\FieldType\DBEnum: ANY: Elke + SilverStripe\ORM\FieldType\DBForeignKey: + DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'Teveel keuzes in de lijst; een alternatief veld wordt getoond.' + SilverStripe\ORM\Hierarchy: + LIMITED_TITLE: 'Teveel onderliggende items ({count})' SilverStripe\ORM\Hierarchy\Hierarchy: InfiniteLoopNotAllowed: 'Oneindige lus gevonden in "{type}" hiërarchie. Wijzig het hogere niveau om dit op te lossen' + LIMITED_TITLE: 'Teveel onderliggende items ({count})' SilverStripe\ORM\ValidationException: DEFAULT_ERROR: Validatiefout SilverStripe\Security\BasicAuth: @@ -91,34 +172,60 @@ nl: PASSWORDEXPIRED: '

Uw wachtwoord is verlopen. Kies een nieuw wachtwoord.

' SilverStripe\Security\CMSSecurity: INVALIDUSER: '

Ongeldige gebruiker Log hier opnieuw in om verder te gaan.

' + LOGIN_MESSAGE: 'Sessie is verlopen' + LOGIN_TITLE: '

U kunt verder met wat u aan het doen was, door opnieuw in te loggen.

' SUCCESS: Succes SUCCESSCONTENT: '

U bent ingelogd. Klik hier als u niet automatisch wordt doorgestuurd.

' + SUCCESS_TITLE: 'Inloggen is gelukt' + SilverStripe\Security\DefaultAdminService: + DefaultAdminFirstname: 'Standaard Beheerder' SilverStripe\Security\Group: AddRole: 'Voeg een rol toe aan deze groep' Code: 'Groep code' DefaultGroupTitleAdministrators: Beheerders DefaultGroupTitleContentAuthors: 'Inhoud Auteurs' Description: 'Omschrijving ' + GROUPNAME: 'Groep naam' GroupReminder: 'Als u de bovenliggende groep selecteert, neemt deze groep alle rollen over' HierarchyPermsError: 'U moet (ADMIN) rechten hebben om de bovenliggende groep "{group}" toe te kennen' Locked: 'Gesloten?' + MEMBERS: Leden + NEWGROUP: 'Nieuwe groep' NoRoles: 'Geen rollen gevonden' + PERMISSIONS: Rechten + PLURALNAME: Groepen + PLURALS: + one: 'Een groep' + other: '{count} groepen' Parent: 'Bovenliggende groep' + ROLES: Rollen + ROLESDESCRIPTION: 'Rollen zijn logische groeperingen van rechten die in het Rollen tabblad gewijzigd kunnen worden.
Rollen worden automatisch overgenomen van bovenliggende groepen.' RolesAddEditLink: 'Rollen beheren' + SINGULARNAME: Groep Sort: Sorteer-richting has_many_Permissions: Rechten many_many_Members: Leden SilverStripe\Security\LoginAttempt: + Email: 'E-mailadres ' + EmailHashed: 'E-mailadres (versleuteld)' IP: 'IP adres' + PLURALNAME: Inlogpogingen + PLURALS: + one: 'Een inlogpoging' + other: '{count} inlogpogingen' + SINGULARNAME: Inlogpogingen Status: Status SilverStripe\Security\Member: ADDGROUP: 'Groep toevoegen' BUTTONCHANGEPASSWORD: 'Wachtwoord veranderen' BUTTONLOGIN: Inloggen BUTTONLOGINOTHER: 'Als iemand anders inloggen' + BUTTONLOGOUT: Uitloggen BUTTONLOSTPASSWORD: 'Ik ben mijn wachtwoord vergeten' CONFIRMNEWPASSWORD: 'Bevestig het nieuwe wachtwoord' CONFIRMPASSWORD: 'Bevestig wachtwoord' + CURRENT_PASSWORD: 'Huidige wachtwoord' + EDIT_PASSWORD: 'Nieuw wachtwoord' EMAIL: E-mail EMPTYNEWPASSWORD: 'Het nieuwe wachtwoord mag niet leeg zijn, probeer opnieuw' ENTEREMAIL: 'Typ uw e-mailadres om een link te ontvangen waarmee u uw wachtwoord kunt resetten.' @@ -128,13 +235,21 @@ nl: ERRORWRONGCRED: 'De ingevulde gegevens lijken niet correct. Probeer het nog een keer.' FIRSTNAME: Voornaam INTERFACELANG: 'Interface taal' + KEEPMESIGNEDIN: 'Houd mij ingelogd' LOGGEDINAS: 'U bent ingelogd als {name}.' NEWPASSWORD: 'Nieuw wachtwoord' PASSWORD: Wachtwoord PASSWORDEXPIRED: 'Uw wachtwoord is verlopen. Kies een nieuw wachtwoord.' + PLURALNAME: Leden + PLURALS: + one: 'Een lid' + other: '{count} leden' + REMEMBERME: 'Onthoud mij voor volgende keer? (voor {count} dagen op dit apparaat)' + SINGULARNAME: Lid SUBJECTPASSWORDCHANGED: 'Uw wachtwoord is veranderd' SUBJECTPASSWORDRESET: 'Link om uw wachtwoord opnieuw aan te maken' SURNAME: Achternaam + VALIDATIONADMINLOSTACCESS: 'Niet mogelijk om alle admin-groepen te verwijderen van uw profiel' ValidationIdentifierFailed: 'Een bestaande gebruiker #{id} kan niet dezelfde unieke velden hebben ({name} = {value}))' WELCOMEBACK: 'Welkom terug, {firstname}' YOUROLDPASSWORD: 'Uw oude wachtwoord' @@ -143,15 +258,38 @@ nl: db_LockedOutUntil: 'Gesloten tot' db_Password: Wachtwoord db_PasswordExpiry: 'Wachtwoord vervaldatum' + SilverStripe\Security\MemberAuthenticator\CMSMemberLoginForm: + AUTHENTICATORNAME: Inlogformulier + BUTTONFORGOTPASSWORD: 'Wachtwoord vergeten' + BUTTONLOGIN: 'Opnieuw inloggen' + BUTTONLOGOUT: Uitloggen + SilverStripe\Security\MemberAuthenticator\MemberAuthenticator: + ERRORWRONGCRED: 'De ingevulde gegevens lijken niet correct. Probeer het nog een keer.' + NoPassword: 'Er is geen wachtwoord voor deze gebruiker.' + SilverStripe\Security\MemberAuthenticator\MemberLoginForm: + AUTHENTICATORNAME: 'E-mail & wachtwoord' + SilverStripe\Security\MemberPassword: + PLURALNAME: Gebruikerswachtwoorden + PLURALS: + one: 'Een gebruikerswachtwoord' + other: '{count} Gebruikerswachtwoorden' + SINGULARNAME: Gebruikerswachtwoord SilverStripe\Security\PasswordValidator: LOWCHARSTRENGTH: 'Maak a.u.b. uw wachtwoord sterker door enkele van de volgende karakters te gebruiken: {chars}' PREVPASSWORD: 'U heeft dit wachtwoord in het verleden al gebruikt, kies a.u.b. een nieuw wachtwoord.' TOOSHORT: 'Het wachtwoord is te kort, het moet minimaal {minimum} karakters hebben' SilverStripe\Security\Permission: AdminGroup: Beheerder + CMS_ACCESS_CATEGORY: 'CMS toegang' CONTENT_CATEGORY: Inhoudsrechten FULLADMINRIGHTS: 'Volledige admin rechten' FULLADMINRIGHTS_HELP: 'Impliceert en overstemt alle andere toegewezen rechten.' + PERMISSIONS_CATEGORY: 'Rollen en toegangsrechten' + PLURALNAME: Rechten + PLURALS: + one: Machtiging + other: '{count} rechten' + SINGULARNAME: Machtiging UserPermissionsIntro: 'Groepen aan deze gebruiker toewijzen zullen diens permissies aanpassen. Zie de sectie Groepen voor meer informatie over machtigingen voor afzonderlijke groepen.' SilverStripe\Security\PermissionCheckboxSetField: AssignedTo: 'toegewezen aan "{title}"' @@ -161,21 +299,37 @@ nl: SilverStripe\Security\PermissionRole: OnlyAdminCanApply: 'Alleen admin kan doorvoeren' PLURALNAME: Rollen + PLURALS: + one: 'Een rol' + other: '{count} rollen' SINGULARNAME: Rol Title: Titel SilverStripe\Security\PermissionRoleCode: + PLURALNAME: 'Permissie codes' + PLURALS: + one: 'Een permissiecode' + other: '{count} permissiecodes' PermsError: 'U moet (ADMIN) rechten hebben om de code "{code}" toe te kennen' + SINGULARNAME: Permissiecode + SilverStripe\Security\RememberLoginHash: + PLURALNAME: 'Versleutelde logins' + PLURALS: + one: 'Een versleutelde login' + other: '{count} versleutelde logins' + SINGULARNAME: 'Versleutelde login' SilverStripe\Security\Security: ALREADYLOGGEDIN: 'U hebt geen toegang tot deze pagina. Als u een andere account met de nodige rechten hebt, kan u hieronder opnieuw inloggen.' BUTTONSEND: 'Nieuw wachtwoord aanmaken' CHANGEPASSWORDBELOW: 'U kunt uw wachtwoord hieronder veranderen.' CHANGEPASSWORDHEADER: 'Verander uw wachtwoord' + CONFIRMLOGOUT: 'Klik op onderstaande knop om uit te loggen.' ENTERNEWPASSWORD: 'Voer een nieuw wachtwoord in.' ERRORPASSWORDPERMISSION: 'U moet ingelogd zijn om uw wachtwoord te kunnen veranderen!' LOGIN: 'Meld aan' + LOGOUT: Uitloggen LOSTPASSWORDHEADER: 'Wachtwoord vergeten' NOTEPAGESECURED: 'Deze pagina is beveiligd. Voer uw gegevens in en u wordt automatisch doorgestuurd.' - NOTERESETLINKINVALID: '

De link om uw wachtwoord te kunnen wijzigen is niet meer geldig.

U kunt een nieuwe link aanvragen of uw wachtwoord aanpassen door in te loggen.

' + NOTERESETLINKINVALID: '

De reset link is ongeldig of komen te vervallen.

Je kan hier een nieuwe link aanvragen of het wachtwoord veranderen nadat je bent ingelogd.

' NOTERESETPASSWORD: 'Voer uw e-mailadres in en we sturen een link waarmee u een nieuw wachtwoord kunt instellen.' - PASSWORDSENTHEADER: 'Wachtwoord herstel link verzonden naar {email}' - PASSWORDSENTTEXT: 'Bedankt! Er is een link verstuurd naar {email} om uw wachtwoord opnieuw in te stellen, in de veronderstelling dat er een account bestaat voor dit e-mailadres.' + PASSWORDRESETSENTHEADER: 'link om uw wachtwoord opnieuw aan te maken' + PASSWORDRESETSENTTEXT: 'Bedankt! Er is een link verstuurd om uw wachtwoord opnieuw in te stellen (mits het mailadres reeds bekend is bij ons).' diff --git a/lang/pl.yml b/lang/pl.yml index 3774d27f3..1905acb38 100644 --- a/lang/pl.yml +++ b/lang/pl.yml @@ -84,7 +84,6 @@ pl: RelationSearch: 'Wyszukiwanie powiązań' ResetFilter: Resetuj SilverStripe\Forms\GridField\GridFieldDeleteAction: - DELETE_DESCRIPTION: Usuń Delete: Usuń DeletePermissionsFailure: 'Brak uprawnień do usuwania' EditPermissionsFailure: 'Nie masz uprawnień, aby odłączyć rekord' @@ -96,8 +95,6 @@ pl: DeletePermissionsFailure: 'Brak uprawnień do usuwania' Deleted: 'Usunięto {type} {name}' Save: Zapisz - SilverStripe\Forms\GridField\GridFieldEditButton_ss: - EDIT: Edytuj SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: UnlinkSelfFailure: 'Nie możesz usunąć siebie z tej grupy, stracone zostałby prawa administratora' SilverStripe\Forms\GridField\GridFieldPaginator: @@ -352,7 +349,4 @@ pl: LOGOUT: 'Wyloguj się' LOSTPASSWORDHEADER: 'Nie pamiętam hasła' NOTEPAGESECURED: 'Ta strona jest zabezpieczona. Wpisz swoje dane a my wyślemy Ci potwierdzenie niebawem' - NOTERESETLINKINVALID: '

Link resetujący hasło wygasł lub jest nieprawidłowy.

Możesz poprosić o nowy tutaj lub zmień swoje hasło po zalogowaniu się.

' NOTERESETPASSWORD: 'Wpisz adres e-mail, na który mamy wysłać link gdzie możesz zresetować swoje hasło' - PASSWORDSENTHEADER: 'Link resetujący hasła został wysłany do ''{email}''' - PASSWORDSENTTEXT: 'Dziękujemy! Link resetujący hasło został wysłany do ''{email}'', o ile konto użytkownika dla takiego e-maila istnieje.' diff --git a/lang/ru.yml b/lang/ru.yml index 73602e013..add72be7f 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -339,7 +339,4 @@ ru: LOGOUT: Выйти LOSTPASSWORDHEADER: 'Восстановление пароля' NOTEPAGESECURED: 'Эта страница защищена. Пожалуйста, введите свои учетные данные для входа.' - NOTERESETLINKINVALID: '

Неверная ссылка переустановки пароля или время действия ссылки истекло.

Вы можете повторно запросить ссылку, щелкнув здесь, или поменять пароль, войдя в систему.

' NOTERESETPASSWORD: 'Введите Ваш адрес email, и Вам будет отправлена ссылка, по которой Вы сможете переустановить свой пароль' - PASSWORDSENTHEADER: 'Ссылка для переустановки пароля выслана на ''{email}''' - PASSWORDSENTTEXT: 'Ссылка переустановки пароля была выслана на адрес ''{email}'' (письмо дойдет до получателя только в том случае, если аккаунт с таким электронным адресом действительно зарегистрирован).' diff --git a/lang/sk.yml b/lang/sk.yml index 861045fc6..88f4bf260 100644 --- a/lang/sk.yml +++ b/lang/sk.yml @@ -228,7 +228,4 @@ sk: LOGIN: Prihlásiť LOSTPASSWORDHEADER: 'Zabudnuté heslo' NOTEPAGESECURED: 'Táto stránka je zabezpečená. Zadajte svoje prihlasovacie údaje a my Vám zároveň pošleme práva.' - NOTERESETLINKINVALID: '

Odkaz na resetovanie hesla nie je platný alebo je vypršala jeho platnosť.

Môžete požiadať o nový tu alebo zmeňte svoje heslo po prihlásení.

' NOTERESETPASSWORD: 'Zadajte svoju e-mailovú adresu a my Vám pošleme odkaz na resetovanie hesla' - PASSWORDSENTHEADER: 'Odkaz na resetovanie hesla bol odoslaný na ''{email}''' - PASSWORDSENTTEXT: 'Ďakujeme! Resetovací odkaz bol odoslaný na ''''{email}'''', pokiaľ účet existuje pre túto emailovú adresu.' diff --git a/lang/sl.yml b/lang/sl.yml index bccd51826..5496aff5f 100644 --- a/lang/sl.yml +++ b/lang/sl.yml @@ -135,7 +135,4 @@ sl: LOGIN: Prijava LOSTPASSWORDHEADER: 'Izgubljeno geslo' NOTEPAGESECURED: 'Stran je zaščitena. Da bi lahko nadaljevali, vpišite svoje podatke.' - NOTERESETLINKINVALID: '

Povezava za ponastavitev gesla je napačna ali pa je njena veljavnost potekla.

Tukaj lahko zaprosite za novo povezavo or pa zamenjate geslo, ko se prijavite v sistem.

' NOTERESETPASSWORD: 'Vpišite e-naslov, na katerega vam bomo poslali povezavo za ponastavitev gesla' - PASSWORDSENTHEADER: 'Povezava za ponastavitev gesla je bila poslana na e-naslov ''{email}''.' - PASSWORDSENTTEXT: 'Hvala! Povezava za ponastavitev gesla je bila poslana na e-naslov ''{email}'', ki je naveden kot e-naslov vašega računa. ' diff --git a/lang/sr.yml b/lang/sr.yml index 07f642278..622b657b3 100644 --- a/lang/sr.yml +++ b/lang/sr.yml @@ -151,7 +151,4 @@ sr: ERRORPASSWORDPERMISSION: 'Морате да будете пријављени да бисте променили своју лозинку!' LOGIN: Пријављивање NOTEPAGESECURED: 'Ова страна је обезбеђена. Унесите своје податке и ми ћемо вам послати садржај.' - NOTERESETLINKINVALID: '

Линк за ресетовање лозинке је погрешан или је истекло време за његово коришћење.

Можете да захтевате нови овде или да промените Вашу лозинку након што се пријавите.

' NOTERESETPASSWORD: 'Унесите своју адресу е-поште и ми ћемо вам послати линк помоћу којег можете да промените своју лозинку' - PASSWORDSENTHEADER: 'Линк за ресетовање лозинке послат је на адресу е-поште: ''{email}''' - PASSWORDSENTTEXT: 'Хвала Вам! Линк за ресетовање лозинке је послат не адресу е-поште ''{email}''. Порука ће стићи примаоцу само ако постоји регистрован налог са том адресом е-поште.' diff --git a/lang/sr@latin.yml b/lang/sr@latin.yml index 1b210ae51..f91aa9337 100644 --- a/lang/sr@latin.yml +++ b/lang/sr@latin.yml @@ -150,7 +150,4 @@ sr@latin: ERRORPASSWORDPERMISSION: 'Morate da budete prijavljeni da biste promenili svoju lozinku!' LOGIN: Prijavljivanje NOTEPAGESECURED: 'Ova strana je obezbeđena. Unesite svoje podatke i mi ćemo vam poslati sadržaj.' - NOTERESETLINKINVALID: '

Link za resetovanje lozinke je pogrešan ili je isteklo vreme za njegovo korišćenje.

Možete da zahtevate novi ovde ili da promenite Vašu lozinku nakon što se prijavite.

' NOTERESETPASSWORD: 'Unesite svoju adresu e-pošte i mi ćemo vam poslati link pomoću kojeg možete da promenite svoju lozinku' - PASSWORDSENTHEADER: 'Link za resetovanje lozinke poslat je na adresu e-pošte: ''{email}''' - PASSWORDSENTTEXT: 'Hvala Vam! Link za resetovanje lozinke je poslat ne adresu e-pošte ''{email}''. Poruka će stići primaocu samo ako postoji registrovan nalog sa tom adresom e-pošte.' diff --git a/lang/sr_RS.yml b/lang/sr_RS.yml index fc79812da..298049430 100644 --- a/lang/sr_RS.yml +++ b/lang/sr_RS.yml @@ -150,7 +150,4 @@ sr_RS: ERRORPASSWORDPERMISSION: 'Морате да будете пријављени да бисте променили своју лозинку!' LOGIN: Пријављивање NOTEPAGESECURED: 'Ова страна је обезбеђена. Унесите своје податке и ми ћемо вам послати садржај.' - NOTERESETLINKINVALID: '

Линк за ресетовање лозинке је погрешан или је истекло време за његово коришћење.

Можете да захтевате нови овде или да промените Вашу лозинку након што се пријавите.

' NOTERESETPASSWORD: 'Унесите своју адресу е-поште и ми ћемо вам послати линк помоћу којег можете да промените своју лозинку' - PASSWORDSENTHEADER: 'Линк за ресетовање лозинке послат је на адресу е-поште: ''{email}''' - PASSWORDSENTTEXT: 'Хвала Вам! Линк за ресетовање лозинке је послат не адресу е-поште ''{email}''. Порука ће стићи примаоцу само ако постоји регистрован налог са том адресом е-поште.' diff --git a/lang/sr_RS@latin.yml b/lang/sr_RS@latin.yml index f39b36058..453ffa17d 100644 --- a/lang/sr_RS@latin.yml +++ b/lang/sr_RS@latin.yml @@ -151,7 +151,4 @@ sr_RS@latin: ERRORPASSWORDPERMISSION: 'Morate da budete prijavljeni da biste promenili svoju lozinku!' LOGIN: Prijavljivanje NOTEPAGESECURED: 'Ova strana je obezbeđena. Unesite svoje podatke i mi ćemo vam poslati sadržaj.' - NOTERESETLINKINVALID: '

Link za resetovanje lozinke je pogrešan ili je isteklo vreme za njegovo korišćenje.

Možete da zahtevate novi ovde ili da promenite Vašu lozinku nakon što se prijavite.

' NOTERESETPASSWORD: 'Unesite svoju adresu e-pošte i mi ćemo vam poslati link pomoću kojeg možete da promenite svoju lozinku' - PASSWORDSENTHEADER: 'Link za resetovanje lozinke poslat je na adresu e-pošte: ''{email}''' - PASSWORDSENTTEXT: 'Hvala Vam! Link za resetovanje lozinke je poslat ne adresu e-pošte ''{email}''. Poruka će stići primaocu samo ako postoji registrovan nalog sa tom adresom e-pošte.' diff --git a/lang/sv.yml b/lang/sv.yml index f35c2ee99..f2a18d659 100644 --- a/lang/sv.yml +++ b/lang/sv.yml @@ -93,7 +93,12 @@ sv: DeletePermissionsFailure: 'Rättighet för att radera saknas' Deleted: 'Raderade {type} {name}' Save: Spara + SilverStripe\Forms\GridField\GridFieldEditButton: + EDIT: Ändra + SilverStripe\Forms\GridField\GridFieldGroupDeleteAction: + UnlinkSelfFailure: 'Du kan inte radera dig själv från den här gruppen, då du då kommer att förlora dina admin-rättigheter' SilverStripe\Forms\GridField\GridFieldPaginator: + OF: av Page: Sida View: Visa SilverStripe\Forms\MoneyField: @@ -108,6 +113,12 @@ sv: VALIDATION: '''{value}'' är inget nummer, bara siffror (utan mellanslag) kan accepteras för det här fältet' SilverStripe\Forms\TimeField: VALIDATEFORMAT: 'Var god att ange tid i ett giltigt format ({format})' + SilverStripe\ORM\DataObject: + PLURALNAME: Dataobjekt + PLURALS: + one: 'Ett dataobjekt' + other: '{count} Dataobjekt' + SINGULARNAME: Dataobjekt SilverStripe\ORM\FieldType\DBBoolean: ANY: 'Vilken som helst' NOANSWER: Nej @@ -136,6 +147,8 @@ sv: other: '{count} år' SilverStripe\ORM\FieldType\DBEnum: ANY: 'Vilken som helst' + SilverStripe\ORM\FieldType\DBForeignKey: + DROPDOWN_THRESHOLD_FALLBACK_MESSAGE: 'För många relaterade objekt; använder fallback-fält' SilverStripe\ORM\Hierarchy: LIMITED_TITLE: 'För många barn ({count})' SilverStripe\ORM\Hierarchy\Hierarchy: @@ -221,6 +234,7 @@ sv: PLURALS: one: 'En medlem' other: '{count} medlemmar' + REMEMBERME: 'Kom ihåg mig nästa gång? (i {count} dagar på denna enhet)' SINGULARNAME: Medlem SUBJECTPASSWORDCHANGED: 'Ditt lösenord har ändrats' SUBJECTPASSWORDRESET: 'Din återställningslänk' @@ -288,7 +302,6 @@ sv: LOGOUT: 'Logga ut' LOSTPASSWORDHEADER: 'Bortglömt lösenord' NOTEPAGESECURED: 'Den här sidan är låst. Fyll i dina uppgifter nedan så skickar vi dig vidare.' - NOTERESETLINKINVALID: '

Återställningslänk för lösenord är felaktig eller för gammal.

Du kan begära en ny här eller ändra ditt lösenord när du loggat in.

' NOTERESETPASSWORD: 'Ange din e-postadress så skickar vi en länk med vilken du kan återställa ditt lösenord' - PASSWORDSENTHEADER: 'Återställningslänk för lösenord har skickats till ''{email}''' - PASSWORDSENTTEXT: 'Tack en återställningslänk har skickats till ''{email}'', förutsatt att ett konto med den addressen finns.' + PASSWORDRESETSENTHEADER: 'Återställningslänk för lösenord skickad' + PASSWORDRESETSENTTEXT: 'Tack. En återställningslänk har skickats, förutsatt att ett konto med denna adress existerar.' diff --git a/lang/zh.yml b/lang/zh.yml index 26c8e2893..748ee671c 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -166,7 +166,4 @@ zh: LOGIN: 登录 LOSTPASSWORDHEADER: 忘记密码 NOTEPAGESECURED: 该页面受安全保护。请在下面输入您的证书,然后我们会立即将您引导至该页面。 - NOTERESETLINKINVALID: '

密码重设链接无效或已过期。

您可以在这里 要求一个新的或在登录后更改您的密码。

' NOTERESETPASSWORD: 请输入您的电子邮件地址,然后我们会将一个链接发送给您,您可以用它来重设您的密码 - PASSWORDSENTHEADER: '密码重设链接已发送至''{email}''' - PASSWORDSENTTEXT: '谢谢!复位链接已发送到 ''{email}'',假定此电子邮件地址存在一个帐户。' diff --git a/src/Control/HTTPApplication.php b/src/Control/HTTPApplication.php index 4d0f7cb9d..35f453d6d 100644 --- a/src/Control/HTTPApplication.php +++ b/src/Control/HTTPApplication.php @@ -41,7 +41,7 @@ class HTTPApplication implements Application */ public function handle(HTTPRequest $request) { - $flush = array_key_exists('flush', $request->getVars()) || strpos($request->getURL(), 'dev/build') === 0; + $flush = array_key_exists('flush', $request->getVars()) || ($request->getURL() === 'dev/build'); // Ensure boot is invoked return $this->execute($request, function (HTTPRequest $request) { diff --git a/src/Core/Startup/AbstractConfirmationToken.php b/src/Core/Startup/AbstractConfirmationToken.php new file mode 100644 index 000000000..11f78b48a --- /dev/null +++ b/src/Core/Startup/AbstractConfirmationToken.php @@ -0,0 +1,192 @@ +reloadRequired() || $token->reloadRequiredIfError()) { + $token->suppress(); + $target = $token; + } + } + return $target; + } + + /** + * Generate a local filesystem path to store a token + * + * @param $token + * @return string + */ + protected function pathForToken($token) + { + return TEMP_PATH . DIRECTORY_SEPARATOR . 'token_' . preg_replace('/[^a-z0-9]+/', '', $token); + } + + /** + * Generate a new random token and store it + * + * @return string Token name + */ + protected function genToken() + { + // Generate a new random token (as random as possible) + $rg = new RandomGenerator(); + $token = $rg->randomToken('md5'); + + // Store a file in the session save path (safer than /tmp, as open_basedir might limit that) + file_put_contents($this->pathForToken($token), $token); + + return $token; + } + + /** + * Is the necessary token provided for this parameter? + * A value must be provided for the token + * + * @return bool + */ + public function tokenProvided() + { + return !empty($this->token); + } + + /** + * Validate a token + * + * @param string $token + * @return boolean True if the token is valid + */ + protected function checkToken($token) + { + if (!$token) { + return false; + } + + $file = $this->pathForToken($token); + $content = null; + + if (file_exists($file)) { + $content = file_get_contents($file); + unlink($file); + } + + return $content === $token; + } + + /** + * Get redirect url, excluding querystring + * + * @return string + */ + public function currentURL() + { + return Controller::join_links(Director::baseURL(), $this->request->getURL(false)); + } + + /** + * Forces a reload of the request with the token included + * + * @return HTTPResponse + */ + public function reloadWithToken() + { + $location = $this->redirectURL(); + $locationJS = Convert::raw2js($location); + $locationATT = Convert::raw2att($location); + $body = <<location.href='$locationJS'; + +You are being redirected. If you are not redirected soon, click here to continue +HTML; + + // Build response + $result = new HTTPResponse($body); + $result->redirect($location); + return $result; + } + + /** + * Is this parameter requested without a valid token? + * + * @return bool True if the parameter is given without a valid token + */ + abstract public function reloadRequired(); + + /** + * Check if this token is provided either in the backurl, or directly, + * but without a token + * + * @return bool + */ + abstract public function reloadRequiredIfError(); + + /** + * Suppress the current parameter for the duration of this request + */ + abstract public function suppress(); + + /** + * Determine the querystring parameters to include + * + * @param bool $includeToken Include the token value? + * @return array List of querystring parameters, possibly including token parameter + */ + abstract public function params($includeToken = true); + + /** + * @return string + */ + abstract public function getRedirectUrlBase(); + + /** + * @return array + */ + abstract public function getRedirectUrlParams(); + + /** + * Get redirection URL + * + * @return string + */ + abstract protected function redirectURL(); +} diff --git a/src/Core/Startup/ConfirmationTokenChain.php b/src/Core/Startup/ConfirmationTokenChain.php new file mode 100644 index 000000000..a47f2c4c0 --- /dev/null +++ b/src/Core/Startup/ConfirmationTokenChain.php @@ -0,0 +1,178 @@ +tokens[] = $token; + } + + /** + * Collect all tokens that require a redirect + * + * @return \Generator + */ + protected function filteredTokens() + { + foreach ($this->tokens as $token) { + if ($token->reloadRequired() || $token->reloadRequiredIfError()) { + yield $token; + } + } + } + + /** + * @return bool + */ + public function suppressionRequired() + { + foreach ($this->tokens as $token) { + if ($token->reloadRequired()) { + return true; + } + } + + return false; + } + + /** + * Suppress URLs & GET vars from tokens that require a redirect + */ + public function suppressTokens() + { + foreach ($this->filteredTokens() as $token) { + $token->suppress(); + } + } + + /** + * @return bool + */ + public function reloadRequired() + { + foreach ($this->tokens as $token) { + if ($token->reloadRequired()) { + return true; + } + } + + return false; + } + + /** + * @return bool + */ + public function reloadRequiredIfError() + { + foreach ($this->tokens as $token) { + if ($token->reloadRequiredIfError()) { + return true; + } + } + + return false; + } + + /** + * @param bool $includeToken + * @return array + */ + public function params($includeToken = true) + { + $params = []; + foreach ($this->tokens as $token) { + $params = array_merge($params, $token->params($includeToken)); + } + + return $params; + } + + /** + * Fetch the URL we want to redirect to, excluding query string parameters. This may + * be the same URL (with a token to be added outside this method), or to a different + * URL if the current one has been suppressed + * + * @return string + */ + public function getRedirectUrlBase() + { + // URLConfirmationTokens may alter the URL to suppress the URL they're protecting, + // so we need to ensure they're inspected last and therefore take priority + $tokens = iterator_to_array($this->filteredTokens(), false); + usort($tokens, function ($a, $b) { + return ($a instanceof URLConfirmationToken) ? 1 : 0; + }); + + $urlBase = Director::baseURL(); + foreach ($tokens as $token) { + $urlBase = $token->getRedirectUrlBase(); + } + + return $urlBase; + } + + /** + * Collate GET vars from all token providers that need to apply a token + * + * @return array + */ + public function getRedirectUrlParams() + { + $params = []; + foreach ($this->filteredTokens() as $token) { + $params = array_merge($params, $token->getRedirectUrlParams()); + } + + return $params; + } + + /** + * @return string + */ + protected function redirectURL() + { + $params = http_build_query($this->getRedirectUrlParams()); + return Controller::join_links($this->getRedirectUrlBase(), '?' . $params); + } + + /** + * @return HTTPResponse + */ + public function reloadWithTokens() + { + $location = $this->redirectURL(); + $locationJS = Convert::raw2js($location); + $locationATT = Convert::raw2att($location); + $body = <<location.href='$locationJS'; + +You are being redirected. If you are not redirected soon, click here to continue +HTML; + + // Build response + $result = new HTTPResponse($body); + $result->redirect($location); + return $result; + } +} diff --git a/src/Core/Startup/ErrorControlChain.php b/src/Core/Startup/ErrorControlChain.php index f34a2c802..e2d14db65 100644 --- a/src/Core/Startup/ErrorControlChain.php +++ b/src/Core/Startup/ErrorControlChain.php @@ -15,8 +15,7 @@ use Exception; * $chain = new ErrorControlChain(); * $chain->then($callback1)->then($callback2)->thenIfErrored($callback3)->execute(); * - * WARNING: This class is experimental and designed specifically for use pre-startup. - * It will likely be heavily refactored before the release of 3.2 + * @internal This class is designed specifically for use pre-startup and may change without warning */ class ErrorControlChain { diff --git a/src/Core/Startup/ErrorControlChainMiddleware.php b/src/Core/Startup/ErrorControlChainMiddleware.php index bdb5ff0a3..c29878e15 100644 --- a/src/Core/Startup/ErrorControlChainMiddleware.php +++ b/src/Core/Startup/ErrorControlChainMiddleware.php @@ -12,6 +12,8 @@ use SilverStripe\Security\Security; /** * Decorates application bootstrapping with errorcontrolchain + * + * @internal This class is designed specifically for use pre-startup and may change without warning */ class ErrorControlChainMiddleware implements HTTPMiddleware { @@ -30,27 +32,42 @@ class ErrorControlChainMiddleware implements HTTPMiddleware $this->application = $application; } + /** + * @param HTTPRequest $request + * @return ConfirmationTokenChain + */ + protected function prepareConfirmationTokenChain(HTTPRequest $request) + { + $chain = new ConfirmationTokenChain(); + $chain->pushToken(new URLConfirmationToken('dev/build', $request)); + + foreach (['isTest', 'isDev', 'flush'] as $parameter) { + $chain->pushToken(new ParameterConfirmationToken($parameter, $request)); + } + + return $chain; + } + public function process(HTTPRequest $request, callable $next) { $result = null; // Prepare tokens and execute chain - $reloadToken = ParameterConfirmationToken::prepare_tokens( - ['isTest', 'isDev', 'flush'], - $request - ); - $chain = new ErrorControlChain(); - $chain - ->then(function () use ($request, $chain, $reloadToken, $next, &$result) { - // If no redirection is necessary then we can disable error supression - if (!$reloadToken) { - $chain->setSuppression(false); + $confirmationTokenChain = $this->prepareConfirmationTokenChain($request); + $errorControlChain = new ErrorControlChain(); + $errorControlChain + ->then(function () use ($request, $errorControlChain, $confirmationTokenChain, $next, &$result) { + if ($confirmationTokenChain->suppressionRequired()) { + $confirmationTokenChain->suppressTokens(); + } else { + // If no redirection is necessary then we can disable error supression + $errorControlChain->setSuppression(false); } try { // Check if a token is requesting a redirect - if ($reloadToken && $reloadToken->reloadRequired()) { - $result = $this->safeReloadWithToken($request, $reloadToken); + if ($confirmationTokenChain && $confirmationTokenChain->reloadRequired()) { + $result = $this->safeReloadWithTokens($request, $confirmationTokenChain); } else { // If no reload necessary, process application $result = call_user_func($next, $request); @@ -60,10 +77,16 @@ class ErrorControlChainMiddleware implements HTTPMiddleware } }) // Finally if a token was requested but there was an error while figuring out if it's allowed, do it anyway - ->thenIfErrored(function () use ($reloadToken) { - if ($reloadToken && $reloadToken->reloadRequiredIfError()) { - $result = $reloadToken->reloadWithToken(); - $result->output(); + ->thenIfErrored(function () use ($confirmationTokenChain) { + if ($confirmationTokenChain && $confirmationTokenChain->reloadRequiredIfError()) { + try { + // Reload requires manual boot + $this->getApplication()->getKernel()->boot(false); + } finally { + // Given we're in an error state here, try to continue even if the kernel boot fails + $result = $confirmationTokenChain->reloadWithTokens(); + $result->output(); + } } }) ->execute(); @@ -75,21 +98,21 @@ class ErrorControlChainMiddleware implements HTTPMiddleware * or authentication is impossible. * * @param HTTPRequest $request - * @param ParameterConfirmationToken $reloadToken + * @param ConfirmationTokenChain $confirmationTokenChain * @return HTTPResponse */ - protected function safeReloadWithToken(HTTPRequest $request, $reloadToken) + protected function safeReloadWithTokens(HTTPRequest $request, ConfirmationTokenChain $confirmationTokenChain) { // Safe reload requires manual boot $this->getApplication()->getKernel()->boot(false); // Ensure session is started $request->getSession()->init($request); - + // Request with ErrorDirector - $result = ErrorDirector::singleton()->handleRequestWithToken( + $result = ErrorDirector::singleton()->handleRequestWithTokenChain( $request, - $reloadToken, + $confirmationTokenChain, $this->getApplication()->getKernel() ); if ($result) { @@ -97,8 +120,8 @@ class ErrorControlChainMiddleware implements HTTPMiddleware } // Fail and redirect the user to the login page - $params = array_merge($request->getVars(), $reloadToken->params(false)); - $backURL = $request->getURL() . '?' . http_build_query($params); + $params = array_merge($request->getVars(), $confirmationTokenChain->params(false)); + $backURL = $confirmationTokenChain->getRedirectUrlBase() . '?' . http_build_query($params); $loginPage = Director::absoluteURL(Security::config()->get('login_url')); $loginPage .= "?BackURL=" . urlencode($backURL); $result = new HTTPResponse(); diff --git a/src/Core/Startup/ErrorDirector.php b/src/Core/Startup/ErrorDirector.php index 3c994c6af..575df7183 100644 --- a/src/Core/Startup/ErrorDirector.php +++ b/src/Core/Startup/ErrorDirector.php @@ -21,18 +21,21 @@ class ErrorDirector extends Director * Redirect with token if allowed, or null if not allowed * * @param HTTPRequest $request - * @param ParameterConfirmationToken $token + * @param ConfirmationTokenChain $confirmationTokenChain * @param Kernel $kernel * @return null|HTTPResponse Redirection response, or null if not able to redirect */ - public function handleRequestWithToken(HTTPRequest $request, ParameterConfirmationToken $token, Kernel $kernel) - { + public function handleRequestWithTokenChain( + HTTPRequest $request, + ConfirmationTokenChain $confirmationTokenChain, + Kernel $kernel + ) { Injector::inst()->registerService($request, HTTPRequest::class); // Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin - $reload = function (HTTPRequest $request) use ($token, $kernel) { + $reload = function (HTTPRequest $request) use ($confirmationTokenChain, $kernel) { if ($kernel->getEnvironment() === Kernel::DEV || !Security::database_is_ready() || Permission::check('ADMIN')) { - return $token->reloadWithToken(); + return $confirmationTokenChain->reloadWithTokens(); } return null; }; diff --git a/src/Core/Startup/ParameterConfirmationToken.php b/src/Core/Startup/ParameterConfirmationToken.php index 1c80db1d0..bc751a2c3 100644 --- a/src/Core/Startup/ParameterConfirmationToken.php +++ b/src/Core/Startup/ParameterConfirmationToken.php @@ -3,36 +3,28 @@ namespace SilverStripe\Core\Startup; use SilverStripe\Control\Controller; +use SilverStripe\Control\Director; use SilverStripe\Control\HTTPRequest; use SilverStripe\Control\HTTPResponse; use SilverStripe\Core\Convert; use SilverStripe\Security\RandomGenerator; /** - * Class ParameterConfirmationToken + * This is used to protect dangerous GET parameters that need to be detected early in the request + * lifecycle by generating a one-time-use token & redirecting with that token included in the + * redirected URL * - * When you need to use a dangerous GET parameter that needs to be set before core/Core.php is - * established, this class takes care of allowing some other code of confirming the parameter, - * by generating a one-time-use token & redirecting with that token included in the redirected URL - * - * WARNING: This class is experimental and designed specifically for use pre-startup. - * It will likely be heavily refactored before the release of 3.2 + * @internal This class is designed specifically for use pre-startup and may change without warning */ -class ParameterConfirmationToken +class ParameterConfirmationToken extends AbstractConfirmationToken { - /** * The name of the parameter * * @var string */ protected $parameterName = null; - - /** - * @var HTTPRequest - */ - protected $request = null; - + /** * The parameter given in the main request * @@ -48,60 +40,6 @@ class ParameterConfirmationToken protected $parameterBackURL = null; /** - * The validated and checked token for this parameter - * - * @var string|null A string value, or null if either not provided or invalid - */ - protected $token = null; - - protected function pathForToken($token) - { - return TEMP_PATH . DIRECTORY_SEPARATOR . 'token_' . preg_replace('/[^a-z0-9]+/', '', $token); - } - - /** - * Generate a new random token and store it - * - * @return string Token name - */ - protected function genToken() - { - // Generate a new random token (as random as possible) - $rg = new RandomGenerator(); - $token = $rg->randomToken('md5'); - - // Store a file in the session save path (safer than /tmp, as open_basedir might limit that) - file_put_contents($this->pathForToken($token), $token); - - return $token; - } - - /** - * Validate a token - * - * @param string $token - * @return boolean True if the token is valid - */ - protected function checkToken($token) - { - if (!$token) { - return false; - } - - $file = $this->pathForToken($token); - $content = null; - - if (file_exists($file)) { - $content = file_get_contents($file); - unlink($file); - } - - return $content == $token; - } - - /** - * Create a new ParameterConfirmationToken - * * @param string $parameterName Name of the querystring parameter to check * @param HTTPRequest $request */ @@ -176,54 +114,23 @@ class ParameterConfirmationToken return $this->parameterBackURL !== null; } - /** - * Is the necessary token provided for this parameter? - * A value must be provided for the token - * - * @return bool - */ - public function tokenProvided() - { - return !empty($this->token); - } - - /** - * Is this parameter requested without a valid token? - * - * @return bool True if the parameter is given without a valid token - */ public function reloadRequired() { return $this->parameterProvided() && !$this->tokenProvided(); } - /** - * Check if this token is provided either in the backurl, or directly, - * but without a token - * - * @return bool - */ public function reloadRequiredIfError() { // Don't reload if token exists return $this->reloadRequired() || $this->existsInReferer(); } - - /** - * Suppress the current parameter by unsetting it from $_GET - */ + public function suppress() { unset($_GET[$this->parameterName]); $this->request->offsetUnset($this->parameterName); } - /** - * Determine the querystring parameters to include - * - * @param bool $includeToken Include the token value as well? - * @return array List of querystring parameters with name and token parameters - */ public function params($includeToken = true) { $params = array( @@ -235,80 +142,21 @@ class ParameterConfirmationToken return $params; } - /** - * Get redirect url, excluding querystring - * - * @return string - */ - protected function currentURL() + public function getRedirectUrlBase() { - return Controller::join_links( - BASE_URL ?: '/', - $this->request->getURL(false) - ); + return ($this->existsInReferer() && !$this->parameterProvided()) ? Director::baseURL() : $this->currentURL(); } - /** - * Get redirection URL - * - * @return string - */ + public function getRedirectUrlParams() + { + return ($this->existsInReferer() && !$this->parameterProvided()) + ? $this->params() + : array_merge($this->request->getVars(), $this->params()); + } + protected function redirectURL() { - // If url is encoded via BackURL, defer to home page (prevent redirect to form action) - if ($this->existsInReferer() && !$this->parameterProvided()) { - $url = BASE_URL ?: '/'; - $params = $this->params(); - } else { - $url = $this->currentURL(); - $params = array_merge($this->request->getVars(), $this->params()); - } - - // Merge get params with current url - return Controller::join_links($url, '?' . http_build_query($params)); - } - - /** - * Forces a reload of the request with the token included - * - * @return HTTPResponse - */ - public function reloadWithToken() - { - $location = $this->redirectURL(); - $locationJS = Convert::raw2js($location); - $locationATT = Convert::raw2att($location); - $body = <<location.href='$locationJS'; - -You are being redirected. If you are not redirected soon, click here to continue the flush -HTML; - - // Build response - $result = new HTTPResponse($body); - $result->redirect($location); - return $result; - } - - /** - * Given a list of token names, suppress all tokens that have not been validated, and - * return the non-validated token with the highest priority - * - * @param array $keys List of token keys in ascending priority (low to high) - * @param HTTPRequest $request - * @return ParameterConfirmationToken The token container for the unvalidated $key given with the highest priority - */ - public static function prepare_tokens($keys, HTTPRequest $request) - { - $target = null; - foreach ($keys as $key) { - $token = new ParameterConfirmationToken($key, $request); - // Validate this token - if ($token->reloadRequired() || $token->reloadRequiredIfError()) { - $token->suppress(); - $target = $token; - } - } - return $target; + $query = http_build_query($this->getRedirectUrlParams()); + return Controller::join_links($this->getRedirectUrlBase(), '?' . $query); } } diff --git a/src/Core/Startup/URLConfirmationToken.php b/src/Core/Startup/URLConfirmationToken.php new file mode 100644 index 000000000..bb509931e --- /dev/null +++ b/src/Core/Startup/URLConfirmationToken.php @@ -0,0 +1,139 @@ +urlToCheck = $urlToCheck; + $this->request = $request; + $this->currentURL = $request->getURL(false); + + $this->tokenParameterName = preg_replace('/[^a-z0-9]/i', '', $urlToCheck) . 'token'; + $this->urlExistsInBackURL = $this->getURLExistsInBackURL($request); + + // If the token provided is valid, mark it as such + $token = $request->getVar($this->tokenParameterName); + if ($this->checkToken($token)) { + $this->token = $token; + } + } + + /** + * @param HTTPRequest $request + * @return bool + */ + protected function getURLExistsInBackURL(HTTPRequest $request) + { + $backURL = ltrim($request->getVar('BackURL'), '/'); + return (strpos($backURL, $this->urlToCheck) === 0); + } + + /** + * @return bool + */ + protected function urlMatches() + { + return ($this->currentURL === $this->urlToCheck); + } + + /** + * @return string + */ + public function getURLToCheck() + { + return $this->urlToCheck; + } + + /** + * @return bool + */ + public function urlExistsInBackURL() + { + return $this->urlExistsInBackURL; + } + + public function reloadRequired() + { + return $this->urlMatches() && !$this->tokenProvided(); + } + + public function reloadRequiredIfError() + { + return $this->reloadRequired() || $this->urlExistsInBackURL(); + } + + public function suppress() + { + $_SERVER['REQUEST_URI'] = '/'; + $this->request->setURL('/'); + } + + public function params($includeToken = true) + { + $params = []; + if ($includeToken) { + $params[$this->tokenParameterName] = $this->genToken(); + } + + return $params; + } + + public function currentURL() + { + return Controller::join_links(Director::baseURL(), $this->currentURL); + } + + public function getRedirectUrlBase() + { + return ($this->urlExistsInBackURL && !$this->urlMatches()) ? Director::baseURL() : $this->currentURL(); + } + + public function getRedirectUrlParams() + { + return ($this->urlExistsInBackURL && !$this->urlMatches()) + ? $this->params() + : array_merge($this->request->getVars(), $this->params()); + } + + protected function redirectURL() + { + $query = http_build_query($this->getRedirectUrlParams()); + return Controller::join_links($this->getRedirectUrlBase(), '?' . $query); + } +} diff --git a/src/Dev/Backtrace.php b/src/Dev/Backtrace.php index daceca751..648cc883f 100644 --- a/src/Dev/Backtrace.php +++ b/src/Dev/Backtrace.php @@ -26,6 +26,7 @@ class Backtrace array('PDO', '__construct'), array('mysqli', 'mysqli'), array('mysqli', 'select_db'), + array('mysqli', 'real_connect'), array('SilverStripe\\ORM\\DB', 'connect'), array('SilverStripe\\Security\\Security', 'check_default_admin'), array('SilverStripe\\Security\\Security', 'encrypt_password'), diff --git a/tests/php/Core/Startup/ConfirmationTokenChainTest.php b/tests/php/Core/Startup/ConfirmationTokenChainTest.php new file mode 100644 index 000000000..adb8fba36 --- /dev/null +++ b/tests/php/Core/Startup/ConfirmationTokenChainTest.php @@ -0,0 +1,185 @@ +createPartialMock(ParameterConfirmationToken::class, $methods); + $mock->expects($this->any()) + ->method('reloadRequired') + ->will($this->returnValue($requiresReload)); + return $mock; + } + + protected function getTokenRequiringReloadIfError($requiresReload = true, $extraMethods = []) + { + $methods = array_merge(['reloadRequired', 'reloadRequiredIfError'], $extraMethods); + $mock = $this->createPartialMock(ParameterConfirmationToken::class, $methods); + $mock->expects($this->any()) + ->method('reloadRequired') + ->will($this->returnValue(false)); + $mock->expects($this->any()) + ->method('reloadRequiredIfError') + ->will($this->returnValue($requiresReload)); + return $mock; + } + + public function testFilteredTokens() + { + $chain = new ConfirmationTokenChain(); + $chain->pushToken($tokenRequiringReload = $this->getTokenRequiringReload()); + $chain->pushToken($tokenNotRequiringReload = $this->getTokenRequiringReload(false)); + $chain->pushToken($tokenRequiringReloadIfError = $this->getTokenRequiringReloadIfError()); + $chain->pushToken($tokenNotRequiringReloadIfError = $this->getTokenRequiringReloadIfError(false)); + + $reflectionMethod = new \ReflectionMethod(ConfirmationTokenChain::class, 'filteredTokens'); + $reflectionMethod->setAccessible(true); + $tokens = iterator_to_array($reflectionMethod->invoke($chain)); + + $this->assertContains($tokenRequiringReload, $tokens, 'Token requiring a reload was not returned'); + $this->assertNotContains($tokenNotRequiringReload, $tokens, 'Token not requiring a reload was returned'); + $this->assertContains($tokenRequiringReloadIfError, $tokens, 'Token requiring a reload on error was not returned'); + $this->assertNotContains($tokenNotRequiringReloadIfError, $tokens, 'Token not requiring a reload on error was returned'); + } + + public function testSuppressionRequired() + { + $chain = new ConfirmationTokenChain(); + $chain->pushToken($this->getTokenRequiringReload(false)); + $this->assertFalse($chain->suppressionRequired(), 'Suppression incorrectly marked as required'); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($this->getTokenRequiringReloadIfError(false)); + $this->assertFalse($chain->suppressionRequired(), 'Suppression incorrectly marked as required'); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($this->getTokenRequiringReload()); + $this->assertTrue($chain->suppressionRequired(), 'Suppression not marked as required'); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($this->getTokenRequiringReloadIfError()); + $this->assertFalse($chain->suppressionRequired(), 'Suppression incorrectly marked as required'); + } + + public function testSuppressTokens() + { + $mockToken = $this->getTokenRequiringReload(true, ['suppress']); + $mockToken->expects($this->once()) + ->method('suppress'); + $secondMockToken = $this->getTokenRequiringReloadIfError(true, ['suppress']); + $secondMockToken->expects($this->once()) + ->method('suppress'); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($mockToken); + $chain->pushToken($secondMockToken); + $chain->suppressTokens(); + } + + public function testReloadRequired() + { + $mockToken = $this->getTokenRequiringReload(true); + $secondMockToken = $this->getTokenRequiringReload(false); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($mockToken); + $chain->pushToken($secondMockToken); + $this->assertTrue($chain->reloadRequired()); + } + + public function testReloadRequiredIfError() + { + $mockToken = $this->getTokenRequiringReloadIfError(true); + $secondMockToken = $this->getTokenRequiringReloadIfError(false); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($mockToken); + $chain->pushToken($secondMockToken); + $this->assertTrue($chain->reloadRequiredIfError()); + } + + public function testParams() + { + $mockToken = $this->getTokenRequiringReload(true, ['params']); + $mockToken->expects($this->once()) + ->method('params') + ->with($this->isTrue()) + ->will($this->returnValue(['mockTokenParam' => '1'])); + $secondMockToken = $this->getTokenRequiringReload(true, ['params']); + $secondMockToken->expects($this->once()) + ->method('params') + ->with($this->isTrue()) + ->will($this->returnValue(['secondMockTokenParam' => '2'])); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($mockToken); + $chain->pushToken($secondMockToken); + $this->assertEquals(['mockTokenParam' => '1', 'secondMockTokenParam' => '2'], $chain->params(true)); + + $mockToken = $this->getTokenRequiringReload(true, ['params']); + $mockToken->expects($this->once()) + ->method('params') + ->with($this->isFalse()) + ->will($this->returnValue(['mockTokenParam' => '1'])); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($mockToken); + $this->assertEquals(['mockTokenParam' => '1'], $chain->params(false)); + } + + public function testGetRedirectUrlBase() + { + $mockUrlToken = $this->createPartialMock(URLConfirmationToken::class, ['reloadRequired', 'getRedirectUrlBase']); + $mockUrlToken->expects($this->any()) + ->method('reloadRequired') + ->will($this->returnValue(true)); + $mockUrlToken->expects($this->any()) + ->method('getRedirectUrlBase') + ->will($this->returnValue('url-base')); + + $mockParameterToken = $this->createPartialMock(ParameterConfirmationToken::class, ['reloadRequired', 'getRedirectUrlBase']); + $mockParameterToken->expects($this->any()) + ->method('reloadRequired') + ->will($this->returnValue(true)); + $mockParameterToken->expects($this->any()) + ->method('getRedirectUrlBase') + ->will($this->returnValue('parameter-base')); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($mockParameterToken); + $chain->pushToken($mockUrlToken); + $this->assertEquals('url-base', $chain->getRedirectUrlBase(), 'URLConfirmationToken url base should take priority'); + + // Push them in reverse order to check priority still correct + $chain = new ConfirmationTokenChain(); + $chain->pushToken($mockUrlToken); + $chain->pushToken($mockParameterToken); + $this->assertEquals('url-base', $chain->getRedirectUrlBase(), 'URLConfirmationToken url base should take priority'); + } + + public function testGetRedirectUrlParams() + { + $mockToken = $this->getTokenRequiringReload(true, ['getRedirectUrlParams']); + $mockToken->expects($this->once()) + ->method('getRedirectUrlParams') + ->will($this->returnValue(['mockTokenParam' => '1'])); + + $secondMockToken = $this->getTokenRequiringReload(true, ['getRedirectUrlParams']); + $secondMockToken->expects($this->once()) + ->method('getRedirectUrlParams') + ->will($this->returnValue(['secondMockTokenParam' => '2'])); + + $chain = new ConfirmationTokenChain(); + $chain->pushToken($mockToken); + $chain->pushToken($secondMockToken); + $this->assertEquals(['mockTokenParam' => '1', 'secondMockTokenParam' => '2'], $chain->getRedirectUrlParams()); + } +} diff --git a/tests/php/Core/Startup/ErrorControlChainMiddlewareTest.php b/tests/php/Core/Startup/ErrorControlChainMiddlewareTest.php index 5cce89d3e..a90799026 100644 --- a/tests/php/Core/Startup/ErrorControlChainMiddlewareTest.php +++ b/tests/php/Core/Startup/ErrorControlChainMiddlewareTest.php @@ -73,4 +73,104 @@ class ErrorControlChainMiddlewareTest extends SapphireTest $this->assertNotContains('?flush=1&flushtoken=', $location); $this->assertContains('Security/login', $location); } + + public function testLiveBuildAdmin() + { + // Mock admin + $adminID = $this->logInWithPermission('ADMIN'); + $this->logOut(); + + // Mock app + $app = new HTTPApplication(new BlankKernel(BASE_PATH)); + $app->getKernel()->setEnvironment(Kernel::LIVE); + + // Test being logged in as admin + $chain = new ErrorControlChainMiddleware($app); + $request = new HTTPRequest('GET', '/dev/build/'); + $request->setSession(new Session(['loggedInAs' => $adminID])); + $result = $chain->process($request, function () { + return null; + }); + + $this->assertInstanceOf(HTTPResponse::class, $result); + $location = $result->getHeader('Location'); + $this->assertContains('/dev/build', $location); + $this->assertContains('?devbuildtoken=', $location); + $this->assertNotContains('Security/login', $location); + } + + public function testLiveBuildUnauthenticated() + { + // Mock app + $app = new HTTPApplication(new BlankKernel(BASE_PATH)); + $app->getKernel()->setEnvironment(Kernel::LIVE); + + // Test being logged in as no one + Security::setCurrentUser(null); + $chain = new ErrorControlChainMiddleware($app); + $request = new HTTPRequest('GET', '/dev/build'); + $request->setSession(new Session(['loggedInAs' => 0])); + $result = $chain->process($request, function () { + return null; + }); + + // Should be directed to login, not to flush + $this->assertInstanceOf(HTTPResponse::class, $result); + $location = $result->getHeader('Location'); + $this->assertNotContains('/dev/build', $location); + $this->assertNotContains('?devbuildtoken=', $location); + $this->assertContains('Security/login', $location); + } + + public function testLiveBuildAndFlushAdmin() + { + // Mock admin + $adminID = $this->logInWithPermission('ADMIN'); + $this->logOut(); + + // Mock app + $app = new HTTPApplication(new BlankKernel(BASE_PATH)); + $app->getKernel()->setEnvironment(Kernel::LIVE); + + // Test being logged in as admin + $chain = new ErrorControlChainMiddleware($app); + $request = new HTTPRequest('GET', '/dev/build/', ['flush' => '1']); + $request->setSession(new Session(['loggedInAs' => $adminID])); + $result = $chain->process($request, function () { + return null; + }); + + $this->assertInstanceOf(HTTPResponse::class, $result); + $location = $result->getHeader('Location'); + $this->assertContains('/dev/build', $location); + $this->assertContains('flush=1', $location); + $this->assertContains('devbuildtoken=', $location); + $this->assertContains('flushtoken=', $location); + $this->assertNotContains('Security/login', $location); + } + + public function testLiveBuildAndFlushUnauthenticated() + { + // Mock app + $app = new HTTPApplication(new BlankKernel(BASE_PATH)); + $app->getKernel()->setEnvironment(Kernel::LIVE); + + // Test being logged in as no one + Security::setCurrentUser(null); + $chain = new ErrorControlChainMiddleware($app); + $request = new HTTPRequest('GET', '/dev/build', ['flush' => '1']); + $request->setSession(new Session(['loggedInAs' => 0])); + $result = $chain->process($request, function () { + return null; + }); + + // Should be directed to login, not to flush + $this->assertInstanceOf(HTTPResponse::class, $result); + $location = $result->getHeader('Location'); + $this->assertNotContains('/dev/build', $location); + $this->assertNotContains('flush=1', $location); + $this->assertNotContains('devbuildtoken=', $location); + $this->assertNotContains('flushtoken=', $location); + $this->assertContains('Security/login', $location); + } } diff --git a/tests/php/Core/Startup/ParameterConfirmationTokenTest.php b/tests/php/Core/Startup/ParameterConfirmationTokenTest.php index 66616433f..e28af8ea5 100644 --- a/tests/php/Core/Startup/ParameterConfirmationTokenTest.php +++ b/tests/php/Core/Startup/ParameterConfirmationTokenTest.php @@ -149,14 +149,14 @@ class ParameterConfirmationTokenTest extends SapphireTest } /** - * currentAbsoluteURL needs to handle base or url being missing, or any combination of slashes. + * currentURL needs to handle base or url being missing, or any combination of slashes. * * There should always be exactly one slash between each part in the result, and any trailing slash * should be preserved. * * @dataProvider dataProviderURLs */ - public function testCurrentAbsoluteURLHandlesSlashes($url) + public function testCurrentURLHandlesSlashes($url) { $this->request->setUrl($url); diff --git a/tests/php/Core/Startup/URLConfirmationTokenTest.php b/tests/php/Core/Startup/URLConfirmationTokenTest.php new file mode 100644 index 000000000..73b07eb13 --- /dev/null +++ b/tests/php/Core/Startup/URLConfirmationTokenTest.php @@ -0,0 +1,148 @@ + 'value']); + $validToken = new StubValidToken('token/test/url', $request); + $this->assertTrue($validToken->urlMatches()); + $this->assertFalse($validToken->urlExistsInBackURL()); + $this->assertTrue($validToken->tokenProvided()); // Actually forced to true for this test + $this->assertFalse($validToken->reloadRequired()); + $this->assertFalse($validToken->reloadRequiredIfError()); + $this->assertStringStartsWith(Controller::join_links(BASE_URL, '/', 'token/test/url'), $validToken->redirectURL()); + } + + public function testTokenWithLeadingSlashInUrl() + { + $request = new HTTPRequest('GET', '/leading/slash/url', []); + $leadingSlash = new StubToken('leading/slash/url', $request); + $this->assertTrue($leadingSlash->urlMatches()); + $this->assertFalse($leadingSlash->urlExistsInBackURL()); + $this->assertFalse($leadingSlash->tokenProvided()); + $this->assertTrue($leadingSlash->reloadRequired()); + $this->assertTrue($leadingSlash->reloadRequiredIfError()); + $this->assertContains('leading/slash/url', $leadingSlash->redirectURL()); + $this->assertContains('leadingslashurltoken', $leadingSlash->redirectURL()); + } + + public function testTokenWithTrailingSlashInUrl() + { + $request = new HTTPRequest('GET', 'trailing/slash/url/', []); + $trailingSlash = new StubToken('trailing/slash/url', $request); + $this->assertTrue($trailingSlash->urlMatches()); + $this->assertFalse($trailingSlash->urlExistsInBackURL()); + $this->assertFalse($trailingSlash->tokenProvided()); + $this->assertTrue($trailingSlash->reloadRequired()); + $this->assertTrue($trailingSlash->reloadRequiredIfError()); + $this->assertContains('trailing/slash/url', $trailingSlash->redirectURL()); + $this->assertContains('trailingslashurltoken', $trailingSlash->redirectURL()); + } + + public function testTokenWithUrlMatchedInBackUrl() + { + $request = new HTTPRequest('GET', '/', ['BackURL' => 'back/url']); + $backUrl = new StubToken('back/url', $request); + $this->assertFalse($backUrl->urlMatches()); + $this->assertTrue($backUrl->urlExistsInBackURL()); + $this->assertFalse($backUrl->tokenProvided()); + $this->assertFalse($backUrl->reloadRequired()); + $this->assertTrue($backUrl->reloadRequiredIfError()); + $home = (BASE_URL ?: '/') . '?'; + $this->assertStringStartsWith($home, $backUrl->redirectURL()); + $this->assertContains('backurltoken', $backUrl->redirectURL()); + } + + public function testUrlSuppressionWhenTokenMissing() + { + // Check suppression + $request = new HTTPRequest('GET', 'test/url', []); + $token = new StubToken('test/url', $request); + $this->assertEquals('test/url', $request->getURL(false)); + $token->suppress(); + $this->assertEquals('', $request->getURL(false)); + } + + public function testPrepareTokens() + { + $request = new HTTPRequest('GET', 'test/url', []); + $token = URLConfirmationToken::prepare_tokens( + [ + 'test/url', + 'test', + 'url' + ], + $request + ); + // Test no invalid tokens + $this->assertEquals('test/url', $token->getURLToCheck()); + $this->assertNotEquals('test/url', $request->getURL(false), 'prepare_tokens() did not suppress URL'); + } + + public function testPrepareTokensDoesntSuppressWhenNotMatched() + { + $request = new HTTPRequest('GET', 'test/url', []); + $token = URLConfirmationToken::prepare_tokens( + ['another/url'], + $request + ); + $this->assertEmpty($token); + $this->assertEquals('test/url', $request->getURL(false), 'prepare_tokens() incorrectly suppressed URL'); + } + + public function testPrepareTokensWithUrlMatchedInBackUrl() + { + // Test backurl token + $request = new HTTPRequest('GET', '/', ['BackURL' => 'back/url']); + $token = URLConfirmationToken::prepare_tokens( + [ 'back/url' ], + $request + ); + $this->assertNotEmpty($token); + $this->assertEquals('back/url', $token->getURLToCheck()); + $this->assertNotEquals('back/url', $request->getURL(false), 'prepare_tokens() did not suppress URL'); + } + + public function dataProviderURLs() + { + return [ + [''], + ['/'], + ['bar'], + ['bar/'], + ['/bar'], + ['/bar/'], + ]; + } + + /** + * currentURL needs to handle base or url being missing, or any combination of slashes. + * + * There should always be exactly one slash between each part in the result, and any trailing slash + * should be preserved. + * + * @dataProvider dataProviderURLs + */ + public function testCurrentURLHandlesSlashes($url) + { + $request = new HTTPRequest('GET', $url, []); + + $token = new StubToken( + 'another/url', + $request + ); + $expected = rtrim(Controller::join_links(BASE_URL, '/', $url), '/') ?: '/'; + $this->assertEquals($expected, $token->currentURL(), "Invalid redirect for request url $url"); + } +} diff --git a/tests/php/Core/Startup/URLConfirmationTokenTest/StubToken.php b/tests/php/Core/Startup/URLConfirmationTokenTest/StubToken.php new file mode 100644 index 000000000..ca08d3e1f --- /dev/null +++ b/tests/php/Core/Startup/URLConfirmationTokenTest/StubToken.php @@ -0,0 +1,27 @@ +