(Grav GitSync) Automatic Commit from buha

This commit is contained in:
buha 2023-01-12 03:30:43 +01:00 committed by GitSync
commit 4da07476f3
5003 changed files with 692846 additions and 0 deletions

42
pages/01.home/default.md Normal file
View File

@ -0,0 +1,42 @@
---
title: Home
body_classes: title-center title-h1h2
---
# Say Hello to Grav!
## installation successful...
Congratulations! You have installed the **Base Grav Package** that provides a **simple page** and the default **Quark** theme to get you started.
!! If you see a **404 Error** when you click `Typography` in the menu, please refer to the [troubleshooting guide](http://learn.getgrav.org/troubleshooting/page-not-found).
### Find out all about Grav
* Learn about **Grav** by checking out our dedicated [Learn Grav](http://learn.getgrav.org) site.
* Download **plugins**, **themes**, as well as other Grav **skeleton** packages from the [Grav Downloads](http://getgrav.org/downloads) page.
* Check out our [Grav Development Blog](http://getgrav.org/blog) to find out the latest goings on in the Grav-verse.
!!! If you want a more **full-featured** base install, you should check out [**Skeleton** packages available in the downloads](http://getgrav.org/downloads).
### Edit this Page
To edit this page, simply navigate to the folder you installed **Grav** into, and then browse to the `user/pages/01.home` folder and open the `default.md` file in your [editor of choice](http://learn.getgrav.org/basics/requirements). You will see the content of this page in [Markdown format](http://learn.getgrav.org/content/markdown).
### Create a New Page
Creating a new page is a simple affair in **Grav**. Simply follow these simple steps:
1. Navigate to your pages folder: `user/pages/` and create a new folder. In this example, we will use [explicit default ordering](http://learn.getgrav.org/content/content-pages) and call the folder `03.mypage`.
2. Launch your text editor and paste in the following sample code:
---
title: My New Page
---
# My New Page!
This is the body of **my new page** and I can easily use _Markdown_ syntax here.
3. Save this file in the `user/pages/03.mypage/` folder as `default.md`. This will tell **Grav** to render the page using the **default** template.
4. That is it! Reload your browser to see your new page in the menu.
! NOTE: The page will automatically show up in the Menu after the "Typography" menu item. If you wish to change the name that shows up in the Menu, simple add: `menu: My Page` between the dashes in the page content. This is called the YAML front matter, and it is where you configure page-specific options.

View File

@ -0,0 +1,18 @@
---
title: 01c-test
hide_git_sync_repo_link: false
blog_url: /news
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
published: true
---
Your page summary goes here..
===
Your page content goes here.

View File

@ -0,0 +1,20 @@
---
title: 01c-test
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
published: true
event:
ticket_show: hidden
---
Your page summary goes here.
===
Your page content goes here.

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 KiB

View File

@ -0,0 +1,24 @@
---
title: 'Dachbodenknacker die ersten Tänzer!'
event:
start: '11-01-2023 16:00'
end: '11-01-2023 18:00'
ticket_show: free
taxonomy:
tag:
- lala
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
media_order: 7knyurya9vy91.jpg
published: true
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur euismod placerat nulla, a faucibus risus tincidunt sit amet. Nulla facilisi. Sed in bibendum nibh, a viverra ligula. Fusce volutpat, augue in varius tristique, augue magna aliquet quam, non congue elit augue non tellus. Sed in mi fringilla, tempor tellus id, tristique elit. Sed euismod malesuada metus, vitae accumsan erat blandit eu. Sed id nisl elementum, tempor nisl id, fringilla velit. Praesent velit velit, aliquam id orci eu, dictum tempus sapien. Praesent faucibus, sapien at congue facilisis
![7knyurya9vy91](7knyurya9vy91.jpg "7knyurya9vy91")

View File

@ -0,0 +1,11 @@
---
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,36 @@
---
title: test5
published: true
event:
ticket_show: free
start: '2023-01-13 16:00'
end: '2023-01-13 19:00'
taxonomy:
category:
- Werkzeug
- Buntstift
tag:
- bumm
- lala
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
![1l8d2hxrcyu91](1l8d2hxrcyu91.webp "1l8d2hxrcyu91")
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

View File

@ -0,0 +1,11 @@
---
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

BIN
pages/01.news/banner.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

35
pages/01.news/blog.de.md Normal file
View File

@ -0,0 +1,35 @@
---
title: News
content:
order:
by: folder
dir: desc
limit: 5
pagination: true
items:
-
'@page.children': /news
-
'@page.children': /events
url_taxonomy_filters: true
child_type: item
published: true
hide_git_sync_repo_link: false
hero_image: banner.webp
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
bricklayer_layout: false
display_post_summary:
enabled: false
feed:
limit: 10
hero_classes: 'hero-medium overlay-dark overlay-dark-gradient title-h1h2 text-light'
taxonomy:
category:
- Werkzeug
tag:
- bumm
---

37
pages/01.news/blog.md Normal file
View File

@ -0,0 +1,37 @@
---
title: News
content:
order:
by: date
dir: desc
limit: 10
pagination: true
items:
-
'@taxonomy':
type: event
-
'@page.children': /news
url_taxonomy_filters: true
child_type: item
published: true
media_order: banner.webp
hide_git_sync_repo_link: false
hero_image: banner.webp
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
bricklayer_layout: false
display_post_summary:
enabled: false
feed:
limit: 10
hero_classes: 'hero-large overlay-dark overlay-dark-gradient title-h1h2 text-light'
taxonomy:
category:
- Werkzeug
tag:
- bumm
---

View File

@ -0,0 +1,20 @@
---
title: Test1
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
event:
ticket_show: hidden
start: '2023-01-29 02:04'
end: '2023-01-29 07:04'
taxonomy:
category:
- Werkzeug
published: true
---

View File

@ -0,0 +1,11 @@
---
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,21 @@
---
title: test3
published: true
event:
ticket_show: hidden
start: '14-01-2023 01:00'
end: '14-01-2023 02:00'
taxonomy:
tag:
- lala
- lulu
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,11 @@
---
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,24 @@
---
title: test4
published: true
event:
ticket_show: hidden
start: '2023-01-26 07:30'
end: '2023-01-26 12:00'
taxonomy:
category:
- Buntstift
- Blauzahn
tag:
- bumm
- lulu
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,11 @@
---
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,20 @@
---
title: test6
published: true
event:
ticket_show: hidden
start: '30-01-2023 01:00'
end: '30-01-2023 08:00'
taxonomy:
tag:
- bumm
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,11 @@
---
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,24 @@
---
title: test7
published: true
event:
ticket_show: hidden
start: '2023-02-28 13:00'
end: '2023-02-28 17:00'
taxonomy:
category:
- Blauzahn
- Werkzeug
tag:
- bumm
- lulu
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,18 @@
---
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
taxonomy:
category:
- schmal
- hell
tag:
- wild
- gruen
---

View File

@ -0,0 +1,22 @@
---
title: test8
published: true
event:
ticket_show: hidden
start: '25-01-2023 01:57'
end: '25-01-2023 05:00'
taxonomy:
tag:
- lala
- lulu
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
date: '08-11-2022 18:40'
---

View File

@ -0,0 +1,11 @@
---
hide_git_sync_repo_link: false
blog_url: /blog
show_sidebar: true
show_breadcrumbs: true
show_pagination: true
hide_from_post_list: false
feed:
limit: 10
---

View File

@ -0,0 +1,11 @@
---
title: Lala1
hide_git_sync_repo_link: false
event:
start: '17-01-2023 12:10'
end: '18-01-2023 09:00'
ticket_show: hidden
visible: false
---
Your page content goes here.

BIN
pages/02.events/banner.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

View File

@ -0,0 +1,11 @@
---
title: 'Dachschaden, die Party'
hide_git_sync_repo_link: false
event:
start: '25-01-2023 14:00'
end: '26-01-2023 03:00'
ticket_show: price
ticket_price: 3€
---
Your page content goes here.

View File

@ -0,0 +1,11 @@
---
title: 'Dachschaden, die Party'
hide_git_sync_repo_link: false
event:
start: '25-01-2023 14:00'
end: '26-01-2023 03:00'
ticket_show: price
ticket_price: 3€
---
Your page content goes here.

View File

@ -0,0 +1,30 @@
---
title: Events
content:
items:
'@page.children': /blog
'@taxonomy':
type: event
'@page':
children: /blog
url_taxonomy_filters: false
limit: '5'
order:
by: date
dir: asc
pagination: true
media_order: banner.webp
hero_classes: 'hero-medium overlay-dark overlay-dark-gradient title-h1h2 text-light'
hide_git_sync_repo_link: false
show_breadcrumbs: true
blog_url: /blog
show_sidebar: true
show_pagination: true
bricklayer_layout: true
display_post_summary:
enabled: false
feed:
limit: 10
child_type: event
---

29
pages/02.events/events.md Normal file
View File

@ -0,0 +1,29 @@
---
title: Events
content:
items:
'@page.children': /blog
'@taxonomy':
type: event
'@page':
children: /blog
url_taxonomy_filters: false
limit: '5'
order:
by: date
dir: asc
pagination: true
media_order: banner.webp
hero_classes: 'hero-medium overlay-dark overlay-dark-gradient title-h1h2 text-light'
hide_git_sync_repo_link: false
show_breadcrumbs: false
blog_url: /blog
show_sidebar: true
show_pagination: true
bricklayer_layout: true
display_post_summary:
enabled: false
feed:
limit: 10
---

View File

@ -0,0 +1,10 @@
---
title: Schnabbeln
hide_git_sync_repo_link: false
event:
start: '19-01-2023 15:16'
end: '19-01-2023 20:16'
ticket_show: hidden
---
Your page content goes here.

View File

@ -0,0 +1,10 @@
---
title: Schnabbeln
hide_git_sync_repo_link: false
event:
start: '19-01-2023 15:16'
end: '19-01-2023 20:16'
ticket_show: hidden
---
Your page content goes here.

View File

@ -0,0 +1,13 @@
---
title: schnurz
hide_git_sync_repo_link: false
event:
start: '15-01-2023 18:00'
end: '16-01-2023 20:00'
repeat: WU
freq: weekly
ticket_show: hidden
until: '31-01-2023 02:59'
---
Your page content goes here.

View File

@ -0,0 +1,10 @@
---
title: test4
hide_git_sync_repo_link: false
event:
start: '15-02-2023 09:00'
end: '15-02-2023 17:00'
ticket_show: hidden
---
Your page content goes here.

View File

@ -0,0 +1,10 @@
---
title: test4
hide_git_sync_repo_link: false
event:
start: '15-02-2023 09:00'
end: '15-02-2023 17:00'
ticket_show: hidden
---
Your page content goes here.

View File

@ -0,0 +1,10 @@
---
title: text3
hide_git_sync_repo_link: false
event:
start: '13-01-2023 08:00'
end: '13-01-2023 12:00'
ticket_show: hidden
---
Your page content goes here.

View File

@ -0,0 +1,10 @@
---
title: text3
hide_git_sync_repo_link: false
event:
start: '13-01-2023 08:00'
end: '13-01-2023 12:00'
ticket_show: hidden
---
Your page content goes here.

View File

@ -0,0 +1,155 @@
---
title: Typography
---
! Details on the full capabilities of Spectre.css can be found in the [Official Spectre Documentation](https://picturepan2.github.io/spectre/elements.html)
The [Quark theme](https://github.com/getgrav/grav-theme-quark) is the new default theme for Grav built with [Spectre.css](https://picturepan2.github.io/spectre/) the lightweight, responsive and modern CSS framework. Spectre provides basic styles for typography, elements, and a responsive layout system that utilizes best practices and consistent language design.
### Headings
# H1 Heading `40px`
## H2 Heading `32px`
### H3 Heading `28px`
#### H4 Heading `24px`
##### H5 Heading `20px`
###### H6 Heading `16px`
```html
# H1 Heading
# H1 Heading `40px`</small>`
<span class="h1">H1 Heading</span>
```
### Paragraphs
Lorem ipsum dolor sit amet, consectetur [adipiscing elit. Praesent risus leo, dictum in vehicula sit amet](#), feugiat tempus tellus. Duis quis sodales risus. Etiam euismod ornare consequat.
Climb leg rub face on everything give attitude nap all day for under the bed. Chase mice attack feet but rub face on everything hopped up on goofballs.
### Markdown Semantic Text Elements
**Bold** `**Bold**`
_Italic_ `_Italic_`
~~Deleted~~ `~~Deleted~~`
`Inline Code` `` `Inline Code` ``
### HTML Semantic Text Elements
<abbr>I18N</abbr> `<abbr>`
<cite>Citation</cite> `<cite>`
<kbd>Ctrl + S</kbd> `<kbd>`
Text<sup>Superscripted</sup> `<sup>`
Text<sub>Subscripted</sub> `<sub>`
<u>Underlined</u> `<u>`
<mark>Highlighted</mark> `<mark>`
<time>20:14</time> `<time>`
<var>x = y + 2</var> `<var>`
### Blockquote
> The advance of technology is based on making it fit in so that you don't really even notice it,
> so it's part of everyday life.
>
> <cite>- Bill Gates</cite>
```markdown
> The advance of technology is based on making it fit in so that you don't really even notice it,
> so it's part of everyday life.
>
> <cite>- Bill Gates</cite>
```
### Unordered List
* list item 1
* list item 2
* list item 2.1
* list item 2.2
* list item 2.3
* list item 3
```markdown
* list item 1
* list item 2
* list item 2.1
* list item 2.2
* list item 2.3
* list item 3
```
### Ordered List
1. list item 1
1. list item 2
1. list item 2.1
1. list item 2.2
1. list item 2.3
1. list item 3
```markdown
1. list item 1
1. list item 2
1. list item 2.1
1. list item 2.2
1. list item 2.3
1. list item 3
```
### Table
| Name | Genre | Release date |
| :-------------------------- | :---------------------------: | -------------------: |
| The Shawshank Redemption | Crime, Drama | 14 October 1994 |
| The Godfather | Crime, Drama | 24 March 1972 |
| Schindler's List | Biography, Drama, History | 4 February 1994 |
| Se7en | Crime, Drama, Mystery | 22 September 1995 |
```markdown
| Name | Genre | Release date |
| :-------------------------- | :---------------------------: | -------------------: |
| The Shawshank Redemption | Crime, Drama | 14 October 1994 |
| The Godfather | Crime, Drama | 24 March 1972 |
| Schindler's List | Biography, Drama, History | 4 February 1994 |
| Se7en | Crime, Drama, Mystery | 22 September 1995 |
```
### Notices
The notices styles are actually provided by the `markdown-notices` plugin but are useful enough to include here:
! This is a warning notification
!! This is a error notification
!!! This is a default notification
!!!! This is a success notification
```markdown
! This is a warning notification
!! This is a error notification
!!! This is a default notification
!!!! This is a success notification
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

View File

@ -0,0 +1,15 @@
---
title: Calendar
hide_git_sync_repo_link: false
hero_classes: 'hero-medium overlay-dark overlay-dark-gradient title-h1h2 text-light'
content:
items:
'@taxonomy':
type: event
order:
by: date
dir: asc
limit: '0'
pagination: false
---

View File

@ -0,0 +1,15 @@
---
title: Calendar
hide_git_sync_repo_link: false
hero_classes: 'hero-medium overlay-dark overlay-dark-gradient title-h1h2 text-light'
content:
items:
'@taxonomy':
type: event
order:
by: date
dir: asc
limit: '0'
pagination: false
---

157
pages/04.typo/default.md Normal file
View File

@ -0,0 +1,157 @@
---
title: Typography
visible: false
hide_git_sync_repo_link: false
---
! Details on the full capabilities of Spectre.css can be found in the [Official Spectre Documentation](https://picturepan2.github.io/spectre/elements.html)
The [Quark theme](https://github.com/getgrav/grav-theme-quark) is the new default theme for Grav built with [Spectre.css](https://picturepan2.github.io/spectre/) the lightweight, responsive and modern CSS framework. Spectre provides basic styles for typography, elements, and a responsive layout system that utilizes best practices and consistent language design.
### Headings
# H1 Heading `40px`
## H2 Heading `32px`
### H3 Heading `28px`
#### H4 Heading `24px`
##### H5 Heading `20px`
###### H6 Heading `16px`
```html
# H1 Heading
# H1 Heading `40px`</small>`
<span class="h1">H1 Heading</span>
```
### Paragraphs
Lorem ipsum dolor sit amet, consectetur [adipiscing elit. Praesent risus leo, dictum in vehicula sit amet](#), feugiat tempus tellus. Duis quis sodales risus. Etiam euismod ornare consequat.
Climb leg rub face on everything give attitude nap all day for under the bed. Chase mice attack feet but rub face on everything hopped up on goofballs.
### Markdown Semantic Text Elements
**Bold** `**Bold**`
_Italic_ `_Italic_`
~~Deleted~~ `~~Deleted~~`
`Inline Code` `` `Inline Code` ``
### HTML Semantic Text Elements
<abbr>I18N</abbr> `<abbr>`
<cite>Citation</cite> `<cite>`
<kbd>Ctrl + S</kbd> `<kbd>`
Text<sup>Superscripted</sup> `<sup>`
Text<sub>Subscripted</sub> `<sub>`
<u>Underlined</u> `<u>`
<mark>Highlighted</mark> `<mark>`
<time>20:14</time> `<time>`
<var>x = y + 2</var> `<var>`
### Blockquote
> The advance of technology is based on making it fit in so that you don't really even notice it,
> so it's part of everyday life.
>
> <cite>- Bill Gates</cite>
```markdown
> The advance of technology is based on making it fit in so that you don't really even notice it,
> so it's part of everyday life.
>
> <cite>- Bill Gates</cite>
```
### Unordered List
* list item 1
* list item 2
* list item 2.1
* list item 2.2
* list item 2.3
* list item 3
```markdown
* list item 1
* list item 2
* list item 2.1
* list item 2.2
* list item 2.3
* list item 3
```
### Ordered List
1. list item 1
1. list item 2
1. list item 2.1
1. list item 2.2
1. list item 2.3
1. list item 3
```markdown
1. list item 1
1. list item 2
1. list item 2.1
1. list item 2.2
1. list item 2.3
1. list item 3
```
### Table
| Name | Genre | Release date |
| :-------------------------- | :---------------------------: | -------------------: |
| The Shawshank Redemption | Crime, Drama | 14 October 1994 |
| The Godfather | Crime, Drama | 24 March 1972 |
| Schindler's List | Biography, Drama, History | 4 February 1994 |
| Se7en | Crime, Drama, Mystery | 22 September 1995 |
```markdown
| Name | Genre | Release date |
| :-------------------------- | :---------------------------: | -------------------: |
| The Shawshank Redemption | Crime, Drama | 14 October 1994 |
| The Godfather | Crime, Drama | 24 March 1972 |
| Schindler's List | Biography, Drama, History | 4 February 1994 |
| Se7en | Crime, Drama, Mystery | 22 September 1995 |
```
### Notices
The notices styles are actually provided by the `markdown-notices` plugin but are useful enough to include here:
! This is a warning notification
!! This is a error notification
!!! This is a default notification
!!!! This is a success notification
```markdown
! This is a warning notification
!! This is a error notification
!!! This is a default notification
!!!! This is a success notification
```

View File

@ -0,0 +1,5 @@
---
title: Tools
visible: false
---

1
plugins/.gitkeep Normal file
View File

@ -0,0 +1 @@
/* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. */

6
plugins/add-page-by-form/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# OS Generated
.DS_Store*
ehthumbs.db
Icon?
Thumbs.db
*.swp

View File

@ -0,0 +1,212 @@
# v3.0.4
## 03/28/2022
1. [](#bugfix)
- A title containing diacritic characters prevented a form submit (issues [#56](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/56) and [#60](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/60)). Thanks goes to davay for the pull request.
# v3.0.3
## 05/29/2020
1. [](#bugfix)
- Fixed not handling overwrite_mode setting properly, issue [#54](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/54), thanks to mooomooo for reporting and testing
# v3.0.2
## 05/17/2020
1. [](#improved)
- Yet another release. This time just to synchronize the versioning number in 'bleuprints.yaml' and this Changelog. For interesting changes see v3.0.0.
# v3.0.1
## 05/17/2020
1. [](#improved)
- Removed a newline between the version number and the date in this Changelog file in an attempt to restore the correct display of this file in the Grav repository
# v3.0.0
## 05/03/2020
1. [](#new)
- New 'overwrite_mode' option 'edit' allows for editing a page. Note: yet undocumented.
- Removing upload files is now handled
1. [](#bugfix)
- Switched to Laravel str_slug function to remedy problems with hyphens on some Windows systems
1. [](#improved)
* (Possibly breaking change:) Changed uploaded files data structure from numeric array to associative array
* (Possibly breaking change:) Changed default setting of 'physical_template_name' to 'true'
* Minimum Grav version is set to 1.6
# v2.4.1
## 04/12/2020
1. [](#bugfix)
* Fixed a bug ([issue #52](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/52)) where an empty value for the 'slug' variable would delete folder(s). Thanks to [anton-mellit](https://github.com/anton-mellit) for reporting this.
# v2.4.0
## 01/28/2020
1. [](#new)
* Added a new config variable 'physical_template_name' to make using the new page template name as the new page's file name optional.
1. [](#improved)
* Removed the Changelog entry that was included in [https://github.com/bleutzinn/grav-plugin-add-page-by-form/pull/47](PR #47) from this file as it was not in the Grav Changelog format and prevented changes showing up correctly in the Grav Plugins download section.
# v2.3.5
## 01/25/2020
1. [](#new)
* Add option to suppress loading of simpleMDE assets (reduces overhead if its known it will not be used)
# v2.3.4
## 01/20/2020
1. [](#bugfix)
* Prepared a new release to mainly consolidate the fix "Use moveTo method not native copy to move uploaded files to final destination" and to bring this changelog format back in line with Grav requirements. Thanks to Dave Nichols (pd-giz-dave).
1. [](#improved)
* Also included the ability to use the new page template name as the new page's folder name. Thanks to Dave Nichols (pd-giz-dave).
# v2.3.3
## 12/05/2019
1. [](#bugfix)
* Fixed inconsistancies in version numbering which prevented the addition of the latest updates in the Grav Plugin repository.
# v2.3.2
## 11/24/2019
1. [](#bugfix)
* Prepared a new release to fix a bug in version numbering. The letter "v" appears to be case sensitive. The versions 2.3.0 and 2.3.1 were tagged with a capital "V" as V2.3.0 and V2.3.1 respectively. Previous versions were tagged using a lowercase "v". This difference causes the Grav Repository to think these are two different plugins.
# v2.3.1
## 10/10/2019
1. [](#bugfix)
* Fixed a subsequent failure to save file uploads to new page folder (form field File with `destination: @self`) introduced with Grav version 1.6.11 ([issue #44](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/44)). Thanks goes to mahagr for tips and to tranduyhung for the fix itself.
# v2.3.0
## 06/06/2019
1. [](#bugfix)
* Fixed the failure to save file uploads to new page folder (form field File with `destination: @self`) introduced with Grav version 1.6 ([issue #40](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/40))
# v2.2.0
## 04/15/2018
1. [](#new)
* Added support for `process.redirect: @self-admin` ([issue #13](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/31))
1. [](#improved)
* Fixed a problem with uploading files
# v2.1.0
## 09/18/2017
1. [](#new)
* Added support for taxonomy types and tags
# v2.0.0
## 06/18/2017
1. [](#new)
* Added support for multiple textarea editors ([issue #21](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/21))
* Added support for `process.redirect: @self` ([issue #23](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/23))
* Added the `overwrite_mode` configuration frontmatter variable
* Added the `subroute` configuration frontmatter variable
* Added the `slug_field` configuration frontmatter variable
* Added filename sanitizing of uploaded files
1. [](#improved)
* In the form page frontmatter configuration variables are separated from variables which main purpose it is to get passed on to the new page
* Uploaded file properties are now included in the new page frontmatter
* Improved safe slug generator
* Removed "use editor" option from `blueprints.yaml` (to allow [issue #21](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/21))
* Extended `blueprints.yaml` to set "fallback" configuration values
1. [](#bugfix)
* Fixed an issue with form pages outside the web root ([issue #20](https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues/20))
* Fixed a problem that prevented having different destinations for file uploads
# v1.4.2
## 03/22/2017
1. [](#new)
* Cleaned up code for release.
# v1.4.1
## 02/16/2017
1. [](#improved)
* Simplified YAML frontmatter formatting as suggested in
https://github.com/getgrav/grav/issues/1287#issuecomment-279965492
# v1.4.0
## 02/12/2017
1. [](#new)
* Added the ability to include the File field in the form. When `destination` is `@self` uploaded files are stored in the new page folder.
# v1.3.2
## 01/31/2017
1. [](#improved)
* Removed the spyc.php class dependency; the page creation and YAML frontmatter handling is now done "the Grav way".
# v1.3.1
## 01/15/2017
1. [](#improved)
* Added jQuery as an asset.
# v1.3.0
## 12/31/2016
1. [](#new)
* Added the SimpleMDE Markdown Editor.
# v1.2.2
## 12/29/2016
1. [](#improved)
* Removed note about the (previous) test release in the ReadMe.
# v1.2.1
## 12/29/2016
1. [](#improved)
* Improved the usage explanation in the ReadMe.
* Removed debug messages.
# v1.2.0
## 12/23/2016
1. [](#improved)
* Improved new page route handling.
# v1.1.1
## 11/17/2016
1. [](#improved)
* Removed dependency of PECL YAML function yaml_emit() in favor of using vendor/spyc.php class.
# v1.1.0
## 11/16/2016
1. [](#new)
* Settings in the pagefrontmatter block in the form page frontmatter now are merged with values from form fields. Form field values ovverride the pagefrontmatter settings.
# v1.0.0
## 11/15/2016
1. [](#new)
* Added an extra form field: 'author'
* Added copying an (optional) frontmatter block from the form page frontmatter to the newly added page's frontmatter
# v0.2.0
## 11/13/2016
1. [](#new)
* Plugin name changed to Add Page By Form (add-page-by-form)
* Added timestamp as date in page header (date format is taken from plugin config)
* Pages with identical titles are saved by adding a incremental number to the page slug (e.g. 'my-page\_2', 'my-page\_2', etc.)
* Added error handling
# v0.1.0
## 11/08/2016
1. [](#new)
* ChangeLog started...

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Ron Wardenier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,444 @@
# Add Page By Form Plugin
The **Add Page By Form** Plugin is for [Grav CMS](http://github.com/getgrav/grav). It allows users to add a new page by filling in a form.
This plugin uses the possibilities of [custom frontmatter](https://learn.getgrav.org/content/headers#custom-page-headers). By setting your own variables in the form page frontmatter a priori and optionally letting users override these variable values by filling in corresponding form fields you can transport these data into the new page frontmatter.
The passing on of both the default settings and form field values entered by the end user to the new page frontmatter makes for an extremely configurable solution.
By mixing default settings and configuring the page form you can to a large extent control the appearence and behaviour of the newly added page by using the frontmatter variables present in the new page in a Twig template.
For example, a new page can act as a new blog post simply by setting the appropriate template variable in the form page definition (with the AntiMatter theme, this is `template: item`). That template value is inserted in the new page frontmatter and, so, will be used by Grav to display the new page.
## Security Warning
Allowing anonymous visitors to create pages is a potential website security risk. It is **strongly advised** to use the [Grav Login Plugin](https://github.com/getgrav/grav-plugin-login) or the [Private Grav Plugin](https://github.com/Diyzzuf/grav-plugin-private) **to restrict the page creation to logged in users only**.
This plugin itself does not provide any security measures. Please take this in consideration before using this plugin.
## Installation and Configuration
Typically the plugin should be installed via [GPM](http://learn.getgrav.org/advanced/grav-gpm) (Grav Package Manager):
```
$ bin/gpm install add-page-by-form
```
Alternatively it can be installed via the [Admin Plugin](http://learn.getgrav.org/admin-panel/plugins).
Another option is to manualy install the plugin by [downloading](https://github.com/bleutzinn/grav-plugin-add-page-by-form/archive/master.zip) the plugin as a zip file. Copy the zip file to your `/user/plugins` directory, unzip it there and rename the folder to `add-page-by-form`.
### Configuration Defaults
Here is the default configuration in the configuration file `add-page-by-form.yaml` plus an explanation of the settings:
```yaml
enabled: true
date_display_format: 'd-m-Y g:ia'
default_title: 'My New Page'
default_content: 'No content.'
overwrite_mode: false
include_username: false
auto_taxonomy_types: false
use_editor_class: true
physical_template_name: true
```
- `enabled` determines whether the plugin is active or not;
- `date_display_format` sets a default date and time format;
- `default_title` will be used as a fallback for the new page title when no other value is set;
- `default_content` will be used as the page content for the new page when no other value is set;
- `include_username` sets whether or not the username of the currently logged in frontend user is included in the new page frontmatter;
- `overwrite_mode` determines how to act when a page with the same name or slug already exists;
- `auto_taxonomy_types` saves any new taxonomy types that were input by the user to the site configuration file `site.yaml`;
- `use_editor_class` determines whether or not to change a textarea field into a Markdown editor when the texture field includes the class "editor" (`classes: editor`);
- `physical_template_name` should normally not be used. For more information see the description in the section 'pageconfig' block variables.
### Customizing the default configuration
To keep your custom configuration when updating the plugin you need to use a configuration file which is stored in the `user/config/plugins` folder.
Simply edit the plugin options in the Admin panel and the changes will be saved to the configuration file in that location. If you don't use the Admin panel, copy the `add-page-by-form.yaml` default file to your `user/config/plugins` folder and use that copy to change configuration settings.
## Usage
Using this plugin requires:
- a normal page containing a Grav Form with a unique name that starts with "add_page";
- optionally, but required for usefulness, one or two blocks of extra page frontmatter variables in that page being:
- a 'pageconfig' block with variables that are used in the new page creation process;
- a 'pagefrontmatter' block with variables that are passed on to the new page frontmatter and can be processed by Twig along the way.
### Modifying frontmatter variables
Every frontmatter variable value can be changed by the user when an input field with the same name as the variable is included in the form.
The basic method of modifying is overriding or replacing an initial value. An extreme case of overriding a variable which is quite uncommon but illustrates the process well:
1. `overwrite_mode` is set in `add-page-by-form.yaml`
2. and can be changed in the Plugin configuration in the Admin Panel
3. can be set in the `pageconfig` block
4. and finally may be changed again by the end user when the form contains an `overwrite_mode` field.
### Page Headers / Frontmatter
This plugin makes extensive use of [Custom Page Headers](https://learn.getgrav.org/content/headers#custom-page-headers). Unfortunately the Grav documentation mixes the terms "frontmatter", "page headers" and simply "headers". This may be confusing at first. They [all](https://learn.getgrav.org/content/headers) refer to the optional top part of a Grav page which contains data in [YAML syntax](https://learn.getgrav.org/advanced/yaml).
## Form page Frontmatter
The frontmatter in the form page and the way it is handled by the plugin is where the flexibility of this plugin originates.
The form page frontmatter is divided into three sections or blocks:
1. So called 'root level' variables are intended to act upon the form page itself. They are not passed on to the new page;
2. the `pageconfig` block contains variables that are used by the plugin in the new page creation process and do get passed on to the new page frontmatter;
3. the `pagefrontmatter` block holds all other variables that must be passed on to the new page frontmatter.
### Root level variables
In the examples above the root level configuration options are:
- `title` sets the title of the page containing the form;
- `template: form` activates the form on this page (not required when the form page is named `form.md`);
- `form` defines the form.
From version 2, the use of `parent` in the, what is now called, root level block is deprecated. It is however still supported for backwards compatibility.
### 'pageconfig' block variables
In the optional pageconfig block you can set these, and only these, variables (other variables will be ignored):
- `parent` sets the parent page for the new page. This variable may be an absolute route (for example `parent: /user_contributions`) or a relative route (e.g. `parent: articles`. In case of an absolute route this route starts from the pages root. A relative route is regarded to start from the form page, so the new page will be a child page of the form page. The form page is also used as the parent page when the set parent page does not exist;
- `subroute` defines a route from the (initial) parent value. If one or more folders in the route do not exist they will be created;
- `slug_field` tells the plugin what field to use as the new page's slug or folder name. When `slug_field` is missing the plugin tries to use the value of `title`;
- `overwrite_mode: true|false|edit` (default `false`) tells the plugin what to do when a page with the same name already exists. With `overwrite_mode: true` the existing page is overwritten. Any additional (media) files besides the page itself which are stored in the existing page folder are deleted as well. With `overwite_mode: false` the new page slug gets a sequential number attached at the end (for example "my-new-page-1" in case "my-new-page" exists).
Using `overwite_mode: edit` allows for the page being saved to it's existing folder respecting any already present uploaded files;
- `include_username: true|false` (default `false`) determines whether or not to include the username of a logged in frontend user in the new page frontmatter;
- `physical_template_name: true|false` (default `true`) does or does not cause the plugin to use the template name of the new page as that new page's filesystem filename. Defaults to "default" when no template is set in the 'pagefrontmatter' block. When set to `true` to avoid future confusion the frontmatter variable `template` is removed from the new page frontmatter.
#### A note on parent and subroute
Together the variables `parent` and `subroute` define the new page's destination. Or, in other words, together they set the path or route of the new page filesystem folder in the page structure.
The difference between parent and subroute worded in another way:
- Parent: works on a page level; when there is no page at the parent route, the form page is used as the parent;
- Subroute: works on a folder level; a subroute may consist of empty folders and if a folder in the subroute does not exist it gets created.
### 'pagefrontmatter' block variables
The content of the optional `pagefrontmatter` block will be included in the new page frontmatter.
## Form usage
The form page needs to use a [simple single form](https://learn.getgrav.org/forms/forms#create-a-simple-single-form).
Two examples are included at the end of this ReadMe file.
### Mandatory fields and values
#### Form Name
It is always a good thing to give each form a unique name, especially when multiple forms are used.
To pre fill form fields with default values the Form name must include the string "add_page". Valid names are for example `add_page.blogpost`, `add_page_profile`.
###Form Actions
**Custom Form processing** ( Important ! )
To let the plugin process the form after a Submit a custom process action must be set like so:
```
process:
-
add_page: true
```
**Redirect to the new page**
To show the new page to the user set the `redirect` action to the custom value `@self` or `@self-admin`.
When using `redirect: '@self'` the page will be shown as a regular web page, for example:
```
process:
-
add_page: true
-
redirect: '@self'
```
To open the new page in the Admin panel use `redirect: '@self-admin'`.
Note that this plugin does not handle the admin user authentication. If the Admin plugin is not installed or is inactive redirection occurs as if `@self` was used.
> Tip: using `@self-admin` is a very convenient way to learn how to use this plugin as it is easy to view and examine the source of the resulting new page including it's frontmatter in the Admin panel.
### Using a Markdown editor in textarea fields
When a `textarea` field is given the class `editor` it will use the [SimpleMDE Markdown Editor](https://simplemde.com).
## Value overrides
The variables which are defined and given a value in the `pageconfig` and `pagefrontmatter` blocks may be 'overridden' or in other words replaced by form input fields. In that respect these variables can be seen to hold a set of default values.
There is only one exception to the default variable override behaviour and that is the handling of `taxonomy` types. Extra taxonomy types and values (for example tags) which are entered via form fields are added to the new page taxonomy.
To override a default value by user input is simply a matter of including a form field by the same name in the page form.
For example in the example 2 - _create a new blog post_, the default title is set to "My new Blog post". The form contains a form field of type text with `name: title`. Thus the user is prompted to enter a title for the new page in the form but does not need to do so because filling in the title field is not mandatory. If the user enters a title that value is used as the title for the new page. If he or she does not, the default title "My new Blog post" will be used.
## Setting taxonomy categories and tags
The Add Blog Post example shows how to let the user add extra tags via the form.
Extra categories may be added in the same way.
## Handling extra taxonomy types
By default Grav 'knows' two taxonomy types, `category` and `tag`. Extra taxonomy types may be defined and added just like with any other variables you can include a form field. The new type is then added to the list of taxonomy types instead of replacing the existing types.
This can be done in the `pagefrontmatter` block. For example, to define a new taxonomy type named 'department':
```
pagefrontmatter:
taxonomy:
- department
```
And/or in the form:
```
form:
name: my_form
fields:
-
name: taxonomy
label: Taxonomy type
type: text
```
This is a feature which calls for a solid look-before-you-leap approach because of it's side effects. Using a new taxonomy type requires it to be included in the list of known taxonomy types. This list is in the site configuration file `site.yaml`.
By setting the plugin configuration option `auto_taxonomy_types: true` new types get automatically saved and can then be used in a collection.
The side effect and possibly downside is that every modification of the site configuration file causes Grav to rebuild the cache, so this may not be desirable with larger sites.
Use with caution!
## Examples
The least error prone way to test and play with the examples is to set up a fresh Grav site and using it's default theme Antimatter.
### Form page example 1: create a normal page
The goal of this example is to show how to let a user create a new page where uploaded images and files are saved along the page (in the same folder). After the user clicked Submit he or she will be shown the new page.
Suppose this minimal Grav website structure in `user/pages/`:
```
03.submit-assignment/
default.md
04.assignments/
cmpt363-e100/
default.md
drafts/
modular.md
reviewed/
modular.md
```
BTW both modular pages are not required but are mentioned as they could be used to display a collection of draft and reviewed assignments.
Then the 'Submit assignment for review' page (with slug `submit-assignment`) full content (both frontmatter and content) could look like:
```
---
routable: true
title: 'Submit assignment for review'
template: form
visible: true
pageconfig:
parent: /submitted-assignments/cmpt363-e100/drafts
pagefrontmatter:
visible: true
status: draft
template: default
course:
assignment: 'CMPT363 E100'
instructor:
name: 'Jane Doe'
form:
name: addpage-assignment-cmpt363-e100
fields:
-
name: name
label: Name
type: text
validate:
required: true
-
name: title
label: Title
type: text
validate:
required: true
-
name: content
label: 'Assignment text'
type: textarea
size: long
classes: editor
validate:
required: true
-
name: attachments
label: 'Attachment (PDF only)'
type: file
multiple: true
accept:
- application/pdf
validate:
required: false
-
name: honeypot
type: honeypot
buttons:
-
type: submit
value: Submit
process:
-
addpage: null
-
redirect: '@self'
---
Please write your assignment and attach any images and/or files.
```
Supposing the user has not changed the pre filled title field, has entered his name "Paul Walker", has entered a simple "q.e.d." as the assignment content and uploaded one PDF document, then the full new page will be:
```
---
visible: true
status: draft
course:
assignment: 'CMPT363 E100'
instructor:
name: 'Jane Doe'
name: 'Paul Walker'
title: 'CMPT363 E100'
attachments:
/Users/rwgc/devroot/repos/grav-test/htdocs/user/pages/assignments/cmpt363-e100/drafts/cmpt363-e100-1/scrum-guide-sept-2013.pdf
name: scrum-guide-sept-2013.pdf
type: application/pdf
size: 273 KB
path: /Users/rwgc/devroot/repos/grav-test/htdocs/user/pages/assignments/cmpt363-e100/drafts/cmpt363-e100-1/scrum-guide-sept-2013.pdf
---
q.e.d.
```
On the file system level the file structure will be:
```
01.home/
default.md
02.add-new-article/
default.md
03.assignments/
cmpt363-e100/
default.md
drafts/
cmpt363-e100-1/
default.md
scrum-guide-sept-2013.pdf
modular.md
reviewed/
modular.md
```
### Form page example 2: create a blog post
In this example the user can add a blog post. To ensure the new page will be treated as a blog post simply set the `template` variable to be used by the new page to `item`. BTW the active theme must include the corresponding template `item.html.twig`. This is why it is best to start with Grav's default theme Antimatter.
```
---
title: 'Add Blog Post'
template: form
pageconfig:
parent: '/blog'
include_username: true
overwrite_mode: true
pagefrontmatter:
template: item
title: My new Blog post
taxonomy:
category: blog
tag: [journal, guest]
form:
name: add_page.blogpost
fields:
-
name: author
label: 'Author'
type: text
-
name: title
label: 'Post Title'
type: text
-
name: taxonomy.tag
label: 'Tags (comma separated)'
type: text
-
name: content
label: 'Post Content'
type: textarea
size: long
classes: editor
-
name: images
label: 'Images to upload'
type: file
multiple: true
accept:
- 'image/*'
-
name: honeypot
type: honeypot
buttons:
-
type: submit
value: Submit
process:
-
add_page: true
-
redirect: '/blog'
---
## New Blog Post
Write your blog post:
```
After the form has been submitted the user is taken to the blog main page where the new post should show up.
## Issues
### Grav Form issue
The form on the form page is a standard Grav form. Please note that the Grav Form Plugin currently (latest test using version 4.0.1) has an issue which prevents the form to be submitted when a form field of type `file` is set to `required: true`(see issue [#106](https://github.com/getgrav/grav-plugin-form/issues/106)).
## Credits
- Team Grav and everyone who contributes to Grav;
- Wes Cossick for [SimpleMDE Markdown Editor](https://simplemde.com);
- All [contributors](https://github.com/bleutzinn/grav-plugin-add-page-by-form/graphs/contributors) who've helped me out on things.

View File

@ -0,0 +1,969 @@
<?php
namespace Grav\Plugin;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Form\FormFlash;
use Grav\Common\Grav;
use Grav\Common\Page\Page;
use Grav\Common\Plugin;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Yaml\Yaml;
/**
* Class AddPageByFormPlugin
* @package Grav\Plugin
*/
class AddPageByFormPlugin extends Plugin
{
private $new_page_route = '';
private $move_self_files = false;
private $say_my_name = ['addpage', 'add_page'];
private $uploads = array();
private $page_frontmatter = array();
protected $post = array();
/**
* Extends a path
*
* @param string $path
* @param string $page
* @param string $slug
*
* @return string $path
*/
public function buildPath($path, $page, $slug)
{
if (!is_null($page)) {
return $path . DS . $page->folder();
} else {
return $path . DS . $slug;
}
}
/**
* Extends a route
*
* @param string $route
* @param string $page
* @param string $slug
*
* @return string $route
*/
public function buildRoute($route, $page, $slug)
{
if (!is_null($page)) {
return $route . DS . $page->slug();
} else {
return $route . DS . $slug;
}
}
/**
* Copy uploaded files and return list of properties
*
* @param string $form
* @param string $new_page
*
* @return array $file_fields
*/
public function copyFiles($form, $new_page)
{
// Copy uploaded files to the new page folder
// and prepare uploaded file properties
$new_page_path = $new_page->relativePagePath();
$file_fields = array();
$deleted_files = array();
$flash = $form->getFlash();
$fields = $flash->getFilesByFields();
foreach ($fields as $file_field => $uploads) {
$file_fields[$file_field] = array();
foreach ($uploads as $file_name => $upload) {
// When upload field has been emptied $upload == null
if ($upload) {
$file_name = $upload->getClientFilename();
$destination = $upload->getDestination();
// Do the actual file copy unless destination is in tmp/forms
if (strpos($destination, 'tmp/forms') === false) {
$full_name = $new_page_path . DS . $file_name;
$upload->moveTo($full_name);
} else {
$full_name = $destination;
// Leave the copy to the Form plugin
}
$file_fields[$file_field][$file_name] = [
'name' => $file_name,
'type' => $upload->getClientMediaType(),
'size' => $upload->getSize(),
'path' => $full_name
];
}
else {
// Uploaded file was removed by the user
$to_be_deleted = $new_page_path . DS . $file_name;
array_push($deleted_files, array('file_field' => $file_field, 'file_path' => $to_be_deleted));
}
}
}
return array('uploaded' => $file_fields, 'deleted' => $deleted_files);
}
/**
*
* @param string $route
* @param boolean $create
*
* @return array|null [route, path]
*
* Crawl a route, using modular page folder names as a fallback and
* optionally creating non existing folders along the way
*/
public function crawlRoute($route, $create = false)
{
$path = USER_DIR . 'pages';
if (!is_null($route) && isset($route)) {
if ($route != DS) {
$slugs = explode(DS, $route);
$route = '';
for ($i = 1; $i < count($slugs); $i++) {
$page = $this->pageExists($route, $slugs[$i]);
if (is_null($page)) {
if ($create) {
// Create folder if it doesn't exist
$slugs[$i] = $this->createFolder($path . DS . $slugs[$i]);
} else {
return null;
}
}
$route = $this->buildRoute($route, $page, $slugs[$i]);
$path = $this->buildPath($path, $page, $slugs[$i]);
}
}
}
return ['route' => $route, 'path' => $path];
}
/**
* Create a folder if it does not exist
*
* @param string $path
*
* @return string $folder_name
*/
public function createFolder($path)
{
$folder_names = explode(DS, $path);
$folder_name = $folder_names[count($folder_names) - 1];
if (!file_exists($path)) {
// split and sanitize leaf as a slug (disallowing periods)
$folder_name = $this->sanitize($folder_name, 'slug');
unset($folder_names[count($folder_names) - 1]);
$path = implode(DS, $folder_names);
Folder::create($path . DS . $folder_name);
}
return $folder_name;
}
/**
* Custom filter_var
*
* Based upon: https://gist.github.com/chtombleson/10002134
* Get a value from $_POST and sanitize it
*
* @param string $var Variable to check
* @param string $type What type is the value (string, email, int, float, encoded, url, email)
* @param array $option Options for filter_var
* @return mixed will return false on failure
*/
public function filter_post_var($var, $type = 'string', $options = array()) {
if (!isset($this->post[$var])) {
return false;
}
return filter_var($this->post[$var], $this->get_filter($type), $options);
}
/**
* Source: https://gist.github.com/chtombleson/10002134
*
*/
private function get_filter($type) {
switch (strtolower($type)) {
case 'string':
$filter = FILTER_SANITIZE_STRING;
break;
case 'int':
$filter = FILTER_SANITIZE_NUMBER_INT;
break;
case 'float' || 'decimal':
$filter = FILTER_SANITIZE_NUMBER_FLOAT;
break;
case 'encoded':
$filter = FILTER_SANITIZE_ENCODED;
break;
case 'url':
$filter = FILTER_SANITIZE_URL;
break;
case 'email':
$filter = FILTER_SANITIZE_EMAIL;
break;
default:
$filter = FILTER_SANITIZE_STRING;
}
return $filter;
}
/**
* Get parent page
*
* @param string $parent
* @param object $page
*
* @return object $parent_page
*/
public function getParentPage($parent, $page)
{
if ($parent != '') {
// Check for a relative parent
if ($parent[0] != DS) {
// Make an absolute route starting from this form page
$parent = $page->route() . DS . $parent;
}
// Check whether the parent page exists, allowing for modular pages
// but disallowing empty folders in the route
if (!is_null($this->crawlRoute($parent, false))) {
$parent_page = $this->grav['page']->find($parent);
} else {
// Gracefully continue and falback to adding the
// new page as a child page of the form page
$parent_page = $page;
}
} else {
$parent_page = $page;
}
return $parent_page; // can be a page or null if there is no page.
}
/**
* @return array
*
* The getSubscribedEvents() gives the core a list of events
* that the plugin wants to listen to. The key of each
* array section is the event that the plugin listens to
* and the value (in the form of an array) contains the
* callable (or function) as well as the priority. The
* higher the number the higher the priority.
*/
public static function getSubscribedEvents()
{
return [
'onPluginsInitialized' => ['onPluginsInitialized', 0],
'onFormProcessed' => ['onFormProcessed', 0]
];
}
/**
* @return string
*
* Return the state of a tristate configuration variable
*
*/
public function getTriStateConfig($value, $state) {
if(!isset($value)) {
return '';
}
if($value === $state) {
return $state;
}
if(gettype($value) === "boolean") {
if($value) {
return "true";
}
else {
return "false";
}
}
if(gettype($value) === "string") {
$value = trim(strtolower($value));
};
if(in_array($value, [1, '1', 'on', 'true'], true)) {
return 'true';
}
else {
return 'false';
}
}
/**
* Handle form action
*
* @param $event
*
*/
public function onFormProcessed(Event $event)
{
$form = $event['form'];
$action = $event['action'];
$params = $event['params'];
switch (true) {
case (in_array($action, $this->say_my_name)):
if (isset($_POST)) {
$pages = $this->grav['pages'];
$page = $this->grav['page'];
$header = $page->header();
$form_page_relative_page_path = $page->relativePagePath();
// Get default settings form plugin config
$include_username = $this->config->get('plugins.add-page-by-form.include_username');
$overwrite_mode = $this->getTriStateConfig($this->config->get('plugins.add-page-by-form.overwrite_mode'), 'edit');
$date_format = $this->config->get('plugins.add-page-by-form.date_display_format');
$auto_taxonomy_types = $this->config->get('plugins.add-page-by-form.auto_taxonomy_types');
$physical_template_name = $this->config->get('plugins.add-page-by-form.physical_template_name');
$slug_field = '';
// For next plugin version
$include_timestamp = false;
// Initialize default subroute
$sub_route = '';
// Get settings from pageconfig block and override values via form fields
if (isset($header->pageconfig) && is_array($header->pageconfig)) {
$positives = ['1', 'on', 'true'];
$pageconfig = $header->pageconfig;
if (isset($pageconfig['parent'])) {
$parent = strtolower(trim($pageconfig['parent']));
}
if (isset($pageconfig['subroute'])) {
$sub_route = strtolower(trim($pageconfig['subroute']));
}
if (isset($pageconfig['include_username'])) {
$include_username = in_array(strtolower(trim($pageconfig['include_username'])), $positives);
}
if (isset($pageconfig['overwrite_mode'])) {
$overwrite_mode = $this->getTriStateConfig($pageconfig['overwrite_mode'], 'edit');
}
if (isset($pageconfig['slug_field'])) {
$slug_field = strtolower(trim($pageconfig['slug_field']));
}
if (isset($pageconfig['physical_template_name'])) {
$physical_template_name = in_array(strtolower(trim($pageconfig['physical_template_name'])), $positives);
}
}
// Assemble the new page frontmatter from the page_frontmatter block
// as set in the form page
if (isset($header->pagefrontmatter) && is_array($header->pagefrontmatter)) {
$page_frontmatter = $header->pagefrontmatter;
} else {
$page_frontmatter = array();
}
// Add username (or not)
if ($include_username) {
$username = null;
if (!is_null($this->grav['session']->user)) {
$username = $this->grav['session']->user->username;
}
if (is_null($username)) {
$username = '';
}
$page_frontmatter['username'] = $username;
}
// Get all form field values
$form_data = $form->value()->toArray();
if (isset($form_data)) {
// Append taxonomy
if (isset($form_data['taxonomy']) && is_array($form_data['taxonomy'])) {
// Convert comma separated list into array assuming double quoted items
foreach ($form_data['taxonomy'] as $key => $value) {
$values = str_getcsv($value, ',', '"');
foreach ($values as $k => $v) {
$values[$k] = trim($v);
}
$form_data['taxonomy'][$key] = $values;
}
if (isset($page_frontmatter['taxonomy'])) {
// Append type/values
$page_frontmatter['taxonomy'] = array_merge_recursive($page_frontmatter['taxonomy'], $form_data['taxonomy']);
// Remove duplicate values
foreach ($page_frontmatter['taxonomy'] as $key => $value) {
if (is_array($page_frontmatter['taxonomy'][$key])) {
$page_frontmatter['taxonomy'][$key] = array_keys(array_flip($page_frontmatter['taxonomy'][$key]));
}
}
} else {
// Add taxonomy, types and values
$page_frontmatter['taxonomy'] = $form_data['taxonomy'];
}
// Remove taxonomy from form data (to prevent merging raw data)
unset($form_data['taxonomy']);
}
// Merge variables from pagefrontmatter block and form fields;
// Values that have been through a Twig Processor are in the
// page_frontmatter and take precedence over the form values
$page_frontmatter = array_merge($page_frontmatter, $form_data);
}
// Here you can insert anything else into the new page frontmatter
/*
$result = 'Hello World';
$page_frontmatter['result'] = $result;
*/
// If content is not included as a form value then fallback to config default
if (isset($page_frontmatter['content'])) {
$content = $page_frontmatter['content'];
} else {
$content = $this->config->get('plugins.add-page-by-form.default_content');
}
if ($physical_template_name && isset($page_frontmatter['template'])) {
$page_template = $page_frontmatter['template'];
// Remove the frontmatter variable template as which template
// must be used will be determined by the new page filename
unset($page_frontmatter['template']);
} else {
$page_template = 'default';
}
// Remove unwanted items from new page frontmatter
unset($page_frontmatter['_json']);
unset($page_frontmatter['content']);
unset($page_frontmatter['parent']);
unset($page_frontmatter['subroute']);
// Initialize default page parent
if (isset($header->parent)) {
// For backwards compatibility
$parent = $header->parent;
} else {
$parent = '';
}
// Override parent if set in pageconfig block
if (isset($pageconfig['parent'])) {
$parent = strtolower(trim($pageconfig['parent']));
}
// Override parent if set in the form
if (isset($form_data['parent'])) {
$parent = strtolower(trim($form_data['parent']));
}
// Removes multiple concatenated slashes plus a trailing slash if present
if (strlen($parent) > 1) {
$parent = preg_replace('/[\/]+/', DS, $parent);
$parent = rtrim($parent, DS);
}
// Get the "parent to be"
$parent_page = $this->getParentPage($parent, $page);
$parent_page_path = $parent_page->path();
$parent_page_route = $parent_page->route();
if ($overwrite_mode === 'edit') {
// Get slug of exisiting page
// Normal method
if(isset($form_data['edit_path'])) {
$slug = basename(dirname($form_data['edit_path']));
}
else {
// Alternative method
if(isset($form_data['file_path'])) {
$slug = basename(dirname($form_data['file_path']));
}
else {
$slug = '';
}
}
}
else {
// Create the slug for the new page
// Override subroute
if (isset($form_data['subroute'])) {
$sub_route = mb_strtolower(trim($form_data['subroute']));
}
if ($sub_route != '') {
// Remove any multiple concatenated slashes
$sub_route = preg_replace('/[\/]+/', DS, $sub_route);
// Remove preceding and trailing slashes if present
$sub_route = trim($sub_route, DS);
// Prepare for crawling
$parent_page_route = $parent_page->route() . DS . $sub_route;
// Create subroute path if it doesn't exist
$parent_destination = $this->crawlRoute($parent_page_route, true);
$parent_page_route = $parent_destination['route'];
$parent_page_path = $parent_destination['path'];
}
// Create a slug to be used as the page name (used publicly in URLs etc.)
if ($slug_field != '') {
if (isset($page_frontmatter[$slug_field])) {
$slug = self::slug($page_frontmatter[$slug_field]);
}
}
if (!isset($slug)) {
if (isset($page_frontmatter['title'])) {
$slug = self::slug($page_frontmatter['title']);
} else {
$slug = $this->config->get('plugins.add-page-by-form.default_title');
$slug = self::slug($slug);
}
}
}
if (empty($slug)) {
$this->grav->fireEvent('onFormValidationError', new Event([
'form' => $form,
'message' => '<strong>ERROR</strong> in Add Page by Form Plugin: Variable \'slug\' is empty']));
$event->stopPropagation();
return;
}
$new_page_folder = $parent_page_path . DS . $slug;
// Check overwrite mode
// When overwrite mode == 'true' replace page including media
// When overwrite mode == 'false' create a sequential named page
// When overwrite mode == 'edit' edit page and page media
if ($overwrite_mode !== 'false') {
if (file_exists($new_page_folder)) {
if ($overwrite_mode === 'edit') {
$original_frontmatter = (array)$pages->get($new_page_folder)->header();
}
else {
Folder::delete($new_page_folder);
}
}
} else {
// Scan for the next available sequential suffix
$version = 0;
// Keep incrementing the page slug suffix to keep earlier versions / duplicates
while (file_exists($new_page_folder)) {
$version += 1;
$new_page_folder = $parent_page_path . DS . $slug . '-' . $version;
}
if ($version > 0) {
$slug = $slug . '-' . $version;
}
}
// Create and add the page to Grav
try {
// Create new page
$new_page = new Page;
// Get active or default language for page filename
// (e.g. 'nl' -> 'default.nl.md')
$language = Grav::instance()['language']->getLanguage() ?: null;
$extension = '.md';
if ($language != '') {
$new_page_name = $page_template . '.' . $language . $extension;
} else {
$new_page_name = $page_template . $extension;
}
$path = $parent_page_path . DS . $slug . DS . $new_page_name;
$route = $parent_page_route . DS . $slug;
// Set page location vars
$new_page->name($new_page_name);
$new_page->folder($slug);
//$new_page->path($path);
$new_page->extension($extension);
$new_page->parent($parent_page);
$new_page->filePath($path);
$new_page->routable(true);
// Add frontmatter vars
$new_page->header((object) $page_frontmatter);
$new_page->frontmatter(Yaml::dump((array)$new_page->header(), 20));
// Set page markdown content vars
$new_page->rawMarkdown((string) $content);
$new_page->file()->markdown($new_page->rawMarkdown());
// Add routing information
$pages->addPage($new_page, $this->new_page_route);
// Fire BeforePageSave event
$this->grav->fireEvent('onAddPageByFormPluginBeforePageSave', new Event(['page' => &$new_page]));
// Set time vars
$new_page->modified(time());
// First page save (required to have an existing new page folder
// to store any files with destination '@self' in)
$new_page->save();
// Copy uploaded files to the new page folder
$copy_files = $this->copyFiles($form, $new_page);
// Get uploaded files plus properties
$file_fields = $copy_files['uploaded'];
// If original frontmatter exists update it to keep
// already present page media
if (isset($original_frontmatter)) {
$file_fields_updated = array();
foreach ($file_fields as $file_field => $uploads) {
$file_fields_updated[$file_field] = array_merge($original_frontmatter[$file_field], $uploads);
// Get any (uploaded and then) deleted files
foreach ($copy_files['deleted'] as $file_to_delete) {
if (file_exists($file_to_delete['file_path'])) {
unlink($file_to_delete['file_path']);
if (in_array($file_to_delete['file_path'], $file_fields_updated[$file_field][basename($file_to_delete['file_path'])])) {
unset($file_fields_updated[$file_field][basename($file_to_delete['file_path'])]);
}
}
}
}
$file_fields = $file_fields_updated;
}
// Add uploaded file properties to frontmatter
if (isset($file_fields) && isset($page_frontmatter)) {
$page_frontmatter = array_merge($page_frontmatter, $file_fields);
}
// Add modified frontmatter to the page header
$new_page->header((object) $page_frontmatter);
$new_page->frontmatter(Yaml::dump((array)$new_page->header(), 20));
// Set time vars
$new_page->modified(time());
// Update the new page
$new_page->save();
// Store the route so it can be used to redirect to if needed and to be saved as a Twig var
$this->new_page_route = $route;
$this->page_frontmatter = $page_frontmatter;
// Process any new taxonomy types
if ($auto_taxonomy_types && isset($page_frontmatter['taxonomy'])) {
// Read site configuration
$grav = Grav::instance();
$locator = $grav['locator'];
$filename = 'config://site.yaml';
$file = YamlFile::instance($locator->findResource($filename, true, true));
$site_config = Yaml::parse($file->load());
// Merge taxonomy types
$taxonomies = (array) $this->config->get('site.taxonomies');
foreach (array_keys($page_frontmatter['taxonomy']) as $type) {
$taxonomies = array_merge($taxonomies, (array) $type);
}
// Don't bother if there are no new taxonomy types
if (count(array_unique($taxonomies)) > count($site_config['taxonomies'])) {
$this->config->set('site.taxonomies', $taxonomies);
$taxonomies_merged = array();
$taxonomies_merged['taxonomies'] = array_values(array_unique($taxonomies));
$site_config = array_merge($site_config, $taxonomies_merged);
// Update taxonomy types in site.yaml
$file->save($site_config);
$file->free();
}
}
// Fire AfterPageSave event
$this->grav->fireEvent('onAddPageByFormPluginAfterPageSave', new Event(['page' => $new_page]));
} catch (\Exception $e) {
$this->grav['debugger']->addMessage($e->getMessage());
$this->grav->fireEvent('onFormValidationError', new Event([
'form' => $form,
'message' => '<strong>ERROR:</strong> ' . $e->getMessage()]));
$event->stopPropagation();
return;
}
}
break;
case ($action == 'redirect'):
// The Form plugin does not know how to handle '@self' as a redirect
// or display parameter, so prepare the redirect to the new page
switch (strtolower((string) $params)) {
case '@self':
$route = $this->new_page_route;
break;
case '@self-admin':
$admin_route = $this->config->get('plugins.admin.route');
if ($admin_route && $this->config->get('plugins.admin.enabled')) {
$base = DS . trim($admin_route, DS);
$route = $base . DS . 'pages' . $this->new_page_route;
} else {
// Admin not installed or inactive
// Fall back to @self
$route = $this->new_page_route;
}
break;
default:
// No valid redirect to self parameter
$route = '';
}
// Do the redirect
// BTW if there is no route the redirect is handed over to the Form plugin
if ($route) {
/** @var Twig $twig */
$twig = $this->grav['twig'];
$twig->twig_vars['form'] = $form;
$twig->twig_vars['pagefrontmatter'] = $this->page_frontmatter;
/** @var Pages $pages */
$pages = $this->grav['pages'];
$page = $pages->dispatch($route, false);
// Redirect to the new page
unset($this->grav['page']);
$this->grav['page'] = $page;
$this->grav->redirect($route);
}
break;
}
}
/**
* Add assets
*
*/
public function onPageInitialized()
{
// Some forms (like forgot_password) do not have frontmatter
if (null !== ($this->grav['page']->frontmatter())) {
$data = Yaml::parse($this->grav['page']->frontmatter());
// Only act upon forms which are intended to be processed by this plugin
if (isset($data['form']) && isset($data['form']['name']) &&
in_array(strtolower(substr($data['form']['name'], 0, 7)), $this->say_my_name)) {
if ($this->config->get('plugins.add-page-by-form.use_editor_class',true)) {
$assets = $this->grav['assets'];
// Add jQuery library (no harm done when already present)
$assets->add('jquery', 101);
// Add SimpleMDE Markdown Editor
$assets->addCss('//cdn.jsdelivr.net/simplemde/latest/simplemde.min.css', 100);
$assets->addJs('//cdn.jsdelivr.net/simplemde/latest/simplemde.min.js', 100);
// Add custom styles
$assets->addCss('plugin://add-page-by-form/assets/css/customstyles.css', 110);
// Load inline Javascript code from configuration file
$assets->addInlineJs(file_get_contents('plugin://add-page-by-form/assets/js/simplemde_config.js'), 110);
}
}
}
}
/**
* Initialize the plugin
*/
public function onPluginsInitialized()
{
// Don't proceed if we are in the admin plugin
if ($this->isAdmin()) {
return;
}
// Enable the events we are interested in
$this->enable([
'onPageInitialized' => ['onPageInitialized', 0],
]);
}
/**
* Check whether a page exists at the specified route irrespective of page type
*
* @param string $route
* @param string $slug
*
* @return object $page
*/
public function pageExists($route, $slug)
{
$page = $this->grav['page']->find($route . DS . $slug);
if (is_null($page) && !empty($slug) && $slug[0] != '_') {
$page = $this->grav['page']->find($route . DS . '_' . $slug);
}
return $page;
}
/**
* Generates a slug of the given string
* Source: Laravel str_slug()
*
* @param string $str
* @param string $separator (optional)
* @return string
*/
public static function slug(string $str, string $separator = '-')
{
// Sanitize non-latin characters
$str = self::sanitize($str, 'slug');
// Convert all dashes/underscores into separator
$flip = $separator === '-' ? '_' : '-';
$str = preg_replace('!['.preg_quote($flip).']+!u', $separator, $str);
// Replace @ with the word 'at'
$str = str_replace('@', $separator.'at'.$separator, $str);
// Remove all characters that are not the separator, letters, numbers, or whitespace.
$str = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', strtolower($str));
// Replace all separator characters and whitespace by a single separator
$str = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $str);
return trim($str, $separator);
}
/**
* Sanitize a string into a safe filename or slug
*
* @param string $f
*
* @return string
*/
public function sanitize($f, $type = 'file')
{
/* A combination of various methods to sanitize a string while retaining
the "essence" of the original file name as much as possible.
Note: unsuitable for file paths as '/' and '\' are filtered out.
Sources:
http://www.house6.com/blog/?p=83
and
http://stackoverflow.com/a/24984010
*/
$replace_chars = array(
'&amp;' => '-and-', '@' => '-at-', '©' => 'c', '®' => 'r', 'À' => 'a',
'Á' => 'a', 'Â' => 'a', 'Ä' => 'a', 'Å' => 'a', 'Æ' => 'ae', 'Ç' => 'c',
'È' => 'e', 'É' => 'e', 'Ë' => 'e', 'Ì' => 'i', 'Í' => 'i', 'Î' => 'i',
'Ï' => 'i', 'Ò' => 'o', 'Ó' => 'o', 'Ô' => 'o', 'Õ' => 'o', 'Ö' => 'o',
'Ø' => 'o', 'Ù' => 'u', 'Ú' => 'u', 'Û' => 'u', 'Ü' => 'u', 'Ý' => 'y',
'ß' => 'ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ä' => 'a', 'å' => 'a',
'æ' => 'ae', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ò' => 'o', 'ó' => 'o',
'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ø' => 'o', 'ù' => 'u', 'ú' => 'u',
'û' => 'u', 'ü' => 'u', 'ý' => 'y', 'þ' => 'p', 'ÿ' => 'y', 'Ā' => 'a',
'ā' => 'a', 'Ă' => 'a', 'ă' => 'a', 'Ą' => 'a', 'ą' => 'a', 'Ć' => 'c',
'ć' => 'c', 'Ĉ' => 'c', 'ĉ' => 'c', 'Ċ' => 'c', 'ċ' => 'c', 'Č' => 'c',
'č' => 'c', 'Ď' => 'd', 'ď' => 'd', 'Đ' => 'd', 'đ' => 'd', 'Ē' => 'e',
'ē' => 'e', 'Ĕ' => 'e', 'ĕ' => 'e', 'Ė' => 'e', 'ė' => 'e', 'Ę' => 'e',
'ę' => 'e', 'Ě' => 'e', 'ě' => 'e', 'Ĝ' => 'g', 'ĝ' => 'g', 'Ğ' => 'g',
'ğ' => 'g', 'Ġ' => 'g', 'ġ' => 'g', 'Ģ' => 'g', 'ģ' => 'g', 'Ĥ' => 'h',
'ĥ' => 'h', 'Ħ' => 'h', 'ħ' => 'h', 'Ĩ' => 'i', 'ĩ' => 'i', 'Ī' => 'i',
'ī' => 'i', 'Ĭ' => 'i', 'ĭ' => 'i', 'Į' => 'i', 'į' => 'i', 'İ' => 'i',
'ı' => 'i', 'IJ' => 'ij', 'ij' => 'ij', 'Ĵ' => 'j', 'ĵ' => 'j', 'Ķ' => 'k',
'ķ' => 'k', 'ĸ' => 'k', 'Ĺ' => 'l', 'ĺ' => 'l', 'Ļ' => 'l', 'ļ' => 'l',
'Ľ' => 'l', 'ľ' => 'l', 'Ŀ' => 'l', 'ŀ' => 'l', 'Ł' => 'l', 'ł' => 'l',
'Ń' => 'n', 'ń' => 'n', 'Ņ' => 'n', 'ņ' => 'n', 'Ň' => 'n', 'ň' => 'n',
'ʼn' => 'n', 'Ŋ' => 'n', 'ŋ' => 'n', 'Ō' => 'o', 'ō' => 'o', 'Ŏ' => 'o',
'ŏ' => 'o', 'Ő' => 'o', 'ő' => 'o', 'Œ' => 'oe', 'œ' => 'oe', 'Ŕ' => 'r',
'ŕ' => 'r', 'Ŗ' => 'r', 'ŗ' => 'r', 'Ř' => 'r', 'ř' => 'r', 'Ś' => 's',
'ś' => 's', 'Ŝ' => 's', 'ŝ' => 's', 'Ş' => 's', 'ş' => 's', 'Š' => 's',
'š' => 's', 'Ţ' => 't', 'ţ' => 't', 'Ť' => 't', 'ť' => 't', 'Ŧ' => 't',
'ŧ' => 't', 'Ũ' => 'u', 'ũ' => 'u', 'Ū' => 'u', 'ū' => 'u', 'Ŭ' => 'u',
'ŭ' => 'u', 'Ů' => 'u', 'ů' => 'u', 'Ű' => 'u', 'ű' => 'u', 'Ų' => 'u',
'ų' => 'u', 'Ŵ' => 'w', 'ŵ' => 'w', 'Ŷ' => 'y', 'ŷ' => 'y', 'Ÿ' => 'y',
'Ź' => 'z', 'ź' => 'z', 'Ż' => 'z', 'ż' => 'z', 'Ž' => 'z', 'ž' => 'z',
'ſ' => 'z', 'Ə' => 'e', 'ƒ' => 'f', 'Ơ' => 'o', 'ơ' => 'o', 'Ư' => 'u',
'ư' => 'u', 'Ǎ' => 'a', 'ǎ' => 'a', 'Ǐ' => 'i', 'ǐ' => 'i', 'Ǒ' => 'o',
'ǒ' => 'o', 'Ǔ' => 'u', 'ǔ' => 'u', 'Ǖ' => 'u', 'ǖ' => 'u', 'Ǘ' => 'u',
'ǘ' => 'u', 'Ǚ' => 'u', 'ǚ' => 'u', 'Ǜ' => 'u', 'ǜ' => 'u', 'Ǻ' => 'a',
'ǻ' => 'a', 'Ǽ' => 'ae', 'ǽ' => 'ae', 'Ǿ' => 'o', 'ǿ' => 'o', 'ə' => 'e',
'Ё' => 'jo', 'Є' => 'e', 'І' => 'i', 'Ї' => 'i', 'А' => 'a', 'Б' => 'b',
'В' => 'v', 'Г' => 'g', 'Д' => 'd', 'Е' => 'e', 'Ж' => 'zh', 'З' => 'z',
'И' => 'i', 'Й' => 'j', 'К' => 'k', 'Л' => 'l', 'М' => 'm', 'Н' => 'n',
'О' => 'o', 'П' => 'p', 'Р' => 'r', 'С' => 's', 'Т' => 't', 'У' => 'u',
'Ф' => 'f', 'Х' => 'h', 'Ц' => 'c', 'Ч' => 'ch', 'Ш' => 'sh', 'Щ' => 'sch',
'Ъ' => '-', 'Ы' => 'y', 'Ь' => '-', 'Э' => 'je', 'Ю' => 'ju', 'Я' => 'ja',
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e',
'ж' => 'zh', 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l',
'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's',
'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 'ч' => 'ch',
'ш' => 'sh', 'щ' => 'sch', 'ъ' => '-', 'ы' => 'y', 'ь' => '-', 'э' => 'je',
'ю' => 'ju', 'я' => 'ja', 'ё' => 'jo', 'є' => 'e', 'і' => 'i', 'ї' => 'i',
'Ґ' => 'g', 'ґ' => 'g', 'א' => 'a', 'ב' => 'b', 'ג' => 'g', 'ד' => 'd',
'ה' => 'h', 'ו' => 'v', 'ז' => 'z', 'ח' => 'h', 'ט' => 't', 'י' => 'i',
'ך' => 'k', 'כ' => 'k', 'ל' => 'l', 'ם' => 'm', 'מ' => 'm', 'ן' => 'n',
'נ' => 'n', 'ס' => 's', 'ע' => 'e', 'ף' => 'p', 'פ' => 'p', 'ץ' => 'C',
'צ' => 'c', 'ק' => 'q', 'ר' => 'r', 'ש' => 'w', 'ת' => 't', '™' => 'tm',
'Ã' => 'A', 'Ð' => 'Dj', 'Ê' => 'E', 'Ñ' => 'N', 'Þ' => 'B', 'ã' => 'a',
'ð' => 'o', 'ñ' => 'n', '#' => '-nr-');
// "Translate" multi byte characters to 'corresponding' ASCII characters
$f = strtr($f, $replace_chars);
// Convert special characters to a hyphen
$f = str_replace(array(
' ', '!', '\\', '/', '\'', '`', '"', '~', '%', '|',
'*', '$', '^', '(', ')', '[', ']', '{', '}',
'+', ',', ':', ';', '<', '=', '>', '?', '|'), '-', $f);
// Remove any non ASCII characters
$f = preg_replace('/[^(\x20-\x7F)]*/', '', $f);
if ($type == 'file') {
// Remove non-word chars (leaving hyphens and periods)
$f = preg_replace('/[^\w\-\.]+/', '', $f);
// Convert multiple adjacent dots into a single one
$f = preg_replace('/[\.]+/', '.', $f);
} else { // Do not allow periods, for instance for a Grav slug
// Convert period to hyphen
$f = str_replace('.', '-', $f);
// Remove non-word chars (leaving hyphens)
$f = preg_replace('/[^\w\-]+/', '', $f);
}
// Convert multiple adjacent hyphens into a single one
$f = preg_replace('/[\-]+/', '-', $f);
// Change into a lowercase string; BTW no need to use mb_strtolower() here ;)
$f = strtolower($f);
return $f;
}
}

View File

@ -0,0 +1,9 @@
enabled: true
date_display_format: 'd-m-Y H:i'
default_title: 'My New Page'
default_content: 'No content.'
overwrite_mode: false
include_username: false
auto_taxonomy_types: false
use_editor_class: true
physical_template_name: true

View File

@ -0,0 +1,8 @@
.editor-toolbar a.active, .editor-toolbar a:focus {
outline: none;
}
.CodeMirror, .CodeMirror-scroll {
min-height: 70px;
}

View File

@ -0,0 +1,16 @@
$(function () {
$(".editor").each(function(){
var simplemde = new SimpleMDE({
element: this,
forceSync: true,
hideIcons: ["side-by-side", "fullscreen"],
spellChecker: false,
toolbar: ["bold", "italic", "heading", "|",
"quote", "unordered-list", "ordered-list", "|",
"link", "table", "|",
"undo", "redo", "|",
"preview", "guide"
]
});
});
});

View File

@ -0,0 +1,109 @@
name: Add Page By Form
version: 3.0.4
description: Adds a page by means of a form
icon: plus-square-o
author:
name: Ron Wardenier
email: ron@wardenier.com
homepage: https://github.com/bleutzinn/grav-plugin-add-page-by-form
keywords: grav, plugin, page, form, frontmatter
bugs: https://github.com/bleutzinn/grav-plugin-add-page-by-form/issues
docs: https://github.com/bleutzinn/grav-plugin-add-page-by-form/blob/master/README.md
license: MIT
dependencies:
- { name: grav, version: '>=1.6.0' }
form:
validation: strict
fields:
enabled:
type: toggle
label: Plugin status
highlight: 1
default: 0
options:
1: Enabled
0: Disabled
validate:
type: bool
default_title:
type: text
size: large
label: Default Page Title
help: Will be used for the new page folder name when no other value is set
default_content:
type: text
size: large
label: Default Page Content
help: Will be used as the page content for the new page when no other value is set
include_username:
type: toggle
label: Include username
help: Include the logged in user username in the new page frontmatter
highlight: 0
default: 0
options:
1: Enabled
0: Disabled
validate:
type: bool
overwrite_mode:
type: select
label: Overwrite mode
help: Overwrite existing page
highlight: 0
default: false
options:
0: Disabled
1: Enabled
edit: Edit
auto_taxonomy_types:
type: toggle
label: Add new taxonomy types
help: Automatically add new taxonomy types to site configuration. May have performance impact on large sites.
highlight: 0
default: 0
options:
1: Enabled
0: Disabled
validate:
type: bool
use_editor_class:
type: toggle
label: Use the editor class
help: When set, adding class=editor to a textarea provides the simpleMDE editor on that area
highlight: 1
default: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
date_display_format:
type: select
size: medium
classes: fancy
label: Date Format
default: 'd-m-Y H:i'
options:
'F jS Y': "February 1st 2014"
'l jS of F': "Saturday 1st of February"
'D, m M Y': "Sat, 01 Feb 2014"
'd-m-y': "01-02-14"
'd-m-Y': "01-02-2014"
'jS M Y': "1st Feb 2014"
'F Y': "Feb 2014"
'Y-m-d': "2014-02-01"
'd-m-Y H:i': "01-02-2014 09:30"
physical_template_name:
type: toggle
label: Physical template name
help: Use the template name in the filename of the new page
highlight: 1
default: 1
options:
1: Enabled
0: Disabled
validate:
type: bool

8
plugins/admin/.gitattributes vendored Normal file
View File

@ -0,0 +1,8 @@
# Linguist Normalizer
*.yaml linguistic-language=PHP
*.twig linguistic-language=PHP
**/gulpfile.babel.js linguist-vendored
**/webpack.conf.js linguist-vendored
**/js/*.js linguist-vendored
**/js/*.json linguist-vendored
**/css-compiled/*.css linguist-vendored

8
plugins/admin/.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,8 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: grav
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
custom: # Replace with a single custom sponsorship URL

2580
plugins/admin/CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
Please read the <a href="https://github.com/getgrav/grav/blob/develop/CONTRIBUTING.md" target="_blank">Contributing Guidelines of the Grav Project</a>

21
plugins/admin/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Grav
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

152
plugins/admin/README.md Normal file
View File

@ -0,0 +1,152 @@
# Grav Standard Administration Panel Plugin
This **admin plugin** for [Grav](https://github.com/getgrav/grav) is an HTML user interface that provides a convenient way to configure Grav and easily create and modify pages. This will remain a totally optional plugin, and is not in any way required or needed to use Grav effectively. In fact, the admin provides an intentionally limited view to ensure it remains easy to use and not overwhelming. I'm sure power users will still prefer to work with the configuration files directly.
![](assets/admin-dashboard.png)
# Features
* User login with automatic password encryption
* Forgot password functionality
* Logged-in-user management
* One click Grav core updates
* Dashboard with maintenance status, site activity and latest page updates
* Notifications system for latest news, blogs, and announcements
* Ajax-powered backup capability
* Ajax-powered clear-cache capability
* System configuration management
* Site configuration management
* Normal and Expert modes which allow editing via forms or YAML
* Page listing with filtering and search
* Page creation, editing, moving, copying, and deleting
* Powerful syntax highlighting code editor with instant Grav-powered preview
* Editor features, hot keys, toolbar, and distraction-free fullscreen mode
* Drag-n-drop upload of page media files including drag-n-drop placement in the editor
* One click theme and plugin updates
* Plugin manager that allows listing and configuration of installed plugins
* Theme manager that allows listing and configuration of installed themes
* GPM-powered installation of new plugins and themes
# Support
#### Support
We have tested internally, but we hope to use this public beta phase to identify, isolate, and fix issues related to the plugin to ensure it is as solid and reliable as possible.
For **live chatting**, please use the dedicated [Discord Chat Room](https://getgrav.org/discord) for discussions directly related to Grav.
For **bugs, features, improvements**, please ensure you [create issues in the admin plugin GitHub repository](https://github.com/getgrav/grav-plugin-admin).
# Installation
First ensure you are running the latest **Grav 1.6.7 or later**. This is required for the admin plugin to run properly (`-f` forces a refresh of the GPM index).
```
$ bin/gpm selfupgrade -f
```
The admin plugin actually requires the help of 3 other plugins, so to get the admin plugin to work you first need to install **admin**, **login**, **forms**, and **email** plugins. These are available via GPM, and because the plugin has dependencies you just need to proceed and install the admin plugin, and agree when prompted to install the others:
```
$ bin/gpm install admin
```
### Manual Installation
Manual installation is not the recommended method of installation, however, it is still possible to install the admin plugin manually. Basically, you need to download each of the following plugins individually:
* [admin](https://github.com/getgrav/grav-plugin-admin/archive/develop.zip)
* [login](https://github.com/getgrav/grav-plugin-login/archive/develop.zip)
* [form](https://github.com/getgrav/grav-plugin-form/archive/develop.zip)
* [email](https://github.com/getgrav/grav-plugin-email/archive/develop.zip)
Extract each archive file into your `user/plugins` folder, then ensure the folders are renamed to just `admin/`, `login/`, `form/`, and `email/`. Then proceed with the **Usage instructions below**.
# Usage
### Create User with CLI
After this you need to create a user account with admin privileges:
```
$ bin/plugin login new-user
```
### Create User Manually
Alternatively, you can create a user account manually, in a file called `user/accounts/admin.yaml`. This **filename** is actually the **username** that you will use to login. The contents will contain the other information for the user.
```
password: 'password'
email: 'youremail@mail.com'
fullname: 'Johnny Appleseed'
title: 'Site Administrator'
access:
admin:
login: true
super: true
```
Of course you should edit your `email`, `password`, `fullname`, and `title` to suit your needs.
> You can use any password when you manually put it in this `.yaml` file. However, when you change your password in the admin, it must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters.
# Accessing the Admin
By default, you can access the admin by pointing your browser to `http://yoursite.com/admin`. You can simply log in with the `username` and `password` set in the YAML file you configured earlier.
> After logging in, your **plaintext password** will be removed and replaced by an **encrypted** one.
# Standard Free & Paid Pro Versions
If you have been following the [blog](https://getgrav.org/blog), [Twitter](https://twitter.com/getgrav), [Discord chat](https://getgrav.org/discord), etc., you probably already know now that our intention is to provide two versions of this plugin.
The **standard free version**, is very powerful, and has more functionality than most commercial flat-file CMS systems.
We also intend to release in the near future a more feature-rich **pro version** that will include enhanced functionality, as well as some additional nice-to-have capabilities. This pro version will be a **paid** plugin the price of which is not yet 100% finalized.
# Admin Events
## General events
- onAdminRegisterPermissions - (admin)
- onAdminThemeInitialized
- onAdminPage - (page)
- onAdminMenu
- onAdminTwigTemplatePaths - (paths)
## Page specific events
- onAdminDashboard
- onAdminTools - (tools)
- onAdminLogFiles - (logs)
- onAdminGenerateReports - (reports)
## Tasks
- onAdminControllerInit - (controller)
- onAdminTaskExecute - (controller, method)
## Editing
- onAdminData
- onAdminSave - (object)
- onAdminAfterSave - (object)
## Pages
- onAdminPageTypes - (types)
- onAdminModularPageTypes
- onAdminSave - (page)
- onAdminAfterSaveAs - (path)
- onAdminAfterSave - (page)
- onAdminAfterDelete - (page)
- onAdminAfterAddMedia - (page)
- onAdminAfterDelMedia - (page)
- onAdminCreatePageFrontmatter - (header, data)
# Running Tests
First install the dev dependencies by running `composer update` from the Grav root.
Then `composer test` will run the Unit Tests, which should be always executed successfully on any site.

6
plugins/admin/UPGRADE.md Normal file
View File

@ -0,0 +1,6 @@
# Upgrading to Admin 1.10
Twig:
* **Admin link**: When linking to another admin page, use `{{ admin_route('/config/site') }}` instead of any other method, such as `{{ base_url_relative }}/config/site` (fixes multi-language issues)

1350
plugins/admin/admin.php Normal file

File diff suppressed because it is too large Load Diff

84
plugins/admin/admin.yaml Normal file
View File

@ -0,0 +1,84 @@
enabled: true
route: '/admin'
cache_enabled: true
theme: grav
logo_text: ''
body_classes: ''
content_padding: true
twofa_enabled: true
sidebar:
activate: tab
hover_delay: 100
size: auto
dashboard:
days_of_stats: 7
widgets_display:
dashboard-maintenance: true
dashboard-statistics: true
dashboard-notifications: true
dashboard-feed: true
dashboard-pages: true
pages:
show_parents: both
show_modular: true
session:
timeout: 1800
edit_mode: normal
frontend_preview_target: inline
show_github_msg: true
admin_icons: line-awesome
enable_auto_updates_check: true
notifications:
feed: true
dashboard: true
plugins: true
themes: true
popularity:
enabled: true
ignore: ['/test*','/modular']
history:
daily: 30
monthly: 12
visitors: 20
whitelabel:
quicktray_recompile: false
codemirror_theme: paper
codemirror_fontsize: md
codemirror_md_font: sans
logo_custom:
logo_login:
color_scheme:
accents:
primary-accent: button
secondary-accent: notice
tertiary-accent: critical
colors:
logo-bg: '#323640'
logo-link: '#FFFFFF'
nav-bg: '#3D424E'
nav-text: '#B7B9BD'
nav-link: '#ffffff'
nav-selected-bg: '#323640'
nav-selected-link: '#ffffff'
nav-hover-bg: '#434753'
nav-hover-link: '#ffffff'
toolbar-bg: '#ffffff'
toolbar-text: '#3D424E'
page-bg: '#F6F6F6'
page-text: '#6f7b8a'
page-link: '#0090D9'
content-bg: '#ffffff'
content-text: '#6f7b8a'
content-link: '#0090D9'
content-link2: '#da4b46'
content-header: '#414147'
content-tabs-bg: '#e6e6e6'
content-tabs-text: '#808080'
button-bg: '#0090D9'
button-text: '#ffffff'
notice-bg: '#06A599'
notice-text: '#ffffff'
update-bg: '#77559D'
update-text: '#ffffff'
critical-bg: '#F45857'
critical-text: '#ffffff'

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

View File

@ -0,0 +1,781 @@
name: Admin Panel
slug: admin
type: plugin
version: 1.10.38
description: Adds an advanced administration panel to manage your site
icon: empire
author:
name: Team Grav
email: devs@getgrav.org
url: https://getgrav.org
homepage: https://github.com/getgrav/grav-plugin-admin
keywords: admin, plugin, manager, panel
bugs: https://github.com/getgrav/grav-plugin-admin/issues
docs: https://github.com/getgrav/grav-plugin-admin/blob/develop/README.md
license: MIT
dependencies:
- { name: grav, version: '>=1.7.32' }
- { name: form, version: '>=6.0.1' }
- { name: login, version: '>=3.7.0' }
- { name: email, version: '>=3.1.6' }
- { name: flex-objects, version: '>=1.2.0' }
form:
validation: loose
fields:
admin_tabs:
type: tabs
fields:
config_tab:
type: tab
title: PLUGIN_ADMIN.CONFIGURATION
fields:
Basics:
type: section
title: PLUGIN_ADMIN.BASICS
underline: false
enabled:
type: hidden
label: PLUGIN_ADMIN.PLUGIN_STATUS
highlight: 1
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
cache_enabled:
type: toggle
label: PLUGIN_ADMIN.ADMIN_CACHING
help: PLUGIN_ADMIN.ADMIN_CACHING_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
twofa_enabled:
type: toggle
label: PLUGIN_LOGIN.2FA_TITLE
help: PLUGIN_LOGIN.2FA_ENABLED_HELP
default: 1
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
route:
type: text
label: PLUGIN_ADMIN.ADMIN_PATH
size: medium
placeholder: ADMIN_PATH_PLACEHOLDER
help: ADMIN_PATH_HELP
logo_text:
type: text
label: PLUGIN_ADMIN.LOGO_TEXT
size: medium
placeholder: "Grav"
help: PLUGIN_ADMIN.LOGO_TEXT_HELP
content_padding:
type: toggle
label: PLUGIN_ADMIN.CONTENT_PADDING
help: PLUGIN_ADMIN.CONTENT_PADDING_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
body_classes:
type: text
label: PLUGIN_ADMIN.BODY_CLASSES
size: medium
help: PLUGIN_ADMIN.BODY_CLASSES_HELP
sidebar.activate:
type: select
label: PLUGIN_ADMIN.SIDEBAR_ACTIVATION
help: PLUGIN_ADMIN.SIDEBAR_ACTIVATION_HELP
size: small
default: tab
options:
tab: PLUGIN_ADMIN.SIDEBAR_ACTIVATION_TAB
hover: PLUGIN_ADMIN.SIDEBAR_ACTIVATION_HOVER
sidebar.hover_delay:
type: text
size: x-small
append: PLUGIN_ADMIN.SIDEBAR_HOVER_DELAY_APPEND
label: PLUGIN_ADMIN.SIDEBAR_HOVER_DELAY
default: 500
validate:
type: number
min: 1
sidebar.size:
type: select
label: PLUGIN_ADMIN.SIDEBAR_SIZE
help: PLUGIN_ADMIN.SIDEBAR_SIZE_HELP
size: medium
default: auto
options:
auto: PLUGIN_ADMIN.SIDEBAR_SIZE_AUTO
small: PLUGIN_ADMIN.SIDEBAR_SIZE_SMALL
theme:
type: hidden
label: PLUGIN_ADMIN.THEME
default: grav
edit_mode:
type: select
label: PLUGIN_ADMIN.EDIT_MODE
size: small
default: normal
options:
normal: PLUGIN_ADMIN.NORMAL
expert: PLUGIN_ADMIN.EXPERT
help: PLUGIN_ADMIN.EDIT_MODE_HELP
frontend_preview_target:
type: select
label: PLUGIN_ADMIN.FRONTEND_PREVIEW_TARGET
size: medium
default: inline
options:
inline: PLUGIN_ADMIN.FRONTEND_PREVIEW_TARGET_INLINE
_blank: PLUGIN_ADMIN.FRONTEND_PREVIEW_TARGET_NEW
_self: PLUGIN_ADMIN.FRONTEND_PREVIEW_TARGET_CURRENT
pages.show_parents:
type: select
size: medium
label: PLUGIN_ADMIN.PARENT_DROPDOWN
highlight: 1
options:
both: PLUGIN_ADMIN.PARENT_DROPDOWN_BOTH
folder: PLUGIN_ADMIN.PARENT_DROPDOWN_FOLDER
fullpath: PLUGIN_ADMIN.PARENT_DROPDOWN_FULLPATH
pages.parents_levels:
type: text
label: PLUGIN_ADMIN.PARENTS_LEVELS
size: small
help: PLUGIN_ADMIN.PARENTS_LEVELS_HELP
pages.show_modular:
type: toggle
label: PLUGIN_ADMIN.MODULAR_PARENTS
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
help: PLUGIN_ADMIN.MODULAR_PARENTS_HELP
show_beta_msg:
type: hidden
show_github_msg:
type: toggle
label: PLUGIN_ADMIN.SHOW_GITHUB_LINK
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
help: PLUGIN_ADMIN.SHOW_GITHUB_LINK_HELP
enable_auto_updates_check:
type: toggle
label: PLUGIN_ADMIN.AUTO_UPDATES
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
help: PLUGIN_ADMIN.AUTO_UPDATES_HELP
session.timeout:
type: text
size: small
label: PLUGIN_ADMIN.TIMEOUT
append: GRAV.NICETIME.SECOND_PLURAL
help: PLUGIN_ADMIN.TIMEOUT_HELP
validate:
type: number
min: 1
hide_page_types:
type: select
size: large
label: PLUGIN_ADMIN.HIDE_PAGE_TYPES
classes: fancy
multiple: true
array: true
selectize:
create: true
data-options@: ['\Grav\Plugin\AdminPlugin::pagesTypes', true]
hide_modular_page_types:
type: select
size: large
label: PLUGIN_ADMIN.HIDE_MODULAR_PAGE_TYPES
classes: fancy
multiple: true
array: true
selectize:
create: true
data-options@: ['\Grav\Plugin\AdminPlugin::pagesModularTypes', true]
Dashboard:
type: section
title: PLUGIN_ADMIN.DASHBOARD
underline: true
widgets_display:
type: widgets
label: PLUGIN_ADMIN.WIDGETS_DISPLAY
validate:
type: array
Notifications:
type: section
title: PLUGIN_ADMIN.NOTIFICATIONS
underline: true
notifications.feed:
type: toggle
label: PLUGIN_ADMIN.FEED_NOTIFICATIONS
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
help: PLUGIN_ADMIN.FEED_NOTIFICATIONS_HELP
notifications.dashboard:
type: toggle
label: PLUGIN_ADMIN.DASHBOARD_NOTIFICATIONS
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
help: PLUGIN_ADMIN.DASHBOARD_NOTIFICATIONS_HELP
notifications.plugins:
type: toggle
label: PLUGIN_ADMIN.PLUGINS_NOTIFICATIONS
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
help: PLUGIN_ADMIN.PLUGINS_NOTIFICATIONS_HELP
notifications.themes:
type: toggle
label: PLUGIN_ADMIN.THEMES_NOTIFICATIONS
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
help: PLUGIN_ADMIN.THEMES_NOTIFICATIONS_HELP
customization_tab:
type: tab
title: PLUGIN_ADMIN.CUSTOMIZATION
fields:
whitelabel.logos:
type: section
underline: true
title: PLUGIN_ADMIN.LOGOS
whitelabel.logo_login:
type: file
label: PLUGIN_ADMIN.LOGIN_SCREEN_CUSTOM_LOGO_LABEL
destination: 'user://assets'
accept:
- image/*
whitelabel.logo_custom:
type: file
label: PLUGIN_ADMIN.TOP_LEFT_CUSTOM_LOGO_LABEL
destination: 'user://assets'
accept:
- image/*
codemirror_section:
type: section
underline: true
title: PLUGIN_ADMIN.CODEMIRROR
whitelabel.codemirror_theme:
type: select
label: PLUGIN_ADMIN.CODEMIRROR_THEME
default: paper
markdown: true
data-options@: '\Grav\Plugin\AdminPlugin::themeOptions'
description: PLUGIN_ADMIN.CODEMIRROR_THEME_DESC
whitelabel.codemirror_fontsize:
type: select
label: PLUGIN_ADMIN.CODEMIRROR_FONTSIZE
default: md
options:
sm: PLUGIN_ADMIN.CODEMIRROR_FONTSIZE_SM
md: PLUGIN_ADMIN.CODEMIRROR_FONTSIZE_MD
lg: PLUGIN_ADMIN.CODEMIRROR_FONTSIZE_LG
whitelabel.codemirror_md_font:
type: select
label: PLUGIN_ADMIN.CODEMIRROR_MD_FONT
default: sans
options:
sans: PLUGIN_ADMIN.CODEMIRROR_MD_FONT_SANS
mono: PLUGIN_ADMIN.CODEMIRROR_MD_FONT_MONO
customization_section:
type: section
underline: true
title: PLUGIN_ADMIN.CUSTOMIZATION
whitelabel.quicktray_recompile:
type: toggle
label: PLUGIN_ADMIN.QUICKTRAY_RECOMPILE
help: PLUGIN_ADMIN.QUICKTRAY_RECOMPILE_HELP
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
whitelabel.color_scheme.name:
type: text
label: PLUGIN_ADMIN.COLOR_SCHEME_NAME
help: PLUGIN_ADMIN.COLOR_SCHEME_NAME_HELP
placeholder: PLUGIN_ADMIN.COLOR_SCHEME_NAME_PLACEHOLDER
themes-preview:
type: themepreview
ignore: true;
label: PLUGIN_ADMIN.PRESETS
style: vertical
colorschemes:
type: colorscheme
label: PLUGIN_ADMIN.COLOR_SCHEME_LABEL
style: vertical
help: PLUGIN_ADMIN.COLOR_SCHEME_HELP
fields:
whitelabel.color_scheme.colors.logo-bg:
type: colorscheme.color
default: '#1e333e'
help: PLUGIN_ADMIN.LOGO_BG_HELP
whitelabel.color_scheme.colors.logo-link:
type: colorscheme.color
default: '#ffffff'
help: PLUGIN_ADMIN.LOGO_LINK_HELP
whitelabel.color_scheme.colors.nav-bg:
type: colorscheme.color
default: '#253a47'
help: PLUGIN_ADMIN.NAV_BG_HELP
whitelabel.color_scheme.colors.nav-text:
type: colorscheme.color
default: '#afc7d5'
help: PLUGIN_ADMIN.NAV_TEXT_HELP
whitelabel.color_scheme.colors.nav-link:
type: colorscheme.color
default: '#d1dee7'
help: PLUGIN_ADMIN.NAV_LINK_HELP
whitelabel.color_scheme.colors.nav-selected-bg:
type: colorscheme.color
default: '#2d4d5b'
help: PLUGIN_ADMIN.NAV_SELECTED_BG_HELP
whitelabel.color_scheme.colors.nav-selected-link:
type: colorscheme.color
default: '#ffffff'
help: PLUGIN_ADMIN.NAV_SELECTED_LINK_HELP
whitelabel.color_scheme.colors.nav-hover-bg:
type: colorscheme.color
default: '#1e333e'
help: PLUGIN_ADMIN.NAV_HOVER_BG_HELP
whitelabel.color_scheme.colors.nav-hover-link:
type: colorscheme.color
default: '#ffffff'
help: PLUGIN_ADMIN.NAV_HOVER_LINK_HELP
whitelabel.color_scheme.colors.toolbar-bg:
type: colorscheme.color
default: '#349886'
help: PLUGIN_ADMIN.TOOLBAR_BG_HELP
whitelabel.color_scheme.colors.toolbar-text:
type: colorscheme.color
default: '#ffffff'
help: PLUGIN_ADMIN.TOOLBAR_TEXT_HELP
whitelabel.color_scheme.colors.page-bg:
type: colorscheme.color
default: '#314d5b'
help: PLUGIN_ADMIN.PAGE_BG_HELP
whitelabel.color_scheme.colors.page-text:
type: colorscheme.color
default: '#81a5b5'
help: PLUGIN_ADMIN.PAGE_TEXT_HELP
whitelabel.color_scheme.colors.page-link:
type: colorscheme.color
default: '#aad9ed'
help: PLUGIN_ADMIN.PAGE_LINK_HELP
whitelabel.color_scheme.colors.content-bg:
type: colorscheme.color
default: '#eeeeee'
help: PLUGIN_ADMIN.CONTENT_BG_HELP
whitelabel.color_scheme.colors.content-text:
type: colorscheme.color
default: '#737c81'
help: PLUGIN_ADMIN.CONTENT_TEXT_HELP
whitelabel.color_scheme.colors.content-link:
type: colorscheme.color
default: '#0082ba'
help: PLUGIN_ADMIN.CONTENT_LINK_HELP
whitelabel.color_scheme.colors.content-link2:
type: colorscheme.color
default: '#da4b46'
help: PLUGIN_ADMIN.CONTENT_LINK2_HELP
whitelabel.color_scheme.colors.content-header:
type: colorscheme.color
default: '#314d5b'
help: PLUGIN_ADMIN.CONTENT_HEADER_HELP
whitelabel.color_scheme.colors.content-tabs-bg:
type: colorscheme.color
default: '#223a47'
help: PLUGIN_ADMIN.CONTENT_TABS_BG_HELP
whitelabel.color_scheme.colors.content-tabs-text:
type: colorscheme.color
default: '#d1dee7'
help: PLUGIN_ADMIN.CONTENT_TABS_TEXT_HELP
whitelabel.color_scheme.colors.content-highlight:
type: colorscheme.color
default: '#ffffd7'
help: PLUGIN_ADMIN.CONTENT_HIGHLIGHT_HELP
whitelabel.color_scheme.colors.button-bg:
type: colorscheme.color
default: '#41bea8'
help: PLUGIN_ADMIN.BUTTON_BG_HELP
whitelabel.color_scheme.colors.button-text:
type: colorscheme.color
default: '#ffffff'
help: PLUGIN_ADMIN.BUTTON_TEXT_HELP
whitelabel.color_scheme.colors.notice-bg:
type: colorscheme.color
default: '#00a6cf'
help: PLUGIN_ADMIN.NOTICE_BG_HELP
whitelabel.color_scheme.colors.notice-text:
type: colorscheme.color
default: '#ffffff'
help: PLUGIN_ADMIN.NOTICE_TEXT_HELP
whitelabel.color_scheme.colors.update-bg:
type: colorscheme.color
default: '#8f5aad'
help: PLUGIN_ADMIN.UPDATES_BG_HELP
whitelabel.color_scheme.colors.update-text:
type: colorscheme.color
default: '#ffffff'
help: PLUGIN_ADMIN.UPDATES_TEXT_HELP
whitelabel.color_scheme.colors.critical-bg:
type: colorscheme.color
default: '#da4b46'
help: PLUGIN_ADMIN.CRITICAL_BG_HELP
whitelabel.color_scheme.colors.critical-text:
type: colorscheme.color
default: '#ffffff'
help: PLUGIN_ADMIN.CRITICAL_TEXT_HELP
whitelabel.color_scheme.accents.primary-accent:
type: select
size: meidum
classes: fancy
label: PLUGIN_ADMIN.PRIMARY_ACCENT_LABEL
help: PLUGIN_ADMIN.PRIMARY_ACCENT_HELP
options:
button: PLUGIN_ADMIN.BUTTON_COLORS
content: PLUGIN_ADMIN.CONTENT_COLORS
tabs: PLUGIN_ADMIN.TABS_COLORS
critical: PLUGIN_ADMIN.CRITICAL_COLORS
logo: PLUGIN_ADMIN.LOGO_COLORS
nav: PLUGIN_ADMIN.NAV_COLORS
notice: PLUGIN_ADMIN.NOTICE_COLORS
page: PLUGIN_ADMIN.PAGE_COLORS
toolbar: PLUGIN_ADMIN.TOOLBAR_COLORS
update: PLUGIN_ADMIN.UPDATE_COLORS
whitelabel.color_scheme.accents.secondary-accent:
type: select
size: meidum
classes: fancy
label: PLUGIN_ADMIN.SECONDARY_ACCENT_LABEL
help: PLUGIN_ADMIN.SECONDARY_ACCENT_HELP
options:
button: PLUGIN_ADMIN.BUTTON_COLORS
content: PLUGIN_ADMIN.CONTENT_COLORS
tabs: PLUGIN_ADMIN.TABS_COLORS
critical: PLUGIN_ADMIN.CRITICAL_COLORS
logo: PLUGIN_ADMIN.LOGO_COLORS
nav: PLUGIN_ADMIN.NAV_COLORS
notice: PLUGIN_ADMIN.NOTICE_COLORS
page: PLUGIN_ADMIN.PAGE_COLORS
toolbar: PLUGIN_ADMIN.TOOLBAR_COLORS
update: PLUGIN_ADMIN.UPDATE_COLORS
whitelabel.color_scheme.accents.tertiary-accent:
type: select
size: meidum
classes: fancy
label: PLUGIN_ADMIN.TERTIARY_ACCENT_LABEL
help: PLUGIN_ADMIN.TERTIARY_ACCENT_HELP
options:
button: PLUGIN_ADMIN.BUTTON_COLORS
content: PLUGIN_ADMIN.CONTENT_COLORS
tabs: PLUGIN_ADMIN.TABS_COLORS
critical: PLUGIN_ADMIN.CRITICAL_COLORS
logo: PLUGIN_ADMIN.LOGO_COLORS
nav: PLUGIN_ADMIN.NAV_COLORS
notice: PLUGIN_ADMIN.NOTICE_COLORS
page: PLUGIN_ADMIN.PAGE_COLORS
toolbar: PLUGIN_ADMIN.TOOLBAR_COLORS
update: PLUGIN_ADMIN.UPDATE_COLORS
whitelabel.custom_footer:
type: textarea
rows: 2
label: PLUGIN_ADMIN.CUSTOM_FOOTER
help: PLUGIN_ADMIN.CUSTOM_FOOTER_HELP
placeholder: PLUGIN_ADMIN.CUSTOM_FOOTER_PLACEHOLDER
whitelabel.custom_css:
label: PLUGIN_ADMIN.CUSTOM_CSS_LABEL
placeholder: PLUGIN_ADMIN.CUSTOM_CSS_PLACEHOLDER
help: PLUGIN_ADMIN.CUSTOM_CSS_HELP
type: editor
codemirror:
mode: 'css'
indentUnit: 2
autofocus: true
indentWithTabs: true
lineNumbers: true
styleActiveLine: true
whitelabel.custom_presets:
label: PLUGIN_ADMIN.CUSTOM_PRESETS
help: PLUGIN_ADMIN.CUSTOM_PRESETS_HELP
placeholder: PLUGIN_ADMIN.CUSTOM_PRESETS_PLACEHOLDER
type: editor
codemirror:
mode: 'yaml'
indentUnit: 2
autofocus: true
indentWithTabs: false
lineNumbers: true
styleActiveLine: true
gutters: ['CodeMirror-lint-markers']
lint: true
extras_tab:
type: tab
title: PLUGIN_ADMIN.EXTRAS
fields:
Popularity:
type: section
title: PLUGIN_ADMIN.POPULARITY
underline: true
popularity.enabled:
type: toggle
label: PLUGIN_ADMIN.VISITOR_TRACKING
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
help: PLUGIN_ADMIN.VISITOR_TRACKING_HELP
dashboard.days_of_stats:
type: text
label: PLUGIN_ADMIN.DAYS_OF_STATS
append: days
size: x-small
default: 7
help: PLUGIN_ADMIN.DAYS_OF_STATS_HELP
validate:
type: int
popularity.ignore:
type: array
label: PLUGIN_ADMIN.IGNORE_URLS
size: large
help: PLUGIN_ADMIN.IGNORE_URLS_HELP
default: ['/test*','/modular']
value_only: true
placeholder_value: /ignore-this-route
popularity.history.daily:
type: hidden
label: PLUGIN_ADMIN.DAILY_HISTORY
default: 30
popularity.history.monthly:
type: hidden
label: PLUGIN_ADMIN.MONTHLY_HISTORY
default: 12
popularity.history.visitors:
type: hidden
label: PLUGIN_ADMIN.VISITORS_HISTORY
default: 20
MediaResize:
type: section
title: PLUGIN_ADMIN.MEDIA_RESIZE
underline: true
MediaResizeNote:
type: spacer
text: PLUGIN_ADMIN.PAGEMEDIA_RESIZER
markdown: true
pagemedia.resize_width:
type: number
size: x-small
append: PLUGIN_ADMIN.PIXELS
label: PLUGIN_ADMIN.RESIZE_WIDTH
default: 0
validate:
type: number
help: PLUGIN_ADMIN.RESIZE_WIDTH_HELP
pagemedia.resize_height:
type: number
size: x-small
append: PLUGIN_ADMIN.PIXELS
label: PLUGIN_ADMIN.RESIZE_HEIGHT
default: 0
validate:
type: number
help: PLUGIN_ADMIN.RESIZE_HEIGHT_HELP
pagemedia.res_min_width:
type: number
size: x-small
append: PLUGIN_ADMIN.PIXELS
label: PLUGIN_ADMIN.RES_MIN_WIDTH
default: 0
validate:
type: number
help: PLUGIN_ADMIN.RES_MIN_WIDTH_HELP
pagemedia.res_min_height:
type: number
size: x-small
append: PLUGIN_ADMIN.PIXELS
label: PLUGIN_ADMIN.RES_MIN_HEIGHT
default: 0
validate:
type: number
help: PLUGIN_ADMIN.RES_MIN_HEIGHT_HELP
pagemedia.res_max_width:
type: number
size: x-small
append: PLUGIN_ADMIN.PIXELS
label: PLUGIN_ADMIN.RES_MAX_WIDTH
default: 0
validate:
type: number
help: PLUGIN_ADMIN.RES_MAX_WIDTH_HELP
pagemedia.res_max_height:
type: number
size: x-small
append: PLUGIN_ADMIN.PIXELS
label: PLUGIN_ADMIN.RES_MAX_HEIGHT
default: 0
validate:
type: number
help: PLUGIN_ADMIN.RES_MAX_HEIGHT_HELP
pagemedia.resize_quality:
type: number
size: x-small
append: 0...1
label: PLUGIN_ADMIN.RESIZE_QUALITY
default: 0.8
validate:
type: number
step: 0.01
help: PLUGIN_ADMIN.RESIZE_QUALITY_HELP

View File

@ -0,0 +1,43 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
section:
type: section
title: PLUGIN_ADMIN.COPY_PAGE
title:
type: text
label: PLUGIN_ADMIN.PAGE_TITLE
help: PLUGIN_ADMIN.PAGE_TITLE_HELP
validate:
required: true
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
rule: slug
required: true
header.published:
id: move-header-published
type: toggle
label: PLUGIN_ADMIN.PUBLISHED
help: PLUGIN_ADMIN.PUBLISHED_HELP
highlight: ''
default: ''
size: medium
options:
'': PLUGIN_ADMIN.AUTO
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool

View File

@ -0,0 +1,52 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
section:
type: section
title: PLUGIN_ADMIN.ADD_MODULE_CONTENT
title:
type: text
label: PLUGIN_ADMIN.PAGE_TITLE
validate:
required: true
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
validate:
rule: slug
required: true
route:
type: parents
label: PLUGIN_ADMIN.PAGE
classes: fancy
validate:
required: true
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.MODULE_TEMPLATE
help: PLUGIN_ADMIN.PAGE_FILE_HELP
default: default
data-options@: '\Grav\Plugin\AdminPlugin::pagesModularTypes'
validate:
required: true
modular:
type: hidden
default: 1
validate:
type: bool
blueprint:
type: blueprint

View File

@ -0,0 +1,104 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: PLUGIN_ADMIN.CONTENT
fields:
xss_check:
type: xss
frontmatter:
classes: frontmatter
type: editor
label: PLUGIN_ADMIN.FRONTMATTER
autofocus: true
codemirror:
mode: 'yaml'
indentUnit: 4
autofocus: true
indentWithTabs: false
lineNumbers: true
styleActiveLine: true
gutters: ['CodeMirror-lint-markers']
lint: true
content:
type: markdown
header.media_order:
type: pagemedia
label: PLUGIN_ADMIN.PAGE_MEDIA
options:
type: tab
title: PLUGIN_ADMIN.OPTIONS
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
ordering:
type: toggle
label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
folder:
type: text
label: PLUGIN_ADMIN.FILENAME
validate:
rule: slug
required: true
route:
type: parents
label: PLUGIN_ADMIN.PARENT
classes: fancy
validate:
required: true
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.MODULE_TEMPLATE
default: default
data-options@: '\Grav\Plugin\AdminPlugin::pagesModularTypes'
validate:
required: true
column2:
type: column
fields:
order:
type: order
label: PLUGIN_ADMIN.ORDERING
blueprint:
type: blueprint

View File

@ -0,0 +1,5 @@
form:
validation: loose
fields:
route:
type: hidden

View File

@ -0,0 +1,62 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
section:
type: section
title: PLUGIN_ADMIN.ADD_PAGE
title:
type: text
label: PLUGIN_ADMIN.PAGE_TITLE
help: PLUGIN_ADMIN.PAGE_TITLE_HELP
validate:
required: true
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
rule: slug
required: true
route:
type: parents
label: PLUGIN_ADMIN.PARENT_PAGE
classes: fancy
validate:
required: true
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.PAGE_FILE
help: PLUGIN_ADMIN.PAGE_FILE_HELP
data-options@: '\Grav\Plugin\AdminPlugin::pagesTypes'
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageName'
validate:
required: true
visible:
type: toggle
label: PLUGIN_ADMIN.VISIBLE
help: PLUGIN_ADMIN.VISIBLE_HELP
highlight: ''
default: ''
options:
'': PLUGIN_ADMIN.AUTO
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
required: true
blueprint:
type: blueprint

View File

@ -0,0 +1,31 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
section:
type: section
title: PLUGIN_ADMIN.ADD_FOLDER
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
rule: slug
required: true
route:
type: parents
label: PLUGIN_ADMIN.PARENT_PAGE
classes: fancy
validate:
required: true
blueprint:
type: blueprint

View File

@ -0,0 +1,104 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: PLUGIN_ADMIN.CONTENT
fields:
xss_check:
type: xss
frontmatter:
classes: frontmatter
type: editor
label: PLUGIN_ADMIN.FRONTMATTER
autofocus: true
codemirror:
mode: 'yaml'
indentUnit: 4
autofocus: true
indentWithTabs: false
lineNumbers: true
styleActiveLine: true
gutters: ['CodeMirror-lint-markers']
lint: true
content:
type: codemirror
header.media_order:
type: pagemedia
label: PLUGIN_ADMIN.PAGE_MEDIA
options:
type: tab
title: PLUGIN_ADMIN.OPTIONS
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
ordering:
type: toggle
label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
rule: slug
required: true
route:
type: parents
label: PLUGIN_ADMIN.PARENT
classes: fancy
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.DISPLAY_TEMPLATE
help: PLUGIN_ADMIN.DISPLAY_TEMPLATE_HELP
default: default
data-options@: '\Grav\Plugin\AdminPlugin::pagesTypes'
validate:
required: true
column2:
type: column
fields:
order:
type: order
label: PLUGIN_ADMIN.ORDERING
blueprint:
type: blueprint

View File

@ -0,0 +1,34 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: PLUGIN_ADMIN.CONTENT
fields:
frontmatter:
classes: frontmatter
type: editor
label: PLUGIN_ADMIN.FRONTMATTER
autofocus: true
codemirror:
mode: 'yaml'
indentUnit: 4
autofocus: true
indentWithTabs: false
lineNumbers: true
styleActiveLine: true
gutters: ['CodeMirror-lint-markers']
lint: true

View File

@ -0,0 +1,36 @@
title: PLUGIN_ADMIN.MEDIA
form:
validation: loose
fields:
'types':
name: medias
type: list
label: PLUGIN_ADMIN.MEDIA_TYPES
style: vertical
key: extension
controls: both
collapsed: true
fields:
.extension:
type: key
label: PLUGIN_ADMIN.FILE_EXTENSION
.type:
type: text
label: PLUGIN_ADMIN.TYPE
.thumb:
type: text
label: PLUGIN_ADMIN.THUMB
.mime:
type: text
label: PLUGIN_ADMIN.MIME_TYPE
validate:
type: lower
.image:
type: textarea
yaml: true
label: PLUGIN_ADMIN.IMAGE_OPTIONS
validate:
type: yaml

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,182 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin;
use ArrayAccess;
use Exception;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
use Grav\Framework\Form\Interfaces\FormFlashInterface;
use Grav\Framework\Form\Interfaces\FormInterface;
use Grav\Framework\Form\Traits\FormTrait;
use InvalidArgumentException;
use JsonSerializable;
/**
* Class AdminForm
* @package Grav\Plugin\Admin
*/
class AdminForm implements FormInterface, JsonSerializable
{
use FormTrait;
/** @var string */
protected $nonce_name;
/** @var string */
protected $nonce_action;
/** @var callable */
protected $submitMethod;
/**
* AdminForm constructor.
*
* @param string $name
* @param array $options
*/
public function __construct(string $name, array $options)
{
$this->name = $name;
$this->nonce_name = $options['nonce_name'] ?? 'admin-nonce';
$this->nonce_action = $options['nonce_action'] ?? 'admin-form';
$this->setId($options['id'] ?? $this->getName());
$this->setUniqueId($options['unique_id'] ?? $this->getName());
$this->setBlueprint($options['blueprint']);
$this->setSubmitMethod($options['submit_method'] ?? null);
$this->setFlashLookupFolder('tmp://admin/forms/[SESSIONID]');
if (!empty($options['reset'])) {
$this->getFlash()->delete();
}
$this->initialize();
}
/**
* @return $this
*/
public function initialize(): AdminForm
{
$this->messages = [];
$this->submitted = false;
$this->unsetFlash();
/** @var FormFlashInterface $flash */
$flash = $this->getFlash();
if ($flash->exists()) {
$data = $flash->getData();
if (null !== $data) {
$data = new Data($data, $this->getBlueprint());
$data->setKeepEmptyValues(true);
$data->setMissingValuesAsNull(true);
}
$this->data = $data;
$this->files = $flash->getFilesByFields(false);
} else {
$this->data = new Data([], $this->getBlueprint());
$this->files = [];
}
return $this;
}
/**
* @return string
*/
public function getNonceName(): string
{
return $this->nonce_name;
}
/**
* @return string
*/
public function getNonceAction(): string
{
return $this->nonce_action;
}
/**
* @return string
*/
public function getScope(): string
{
return 'data.';
}
/**
* @param Blueprint $blueprint
*/
public function setBlueprint(Blueprint $blueprint): void
{
if (null === $blueprint) {
throw new InvalidArgumentException('Blueprint is required');
}
$this->blueprint = $blueprint;
}
/**
* @param string $field
* @param mixed $value
*/
public function setData(string $field, $value): void
{
$this->getData()->set($field, $value);
}
/**
* @return Blueprint
*/
public function getBlueprint(): Blueprint
{
return $this->blueprint;
}
/**
* @param callable|null $submitMethod
*/
public function setSubmitMethod(?callable $submitMethod): void
{
if (null === $submitMethod) {
throw new InvalidArgumentException('Submit method is required');
}
$this->submitMethod = $submitMethod;
}
/**
* @param array $data
* @param array $files
* @return void
* @throws Exception
*/
protected function doSubmit(array $data, array $files): void
{
$method = $this->submitMethod;
$method($data, $files);
$this->reset();
}
/**
* Filter validated data.
*
* @param ArrayAccess|Data|null $data
* @return void
*/
protected function filterData($data = null): void
{
if ($data instanceof Data) {
$data->filter(true, true);
}
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
declare(strict_types=1);
namespace Grav\Plugin\Admin;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Page;
use Grav\Framework\Form\Interfaces\FormFactoryInterface;
use Grav\Framework\Form\Interfaces\FormInterface;
/**
* Class FlexFormFactory
* @package Grav\Plugin\FlexObjects
*/
class AdminFormFactory implements FormFactoryInterface
{
/**
* @param Page $page
* @param string $name
* @param array $form
* @return FormInterface|null
*/
public function createPageForm(Page $page, string $name, array $form): ?FormInterface
{
return $this->createFormForPage($page, $name, $form);
}
/**
* @param PageInterface $page
* @param string $name
* @param array $form
* @return FormInterface|null
*/
public function createFormForPage(PageInterface $page, string $name, array $form): ?FormInterface
{
/** @var Admin|null $admin */
$admin = Grav::instance()['admin'] ?? null;
$object = $admin->form ?? null;
return $object && $object->getName() === $name ? $object : null;
}
}

View File

@ -0,0 +1,414 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
declare(strict_types=1);
namespace Grav\Plugin\Admin\Controllers;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Inflector;
use Grav\Common\Language\Language;
use Grav\Common\Utils;
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
use Grav\Framework\Form\Interfaces\FormInterface;
use Grav\Framework\Psr7\Response;
use Grav\Framework\RequestHandler\Exception\NotFoundException;
use Grav\Framework\RequestHandler\Exception\PageExpiredException;
use Grav\Framework\RequestHandler\Exception\RequestException;
use Grav\Framework\Route\Route;
use Grav\Framework\Session\SessionInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Session\Message;
abstract class AbstractController implements RequestHandlerInterface
{
/** @var string */
protected $nonce_action = 'admin-form';
/** @var string */
protected $nonce_name = 'admin-nonce';
/** @var ServerRequestInterface */
protected $request;
/** @var Grav */
protected $grav;
/** @var string */
protected $type;
/** @var string */
protected $key;
/**
* Handle request.
*
* Fires event: admin.[directory].[task|action].[command]
*
* @param ServerRequestInterface $request
* @return Response
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$attributes = $request->getAttributes();
$this->request = $request;
$this->grav = $attributes['grav'] ?? Grav::instance();
$this->type = $attributes['type'] ?? null;
$this->key = $attributes['key'] ?? null;
/** @var Route $route */
$route = $attributes['route'];
$post = $this->getPost();
if ($this->isFormSubmit()) {
$form = $this->getForm();
$this->nonce_name = $attributes['nonce_name'] ?? $form->getNonceName();
$this->nonce_action = $attributes['nonce_action'] ?? $form->getNonceAction();
}
try {
$task = $request->getAttribute('task') ?? $post['task'] ?? $route->getParam('task');
if ($task) {
if (empty($attributes['forwarded'])) {
$this->checkNonce($task);
}
$type = 'task';
$command = $task;
} else {
$type = 'action';
$command = $request->getAttribute('action') ?? $post['action'] ?? $route->getParam('action') ?? 'display';
}
$command = strtolower($command);
$event = new Event(
[
'controller' => $this,
'response' => null
]
);
$this->grav->fireEvent("admin.{$this->type}.{$type}.{$command}", $event);
$response = $event['response'];
if (!$response) {
/** @var Inflector $inflector */
$inflector = $this->grav['inflector'];
$method = $type . $inflector::camelize($command);
if ($method && method_exists($this, $method)) {
$response = $this->{$method}($request);
} else {
throw new NotFoundException($request);
}
}
} catch (\Exception $e) {
/** @var Debugger $debugger */
$debugger = $this->grav['debugger'];
$debugger->addException($e);
$response = $this->createErrorResponse($e);
}
if ($response instanceof Response) {
return $response;
}
return $this->createJsonResponse($response);
}
/**
* Get request.
*
* @return ServerRequestInterface
*/
public function getRequest(): ServerRequestInterface
{
return $this->request;
}
/**
* @param string|null $name
* @param mixed $default
* @return mixed
*/
public function getPost(string $name = null, $default = null)
{
$body = $this->request->getParsedBody();
if ($name) {
return $body[$name] ?? $default;
}
return $body;
}
/**
* Check if a form has been submitted.
*
* @return bool
*/
public function isFormSubmit(): bool
{
return (bool)$this->getPost('__form-name__');
}
/**
* Get form.
*
* @param string|null $type
* @return FormInterface
*/
public function getForm(string $type = null): FormInterface
{
$object = $this->getObject();
if (!$object) {
throw new \RuntimeException('Not Found', 404);
}
$formName = $this->getPost('__form-name__');
$uniqueId = $this->getPost('__unique_form_id__') ?: $formName;
$form = $object->getForm($type ?? 'edit');
if ($uniqueId) {
$form->setUniqueId($uniqueId);
}
return $form;
}
/**
* @return FlexObjectInterface
*/
abstract public function getObject();
/**
* Get Grav instance.
*
* @return Grav
*/
public function getGrav(): Grav
{
return $this->grav;
}
/**
* Get session.
*
* @return SessionInterface
*/
public function getSession(): SessionInterface
{
return $this->getGrav()['session'];
}
/**
* Display the current admin page.
*
* @return Response
*/
public function createDisplayResponse(): ResponseInterface
{
return new Response(418);
}
/**
* Create custom HTML response.
*
* @param string $content
* @param int $code
* @return Response
*/
public function createHtmlResponse(string $content, int $code = null): ResponseInterface
{
return new Response($code ?: 200, [], $content);
}
/**
* Create JSON response.
*
* @param array $content
* @return Response
*/
public function createJsonResponse(array $content): ResponseInterface
{
$code = $content['code'] ?? 200;
if ($code >= 301 && $code <= 307) {
$code = 200;
}
return new Response($code, ['Content-Type' => 'application/json'], json_encode($content));
}
/**
* Create redirect response.
*
* @param string $url
* @param int $code
* @return Response
*/
public function createRedirectResponse(string $url, int $code = null): ResponseInterface
{
if (null === $code || $code < 301 || $code > 307) {
$code = $this->grav['config']->get('system.pages.redirect_default_code', 302);
}
$accept = $this->getAccept(['application/json', 'text/html']);
if ($accept === 'application/json') {
return $this->createJsonResponse(['code' => $code, 'status' => 'redirect', 'redirect' => $url]);
}
return new Response($code, ['Location' => $url]);
}
/**
* Create error response.
*
* @param \Exception $exception
* @return Response
*/
public function createErrorResponse(\Exception $exception): ResponseInterface
{
$validCodes = [
400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418,
422, 423, 424, 425, 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 511
];
if ($exception instanceof RequestException) {
$code = $exception->getHttpCode();
$reason = $exception->getHttpReason();
} else {
$code = $exception->getCode();
$reason = null;
}
if (!in_array($code, $validCodes, true)) {
$code = 500;
}
$message = $exception->getMessage();
$response = [
'code' => $code,
'status' => 'error',
'message' => htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8')
];
$accept = $this->getAccept(['application/json', 'text/html']);
if ($accept === 'text/html') {
$method = $this->getRequest()->getMethod();
// On POST etc, redirect back to the previous page.
if ($method !== 'GET' && $method !== 'HEAD') {
$this->setMessage($message, 'error');
$referer = $this->request->getHeaderLine('Referer');
return $this->createRedirectResponse($referer, 303);
}
// TODO: improve error page
return $this->createHtmlResponse($response['message']);
}
return new Response($code, ['Content-Type' => 'application/json'], json_encode($response), '1.1', $reason);
}
/**
* Translate a string.
*
* @param string $string
* @return string
*/
public function translate(string $string): string
{
/** @var Language $language */
$language = $this->grav['language'];
return $language->translate($string);
}
/**
* Set message to be shown in the admin.
*
* @param string $message
* @param string $type
* @return $this
*/
public function setMessage($message, $type = 'info')
{
/** @var Message $messages */
$messages = $this->grav['messages'];
$messages->add($message, $type);
return $this;
}
/**
* Check if request nonce is valid.
*
* @param string $task
* @throws PageExpiredException If nonce is not valid.
*/
protected function checkNonce(string $task): void
{
$nonce = null;
if (\in_array(strtoupper($this->request->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
$nonce = $this->getPost($this->nonce_name);
}
if (!$nonce) {
$nonce = $this->grav['uri']->param($this->nonce_name);
}
if (!$nonce) {
$nonce = $this->grav['uri']->query($this->nonce_name);
}
if (!$nonce || !Utils::verifyNonce($nonce, $this->nonce_action)) {
throw new PageExpiredException($this->request);
}
}
/**
* Return the best matching mime type for the request.
*
* @param string[] $compare
* @return string|null
*/
protected function getAccept(array $compare): ?string
{
$accepted = [];
foreach ($this->request->getHeader('Accept') as $accept) {
foreach (explode(',', $accept) as $item) {
if (!$item) {
continue;
}
$split = explode(';q=', $item);
$mime = array_shift($split);
$priority = array_shift($split) ?? 1.0;
$accepted[$mime] = $priority;
}
}
arsort($accepted);
// TODO: add support for image/* etc
$list = array_intersect($compare, array_keys($accepted));
if (!$list && (isset($accepted['*/*']) || isset($accepted['*']))) {
return reset($compare) ?: null;
}
return reset($list) ?: null;
}
}

View File

@ -0,0 +1,359 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
declare(strict_types=1);
namespace Grav\Plugin\Admin\Controllers;
use Grav\Common\Config\Config;
use Grav\Common\Data\Blueprint;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use Grav\Common\Uri;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\Utils;
use Grav\Framework\Controller\Traits\ControllerResponseTrait;
use Grav\Framework\RequestHandler\Exception\PageExpiredException;
use Grav\Framework\Session\SessionInterface;
use Grav\Plugin\Admin\Admin;
use Grav\Plugin\Admin\AdminForm;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RocketTheme\Toolbox\Session\Message;
abstract class AdminController
{
use ControllerResponseTrait {
createRedirectResponse as traitCreateRedirectResponse;
getErrorJson as traitGetErrorJson;
}
/** @var string */
protected $nonce_action = 'admin-form';
/** @var string */
protected $nonce_name = 'admin-nonce';
/** @var Grav */
protected $grav;
/** @var PageInterface */
protected $page;
/** @var AdminForm|null */
protected $form;
public function __construct(Grav $grav)
{
$this->grav = $grav;
}
/**
* @return PageInterface|null
*/
public function getPage(): ?PageInterface
{
return $this->page;
}
/**
* Get currently active form.
*
* @return AdminForm|null
*/
public function getActiveForm(): ?AdminForm
{
if (null === $this->form) {
$post = $this->getPost();
$active = $post['__form-name__'] ?? null;
$this->form = $active ? $this->getForm($active) : null;
}
return $this->form;
}
/**
* Get a form.
*
* @param string $name
* @param array $options
* @return AdminForm|null
*/
public function getForm(string $name, array $options = []): ?AdminForm
{
$post = $this->getPost();
$page = $this->getPage();
$forms = $page ? $page->forms() : [];
$blueprint = $forms[$name] ?? null;
if (null === $blueprint) {
return null;
}
$active = $post['__form-name__'] ?? null;
$unique_id = $active && $active === $name ? ($post['__unique_form_id__'] ?? null) : null;
$options += [
'unique_id' => $unique_id,
'blueprint' => new Blueprint(null, ['form' => $blueprint]),
'submit_method' => $this->getFormSubmitMethod($name),
'nonce_name' => $this->nonce_name,
'nonce_action' => $this->nonce_action,
];
return new AdminForm($name, $options);
}
abstract protected function getFormSubmitMethod(string $name): callable;
/**
* @param string $route
* @param string|null $lang
* @return string
*/
public function getAdminUrl(string $route, string $lang = null): string
{
/** @var Pages $pages */
$pages = $this->grav['pages'];
$admin = $this->getAdmin();
return $pages->baseUrl($lang) . $admin->base . $route;
}
/**
* @param string $route
* @param string|null $lang
* @return string
*/
public function getAbsoluteAdminUrl(string $route, string $lang = null): string
{
/** @var Pages $pages */
$pages = $this->grav['pages'];
$admin = $this->getAdmin();
return $pages->baseUrl($lang, true) . $admin->base . $route;
}
/**
* Get session.
*
* @return SessionInterface
*/
public function getSession(): SessionInterface
{
return $this->grav['session'];
}
/**
* @return Admin
*/
protected function getAdmin(): Admin
{
return $this->grav['admin'];
}
/**
* @return UserInterface
*/
protected function getUser(): UserInterface
{
return $this->getAdmin()->user;
}
/**
* @return ServerRequestInterface
*/
public function getRequest(): ServerRequestInterface
{
return $this->getAdmin()->request;
}
/**
* @return array
*/
public function getPost(): array
{
return (array)($this->getRequest()->getParsedBody() ?? []);
}
/**
* Translate a string.
*
* @param string $string
* @param mixed ...$args
* @return string
*/
public function translate(string $string, ...$args): string
{
/** @var Language $language */
$language = $this->grav['language'];
array_unshift($args, $string);
return $language->translate($args);
}
/**
* Set message to be shown in the admin.
*
* @param string $message
* @param string $type
* @return $this
*/
public function setMessage(string $message, string $type = 'info'): AdminController
{
/** @var Message $messages */
$messages = $this->grav['messages'];
$messages->add($message, $type);
return $this;
}
/**
* @return Config
*/
protected function getConfig(): Config
{
return $this->grav['config'];
}
/**
* Check if request nonce is valid.
*
* @return void
* @throws PageExpiredException If nonce is not valid.
*/
protected function checkNonce(): void
{
$nonce = null;
$nonce_name = $this->form ? $this->form->getNonceName() : $this->nonce_name;
$nonce_action = $this->form ? $this->form->getNonceAction() : $this->nonce_action;
if (\in_array(strtoupper($this->getRequest()->getMethod()), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
$post = $this->getPost();
$nonce = $post[$nonce_name] ?? null;
}
/** @var Uri $uri */
$uri = $this->grav['uri'];
if (!$nonce) {
$nonce = $uri->param($nonce_name);
}
if (!$nonce) {
$nonce = $uri->query($nonce_name);
}
if (!$nonce || !Utils::verifyNonce($nonce, $nonce_action)) {
throw new PageExpiredException($this->getRequest());
}
}
/**
* Return the best matching mime type for the request.
*
* @param string[] $compare
* @return string|null
*/
protected function getAccept(array $compare): ?string
{
$accepted = [];
foreach ($this->getRequest()->getHeader('Accept') as $accept) {
foreach (explode(',', $accept) as $item) {
if (!$item) {
continue;
}
$split = explode(';q=', $item);
$mime = array_shift($split);
$priority = array_shift($split) ?? 1.0;
$accepted[$mime] = $priority;
}
}
arsort($accepted);
// TODO: add support for image/* etc
$list = array_intersect($compare, array_keys($accepted));
if (!$list && (isset($accepted['*/*']) || isset($accepted['*']))) {
return reset($compare) ?: null;
}
return reset($list) ?: null;
}
/**
* @param string $template
* @return PageInterface
*/
protected function createPage(string $template): PageInterface
{
$page = new Page();
// Plugins may not have the correct Cache-Control header set, force no-store for the proxies.
$page->expires(0);
$filename = "plugin://admin/pages/admin/{$template}.md";
if (!file_exists($filename)) {
throw new \RuntimeException(sprintf('Creating admin page %s failed: not found', $template));
}
Admin::DEBUG && Admin::addDebugMessage("Admin page: {$template}");
$page->init(new \SplFileInfo($filename));
$page->slug($template);
return $page;
}
/**
* @param string|null $url
* @param int|null $code
* @return ResponseInterface
*/
protected function createRedirectResponse(string $url = null, int $code = null): ResponseInterface
{
$request = $this->getRequest();
if (null === $url || '' === $url) {
$url = (string)$request->getUri();
} elseif (mb_strpos($url, '/') === 0) {
$url = $this->getAbsoluteAdminUrl($url);
}
if (null === $code) {
if (in_array($request->getMethod(), ['GET', 'HEAD'])) {
$code = 302;
} else {
$code = 303;
}
}
return $this->traitCreateRedirectResponse($url, $code);
}
/**
* @param \Throwable $e
* @return array
*/
protected function getErrorJson(\Throwable $e): array
{
$json = $this->traitGetErrorJson($e);
$code = $e->getCode();
if ($code === 401) {
$json['redirect'] = $this->getAbsoluteAdminUrl('/');
}
return $json;
}
}

View File

@ -0,0 +1,634 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin\Controllers\Login;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Page\Pages;
use Grav\Common\Uri;
use Grav\Common\User\Interfaces\UserCollectionInterface;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\Utils;
use Grav\Framework\RequestHandler\Exception\PageExpiredException;
use Grav\Framework\RequestHandler\Exception\RequestException;
use Grav\Plugin\Admin\Admin;
use Grav\Plugin\Admin\Controllers\AdminController;
use Grav\Plugin\Email\Email;
use Grav\Plugin\Login\Login;
use Psr\Http\Message\ResponseInterface;
use RobThree\Auth\TwoFactorAuthException;
/**
* Class LoginController
* @package Grav\Plugin\Admin\Controllers\Login
*/
class LoginController extends AdminController
{
/** @var string */
protected $nonce_action = 'admin-login';
/** @var string */
protected $nonce_name = 'login-nonce';
/**
* @return ResponseInterface
*/
public function displayLogin(): ResponseInterface
{
$this->page = $this->createPage('login');
$user = $this->getUser();
if ($this->is2FA($user)) {
$this->form = $this->getForm('login-twofa', ['reset' => true]);
} else {
$this->form = $this->getForm('login', ['reset' => true]);
}
return $this->createDisplayResponse();
}
/**
* @return ResponseInterface
*/
public function displayForgot(): ResponseInterface
{
$this->page = $this->createPage('forgot');
$this->form = $this->getForm('admin-login-forgot', ['reset' => true]);
return $this->createDisplayResponse();
}
/**
* Handle the reset password action.
*
* @param string|null $username
* @param string|null $token
* @return ResponseInterface
*/
public function displayReset(string $username = null, string $token = null): ResponseInterface
{
if ('' === (string)$username || '' === (string)$token) {
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
return $this->createRedirectResponse('/forgot');
}
$this->page = $this->createPage('reset');
$this->form = $this->getForm('admin-login-reset', ['reset' => true]);
$this->form->setData('username', $username);
$this->form->setData('token', $token);
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_NEW_PASSWORD'));
return $this->createDisplayResponse();
}
/**
* @return ResponseInterface
*/
public function displayRegister(): ResponseInterface
{
$route = $this->getRequest()->getAttribute('admin')['route'] ?? '';
if ('' !== $route) {
return $this->createRedirectResponse('/');
}
$this->page = $this->createPage('register');
$this->form = $this->getForm('admin-login-register');
return $this->createDisplayResponse();
}
/**
* @return ResponseInterface
*/
public function displayUnauthorized(): ResponseInterface
{
$uri = (string)$this->getRequest()->getUri();
$ext = Utils::pathinfo($uri, PATHINFO_EXTENSION);
$accept = $this->getAccept(['application/json', 'text/html']);
if ($ext === 'json' || $accept === 'application/json') {
return $this->createErrorResponse(new RequestException($this->getRequest(), $this->translate('PLUGIN_ADMIN.LOGGED_OUT'), 401));
}
$this->setMessage($this->translate('PLUGIN_ADMIN.LOGGED_OUT'), 'warning');
return $this->createRedirectResponse('/');
}
/**
* Handle login.
*
* @return ResponseInterface
*/
public function taskLogin(): ResponseInterface
{
$this->page = $this->createPage('login');
$this->form = $this->getActiveForm() ?? $this->getForm('login');
try {
$this->checkNonce();
} catch (PageExpiredException $e) {
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
return $this->createDisplayResponse();
}
$post = $this->getPost();
$credentials = (array)($post['data'] ?? []);
$login = $this->getLogin();
$config = $this->getConfig();
$userKey = (string)($credentials['username'] ?? '');
// Pseudonymization of the IP.
$ipKey = sha1(Uri::ip() . $config->get('security.salt'));
$rateLimiter = $login->getRateLimiter('login_attempts');
// Check if the current IP has been used in failed login attempts.
$attempts = count($rateLimiter->getAttempts($ipKey, 'ip'));
$rateLimiter->registerRateLimitedAction($ipKey, 'ip')->registerRateLimitedAction($userKey);
// Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited.
if ($rateLimiter->isRateLimited($ipKey, 'ip') || ($attempts && $rateLimiter->isRateLimited($userKey))) {
Admin::DEBUG && Admin::addDebugMessage('Admin login: rate limit, redirecting', $credentials);
$this->setMessage($this->translate('PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $rateLimiter->getInterval()), 'error');
$this->form->reset();
/** @var Pages $pages */
$pages = $this->grav['pages'];
// Redirect to the home page of the site.
return $this->createRedirectResponse($pages->homeUrl(null, true));
}
Admin::DEBUG && Admin::addDebugMessage('Admin login', $credentials);
// Fire Login process.
$event = $login->login(
$credentials,
['admin' => true, 'twofa' => $config->get('plugins.admin.twofa_enabled', false)],
['authorize' => 'admin.login', 'return_event' => true]
);
$user = $event->getUser();
Admin::DEBUG && Admin::addDebugMessage('Admin login: user', $user);
$redirect = (string)$this->getRequest()->getUri();
if ($user->authenticated) {
$rateLimiter->resetRateLimit($ipKey, 'ip')->resetRateLimit($userKey);
if ($user->authorized) {
$event->defMessage('PLUGIN_ADMIN.LOGIN_LOGGED_IN', 'info');
}
$event->defRedirect($redirect);
} elseif ($user->authorized) {
$event->defMessage('PLUGIN_LOGIN.ACCESS_DENIED', 'error');
} else {
$event->defMessage('PLUGIN_LOGIN.LOGIN_FAILED', 'error');
}
$event->defRedirect($redirect);
$message = $event->getMessage();
if ($message) {
$this->setMessage($this->translate($message), $event->getMessageType());
}
$this->form->reset();
return $this->createRedirectResponse($event->getRedirect());
}
/**
* Handle logout when user isn't fully logged in or clicks logout after the session has been expired.
*
* @return ResponseInterface
*/
public function taskLogout(): ResponseInterface
{
// We do not need to check the nonce here as user session has been expired or user hasn't fully logged in (2FA).
// Just be sure we terminate the current session.
$login = $this->getLogin();
$event = $login->logout(['admin' => true], ['return_event' => true]);
$event->defMessage('PLUGIN_ADMIN.LOGGED_OUT', 'info');
$message = $event->getMessage();
if ($message) {
$this->getSession()->setFlashCookieObject(Admin::TMP_COOKIE_NAME, ['message' => $this->translate($message), 'status' => $event->getMessageType()]);
}
return $this->createRedirectResponse('/');
}
/**
* Handle 2FA verification.
*
* @return ResponseInterface
*/
public function taskTwofa(): ResponseInterface
{
$user = $this->getUser();
if (!$this->is2FA($user)) {
Admin::DEBUG && Admin::addDebugMessage('Admin login: user is not logged in or does not have 2FA enabled', $user);
// Task is visible only for users who have enabled 2FA.
return $this->createRedirectResponse('/');
}
$login = $this->getLogin();
$this->page = $this->createPage('login');
$this->form = $this->getForm('login-twofa');
try {
$this->checkNonce();
} catch (PageExpiredException $e) {
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
// Failed 2FA nonce check, logout and redirect.
$login->logout(['admin' => true]);
$this->form->reset();
return $this->createRedirectResponse('/');
}
$post = $this->getPost();
$data = $post['data'] ?? [];
try {
$twoFa = $login->twoFactorAuth();
} catch (TwoFactorAuthException $e) {
/** @var Debugger $debugger */
$debugger = $this->grav['debugger'];
$debugger->addException($e);
$twoFa = null;
}
$code = $data['2fa_code'] ?? '';
$secret = $user->twofa_secret ?? '';
$twofa_valid = $twoFa->verifyCode($secret, $code);
$yubikey_otp = $data['yubikey_otp'] ?? '';
$yubikey_id = $user->yubikey_id ?? '';
$yubikey_valid = $twoFa->verifyYubikeyOTP($yubikey_id, $yubikey_otp);
$redirect = (string)$this->getRequest()->getUri();
if (null === $twoFa || !$user->authenticated || (!$twofa_valid && !$yubikey_valid) ) {
Admin::DEBUG && Admin::addDebugMessage('Admin login: 2FA check failed, log out!');
// Failed 2FA auth, logout and redirect to the current page.
$login->logout(['admin' => true]);
$this->grav['session']->setFlashCookieObject(Admin::TMP_COOKIE_NAME, ['message' => $this->translate('PLUGIN_ADMIN.2FA_FAILED'), 'status' => 'error']);
$this->form->reset();
return $this->createRedirectResponse($redirect);
}
// Successful 2FA, authorize user and redirect.
Grav::instance()['user']->authorized = true;
Admin::DEBUG && Admin::addDebugMessage('Admin login: 2FA check succeeded, authorize user and redirect');
$this->setMessage($this->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'));
$this->form->reset();
return $this->createRedirectResponse($redirect);
}
/**
* Handle the reset password action.
*
* @param string|null $username
* @param string|null $token
* @return ResponseInterface
*/
public function taskReset(string $username = null, string $token = null): ResponseInterface
{
$this->page = $this->createPage('reset');
$this->form = $this->getForm('admin-login-reset');
try {
$this->checkNonce();
} catch (PageExpiredException $e) {
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
return $this->createDisplayResponse();
}
$post = $this->getPost();
$data = $post['data'] ?? [];
$users = $this->getAccounts();
$username = $username ?? $data['username'] ?? null;
$token = $token ?? $data['token'] ?? null;
$user = $username ? $users->load($username) : null;
$password = $data['password'];
if ($user && $user->exists() && !empty($user->get('reset'))) {
[$good_token, $expire] = explode('::', $user->get('reset'));
if ($good_token === $token) {
if (time() > $expire) {
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_LINK_EXPIRED'), 'error');
$this->form->reset();
return $this->createRedirectResponse('/forgot');
}
// Set new password.
$login = $this->getLogin();
try {
$login->validateField('password1', $password);
} catch (\RuntimeException $e) {
$this->setMessage($this->translate($e->getMessage()), 'error');
return $this->createRedirectResponse("/reset/u/{$username}/{$token}");
}
$user->undef('hashed_password');
$user->undef('reset');
$user->update(['password' => $password]);
$user->save();
$this->form->reset();
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_PASSWORD_RESET'));
return $this->createRedirectResponse('/login');
}
Admin::DEBUG && Admin::addDebugMessage(sprintf('Failed to reset password: Token %s is not good', $token));
} else {
Admin::DEBUG && Admin::addDebugMessage(sprintf('Failed to reset password: User %s does not exist or has not requested reset', $username));
}
$this->setMessage($this->translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
$this->form->reset();
return $this->createRedirectResponse('/forgot');
}
/**
* Handle the email password recovery procedure.
*
* Sends email to the user.
*
* @return ResponseInterface
*/
public function taskForgot(): ResponseInterface
{
$this->page = $this->createPage('forgot');
$this->form = $this->getForm('admin-login-forgot');
try {
$this->checkNonce();
} catch (PageExpiredException $e) {
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
return $this->createDisplayResponse();
}
$post = $this->getPost();
$data = $post['data'] ?? [];
$login = $this->getLogin();
$users = $this->getAccounts();
$email = $this->getEmail();
$current = (string)$this->getRequest()->getUri();
$search = isset($data['username']) ? strip_tags($data['username']) : '';
$user = !empty($search) ? $users->load($search) : null;
$username = $user->username ?? null;
$to = $user->email ?? null;
// Only send email to users which are enabled and have an email address.
if (null === $user || $user->state !== 'enabled' || !$to) {
Admin::DEBUG && Admin::addDebugMessage(sprintf('Failed sending email: %s <%s> was not found or is blocked', $search, $to ?? 'N/A'));
$this->form->reset();
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'));
return $this->createRedirectResponse($current);
}
$config = $this->getConfig();
// Check rate limit for the user.
$rateLimiter = $login->getRateLimiter('pw_resets');
$rateLimiter->registerRateLimitedAction($username);
if ($rateLimiter->isRateLimited($username)) {
Admin::DEBUG && Admin::addDebugMessage(sprintf('Failed sending email: user %s <%s> is rate limited', $search, $to));
$this->form->reset();
$interval = $config->get('plugins.login.max_pw_resets_interval', 2);
$this->setMessage($this->translate('PLUGIN_LOGIN.FORGOT_CANNOT_RESET_IT_IS_BLOCKED', $to, $interval), 'error');
return $this->createRedirectResponse($current);
}
$token = md5(uniqid(mt_rand(), true));
$expire = time() + 3600; // 1 hour
$user->set('reset', $token . '::' . $expire);
$user->save();
$from = $config->get('plugins.email.from');
if (empty($from)) {
Admin::DEBUG && Admin::addDebugMessage('Failed sending email: from address is not configured in email plugin');
$this->form->reset();
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
return $this->createRedirectResponse($current);
}
// Do not trust username from the request.
$fullname = $user->fullname ?: $username;
$author = $config->get('site.author.name', '');
$sitename = $config->get('site.title', 'Website');
$reset_link = $this->getAbsoluteAdminUrl("/reset/u/{$username}/{$token}");
// For testing only!
//Admin::DEBUG && Admin::addDebugMessage(sprintf('Reset link: %s', $reset_link));
$subject = $this->translate('PLUGIN_ADMIN.FORGOT_EMAIL_SUBJECT', $sitename);
$content = $this->translate('PLUGIN_ADMIN.FORGOT_EMAIL_BODY', $fullname, $reset_link, $author, $sitename);
$this->grav['twig']->init();
$body = $this->grav['twig']->processTemplate('email/base.html.twig', ['content' => $content]);
try {
$message = $email->message($subject, $body, 'text/html')->setFrom($from)->setTo($to);
$sent = $email->send($message);
if ($sent < 1) {
throw new \RuntimeException('Sending email failed');
}
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'));
} catch (\Exception $e) {
$rateLimiter->resetRateLimit($username);
/** @var Debugger $debugger */
$debugger = $this->grav['debugger'];
$debugger->addException($e);
$this->form->reset();
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_FAILED_TO_EMAIL'), 'error');
return $this->createRedirectResponse('/forgot');
}
$this->form->reset();
return $this->createRedirectResponse('/login');
}
/**
* @return ResponseInterface
*/
public function taskRegister(): ResponseInterface
{
$this->page = $this->createPage('register');
$this->form = $form = $this->getForm('admin-login-register');
try {
$this->checkNonce();
} catch (PageExpiredException $e) {
$this->setMessage($this->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
return $this->createDisplayResponse();
}
// Note: Calls $this->doRegistration() to perform the user registration.
$form->handleRequest($this->getRequest());
$error = $form->getError();
$errors = $form->getErrors();
if ($error || $errors) {
foreach ($errors as $field => $list) {
foreach ((array)$list as $message) {
if ($message !== $error) {
$this->setMessage($message, 'error');
}
}
}
return $this->createDisplayResponse();
}
$this->setMessage($this->translate('PLUGIN_ADMIN.LOGIN_LOGGED_IN'));
return $this->createRedirectResponse('/');
}
/**
* @param UserInterface $user
* @return bool
*/
protected function is2FA(UserInterface $user): bool
{
return $user && $user->authenticated && !$user->authorized && $user->get('twofa_enabled');
}
/**
* @param string $name
* @return callable
*/
protected function getFormSubmitMethod(string $name): callable
{
switch ($name) {
case 'login':
case 'login-twofa':
case 'admin-login-forgot':
case 'admin-login-reset':
return static function(array $data, array $files) {};
case 'admin-login-register':
return function(array $data, array $files) {
$this->doRegistration($data, $files);
};
}
throw new \RuntimeException('Unknown form');
}
/**
* Called by registration form when calling handleRequest().
*
* @param array $data
* @param array $files
*/
private function doRegistration(array $data, array $files): void
{
if (Admin::doAnyUsersExist()) {
throw new \RuntimeException('A user account already exists, please create an admin account manually.', 400);
}
$login = $this->getLogin();
if (!$login) {
throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED', 500));
}
$data['title'] = $data['title'] ?? 'Administrator';
// Do not allow form to set the following fields (make super user):
$data['state'] = 'enabled';
$data['access'] = ['admin' => ['login' => true, 'super' => true], 'site' => ['login' => true]];
unset($data['groups']);
// Create user.
$user = $login->register($data, $files);
// Log in the new super admin user.
unset($this->grav['user']);
$this->grav['user'] = $user;
$this->grav['session']->user = $user;
$user->authenticated = true;
$user->authorized = $user->authorize('admin.login') ?? false;
}
/**
* @return Login
*/
private function getLogin(): Login
{
return $this->grav['login'];
}
/**
* @return Email
*/
private function getEmail(): Email
{
return $this->grav['Email'];
}
/**
* @return UserCollectionInterface
*/
private function getAccounts(): UserCollectionInterface
{
return $this->grav['accounts'];
}
}

View File

@ -0,0 +1,442 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin;
use Grav\Common\Cache;
use Grav\Common\Grav;
use Grav\Common\GPM\GPM as GravGPM;
use Grav\Common\GPM\Licenses;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Upgrader;
use Grav\Common\HTTP\Response;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\Common\Package;
/**
* Class Gpm
*
* @package Grav\Plugin\Admin
*/
class Gpm
{
// Probably should move this to Grav DI container?
/** @var GravGPM */
protected static $GPM;
public static function GPM()
{
if (!static::$GPM) {
static::$GPM = new GravGPM();
}
return static::$GPM;
}
/**
* Default options for the install
*
* @var array
*/
protected static $options = [
'destination' => GRAV_ROOT,
'overwrite' => true,
'ignore_symlinks' => true,
'skip_invalid' => true,
'install_deps' => true,
'theme' => false
];
/**
* @param Package[]|string[]|string $packages
* @param array $options
*
* @return string|bool
*/
public static function install($packages, array $options)
{
$options = array_merge(self::$options, $options);
if (!Installer::isGravInstance($options['destination']) || !Installer::isValidDestination($options['destination'],
[Installer::EXISTS, Installer::IS_LINK])
) {
return false;
}
$packages = is_array($packages) ? $packages : [$packages];
$count = count($packages);
$packages = array_filter(array_map(function ($p) {
return !is_string($p) ? $p instanceof Package ? $p : false : self::GPM()->findPackage($p);
}, $packages));
if (!$options['skip_invalid'] && $count !== count($packages)) {
return false;
}
$messages = '';
foreach ($packages as $package) {
if (isset($package->dependencies) && $options['install_deps']) {
$result = static::install($package->dependencies, $options);
if (!$result) {
return false;
}
}
// Check destination
Installer::isValidDestination($options['destination'] . DS . $package->install_path);
if (!$options['overwrite'] && Installer::lastErrorCode() === Installer::EXISTS) {
return false;
}
if (!$options['ignore_symlinks'] && Installer::lastErrorCode() === Installer::IS_LINK) {
return false;
}
$license = Licenses::get($package->slug);
$local = static::download($package, $license);
Installer::install($local, $options['destination'],
['install_path' => $package->install_path, 'theme' => $options['theme']]);
Folder::delete(dirname($local));
$errorCode = Installer::lastErrorCode();
if ($errorCode) {
$msg = Installer::lastErrorMsg();
throw new \RuntimeException($msg);
}
if (count($packages) === 1) {
$message = Installer::getMessage();
if ($message) {
return $message;
}
$messages .= $message;
}
}
Cache::clearCache();
return $messages ?: true;
}
/**
* @param Package[]|string[]|string $packages
* @param array $options
*
* @return string|bool
*/
public static function update($packages, array $options)
{
$options['overwrite'] = true;
return static::install($packages, $options);
}
/**
* @param Package[]|string[]|string $packages
* @param array $options
*
* @return string|bool
*/
public static function uninstall($packages, array $options)
{
$options = array_merge(self::$options, $options);
$packages = (array)$packages;
$count = count($packages);
$packages = array_filter(array_map(function ($p) {
if (is_string($p)) {
$p = strtolower($p);
$plugin = static::GPM()->getInstalledPlugin($p);
$p = $plugin ?: static::GPM()->getInstalledTheme($p);
}
return $p instanceof Package ? $p : false;
}, $packages));
if (!$options['skip_invalid'] && $count !== count($packages)) {
return false;
}
foreach ($packages as $package) {
$location = Grav::instance()['locator']->findResource($package->package_type . '://' . $package->slug);
// Check destination
Installer::isValidDestination($location);
if (!$options['ignore_symlinks'] && Installer::lastErrorCode() === Installer::IS_LINK) {
return false;
}
Installer::uninstall($location);
$errorCode = Installer::lastErrorCode();
if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) {
$msg = Installer::lastErrorMsg();
throw new \RuntimeException($msg);
}
if (count($packages) === 1) {
$message = Installer::getMessage();
if ($message) {
return $message;
}
}
}
Cache::clearCache();
return true;
}
/**
* Direct install a file
*
* @param string $package_file
*
* @return string|bool
*/
public static function directInstall($package_file)
{
if (!$package_file) {
return Admin::translate('PLUGIN_ADMIN.NO_PACKAGE_NAME');
}
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$tmp_zip = $tmp_dir . '/Grav-' . uniqid('', false);
if (Response::isRemote($package_file)) {
$zip = GravGPM::downloadPackage($package_file, $tmp_zip);
} else {
$zip = GravGPM::copyPackage($package_file, $tmp_zip);
}
if (file_exists($zip)) {
$tmp_source = $tmp_dir . '/Grav-' . uniqid('', false);
$extracted = Installer::unZip($zip, $tmp_source);
if (!$extracted) {
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
return Admin::translate('PLUGIN_ADMIN.PACKAGE_EXTRACTION_FAILED');
}
$type = GravGPM::getPackageType($extracted);
if (!$type) {
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
return Admin::translate('PLUGIN_ADMIN.NOT_VALID_GRAV_PACKAGE');
}
if ($type === 'grav') {
Installer::isValidDestination(GRAV_ROOT . '/system');
if (Installer::IS_LINK === Installer::lastErrorCode()) {
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
return Admin::translate('PLUGIN_ADMIN.CANNOT_OVERWRITE_SYMLINKS');
}
static::upgradeGrav($zip, $extracted);
} else {
$name = GravGPM::getPackageName($extracted);
if (!$name) {
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
return Admin::translate('PLUGIN_ADMIN.NAME_COULD_NOT_BE_DETERMINED');
}
$install_path = GravGPM::getInstallPath($type, $name);
$is_update = file_exists($install_path);
Installer::isValidDestination(GRAV_ROOT . DS . $install_path);
if (Installer::lastErrorCode() === Installer::IS_LINK) {
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
return Admin::translate('PLUGIN_ADMIN.CANNOT_OVERWRITE_SYMLINKS');
}
Installer::install($zip, GRAV_ROOT,
['install_path' => $install_path, 'theme' => $type === 'theme', 'is_update' => $is_update],
$extracted);
}
Folder::delete($tmp_source);
if (Installer::lastErrorCode()) {
return Installer::lastErrorMsg();
}
} else {
return Admin::translate('PLUGIN_ADMIN.ZIP_PACKAGE_NOT_FOUND');
}
Folder::delete($tmp_zip);
Cache::clearCache();
return true;
}
/**
* @param Package $package
*
* @return string
*/
private static function download(Package $package, $license = null)
{
$query = '';
if ($package->premium) {
$query = \json_encode(array_merge($package->premium, [
'slug' => $package->slug,
'license_key' => $license,
'sid' => md5(GRAV_ROOT)
]));
$query = '?d=' . base64_encode($query);
}
try {
$contents = Response::get($package->zipball_url . $query, []);
} catch (\Exception $e) {
throw new \RuntimeException($e->getMessage());
}
$tmp_dir = Admin::getTempDir() . '/Grav-' . uniqid('', false);
Folder::mkdir($tmp_dir);
$bad_chars = array_merge(array_map('chr', range(0, 31)), ['<', '>', ':', '"', '/', '\\', '|', '?', '*']);
$filename = $package->slug . str_replace($bad_chars, '', \Grav\Common\Utils::basename($package->zipball_url));
$filename = preg_replace('/[\\\\\/:"*?&<>|]+/m', '-', $filename);
file_put_contents($tmp_dir . DS . $filename . '.zip', $contents);
return $tmp_dir . DS . $filename . '.zip';
}
/**
* @param array $package
* @param string $tmp
*
* @return string
*/
private static function _downloadSelfupgrade(array $package, $tmp)
{
$output = Response::get($package['download'], []);
Folder::mkdir($tmp);
file_put_contents($tmp . DS . $package['name'], $output);
return $tmp . DS . $package['name'];
}
/**
* @return bool
*/
public static function selfupgrade()
{
$upgrader = new Upgrader();
if (!Installer::isGravInstance(GRAV_ROOT)) {
return false;
}
if (is_link(GRAV_ROOT . DS . 'index.php')) {
Installer::setError(Installer::IS_LINK);
return false;
}
if (method_exists($upgrader, 'meetsRequirements') &&
method_exists($upgrader, 'minPHPVersion') &&
!$upgrader->meetsRequirements()) {
$error = [];
$error[] = '<p>Grav has increased the minimum PHP requirement.<br />';
$error[] = 'You are currently running PHP <strong>' . phpversion() . '</strong>';
$error[] = ', but PHP <strong>' . $upgrader->minPHPVersion() . '</strong> is required.</p>';
$error[] = '<p><a href="https://getgrav.org/blog/changing-php-requirements-to-5.5" class="button button-small secondary">Additional information</a></p>';
Installer::setError(implode("\n", $error));
return false;
}
$update = $upgrader->getAssets()['grav-update'];
$tmp = Admin::getTempDir() . '/Grav-' . uniqid('', false);
if ($tmp) {
$file = self::_downloadSelfupgrade($update, $tmp);
$folder = Installer::unZip($file, $tmp . '/zip');
$keepFolder = false;
} else {
// If you make $tmp empty, you can install your local copy of Grav (for testing purposes only).
$file = 'grav.zip';
$folder = '~/phpstorm/grav-clones/grav';
//$folder = '/home/matias/phpstorm/rockettheme/grav-devtools/grav-clones/grav';
$keepFolder = true;
}
static::upgradeGrav($file, $folder, $keepFolder);
$errorCode = Installer::lastErrorCode();
if ($tmp) {
Folder::delete($tmp);
}
return !(is_string($errorCode) || ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)));
}
private static function upgradeGrav($zip, $folder, $keepFolder = false)
{
static $ignores = [
'backup',
'cache',
'images',
'logs',
'tmp',
'user',
'.htaccess',
'robots.txt'
];
if (!is_dir($folder)) {
Installer::setError('Invalid source folder');
}
try {
$script = $folder . '/system/install.php';
/** Install $installer */
if ((file_exists($script) && $install = include $script) && is_callable($install)) {
$install($zip);
} else {
Installer::install(
$zip,
GRAV_ROOT,
['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true, 'ignores' => $ignores],
$folder,
$keepFolder
);
Cache::clearCache();
}
} catch (\Exception $e) {
Installer::setError($e->getMessage());
}
}
}

View File

@ -0,0 +1,310 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageInterface;
/**
* Class Popularity
* @package Grav\Plugin
*/
class Popularity
{
/** @var Config */
protected $config;
protected $data_path;
protected $daily_file;
protected $monthly_file;
protected $totals_file;
protected $visitors_file;
protected $daily_data;
protected $monthly_data;
protected $totals_data;
protected $visitors_data;
const DAILY_FORMAT = 'd-m-Y';
const MONTHLY_FORMAT = 'm-Y';
const DAILY_FILE = 'daily.json';
const MONTHLY_FILE = 'monthly.json';
const TOTALS_FILE = 'totals.json';
const VISITORS_FILE = 'visitors.json';
public function __construct()
{
$this->config = Grav::instance()['config'];
$this->data_path = Grav::instance()['locator']->findResource('log://popularity', true, true);
$this->daily_file = $this->data_path . '/' . self::DAILY_FILE;
$this->monthly_file = $this->data_path . '/' . self::MONTHLY_FILE;
$this->totals_file = $this->data_path . '/' . self::TOTALS_FILE;
$this->visitors_file = $this->data_path . '/' . self::VISITORS_FILE;
}
public function trackHit()
{
// Don't track bot or crawler requests
if (!Grav::instance()['browser']->isHuman()) {
return;
}
// Respect visitors "do not track" setting
if (!Grav::instance()['browser']->isTrackable()) {
return;
}
/** @var PageInterface $page */
$page = Grav::instance()['page'];
$relative_url = str_replace(Grav::instance()['base_url_relative'], '', $page->url());
// Don't track error pages or pages that have no route
if ($page->template() === 'error' || !$page->route()) {
return;
}
// Make sure no 'widcard-style' ignore matches this url
foreach ((array)$this->config->get('plugins.admin.popularity.ignore') as $ignore) {
if (fnmatch($ignore, $relative_url)) {
return;
}
}
// initial creation if it doesn't exist
if (!file_exists($this->data_path)) {
mkdir($this->data_path);
$this->flushPopularity();
}
// Update the data we want to track
$this->updateDaily();
$this->updateMonthly();
$this->updateTotals($page->route());
$this->updateVisitors(Grav::instance()['uri']->ip());
}
protected function updateDaily()
{
if (!$this->daily_data) {
$this->daily_data = $this->getData($this->daily_file);
}
$day_month_year = date(self::DAILY_FORMAT);
// get the daily access count
if (array_key_exists($day_month_year, $this->daily_data)) {
$this->daily_data[$day_month_year] = (int)$this->daily_data[$day_month_year] + 1;
} else {
$this->daily_data[$day_month_year] = 1;
}
// keep correct number as set by history
$count = (int)$this->config->get('plugins.admin.popularity.history.daily', 30);
$total = count($this->daily_data);
if ($total > $count) {
$this->daily_data = array_slice($this->daily_data, -$count, $count, true);
}
file_put_contents($this->daily_file, json_encode($this->daily_data));
}
/**
* @return array
*/
public function getDailyChartData()
{
if (!$this->daily_data) {
$this->daily_data = $this->getData($this->daily_file);
}
$limit = (int)$this->config->get('plugins.admin.popularity.dashboard.days_of_stats', 7);
$chart_data = array_slice($this->daily_data, -$limit, $limit);
$labels = [];
$data = [];
/** @var Admin $admin */
$admin = Grav::instance()['admin'];
foreach ($chart_data as $date => $count) {
$labels[] = $admin::translate([
'PLUGIN_ADMIN.' . strtoupper(date('D', strtotime($date)))]) .
'<br>' . date('M d', strtotime($date));
$data[] = $count;
}
return ['labels' => $labels, 'data' => $data];
}
/**
* @return int
*/
public function getDailyTotal()
{
if (!$this->daily_data) {
$this->daily_data = $this->getData($this->daily_file);
}
if (isset($this->daily_data[date(self::DAILY_FORMAT)])) {
return $this->daily_data[date(self::DAILY_FORMAT)];
}
return 0;
}
/**
* @return int
*/
public function getWeeklyTotal()
{
if (!$this->daily_data) {
$this->daily_data = $this->getData($this->daily_file);
}
$day = 0;
$total = 0;
foreach (array_reverse($this->daily_data) as $daily) {
$total += $daily;
$day++;
if ($day === 7) {
break;
}
}
return $total;
}
/**
* @return int
*/
public function getMonthlyTotal()
{
if (!$this->monthly_data) {
$this->monthly_data = $this->getData($this->monthly_file);
}
if (isset($this->monthly_data[date(self::MONTHLY_FORMAT)])) {
return $this->monthly_data[date(self::MONTHLY_FORMAT)];
}
return 0;
}
protected function updateMonthly()
{
if (!$this->monthly_data) {
$this->monthly_data = $this->getData($this->monthly_file);
}
$month_year = date(self::MONTHLY_FORMAT);
// get the monthly access count
if (array_key_exists($month_year, $this->monthly_data)) {
$this->monthly_data[$month_year] = (int)$this->monthly_data[$month_year] + 1;
} else {
$this->monthly_data[$month_year] = 1;
}
// keep correct number as set by history
$count = (int)$this->config->get('plugins.admin.popularity.history.monthly', 12);
$total = count($this->monthly_data);
$this->monthly_data = array_slice($this->monthly_data, $total - $count, $count);
file_put_contents($this->monthly_file, json_encode($this->monthly_data));
}
/**
* @return array
*/
protected function getMonthyChartData()
{
if (!$this->monthly_data) {
$this->monthly_data = $this->getData($this->monthly_file);
}
$labels = [];
$data = [];
foreach ($this->monthly_data as $date => $count) {
$labels[] = date('M', strtotime($date));
$data[] = $count;
}
return ['labels' => $labels, 'data' => $data];
}
/**
* @param string $url
*/
protected function updateTotals($url)
{
if (!$this->totals_data) {
$this->totals_data = $this->getData($this->totals_file);
}
// get the totals for this url
if (array_key_exists($url, $this->totals_data)) {
$this->totals_data[$url] = (int)$this->totals_data[$url] + 1;
} else {
$this->totals_data[$url] = 1;
}
file_put_contents($this->totals_file, json_encode($this->totals_data));
}
/**
* @param string $ip
*/
protected function updateVisitors($ip)
{
if (!$this->visitors_data) {
$this->visitors_data = $this->getData($this->visitors_file);
}
// update with current timestamp
$this->visitors_data[hash('sha1', $ip)] = time();
$visitors = $this->visitors_data;
arsort($visitors);
$count = (int)$this->config->get('plugins.admin.popularity.history.visitors', 20);
$this->visitors_data = array_slice($visitors, 0, $count, true);
file_put_contents($this->visitors_file, json_encode($this->visitors_data));
}
/**
* @param string $path
*
* @return array
*/
protected function getData($path)
{
if (file_exists($path)) {
return (array)json_decode(file_get_contents($path), true);
}
return [];
}
public function flushPopularity()
{
file_put_contents($this->daily_file, []);
file_put_contents($this->monthly_file, []);
file_put_contents($this->totals_file, []);
file_put_contents($this->visitors_file, []);
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin;
use Grav\Common\Grav;
use Grav\Common\Processors\ProcessorBase;
use Grav\Framework\Route\Route;
use Grav\Plugin\Admin\Routers\LoginRouter;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class Router extends ProcessorBase
{
public $id = 'admin_router';
public $title = 'Admin Panel';
/** @var Admin */
protected $admin;
public function __construct(Grav $container, Admin $admin)
{
parent::__construct($container);
$this->admin = $admin;
}
/**
* Handle routing to the dashboard, group and build objects.
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$this->startTimer();
$context = $request->getAttributes();
$query = $request->getQueryParams();
/** @var Route $route */
$route = $context['route'];
$normalized = mb_strtolower(trim($route->getRoute(), '/'));
$parts = explode('/', $normalized);
array_shift($parts); // Admin path
$routeStr = implode('/', $parts);
$view = array_shift($parts);
$path = implode('/', $parts);
$task = $this->container['task'] ?? $query['task'] ?? null;
$action = $this->container['action'] ?? $query['action'] ?? null;
$params = ['view' => $view, 'route' => $routeStr, 'path' => $path, 'parts' => $parts, 'task' => $task, 'action' => $action];
$request = $request->withAttribute('admin', $params);
// Run login controller if user isn't fully logged in or asks to logout.
$user = $this->admin->user;
if (!$user->authorized || !$user->authorize('admin.login')) {
$params = (new LoginRouter())->matchServerRequest($request);
$request = $request->withAttribute('admin', $params + $request->getAttribute('admin'));
}
$this->admin->request = $request;
$response = $handler->handle($request);
$this->stopTimer();
// Never allow admin pages to be rendered in <frame>, <iframe>, <embed> or <object> for improved security.
return $response->withHeader('X-Frame-Options', 'DENY');
}
}

View File

@ -0,0 +1,93 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin\Routers;
use Grav\Plugin\Admin\Admin;
use Grav\Plugin\Admin\Controllers\Login\LoginController;
use Psr\Http\Message\ServerRequestInterface;
class LoginRouter
{
/** @var string[] */
private $taskTemplates = [
'logout' => 'login',
'twofa' => 'login',
'forgot' => 'forgot',
'reset' => 'reset'
];
/**
* @param ServerRequestInterface $request
* @return array
*/
public function matchServerRequest(ServerRequestInterface $request): array
{
$adminInfo = $request->getAttribute('admin');
$task = $adminInfo['task'];
$class = LoginController::class;
// Special controller for the new sites.
if (!Admin::doAnyUsersExist()) {
$method = $task === 'register' ? 'taskRegister' : 'displayRegister';
return [
'controller' => [
'class' => $class,
'method' => $method,
'params' => []
],
'template' => 'register',
];
}
$httpMethod = $request->getMethod();
$template = $this->taskTemplates[$task] ?? $adminInfo['view'];
$params = [];
switch ($template) {
case 'forgot':
break;
case 'reset':
$path = $adminInfo['path'];
if (str_starts_with($path, 'u/')) {
// Path is 'u/username/token'
$parts = explode('/', $path, 4);
$user = $parts[1] ?? null;
$token = $parts[2] ?? null;
} else {
// Old path used to be 'task:reset/user:username/token:token'
if ($httpMethod === 'GET' || $httpMethod === 'HEAD') {
$task = null;
}
$route = $request->getAttribute('route');
$user = $route->getGravParam('user');
$token = $route->getGravParam('token');
}
$params = [$user, $token];
break;
default:
$template = 'login';
}
$method = ($task ? 'task' : 'display') . ucfirst($task ?? $template);
if (!method_exists($class, $method)) {
$method = 'displayUnauthorized';
}
return [
'controller' => [
'class' => $class,
'method' => $method,
'params' => $params
],
'template' => $template,
];
}
}

View File

@ -0,0 +1,63 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin;
use ScssPhp\ScssPhp\Compiler;
class ScssCompiler
{
protected $compiler;
public function compiler()
{
if ($this->compiler === null) {
$this->reset();
}
return $this->compiler;
}
public function reset()
{
$this->compiler = new Compiler();
return $this;
}
public function setVariables(array $variables)
{
$this->compiler()->setVariables($variables);
return $this;
}
public function setImportPaths(array $paths)
{
$this->compiler()->setImportPaths($paths);
return $this;
}
public function compile(string $input_file, string $output_file)
{
$input = file_get_contents($input_file);
$output = $this->compiler()->compile($input);
file_put_contents($output_file, $output);
return $this;
}
public function compileAll(array $input_paths, string $output_file)
{
$input = '';
foreach ($input_paths as $input_file) {
$input .= trim(file_get_contents($input_file)) . "\n\n";
}
$output = $this->compiler()->compile($input);
file_put_contents($output_file, $output);
return $this;
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin;
class ScssList
{
/** @var string[] */
protected $list = [];
/**
* ScssList constructor.
* @param string|null $item
*/
public function __construct($item = null)
{
if ($item) {
$this->add($item);
}
}
/**
* @return array
*/
public function all(): array
{
return $this->list;
}
/**
* @param string $item
* @return void
*/
public function add($item): void
{
if ($item) {
$this->list[] = $item;
}
}
/**
* @param string $item
* @return void
*/
public function remove($item): void
{
$pos = array_search($item, $this->list, true);
if ($pos) {
unset($this->list[$pos]);
}
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin;
/**
* Admin theme object
*
* @author RocketTheme
* @license MIT
*/
class Themes extends \Grav\Common\Themes
{
public function init()
{
/** @var Themes $themes */
$themes = $this->grav['themes'];
$themes->configure();
$themes->initTheme();
$this->grav->fireEvent('onAdminThemeInitialized');
}
}

View File

@ -0,0 +1,138 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin\Twig;
use Grav\Common\Data\Data;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
use Grav\Common\Yaml;
use Grav\Common\Language\Language;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Grav\Plugin\Admin\Admin;
class AdminTwigExtension extends AbstractExtension
{
/** @var Grav */
protected $grav;
/** @var Language $lang */
protected $lang;
public function __construct()
{
$this->grav = Grav::instance();
$this->lang = $this->grav['user']->language;
}
public function getFilters(): array
{
return [
new TwigFilter('tu', [$this, 'tuFilter']),
new TwigFilter('toYaml', [$this, 'toYamlFilter']),
new TwigFilter('fromYaml', [$this, 'fromYamlFilter']),
new TwigFilter('adminNicetime', [$this, 'adminNicetimeFilter']),
new TwigFilter('nested', [$this, 'nestedFilter']),
new TwigFilter('flatten', [$this, 'flattenFilter']),
];
}
public function getFunctions(): array
{
return [
new TwigFunction('admin_route', [$this, 'adminRouteFunc']),
new TwigFunction('getPageUrl', [$this, 'getPageUrl']),
new TwigFunction('clone', [$this, 'cloneFunc']),
new TwigFunction('data', [$this, 'dataFunc']),
];
}
public function nestedFilter($current, $name)
{
$path = explode('.', trim($name, '.'));
foreach ($path as $field) {
if (is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
} elseif (is_array($current) && isset($current[$field])) {
$current = $current[$field];
} else {
return null;
}
}
return $current;
}
public function flattenFilter($array)
{
return Utils::arrayFlattenDotNotation($array);
}
public function cloneFunc($obj)
{
return clone $obj;
}
public function adminRouteFunc(string $route = '', string $languageCode = null)
{
/** @var Admin $admin */
$admin = Grav::instance()['admin'];
return $admin->getAdminRoute($route, $languageCode)->toString(true);
}
public function getPageUrl(PageInterface $page)
{
/** @var Admin $admin */
$admin = Grav::instance()['admin'];
return $admin->getAdminRoute('/pages' . $page->rawRoute(), $page->language())->toString(true);
}
public static function tuFilter()
{
$args = func_get_args();
$numargs = count($args);
$lang = null;
if (($numargs === 3 && is_array($args[1])) || ($numargs === 2 && !is_array($args[1]))) {
$lang = array_pop($args);
} elseif ($numargs === 2 && is_array($args[1])) {
$subs = array_pop($args);
$args = array_merge($args, $subs);
}
return Grav::instance()['admin']->translate($args, $lang);
}
public function toYamlFilter($value, $inline = null)
{
return Yaml::dump($value, $inline);
}
public function fromYamlFilter($value)
{
return Yaml::parse($value);
}
public function adminNicetimeFilter($date, $long_strings = true)
{
return Grav::instance()['admin']->adminNiceTime($date, $long_strings);
}
public function dataFunc(array $data, $blueprints = null)
{
return new Data($data, $blueprints);
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Plugin\Admin;
use Grav\Common\Grav;
use Grav\Common\User\Interfaces\UserCollectionInterface;
use Grav\Common\User\Interfaces\UserInterface;
/**
* Admin utils class
*
* @license MIT
*/
class Utils
{
/**
* Matches an email to a user
*
* @param string $email
*
* @return UserInterface
*/
public static function findUserByEmail(string $email)
{
$grav = Grav::instance();
/** @var UserCollectionInterface $users */
$users = $grav['accounts'];
return $users->find($email, ['email']);
}
/**
* Generates a slug of the given string
*
* @param string $str
* @return string
*/
public static function slug(string $str)
{
if (function_exists('transliterator_transliterate')) {
$str = transliterator_transliterate('Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove;', $str);
} else {
$str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
}
$str = strtolower($str);
$str = preg_replace('/[-\s]+/', '-', $str);
$str = preg_replace('/[^a-z0-9-]/i', '', $str);
$str = trim($str, '-');
return $str;
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace Grav\Plugin\Admin;
/**
* @package Grav\Plugin\Admin
*
* @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Grav\Framework\File\File;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Symfony\Component\Yaml\Yaml;
class WhiteLabel
{
protected $grav;
protected $scss;
public function __construct()
{
$this->grav = Grav::instance();
$this->scss = new ScssCompiler();
}
public function compilePresetScss($config, $options = [
'input' => 'plugin://admin/themes/grav/scss/preset.scss',
'output' => 'asset://admin-preset.css'
])
{
if (is_array($config)) {
$color_scheme = $config['color_scheme'];
} else {
$color_scheme = $config->get('whitelabel.color_scheme');
}
if ($color_scheme) {
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
// Use ScssList object to make it easier ot handle in event
$scss_list = new ScssList($locator->findResource($options['input']));
$output_css = $locator->findResource(($options['output']), true, true);
Folder::create(dirname($output_css));
Grav::instance()->fireEvent('onAdminCompilePresetSCSS', new Event(['scss' => $scss_list]));
// Convert bak to regular array now we have run the event
$input_scss = $scss_list->all();
$imports = [$locator->findResource('plugin://admin/themes/grav/scss')];
foreach ($input_scss as $scss) {
$input_path = dirname($scss);
if (!in_array($input_path, $imports)) {
$imports[] = $input_path;
}
}
try {
$compiler = $this->scss->reset();
$compiler->setVariables($color_scheme['colors'] + $color_scheme['accents']);
$compiler->setImportPaths($imports);
$compiler->compileAll($input_scss, $output_css);
} catch (\Exception $e) {
return [false, $e->getMessage()];
}
return [true, 'Recompiled successfully'];
}
return [false, ' Could not be recompiled, missing color scheme...'];
}
public function exportPresetScsss($config, $location = 'asset://admin-theme-export.yaml')
{
if (isset($config['color_scheme'])) {
$color_scheme = $config['color_scheme'];
$body = Yaml::dump($color_scheme);
$file = new File($location);
$file->save($body);
// todo: handle errors/exceptions?
return [true, 'File created successfully'];
} else {
return [false, ' Could not export, missing color scheme...'];
}
}
}

View File

@ -0,0 +1,18 @@
actor: Tester
paths:
tests: tests
log: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
extensions:
enabled:
- Codeception\Extension\RunFailed
# - Codeception\Extension\Recorder
modules:
config:

View File

@ -0,0 +1,58 @@
{
"name": "getgrav/grav-plugin-admin",
"type": "grav-plugin",
"description": "Admin plugin for Grav CMS",
"keywords": ["admin", "plugin", "manager", "panel"],
"homepage": "https://github.com/getgrav/grav-plugin-admin",
"license": "MIT",
"authors": [
{
"name": "Team Grav",
"email": "devs@getgrav.org",
"homepage": "https://getgrav.org",
"role": "Developer"
}
],
"support": {
"issues": "https://github.com/getgrav/grav-plugin-admin/issues",
"irc": "https://chat.getgrav.org",
"forum": "https://discourse.getgrav.org",
"docs": "https://github.com/getgrav/grav-plugin-admin/blob/master/README.md"
},
"require": {
"php": "^7.3.6 || ^8.0",
"ext-json": "*",
"p3k/picofeed": "@stable",
"scssphp/scssphp": "^1.7",
"laminas/laminas-zendframework-bridge": "^1.4"
},
"require-dev": {
"codeception/codeception": "^2.4",
"fzaninotto/faker": "^1.8",
"symfony/yaml": "~4.4",
"symfony/console": "~4.4",
"symfony/finder": "~4.4",
"symfony/event-dispatcher": "~4.4"
},
"replace": {
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*"
},
"autoload": {
"psr-4": {
"Grav\\Plugin\\Admin\\": "classes/plugin"
},
"classmap": [
"admin.php"
]
},
"config": {
"platform": {
"php": "7.3.6"
}
},
"scripts": {
"test": "vendor/bin/codecept run unit",
"test-windows": "vendor\\bin\\codecept run unit"
}
}

3951
plugins/admin/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,307 @@
---
PLUGIN_ADMIN:
ADMIN_BETA_MSG: "هذا إصدار بيتا! استخدم هذا في الإنتاج على مسؤوليتك الخاصة..."
ADMIN_REPORT_ISSUE: "وجدت مشكلة؟ الرجاء الإبلاغ عن GitHub."
EMAIL_FOOTER: "<a href=\"https://getgrav.org\">Powered by Grav</a> - The Modern Flat File CMS"
LOGIN_BTN: "تسجل الدخول"
LOGIN_BTN_FORGOT: "نسيت"
LOGIN_BTN_RESET: "إعادة تعيين كلمة المرور"
LOGIN_BTN_SEND_INSTRUCTIONS: "إرسال إرشادات إعادة تعيين"
LOGIN_BTN_CLEAR: "مسح النموذج"
LOGIN_BTN_CREATE_USER: "أنشاء مستخدم جديد"
LOGIN_LOGGED_IN: "لقد تم تسجيل بنجاح"
LOGIN_FAILED: "فشل تسجيل الخول"
LOGGED_OUT: "لقد قمت بتسجيل الخروج"
RESET_NEW_PASSWORD: "إدخال كلمة سر جديدة رجاءً &hellip;"
RESET_LINK_EXPIRED: "انتهت مدة صلاحية إعادة الارتباط، الرجاء المحاولة مرة أخرى"
RESET_PASSWORD_RESET: "لقد تم إعادة تعيين كلمة المرور"
RESET_INVALID_LINK: "اللينك خاطئ ، الرجاء المحاولة مرة أخرى"
FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "تم إرسال إرشادات إعادة تعيين كلمة المرور الخاصة بك عبر البريد الإلكتروني إلى %s"
FORGOT_FAILED_TO_EMAIL: "فشل في تعليمات البريد الإلكتروني، الرجاء المحاولة مرة أخرى لاحقاً"
FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "لا يمكن إعادة تعيين كلمة المرور ل %s، لم يتم تعيين عنوان البريد الإلكتروني"
FORGOT_USERNAME_DOES_NOT_EXIST: "لا يوجد المستخدم مع اسم المستخدم <b>%s</b>"
FORGOT_EMAIL_NOT_CONFIGURED: "لا يمكن إعادة تعيين كلمة المرور. لم يتم تكوين هذا الموقع لإرسال رسائل البريد الإلكتروني"
FORGOT_EMAIL_SUBJECT: "طلب إعادة تعيين كلمة المرور %s"
FORGOT_EMAIL_BODY: "<h1>\"إعادة تعيين كلمة المرور\"</h1><p>عزيزي %1$s،</p><p>طلبا قدم في <b>%4$s</b> لإعادة تعيين كلمة المرور الخاصة بك.</p><p><br /><a href=\"%2$s\" class=\"btn-primary\">انقر فوق هذا الخيار لإعادة تعيين الخاص بك كلمة مرور</a><br /><br /></p><p>بدلاً من ذلك، نسخ عنوان URL التالي في شريط العناوين في المستعرض الخاص بك:</p> <p>%2$s</p><p><br />أطيب التحيات،<br /><br />%3$s</p>"
MANAGE_PAGES: "إدارة الصفحات"
PAGES: "الصفحات"
PLUGINS: "الملحقات"
PLUGIN: "البرنامج الإضافي"
THEMES: "المواضيع"
LOGOUT: "تسجيل الخروج"
BACK: "الرجوع"
NEXT: "التالي"
PREVIOUS: "السابق"
ADD_PAGE: "إضافة صفحة"
MOVE: "انقل"
DELETE: "حذف"
UNSET: "تراجع عن التعيين"
VIEW: "عرض"
SAVE: "حفظ"
NORMAL: "عادي"
EXPERT: "خبير"
EXPAND_ALL: "عرض الكل"
COLLAPSE_ALL: "طي الكل"
ERROR: "خطأ"
CLOSE: "أغلق"
CANCEL: "إلغاء"
CONTINUE: "المتابعة"
MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "التأكيد مطلوب"
MODAL_CHANGED_DETECTED_TITLE: "تم الكشف عن التغييرات"
MODAL_CHANGED_DETECTED_DESC: "وقد تغييرات غير محفوظة. هل أنت متأكد من أنك تريد ترك دون الحفظ؟"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "التأكيد مطلوب"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "هل أنت متأكد من حذف هذا الملف؟ لا يمكن التراجع عن هذا الإجراء."
ADD_FILTERS: "إضافة عامل تصفية"
SEARCH_PAGES: "صفحات البحث"
VERSION: "النسخة"
WAS_MADE_WITH: "تم عمله مع"
BY: "بواسطة"
UPDATE_THEME: "تحديث الموضوع"
UPDATE_PLUGIN: "تحديث البرنامج الإضافي"
OF_THIS_THEME_IS_NOW_AVAILABLE: "من هذه السمة الآن متوفرة"
OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "من هذه الإضافة متوفرة الآن"
AUTHOR: "المؤلّف"
HOMEPAGE: "الصفحة الرئيسية"
DEMO: "عرض تجريبي"
BUG_TRACKER: "متتبع الأخطاء"
KEYWORDS: "الكلمات الرئيسية"
LICENSE: "الرخصة"
DESCRIPTION: "الوصف"
README: "الملف التمهيدي"
REMOVE_THEME: "إزالة الموضوع"
INSTALL_THEME: "تثبيت الموضوع"
THEME: "الموضوع"
BACK_TO_THEMES: "العودة إلى المواضيع"
BACK_TO_PLUGINS: "العودة إلى البرامج الإضافية"
CHECK_FOR_UPDATES: "التحقق من وجود تحديثات"
ADD: "أَضِف"
CLEAR_CACHE: "مسح ذاكرة التخزين المؤقتة"
CLEAR_CACHE_ALL_CACHE: "كافة المحفوظات"
CLEAR_CACHE_ASSETS_ONLY: "الاصول فقط"
CLEAR_CACHE_IMAGES_ONLY: "الصور فقط"
CLEAR_CACHE_CACHE_ONLY: "المحفوظات فقط"
CLEAR_CACHE_TMP_ONLY: "المؤقتة فقط"
UPDATES_AVAILABLE: "تحديثات متوفّرة"
DAYS: "أيام"
UPDATE: "تحديث"
BACKUP: "نسخة احتياطية"
BACKUPS: "النسخ الاحتياطية"
BACKUP_NOW: "قم بحفض نسخة احتياطية"
BACKUPS_STATS: "قم بحفض نسخة احتياطية لاحصائيات"
BACKUPS_HISTORY: "قم بحفض نسخة احتياطية للسجل"
BACKUPS_PROFILES: "قم بحفض نسخة احتياطي للحسابات"
BACKUPS_COUNT: "عدد النسخ الاحتياطية"
BACKUPS_PROFILES_COUNT: "عدد الحسابات"
BACKUPS_TOTAL_SIZE: "المساحة المستعملة"
BACKUPS_NEWEST: "النسخ الاحتياطية الحديثة"
BACKUPS_OLDEST: "النسخ الاحتياطية القديمة"
BACKUPS_PURGE: "تطهير"
BACKUPS_NOT_GENERATED: "لم يتم إنشاء نسخ احتياطية بعد..."
BACKUPS_PURGE_NUMBER: "استخدام %s من %s من فتحات النسخ الاحتياطي"
BACKUPS_PURGE_TIME: "%s ايام من النسخ الاحتياطية بقية"
BACKUPS_PURGE_SPACE: "استعمال %s من %s"
BACKUP_DELETED: "تم حذف النسخة الاحتياطية بنجاح"
BACKUP_NOT_FOUND: "لم يتم العثور على نسخة احتياطية"
BACKUP_DATE: "تاريخ النسخ الاحتياطي"
STATISTICS: "إحصائيات"
TODAY: "اليوم"
WEEK: "اسبوع"
MONTH: "شهر"
LATEST_PAGE_UPDATES: "آخر التحديثات"
MAINTENANCE: "الصيانه"
UPDATED: "تم تحديثه"
MON: "الإثنين"
TUE: "الثلاثاء"
WED: "الإربعاء"
THU: "الخميس"
FRI: "الجمعة"
SAT: "السبت"
SUN: "الأحد"
COPY: "نسخ"
EDIT: "تحرير"
CREATE: "انشاء"
GRAV_ADMIN: "مدير جريف"
GRAV_OFFICIAL_PLUGIN: "اضاف رسمية ل جريف"
GRAV_OFFICIAL_THEME: "سمة جريف رسمية"
PLUGIN_SYMBOLICALLY_LINKED: "هذه الاضافه مرتبطة بمرجع. لن يام اكتشاف التحديثات."
THEME_SYMBOLICALLY_LINKED: "هذه السمة مرتبطة بمرجع. لم يتم اكتشاف التحديثات"
REMOVE_PLUGIN: "حذف الإضافة"
INSTALL_PLUGIN: "تثبيت الإضافة"
AVAILABLE: "متوفر"
INSTALLED: "مثبت"
INSTALL: "تثبيت"
ACTIVE_THEME: "سمة نشطة"
SWITCHING_TO: "التبديل إلى"
SWITCHING_TO_DESCRIPTION: "بالتبديل إلى سمة مختلفة, لايوجد أي ضمانات بأن صفحات الواجهة مدعومة, من المحتمل أن تحدث أخطاء عند محاولة تحميل الصفحات المذكورة."
SWITCHING_TO_CONFIRMATION: "هل تريد التبديل الى السمة والمتابعة"
CREATE_NEW_USER: "إنشاء مستخدم جديد"
REMOVE_USER: "حذف المستخدم"
ACCESS_DENIED: "الدخول ممنوع"
ACCOUNT_NOT_ADMIN: "لا يملك حسابك أي صلاحيات للإدارة"
PHP_INFO: "معلومات بي إش بي"
INSTALLER: "المثبت"
AVAILABLE_THEMES: "سمات متوفرة"
AVAILABLE_PLUGINS: "إضافات متوفرة"
INSTALLED_THEMES: "السمات المثبتة"
INSTALLED_PLUGINS: "الإضافات المثبتة"
BROWSE_ERROR_LOGS: "سجل أخطاء التصفح"
SITE: "موقع"
INFO: "معلومات"
SYSTEM: "النظام"
USER: "المستخدم"
ADD_ACCOUNT: "إضافة حساب"
SWITCH_LANGUAGE: "تبديل اللغة"
SUCCESSFULLY_ENABLED_PLUGIN: "تم تفعيل الإضافة بنجاح"
SUCCESSFULLY_DISABLED_PLUGIN: "تم إيقاف الإضافة بنجاح"
SUCCESSFULLY_CHANGED_THEME: "تم تبديل السمة الإفتراضية بنجاح"
INSTALLATION_FAILED: "فشل التثبيت"
INSTALLATION_SUCCESSFUL: "تم التثبيت بنجاح"
UNINSTALL_FAILED: "فشل الغاء التثبيت"
UNINSTALL_SUCCESSFUL: "تم إلغاء التثبيت بنجاح"
SUCCESSFULLY_SAVED: "حفظ بنجاح"
SUCCESSFULLY_COPIED: "نسخ بنجاح"
REORDERING_WAS_SUCCESSFUL: "إعادة ترتيب تم بنجاح"
SUCCESSFULLY_DELETED: "تم الحذف بنجاح"
SUCCESSFULLY_SWITCHED_LANGUAGE: "تم تغيير اللغة بنجاح"
INSUFFICIENT_PERMISSIONS_FOR_TASK: "لديك أذونات غير كافية للقيام بالمهمة"
CACHE_CLEARED: "تم مسح الذاكرة المؤقتة"
METHOD: "الأسلوب"
ERROR_CLEARING_CACHE: "خطأ مسح ذاكرة التخزين المؤقت"
AN_ERROR_OCCURRED: "حدث خطأ ما"
YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "النسخة الاحتياطية جاهزة للتحميل"
DOWNLOAD_BACKUP: "تنزيل النسخة الاحتياطية"
PAGES_FILTERED: "الصفحات التي تمت تصفيتها"
NO_PAGE_FOUND: "لم يتم العثور على أي صفحة"
INVALID_PARAMETERS: "متغيرات غير صالحة"
NO_FILES_SENT: "لا توجد ملفات أرسلت حتى الآن"
EXCEEDED_FILESIZE_LIMIT: "تجاوز الحد الأقصى لحجم ملف التكوين بي إتش بي"
UNKNOWN_ERRORS: "خطأ غير معروف"
EXCEEDED_GRAV_FILESIZE_LIMIT: "تجاوز الحد الأقصى لحجم ملف التكوين بي إتش بي"
UNSUPPORTED_FILE_TYPE: "نوع ملف غير معتمد"
FAILED_TO_MOVE_UPLOADED_FILE: "فشل في تحميل الملف"
FILE_UPLOADED_SUCCESSFULLY: "تم رفع الملف بنجاح"
FILE_DELETED: "تم حذف الملف"
FILE_COULD_NOT_BE_DELETED: "لا يمكن حذف الملف"
FILE_NOT_FOUND: "لم يتم العثور على الملف"
NO_FILE_FOUND: "لا يوجد ملف"
GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "تم تحديث النظام بنجاح الى"
GRAV_UPDATE_FAILED: "فشل تحديث نظام جراف"
EVERYTHING_UPDATED: "تم تحديث كل شيء"
UPDATES_FAILED: "فشل التحديث"
AVATAR_BY: "الصورة الرمزية التي"
AVATAR_UPLOAD_OWN: "أو قم بتحميل الخاصة بك..."
LAST_BACKUP: "آخر نسخ احتياطي"
FULL_NAME: "الإسم الكامل"
USERNAME: "إسم المستخدم"
EMAIL: "البريد الإلكتروني"
USERNAME_EMAIL: "إسم المستخدم أو البريد الإلكتروني"
PASSWORD: "كلمة السر"
PASSWORD_CONFIRM: "تأكيد كلمة السر"
TITLE: "العنوان"
LANGUAGE: "اللّغة"
ACCOUNT: "الحساب"
EMAIL_VALIDATION_MESSAGE: "يجب أن يكون البريد الإلكتروني صحيحاً"
PASSWORD_VALIDATION_MESSAGE: "يجب أن تحتوي كلمة المرور على الأقل على رقم وعلى حرف كبير وعلى حرف صغير، و أن تكون مكونة على الأقل من 8 أحرف أو أكثر"
LANGUAGE_HELP: "تعيين اللغة المفضلة"
MEDIA: "وسائط"
DEFAULTS: "الإعدادات الافتراضية"
SITE_TITLE: "عنوان الموقع"
SITE_TITLE_PLACEHOLDER: "عنوان الموقع العريض"
SITE_TITLE_HELP: "العنوان الإفتراضي لموقعك, غالبا يستخدم في السمات"
SITE_DEFAULT_LANG: "اللغة الإفتراضية"
SITE_DEFAULT_LANG_PLACEHOLDER: "اللغة الإفتراضية المستخدمة في السمات وسم <HTML>"
SITE_DEFAULT_LANG_HELP: "اللغة الإفتراضية المستخدمة في السمات وسم <HTML>"
DEFAULT_AUTHOR: "المؤلف الافتراضي"
DEFAULT_AUTHOR_HELP: "إسم المؤلف الإفتراضي, يستخدم عادة في السمات او محتوى الصفحة"
DEFAULT_EMAIL: "البريد الإلكتروني الإفتراضي"
DEFAULT_EMAIL_HELP: "البريد الإلكتروني للإشارة في المواضيع أو صفحات"
TAXONOMY_TYPES: "أنواع التصنيف"
TAXONOMY_TYPES_HELP: "يجب أن يتم تعريف أنواع التصنيف هنا إذا كنت ترغب في استخدامها في صفحات"
PAGE_SUMMARY: "ملخص الصفحة"
ENABLED: "فعال"
ENABLED_HELP: "تمكين صفحة الموجز (الملخص ترجع نفس محتوى الصفحة)"
'YES': "نعم"
'NO': "لا"
SUMMARY_SIZE: "حجم الملخص"
SUMMARY_SIZE_HELP: "مقدار أحرف صفحة لاستخدامها كمحتوى موجز"
FORMAT: "الشكل"
FORMAT_HELP: "اختصار = الاستخدام الأولى لوقوع محدد أو الحجم؛ = فترة طويلة سيتم تجاهل موجز محدد"
SHORT: "قصير"
LONG: "طويل"
DELIMITER: "الفاصل"
DELIMITER_HELP: "موجز محدد (الافتراضي '= = =')"
METADATA: "البيانات الوصفية"
METADATA_HELP: "قيم بيانات التعريف الافتراضية التي سيتم عرضها في كل صفحة ما لم يتم تجاوز الصفحة"
NAME: "الإسم"
CONTENT: "المحتوى"
SIZE: "حجم"
ACTION: "اجراء"
REDIRECTS_AND_ROUTES: "اعادة التوجيه و المسارات"
CUSTOM_REDIRECTS: "تخصيص اعادة التوجيه"
CUSTOM_REDIRECTS_HELP: "مسارات لإعادة التوجيه إلى صفحات أخرى. الاستبدال الاساسي ل Regex صحيح"
CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/ الخاص / الاسم المستعار"
CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/ الخاص /إعادة التوجيه"
CUSTOM_ROUTES: "تخصيص المسارات"
CUSTOM_ROUTES_HELP: "مسارات لاسماء المستعارة إلى صفحات أخرى. الاستبدال الاساسي ل Regex صحيح"
CUSTOM_ROUTES_PLACEHOLDER_KEY: "/ الخاص / الاسم المستعار"
CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ الخاص / المسار"
FILE_STREAMS: "تيارات الملف"
DEFAULT: "الإعدادات العامة"
PAGE_MEDIA: "صور و فيديوهات الصفحة"
OPTIONS: "الخيارات"
PUBLISHED: "نُشِرَ"
PUBLISHED_HELP: "بشكل افتراضي، يتم نشر صفحة إلا إذا قمت بتعيين المنشور: false أو عن طريق publish_date يجري في المستقبل، أو unpublish_date في الماضي"
DATE: "التاريخ"
PUBLISHED_DATE: "تاريخ النشر"
ROBOTS: "الروبوتات"
ADVANCED: "خيارات متقدمة"
SETTINGS: "الإعدادات"
FOLDER_NAME: "إسم المجلد"
MENU: "القائمة"
USE_GLOBAL: "الاستخدام العام"
ROUTABLE: "قابل للتوجيه"
ROUTABLE_HELP: "إذا أمكن ولوج هذه الصفحة عبر عنوان URL"
VISIBLE: "مرئي"
ASCENDING: "تصاعدي"
DESCENDING: "تنازلي"
PAGE_TITLE: "موضوع عنوان الصفحة"
PAGE_TITLE_HELP: "عنوان الصفحة"
PAGE: "صفحة"
FILENAME: "اسم الملف"
PARENT_PAGE: "الصفحة الأصل"
HOME_PAGE: "الصفحة الرئيسية"
TIMEZONE: "المنطقة الزمنية"
LANGUAGES: "اللغات"
EXPIRES: "انتهاء الصلاحية"
LAST_MODIFIED: "آخر تعديل"
SESSION: "الجلسة"
CURRENT: "الحالي"
SAVE_AS: "حفظ كـ"
AND: "و"
FULLY_UPDATED: "تم تحديث النظام بالكامل"
GROUPS: "الفِرَق"
SESSION_SECURE: "آمن"
ADD_FOLDER: "إضافة مجلد"
ADD_ITEM: "إضافة عنصر"
LOADING: "جار التحميل …"
PACKAGES_SUCCESSFULLY_UPDATED: "تم تحديث حزمة أو حزمات بنجاح."
INSERT: "إدراج"
UNDO: "تراجع"
REDO: "إعادة"
HEADERS: "العناوين الرأسية"
ITALIC: "مائل"
LINK: "رابط"
IMAGE: "صورة"
PREVIEW: "معاينة"
PUBLISHING: "النشر"
IMAGE_OPTIONS: "خيارات الصورة"
ALL: "الكل"
FROM: "من"
TO: "إلى"
RELEASE_DATE: "تاريخ الإصدار"
DROPZONE_REMOVE_FILE: "إزالة الملف"
TOOLS: "الأدوات"
2FA_CODE_INPUT: "000000"
2FA_REGENERATE: "إعادة التوليد"
CONFIGURATION: "الإعدادات"
DASHBOARD: "لوحة المعلومات"

View File

@ -0,0 +1,356 @@
---
PLUGIN_ADMIN:
ADMIN_BETA_MSG: "Това е Бета версия! Използвате на ваша отговорност..."
ADMIN_REPORT_ISSUE: "Открили сте проблем? Моля, съобщете за него в GitHub."
EMAIL_FOOTER: "<a href=\"https://getgrav.org\">Задвижван от Grav</a> - Модерният Флат Файл CMS"
LOGIN_BTN: "Вход"
LOGIN_BTN_FORGOT: "Забравена парола"
LOGIN_BTN_RESET: "Промяна на паролата"
LOGIN_BTN_SEND_INSTRUCTIONS: "Изпращане на инструкциите за възстановяването"
LOGIN_BTN_CLEAR: "Изтриване на формуляра"
LOGIN_BTN_CREATE_USER: "Създаване на потребител"
LOGIN_LOGGED_IN: "Влязохте успешно"
LOGIN_FAILED: "Влизането не е успешно"
LOGGED_OUT: "Излязохте от системата"
RESET_NEW_PASSWORD: "Въведете нова парола &hellip;"
RESET_LINK_EXPIRED: "Връзката за нулиране е изтекла, опитайте отново"
RESET_PASSWORD_RESET: "Паролата е променена"
RESET_INVALID_LINK: "Използвана невалидна връзка за нулиране, моля опитайте отново"
FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "На Вашия имейл, бяха изпратени инструкции за възстановяване на паролата"
FORGOT_FAILED_TO_EMAIL: "Неуспешно изпращане на имейл с инструкции, опитайте по-късно"
FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Паролата на %s не може да бъде обновена, няма въведен имейл адрес"
FORGOT_USERNAME_DOES_NOT_EXIST: "Потребител с име <b>%s</b> не съществува"
FORGOT_EMAIL_NOT_CONFIGURED: "Не може да обнови паролата. Този сайт не е конфигуриран да изпраща имейли"
FORGOT_EMAIL_SUBJECT: "%s - искане за смяна на парола"
FORGOT_EMAIL_BODY: "<h1>Смяна на парола</h1><p>Уважаеми %1$s</p><p>На <b>%4$s</b> бе направено искане за смяна на парола.</p><p><br /><a href=\"%2$s\" class=\"btn-primary\">Натиснете тук за да обновите паролата си</a><br /><br /></p><p>Като алтернатива, можете да копирате линка в адресната лента на браузъра си:</p><p>%2$s</p><p><br />С уважение,<br /><br />%3$s</p>"
MANAGE_PAGES: "Управление на страниците"
PAGES: "Страници"
PLUGINS: "Разширения"
PLUGIN: "Разширение"
THEMES: "Теми"
LOGOUT: "Изход"
BACK: "Назад"
NEXT: "Напред"
PREVIOUS: "Назад"
ADD_PAGE: "Добавяне на страница"
MOVE: "Преместване"
DELETE: "Изтриване"
UNSET: "Незададен"
VIEW: "Виж"
SAVE: "Запазване"
NORMAL: "Обикновен"
EXPERT: "Експертен"
EXPAND_ALL: "Разгъване на всички"
COLLAPSE_ALL: "Свиване на всички"
ERROR: "Грешка"
CLOSE: "Затваряне"
CANCEL: "Отказ"
CONTINUE: "Продължаване"
MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Изисква се потвърждение"
MODAL_CHANGED_DETECTED_TITLE: "Засечени са промени"
MODAL_CHANGED_DETECTED_DESC: "Имате незапазени промени. Наистина ли искате да излезете без да сте ги запазили?"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Изисква се потвърждение"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Наистина ли искате да изтриете този файл? Това действие не може да бъде отменено."
ADD_FILTERS: "Добавяне на филтри"
SEARCH_PAGES: "Търсене"
VERSION: "Версия"
WAS_MADE_WITH: "е създаден с"
BY: "от"
UPDATE_THEME: "Актуализация на тема"
UPDATE_PLUGIN: "Актуализация на разширение"
OF_THIS_THEME_IS_NOW_AVAILABLE: "на тази тема е наличен"
OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "на този плъгин е вече наличен"
AUTHOR: "Автор"
HOMEPAGE: "Страница"
DEMO: "Демо"
BUG_TRACKER: "Докладване за грешки"
KEYWORDS: "Ключови думи"
LICENSE: "Лиценз"
DESCRIPTION: "Описание"
README: "Документация"
REMOVE_THEME: "Премахване на тема"
INSTALL_THEME: "Инсталиране на тема"
THEME: "Тема"
BACK_TO_THEMES: "Обратно към темите"
BACK_TO_PLUGINS: "Обратно към разширенията"
CHECK_FOR_UPDATES: "Проверка за актуализации"
ADD: "Добавяне"
CLEAR_CACHE: "Изтриване на временните файлове"
CLEAR_CACHE_ALL_CACHE: "Всички"
CLEAR_CACHE_ASSETS_ONLY: "Само Assets"
CLEAR_CACHE_IMAGES_ONLY: "Само изображенията"
CLEAR_CACHE_CACHE_ONLY: "Само временните файлове"
CLEAR_CACHE_TMP_ONLY: "само временен"
UPDATES_AVAILABLE: "Налични актуализации"
DAYS: "Дни"
UPDATE: "Актуализация"
BACKUP: "Резервно копие"
BACKUPS: "Резервно копие"
BACKUP_NOW: "Създай резервно копие"
BACKUPS_STATS: "Статистика резервни копия"
BACKUPS_HISTORY: "История резервни копия"
BACKUPS_PURGE_CONFIG: "Настройка изтриване на резервно копие"
BACKUPS_PROFILES: "Профили на резервни копия"
BACKUPS_COUNT: "Брой резервни копия"
BACKUPS_PROFILES_COUNT: "Брой профили"
BACKUPS_TOTAL_SIZE: "Използвано пространство"
BACKUPS_NEWEST: "Най-ново резервно копие"
BACKUPS_OLDEST: "Най-старо резервно копие"
BACKUPS_PURGE: "Изтриване"
BACKUPS_NOT_GENERATED: "Все още не са генериране резервни копия..."
BACKUPS_PURGE_NUMBER: "Използва %s от %s слота за резервни копия"
BACKUPS_PURGE_TIME: "Остават %s дни за резервно копие"
BACKUPS_PURGE_SPACE: "Използва %s от %s"
BACKUP_DELETED: "Успешно изтрито резервно копие"
BACKUP_NOT_FOUND: "Не е намерено резервно копие"
BACKUP_DATE: "Данни за резервно копие"
STATISTICS: "Статистика"
TODAY: "Днес"
WEEK: "Седмица"
MONTH: "Месец"
LATEST_PAGE_UPDATES: "Скоро актуализирани страници"
MAINTENANCE: "Техническа поддръжка"
UPDATED: "Актуализиран"
MON: "пон"
TUE: "вт"
WED: "ср"
THU: "чт"
FRI: "пт"
SAT: "сб"
SUN: "нд"
COPY: "Копиране"
EDIT: "Редактиране"
CREATE: "Създаване"
GRAV_ADMIN: "Grav Admin"
GRAV_OFFICIAL_PLUGIN: "Официално разширение на Grav"
GRAV_OFFICIAL_THEME: "Официална тема на Grav"
PLUGIN_SYMBOLICALLY_LINKED: "Този плъгин е символично свързан. Актуализации няма да бъдат отразени."
THEME_SYMBOLICALLY_LINKED: "Тази тема е символично свързана. Актуализации няма да бъдат отразени."
REMOVE_PLUGIN: "Премахване на разширение"
INSTALL_PLUGIN: "Инсталиране на разширение"
AVAILABLE: "Налични"
INSTALLED: "Инсталирани"
INSTALL: "Инсталиране"
ACTIVE_THEME: "Активна тема"
SWITCHING_TO: "Превключване към"
SWITCHING_TO_DESCRIPTION: "При превключването към различна тема няма гаранция, че всички страници са поддържани, което може да доведе до потенциални грешки при опит за зареждане на тези страници."
SWITCHING_TO_CONFIRMATION: "Искате ли да продължите и да превключите към темата"
CREATE_NEW_USER: "Създаване на нов потребител"
REMOVE_USER: "Премахване на потребител"
ACCESS_DENIED: "Нямате достъп"
ACCOUNT_NOT_ADMIN: "вашият профил няма администраторски права"
PHP_INFO: "Информация за PHP"
INSTALLER: "Инсталатор"
AVAILABLE_THEMES: "Налични теми"
AVAILABLE_PLUGINS: "Налични разширения"
INSTALLED_THEMES: "Инсталирани теми"
INSTALLED_PLUGINS: "Инсталирани разширения"
BROWSE_ERROR_LOGS: "Преглед на дневниците за грешки"
SITE: "Сайт"
INFO: "Информация"
SYSTEM: "Система"
USER: "Потребител"
ADD_ACCOUNT: "Добавяне на профил"
SWITCH_LANGUAGE: "Превключване на езика"
SUCCESSFULLY_ENABLED_PLUGIN: "Разширението е активирано успешно"
SUCCESSFULLY_DISABLED_PLUGIN: "Разширението е спряно"
SUCCESSFULLY_CHANGED_THEME: "Промяната на подразбиращата се тема е успешно"
INSTALLATION_FAILED: "Неуспешна инсталация"
INSTALLATION_SUCCESSFUL: "Инсталацията е успешна"
UNINSTALL_FAILED: "Неуспешно деинсталиране"
UNINSTALL_SUCCESSFUL: "Деинсталирането е успешно"
SUCCESSFULLY_SAVED: "Успешно запазено"
SUCCESSFULLY_COPIED: "Успешно копирано"
REORDERING_WAS_SUCCESSFUL: "Записът бе успешен"
SUCCESSFULLY_DELETED: "Успешно изтрити"
SUCCESSFULLY_SWITCHED_LANGUAGE: "Езикът е променен успешно"
INSUFFICIENT_PERMISSIONS_FOR_TASK: "Нямате достатъчно права за тази задача"
CACHE_CLEARED: "Временните файлове са изчистени"
METHOD: "Метод"
ERROR_CLEARING_CACHE: "Грешка при изтриването на временните файлове"
AN_ERROR_OCCURRED: "Възникна грешка"
YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Резервното копие е готово за изтегляне"
DOWNLOAD_BACKUP: "Изтегляне на резервното копие"
PAGES_FILTERED: "Филтрирани страници"
NO_PAGE_FOUND: "Няма намерени страници"
INVALID_PARAMETERS: "Невалидни параметри"
NO_FILES_SENT: "Няма изпратени файлове"
EXCEEDED_FILESIZE_LIMIT: "Надхвърлен лимит за размер на PHP конфигурационен файл"
UNKNOWN_ERRORS: "Неизвестни грешки"
EXCEEDED_GRAV_FILESIZE_LIMIT: "Превишен лимит за размера на конфигурационен GRAV файл"
UNSUPPORTED_FILE_TYPE: "Този файлов формат не се поддържа"
FAILED_TO_MOVE_UPLOADED_FILE: "Преместването на качения файл не е успешно."
FILE_UPLOADED_SUCCESSFULLY: "Файлът е качен успешно"
FILE_DELETED: "Файлът е изтрит"
FILE_COULD_NOT_BE_DELETED: "Файлът не може да бъде изтрит"
FILE_NOT_FOUND: "Файлът не е намерен"
NO_FILE_FOUND: "Няма намерени файлове"
GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav беше успешно актуализиран до"
GRAV_UPDATE_FAILED: "Актуализацията на Grav е неуспешна"
EVERYTHING_UPDATED: "Всичко е актуализирано"
UPDATES_FAILED: "Актуализациите не бяха успшено"
AVATAR_BY: "Аватар от"
AVATAR_UPLOAD_OWN: "Или качи собствени..."
LAST_BACKUP: "Последно резервно копие"
FULL_NAME: "Пълно име"
USERNAME: "Потребителско име"
EMAIL: "Ел. поща"
USERNAME_EMAIL: "Потребителско име или инейл"
PASSWORD: "Парола"
PASSWORD_CONFIRM: "Потвърждение на паролата"
TITLE: "Титла"
LANGUAGE: "Език"
ACCOUNT: "Профил"
EMAIL_VALIDATION_MESSAGE: "Ел. поща трябва да бъде валидна"
PASSWORD_VALIDATION_MESSAGE: "Паролата трябва да съдържа поне един номер, една главна буква, една малка буква и да съдържа поне 8 или повече знака"
LANGUAGE_HELP: "Задаване на любим език"
MEDIA: "Медиа"
DEFAULTS: "По подразбиране"
SITE_TITLE: "Заглавие на сайта"
SITE_TITLE_PLACEHOLDER: "Заглавие за всички страници"
SITE_TITLE_HELP: "Подразбиращо се заглавие за вашият сайт, често се използва от темите"
SITE_DEFAULT_LANG: "Език по подразбиране"
SITE_DEFAULT_LANG_PLACEHOLDER: "Език по подразбиране, използван от <HTML> тага на темите"
SITE_DEFAULT_LANG_HELP: "Език по подразбиране, използван от <HTML> тага на темите"
DEFAULT_AUTHOR: "Подразбиращ се автор"
DEFAULT_AUTHOR_HELP: "Име на автор по подразбиране, често използвано в теми или страници"
DEFAULT_EMAIL: "Имейл по подразбиране"
DEFAULT_EMAIL_HELP: "Имейл по подразбиране, използван в теми или страници"
TAXONOMY_TYPES: "Видове таксономии"
TAXONOMY_TYPES_HELP: "Типовете таксономия трябва да бъдат дефинирани тук, ако искате да ги използвате в страници"
PAGE_SUMMARY: "Резюме на страницата"
ENABLED: "Включен"
ENABLED_HELP: "Разреши извлечение (извлечението връща същото съдържание, като в страницата)"
'YES': "Да"
'NO': "Не"
SUMMARY_SIZE: "Размер на резюмето"
SUMMARY_SIZE_HELP: "Брой знаци, които да бъдат използвани при създаването на резюме за страницата"
FORMAT: "Формат"
SHORT: "Къс"
LONG: "Дълъг"
DELIMITER: "Делител"
DELIMITER_HELP: "Разделител на извлечението (по подразбиране '===')"
METADATA: "Мета-данни"
METADATA_HELP: "Ще бъдат показани метадата стойностите по подразбиране на всяка страница, освен ако не са отхвърлени от страницата"
NAME: "Име"
CONTENT: "Съдържание"
SIZE: "Размер"
ACTION: "Действие"
REDIRECTS_AND_ROUTES: "Пренасочвания и пътища"
CUSTOM_REDIRECTS: "Потребителски пренасочвания"
CUSTOM_REDIRECTS_HELP: "маршрути за пренасочване към сдруга страница. Подмяна със стандартен Regex е валидна"
CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/your/alias"
CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/your/redirect"
CUSTOM_ROUTES: "Потребителски пренасочвания"
CUSTOM_ROUTES_PLACEHOLDER_KEY: "/your/alias"
CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/your/route"
FILE_STREAMS: "Потоци файлове"
DEFAULT: "По подразбиране"
PAGE_MEDIA: "Страница с медия"
OPTIONS: "Опции"
PUBLISHED: "Публикувано"
PUBLISHED_HELP: "По подразбиране страницата се публикува, освен ако не е зададео published: false или publish_date е в бъдеще или unpublish_date е в миналото"
DATE: "Дата"
DATE_HELP: "Променливата за дата позволява да се зададе дата асоциирана със страницата."
PUBLISHED_DATE: "Дата на публикуване"
PUBLISHED_DATE_HELP: "Дата, на която автоматично ще се публикува."
UNPUBLISHED_DATE: "Дата на непубликуване"
UNPUBLISHED_DATE_HELP: "Може да зададе дата за автоматично непубликуване."
ROBOTS: "Роботи"
TAXONOMIES: "Таксономии"
TAXONOMY: "Таксономия"
ADVANCED: "Разширено"
SETTINGS: "Настройки"
FOLDER_NUMERIC_PREFIX: "Цифров префикс на папка"
FOLDER_NUMERIC_PREFIX_HELP: "Цифров префикс осигуряващ ръчно подреждане и по-добра видимост"
FOLDER_NAME: "Име на папка"
FOLDER_NAME_HELP: "Името на папката, която ще се съхрани във файлова система за тази страница"
PARENT: "Родител"
DEFAULT_OPTION_ROOT: "- Коренова папка -"
DEFAULT_OPTION_SELECT: "- Избор -"
DISPLAY_TEMPLATE: "Показване на шаблон"
ORDERING: "Подреждане"
PAGE_ORDER: "Подредба на страниците"
OVERRIDES: "Замени"
MENU: "Меню"
MENU_HELP: "Стрингът, който ще се използва в меню. Ако не се зададе, ще се използва Title."
SLUG: "Слъг"
SLUG_HELP: "Слъг променливата Ви позволява да зададете конкретна порция от URL на страницата"
SLUG_VALIDATE_MESSAGE: "Слъговете могат да съдържат само малки букви, цифри и тирета"
PROCESS: "Обработване"
PROCESS_HELP: "Контролирайте обработката на страниците. Може да бъде за отделна страница или глобално"
DEFAULT_CHILD_TYPE: "Тип по подразбиране"
USE_GLOBAL: "Използвай глобални"
ROUTABLE: "Маршрутизируем"
ROUTABLE_HELP: "Ако страницата е достъпна през URL"
CACHING: "Създаване на временни файлове"
VISIBLE: "Видим"
VISIBLE_HELP: "Определя дали една страница е видима в навигацията."
DISABLED: "Изключено"
ITEMS: "Елементи"
ORDER_BY: "Подреждане по"
ORDER: "Подреждане"
FOLDER: "Папка"
ASCENDING: "Възходящо"
DESCENDING: "Низходящо"
PAGE_TITLE: "Заглавие на страницата"
PAGE_TITLE_HELP: "Заглавието на страницата"
PAGE: "Страница"
FRONTMATTER: "Встъпление"
FILENAME: "Име на файла"
PARENT_PAGE: "Родителска страница"
HOME_PAGE: "Начална страница"
HOME_PAGE_HELP: "Страницата, която Grav ще използва по подразбиране за начална страница"
DEFAULT_THEME: "Тема по подразбиране"
DEFAULT_THEME_HELP: "Задаване на темата по подразбиране, която Grav ще използва (по подразбиране това е Antimatter)"
TIMEZONE: "Часова зона"
TIMEZONE_HELP: "Презаписване на времевата зона на сървъра"
SHORT_DATE_FORMAT: "Кратък формат дата"
SHORT_DATE_FORMAT_HELP: "Задай кратък формат дата, който може да се използва от темите"
LONG_DATE_FORMAT: "Пълен формат дата"
LONG_DATE_FORMAT_HELP: "Задай пълен формат дата, който може да се използва от темите"
DEFAULT_ORDERING: "По подразбиране"
DEFAULT_ORDERING_DEFAULT: "По подразбиране - според име на папка"
DEFAULT_ORDERING_FOLDER: "Папка - според името на папката без префикс"
DEFAULT_ORDERING_TITLE: "Заглавие - според заглавно поле в главата"
DEFAULT_ORDERING_DATE: "Дата - според поле за дата в главата"
DEFAULT_ORDER_DIRECTION: "Подреждане по подразбиране"
DEFAULT_ORDER_DIRECTION_HELP: "Посоката на страниците в списък"
DEFAULT_PAGE_COUNT: "Брой страници по подразбиране"
DEFAULT_PAGE_COUNT_HELP: "Максимален брой страници в списък по подразбиране"
DATE_BASED_PUBLISHING: "Публикуване според датата"
DATE_BASED_PUBLISHING_HELP: "Автоматично (не)публикувай постове според датата"
EVENTS: "Събития"
EVENTS_HELP: "Пускане или спиране на специфични събития. Спирането на някои събития може да счупи определени приставки"
REDIRECT_DEFAULT_ROUTE: "Пренасочване на пътя по подразбиране"
REDIRECT_DEFAULT_ROUTE_HELP: "Автоматично пренасочване към пътя по подразбиране на страницата"
LANGUAGES: "Езици"
SUPPORTED: "Поддържани"
SUPPORTED_HELP: "Списък от двубуквени езикови кодове, отделени със запетая (пример 'bg,en,de')"
HTTP_HEADERS: "HTTP заглавки"
EXPIRES: "Изтича на"
CACHE_CONTROL: "HTTP кеш-контрол"
LAST_MODIFIED: "Последна промяна"
CACHE_CHECK_METHOD: "Мотод проверка на кеша"
CACHE_DRIVER: "Кеш драйвър"
CACHE_PREFIX: "Кеш представка"
CACHE_PURGE: "Изтриване на стр кеш"
LIFETIME: "Продължителност на живот"
GZIP_COMPRESSION: "Gzip компресия"
GZIP_COMPRESSION_HELP: "Разреши GZip компресия на Grav страницата, за оптимизация."
CSS_PIPELINE: "CSS pipeline"
JAVASCRIPT_PIPELINE: "JavaScript pipeline"
LOG_HANDLER: "Обработка на лога"
DEBUGGER: "Дибъгър"
SESSION: "Сесия"
CURRENT: "Текущ"
SAVE_AS: "Запази като"
AND: "и"
UPDATE_AVAILABLE: "Налична актуализация"
FULLY_UPDATED: "Напълно обновен"
SAVE_LOCATION: "Местоположение за запис"
IGNORE_HIDDEN_HELP: "Игнорирай всички файлове и папки, които започват с точка"
WRAPPED_SITE: "Опаковани сайт"
CONFIGURATION: "Настройки"
TIMEOUT: "Таймаут"
DASHBOARD: "Контролен панел"

View File

@ -0,0 +1,4 @@
---
PLUGIN_ADMIN:
LOGIN_BTN: "লগ ইন"
LOGIN_BTN_FORGOT: "ভুলে গেছি"

View File

@ -0,0 +1,592 @@
---
PLUGIN_ADMIN:
ADMIN_BETA_MSG: "Un ermaeziadenn beta an hini eo! Arverit en endro produadur gant evezh..."
ADMIN_REPORT_ISSUE: "Kavet hoc'h eus ur gudenn? Danevellit anezhi war Github."
EMAIL_FOOTER: "<a href=\"https://getgrav.org\">Lusket gant Grav</a> - Ar CMS Restr plad modern"
LOGIN_BTN: "Anv arveriad"
LOGIN_BTN_FORGOT: "Ankouaet"
LOGIN_BTN_RESET: "Adderaouekaat ar ger-tremen"
LOGIN_BTN_SEND_INSTRUCTIONS: "Kas an ditouroù adderaouekaat"
LOGIN_BTN_CLEAR: "Skarzhañ ar furmskrid"
LOGIN_BTN_CREATE_USER: "Krouiñ an arveriad"
LOGIN_LOGGED_IN: "Kennasket oc'h gant berzh"
LOGIN_FAILED: "C'hwitadenn war ar c'hennask"
LOGGED_OUT: "Digennasket oc'h"
RESET_NEW_PASSWORD: "Enankit ur ger-tremen nevez &hellip;"
RESET_LINK_EXPIRED: "Diamzeret eo an ere adderaouekaat, klaskit en-dro"
RESET_PASSWORD_RESET: "Adderaouekaet eo bet ar ger-tremen"
RESET_INVALID_LINK: "Ere adderaouekaat didalvoudek, klaskit en-dro"
FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Kaset eo bet an ditouroù da adderaouekaat ho ker-tremen d'ho chmolec'h postel"
FORGOT_FAILED_TO_EMAIL: "C'hwitadenn en ur gas an ditouroù, klaskit en-dro diwezhatoc'h"
FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "N'haller ket adderaouekaat ar ger-tremen evit %s, chomlec'h postel ebet arventennet"
FORGOT_USERNAME_DOES_NOT_EXIST: "N'eus ket eus an arveriad gant an anv <b>%s</b>"
FORGOT_EMAIL_NOT_CONFIGURED: "N'haller ket adderaouekaat ar ger-tremen. N'eo ket kefluniet al lec'hienn evit kas posteloù"
FORGOT_EMAIL_SUBJECT: "%s Goulenn adderaouekaat ar ger-tremen"
FORGOT_EMAIL_BODY: "<h1>Adderaouekaat ar ger-tremen/h1><p>%1$s,</p><p>Graet eo bet un azgoulenn war <b>%4$s</b> evit adderaouekaat ho ker-tremen.</p><p><br /><a href=\"%2$s\" class=\"btn-primary\">Klikit amañ da adderaouekaat ho ker-tremen</a><br /><br /></p><p>Gallout a rit ivez eilañ an URL da heul er varrenn chomlec'h en ho merdeer:</p> <p>%2$s</p><p><br />A galon,<br /><br />%3$s</p>"
MANAGE_PAGES: "Ardeiñ ar pajennoù"
PAGES: "Pajennoù"
PLUGINS: "Enlugelladoù"
PLUGIN: "Enlugellad"
THEMES: "Neuzioù"
LOGOUT: "Digennaskañ"
BACK: "Distreiñ"
NEXT: "War-lerc'h"
PREVIOUS: "Diaraog"
ADD_PAGE: "Ouzhpennañ ur bajenn"
MOVE: "Dilec'hiañ"
DELETE: "Dilemel"
VIEW: "Gwel"
SAVE: "Enrollañ"
NORMAL: "Reoliek"
EXPERT: "Kemplezhoc'h"
EXPAND_ALL: "Astenn an holl"
COLLAPSE_ALL: "Bihanaat an holl"
ERROR: "Fazi"
CLOSE: "Serriñ"
CANCEL: "Nullañ"
CONTINUE: "Kenderc'hel"
MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Kadarnadur azgoulennet"
MODAL_CHANGED_DETECTED_TITLE: "Kemmoù dinoet"
MODAL_CHANGED_DETECTED_DESC: "Kemmoù dienrollet a zo. Sur oc'h e fell deoc'h kuitaat hep enrollañ?"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Kadarnadur azgoulennet"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Sur oc'h e fell deoc'h dilemel ar restr-mañ? N'haller ket dizober ar gwered-mañ."
ADD_FILTERS: "Ouzhpennañ siloù"
SEARCH_PAGES: "Klask pajennoù"
VERSION: "Handelv"
WAS_MADE_WITH: "Savet eo bet gant"
BY: "Gant"
UPDATE_THEME: "Hizivaat an neuz"
UPDATE_PLUGIN: "Hizivaat an enlugellad"
OF_THIS_THEME_IS_NOW_AVAILABLE: "an neuz-mañ a zo hegerz"
OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "an enlugellad-mañ a zo hegerz"
AUTHOR: "Aozer"
HOMEPAGE: "Pennbajenn"
DEMO: "Tañva"
BUG_TRACKER: "Heulier beugoù"
KEYWORDS: "Gerioù-alc'hwez"
LICENSE: "Lañvaz"
DESCRIPTION: "Deskrivadur"
README: "Skoazell"
REMOVE_THEME: "Dilemel an neuz"
INSTALL_THEME: "Staliañ an neuz"
THEME: "Neuz"
BACK_TO_THEMES: "Distreiñ d'an neuzioù"
BACK_TO_PLUGINS: "Distreiñ d'an enlugelladoù"
CHECK_FOR_UPDATES: "Klask hizivadennoù"
ADD: "Ouzhpenañ"
CLEAR_CACHE: "Skarzhañ ar c'hrubuilh"
CLEAR_CACHE_ALL_CACHE: "Ar c'hrubuilh a-bezh"
CLEAR_CACHE_ASSETS_ONLY: "Loazioù nemetken"
CLEAR_CACHE_IMAGES_ONLY: "Skeudennoù nemetken"
CLEAR_CACHE_CACHE_ONLY: "Krubuilh nemetken"
CLEAR_CACHE_TMP_ONLY: "Padennek hepken"
UPDATES_AVAILABLE: "Hizivadennoù hegerz"
DAYS: "Devezhioù"
UPDATE: "Hizivadenn"
BACKUP: "Gwared"
STATISTICS: "Stadegoù"
TODAY: "Hiziv"
WEEK: "Sizhun"
MONTH: "Miz"
LATEST_PAGE_UPDATES: "Hizivadennoù diwezhañ ar bajenn"
MAINTENANCE: "Trezalc'h"
UPDATED: "Hizivaet"
MON: "Lun"
TUE: "Meu"
WED: "Mer"
THU: "Yao"
FRI: "Gwe"
SAT: "Sad"
SUN: "Sul"
COPY: "Eilañ"
EDIT: "Kemmañ"
CREATE: "Krouiñ"
GRAV_ADMIN: "Merour Grav"
GRAV_OFFICIAL_PLUGIN: "Enlugellad Kefridiel Grav"
GRAV_OFFICIAL_THEME: "Neuz Kefridiel Grav"
PLUGIN_SYMBOLICALLY_LINKED: "Gant un ere arouezus eo lakaet an enlugellad. Ne vo ket dinoet an hizivadennoù."
THEME_SYMBOLICALLY_LINKED: "Gant un ere arouezus eo lakaet an neuz. Ne vo ket dinoet an hizivadennoù"
REMOVE_PLUGIN: "Dilemel an enlugellad"
INSTALL_PLUGIN: "Staliañ an enlugellad"
AVAILABLE: "Hegerz"
INSTALLED: "Staliet"
INSTALL: "Staliañ"
ACTIVE_THEME: "Neuz oberiant"
SWITCHING_TO: "Kemmañ da"
SWITCHING_TO_DESCRIPTION: "En ur gemmañ d'un neuz disheñvel n'eus gwarant ebet e vo skoret an holl frammoù pajenn, ar pezh a zegasfe fazioù en ur gargañ ar pajennoù-mañ."
SWITCHING_TO_CONFIRMATION: "Fellout a ra deoc'h kenderc'hel ha kemmañ an neuz"
CREATE_NEW_USER: "Krouiñ un arveriad nevez"
REMOVE_USER: "Dilemel an arveriad"
ACCESS_DENIED: "Haeziñ nac'het"
ACCOUNT_NOT_ADMIN: "n'hoc'h eus ket an aotreoù a-zere"
PHP_INFO: "Titouroù PHP"
INSTALLER: "Stalier"
AVAILABLE_THEMES: "Neuzioù hegerz"
AVAILABLE_PLUGINS: "Enlugelladoù hegerz"
INSTALLED_THEMES: "Neuzioù staliet"
INSTALLED_PLUGINS: "Enlugelladoù staliet"
BROWSE_ERROR_LOGS: "Furchal er c'herzhlevr fazioù"
SITE: "Lec'hienn"
INFO: "Titouroù"
SYSTEM: "Reizhiad"
USER: "Arveriad"
ADD_ACCOUNT: "Ouzhpennañ ur gont"
SWITCH_LANGUAGE: "Kemmañ ar yezh"
SUCCESSFULLY_ENABLED_PLUGIN: "Gweredekaet an enlugellad gant berzh"
SUCCESSFULLY_DISABLED_PLUGIN: "Diweredekaet an enlugellad gant berzh"
SUCCESSFULLY_CHANGED_THEME: "Kemmet an neuz dre ziouer gant berzh"
INSTALLATION_FAILED: "C'hwitadenn war ar staliadur"
INSTALLATION_SUCCESSFUL: "Berzh war ar staliadur"
UNINSTALL_FAILED: "C'hwitadenn war an distaliadur"
UNINSTALL_SUCCESSFUL: "Berzh war an distaliadur"
SUCCESSFULLY_SAVED: "Enrollet gant berzh"
SUCCESSFULLY_COPIED: "Eilet gant berzh"
REORDERING_WAS_SUCCESSFUL: "Adurzhiet gant berzh"
SUCCESSFULLY_DELETED: "Dilamet gant berzh"
SUCCESSFULLY_SWITCHED_LANGUAGE: "Kemmet ar yezh gant berzh"
INSUFFICIENT_PERMISSIONS_FOR_TASK: "N'ho peus ket trawalc'h a aotreoù evit ar gwered"
CACHE_CLEARED: "Skarzhet ar c'hrubuilh"
METHOD: "Hentenn"
ERROR_CLEARING_CACHE: "Fazi en ur skarzhañ ar c'hrubuilh"
AN_ERROR_OCCURRED: "Degouezhet ez eus bet ur fazi"
YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "Prest eo ho kwared da vezañ pellgarget"
DOWNLOAD_BACKUP: "Pellgargañ ar gwared"
PAGES_FILTERED: "Pajennoù silet"
NO_PAGE_FOUND: "Pajenn ebet kavet"
INVALID_PARAMETERS: "Arventennoù didalvoudek"
NO_FILES_SENT: "Restr ebet kaset"
UNKNOWN_ERRORS: "Fazioù dianav"
UNSUPPORTED_FILE_TYPE: "Doare restr amskor"
FAILED_TO_MOVE_UPLOADED_FILE: "C'hwitadenn en ur zilec'hiañ ar restr pellgaset."
FILE_UPLOADED_SUCCESSFULLY: "Restr pellgaset gant berzh"
FILE_DELETED: "Restr dilamet"
FILE_COULD_NOT_BE_DELETED: "N'haller ket dilemel ar restr"
FILE_NOT_FOUND: "N'eus ket bet kavet ar restr"
NO_FILE_FOUND: "Restr ebet kavet"
GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Hizivaet eo bet Grav da"
GRAV_UPDATE_FAILED: "C'hwitadenn war hizivadenn Grav"
EVERYTHING_UPDATED: "Hizivaet pep tra"
UPDATES_FAILED: "C'hwitadenn war a hizivadennoù"
AVATAR_BY: "Avatar gant"
LAST_BACKUP: "Gwared diwezhañ"
FULL_NAME: "Anv klok"
USERNAME: "Anv arveriad"
EMAIL: "Chomlec'h postel"
PASSWORD: "Ger-tremen"
PASSWORD_CONFIRM: "Kadarnat ar ger-tremen"
TITLE: "Titl"
LANGUAGE: "Yezh"
ACCOUNT: "Kont"
EMAIL_VALIDATION_MESSAGE: "Ret eo reiñ ur chomlec'h postel talvoudek"
PASSWORD_VALIDATION_MESSAGE: "Ret eo d'ar ger-tremen enderc'hel ur niverenn, ul lizherenn vras hag ul lizherenn vihan hag 8 arouezenn d'an nebeutañ"
LANGUAGE_HELP: "Dibabit ar yezh"
MEDIA: "Media"
DEFAULTS: "Dre ziouer"
SITE_TITLE: "Titl al lec'hienn"
SITE_TITLE_PLACEHOLDER: "Titl ledan al lec'hienn"
SITE_TITLE_HELP: "Titl dre ziouer ho lec'hienn, arveret en neuzioù"
SITE_DEFAULT_LANG: "Yezh defot"
DEFAULT_AUTHOR: "Aozer dre ziouer"
DEFAULT_AUTHOR_HELP: "Un anv aozer dre ziouer, arveret en neuzioù pe er pajennoù"
DEFAULT_EMAIL: "Chomlec'h postel dre ziouer"
DEFAULT_EMAIL_HELP: "Ur chomlec'h postel dre ziouer, arveret en neuze pe er pajennoù"
TAXONOMY_TYPES: "Doareoù rummadoù"
TAXONOMY_TYPES_HELP: "An doareoù rummadoù a rank bezañ erspizet amañ ma fell deoc'h arverañ anezho er pajennoù"
PAGE_SUMMARY: "Berradenn ar bajenn"
ENABLED: "Gweredekaet"
ENABLED_HELP: "Gweredekaat berradenn ar bajenn (ar verradenn a zistro an hevelep tra hag endalc'had ar bajenn)"
'YES': "Ya"
'NO': "Ket"
SUMMARY_SIZE: "Ment ar verradenn"
SUMMARY_SIZE_HELP: "An niverenn a arouezenn da arverañ evel berradenn ur bajenn"
FORMAT: "Mentrezh"
FORMAT_HELP: "berr = arverañ degouezh kentañ an disranner pe ment; hir = laosket e vo an disranner berradenn a-gostez"
SHORT: "Berr"
LONG: "Hir"
DELIMITER: "Disranner"
DELIMITER_HELP: "Disranner ar verradenn (diouer '===')"
METADATA: "Metaroadennoù"
METADATA_HELP: "Skrammet e vo ar gwerzhioù metaroadennoù dre ziouer war an holl bajennoù war-bouez m'eo flastret gant ar bajenn"
NAME: "Anv"
CONTENT: "Endalc'had"
REDIRECTS_AND_ROUTES: "Adheñchañ ha treugoù"
CUSTOM_REDIRECTS: "Adheñchañ personelaet"
CUSTOM_REDIRECTS_HELP: "treugoù da adheñchañ davet pajennoù all. Talvoudek eo an amsaviñ regex"
CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/un/anv"
CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/un/adeñchañ"
CUSTOM_ROUTES: "Treugoù personelaet"
CUSTOM_ROUTES_HELP: "treugoù da adheñchañ davet pajennoù all. Talvoudek eo an amsaviñ Regex"
CUSTOM_ROUTES_PLACEHOLDER_KEY: "/ho/anv"
CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/ho/treug"
FILE_STREAMS: "Lanvioù restroù"
DEFAULT: "Dre ziouer"
PAGE_MEDIA: "Media ar bajenn"
OPTIONS: "Dibarzhioù"
PUBLISHED: "Embannet"
PUBLISHED_HELP: "Dre ziouer eo embannet ur bajenn war-bouez m'eo lakaet da \"Embannet: ket\" pe dre un deiziad embann en dazont, pe un deiziad diembannañ tremenet"
DATE: "Deiziad"
DATE_HELP: "Ar vaezienn deiziad a laosk ac'hanoc'h da arventennañ un deiziad liammet gant ar bajenn."
PUBLISHED_DATE: "Deiziad embann"
PUBLISHED_DATE_HELP: "Gallout a rit reiñ un deiziad da embann ent emgefreek."
UNPUBLISHED_DATE: "Deiziad diembannañ"
UNPUBLISHED_DATE_HELP: "Gallout a rit reiñ un deiziad evit diembannañ ent emgefreek."
ROBOTS: "Robotoù"
TAXONOMIES: "Rummadoù"
TAXONOMY: "Rummad"
ADVANCED: "Kempleshoc'h"
SETTINGS: "Arventennoù"
FOLDER_NUMERIC_PREFIX: "Rakger niverel an teuliad"
FOLDER_NUMERIC_PREFIX_HELP: "Rakgerioù niverel evit urzhiañ gant an dorn ha emplegañ ar gwelusted"
FOLDER_NAME: "Anv an teuliad"
FOLDER_NAME_HELP: "Anv an teuliad a vo kadavet er reizhiad restroù evit ar bajenn"
PARENT: "Kar"
DEFAULT_OPTION_ROOT: "- Gwrizienn -"
DEFAULT_OPTION_SELECT: "- Diuzañ -"
DISPLAY_TEMPLATE: "Skrammañ ar patrom"
DISPLAY_TEMPLATE_HELP: "An doare pajenn a ziviz peseurt patrom twig a zeznaouo ar bajenn"
ORDERING: "Urzh"
PAGE_ORDER: "Urzh ar pajennoù"
OVERRIDES: "Flastrañ"
MENU: "Lañser"
MENU_HELP: "Ar chadennoù da arverañ el lañser. Ma n'eo ket arventennet, Titl a vo arveret."
SLUG: "Slug"
SLUG_HELP: "An argemenn slug a aotren ac'hanoc'h da arventennañ URL lodenn ar bajenn"
SLUG_VALIDATE_MESSAGE: "Lizherennoù bihan, sifroù ha tiredoù a c'hall bezañ er slug hepken"
PROCESS: "Keweriañ"
PROCESS_HELP: "Reoliañ penaos eo keweriet ar pajennoù. Gallout a ra bezañ lakaet dre bajenn kentoc'h eget en un doare hollek"
DEFAULT_CHILD_TYPE: "Doare bugel dre ziouer"
USE_GLOBAL: "Arverañ Hollek"
ROUTABLE: "Treugus"
ROUTABLE_HELP: "M'eo haezadus ar bajenn dre un URL"
CACHING: "Krubuilhiñ"
VISIBLE: "Gwelus"
VISIBLE_HELP: "Despizañ a ra gwelusted ur bajenn er merdeiñ."
DISABLED: "Diweredekaet"
ITEMS: "Ergorennoù"
ORDER_BY: "Urzhiañ dre"
ORDER: "Urzh"
FOLDER: "Teuliad"
ASCENDING: "War-gresk"
DESCENDING: "War-zigresk"
PAGE_TITLE: "Titl ar bajenn"
PAGE_TITLE_HELP: "Titl ar bajenn"
PAGE: "Pajenn"
FRONTMATTER: "Frontmatter"
FILENAME: "Anv ar restr"
PARENT_PAGE: "Pajenn gar"
HOME_PAGE: "Pennbajenn"
HOME_PAGE_HELP: "Pajenn arveret gant Grav evel pajenn degemer dre ziouer"
DEFAULT_THEME: "Neuz dre ziouer"
DEFAULT_THEME_HELP: "Arventennañ an neuz arveret gant Grav dre ziouer (Antimatter dre ziouer)"
TIMEZONE: "Gwerzhid-eur"
TIMEZONE_HELP: "Flastrañ gwerzhid-eur dre ziouer an dafariad"
SHORT_DATE_FORMAT: "Mentrezh skrammañ an deiziad berr"
SHORT_DATE_FORMAT_HELP: "Arventennan ar mentrezh deiziad berr da arverañ gant an neuzioù"
LONG_DATE_FORMAT: "Mentrezh deiziad hir"
LONG_DATE_FORMAT_HELP: "Arventennañ ar mentrezh deiziad hir a vo arveret en neuzioù"
DEFAULT_ORDERING: "Urzh dre ziouer"
DEFAULT_ORDERING_HELP: "Pajennoù er roll a vo skrammet en urzh-mañ war-bouez m'eo flastret"
DEFAULT_ORDERING_DEFAULT: "Dre ziouer - diazezet war anv an teuliad"
DEFAULT_ORDERING_FOLDER: "Teuliad - diazezet war anv an teuliad hep rakger"
DEFAULT_ORDERING_TITLE: "Titl - diazezet war vaezienn ditl an talbenn"
DEFAULT_ORDERING_DATE: "Deiziad - diazezet war vaezienn deiziad an talbenn"
DEFAULT_ORDER_DIRECTION: "Tu an urzh dre ziouer"
DEFAULT_ORDER_DIRECTION_HELP: "Tu ar pajennoù er roll"
DEFAULT_PAGE_COUNT: "Niver a bajennoù dre ziouer"
DEFAULT_PAGE_COUNT_HELP: "Niver a bajennoù en ur roll d'ar muiañ"
DATE_BASED_PUBLISHING: "Embannadenn diazezet war un deiziad"
DATE_BASED_PUBLISHING_HELP: "(Di)embann pennadoù ent emgefreek hervez o deiziad"
EVENTS: "Darvoudoù"
EVENTS_HELP: "(Di)weredekaat darvoudoù resis. Diweredekaat anezho a c'hall terriñ enlugelladoù"
REDIRECT_DEFAULT_ROUTE: "Adheñchañ an treug dre ziouer"
REDIRECT_DEFAULT_ROUTE_HELP: "Adheñchañ ent emgefreek d'un treug pajenn dre ziouer"
LANGUAGES: "Yezhoù"
SUPPORTED: "Skoret"
SUPPORTED_HELP: "Roll bonegoù yezh 2 lizherenn ennañ disrannet gant skejoù (skouer: 'br, cy, en')"
TRANSLATIONS_FALLBACK: "Troidigezh dre ziouer"
TRANSLATIONS_FALLBACK_HELP: "Arverañ un droidigezh all ma n'eus ket eus ar tezh oberiant"
ACTIVE_LANGUAGE_IN_SESSION: "Yezhoù oberiant en estez"
ACTIVE_LANGUAGE_IN_SESSION_HELP: "Kadaviñ ar yezh oberiant en estez"
HTTP_HEADERS: "Talbennoù HTTP"
EXPIRES: "Diamzer"
EXPIRES_HELP: "Arventennañ an talbenn diamzeriñ e eilennoù."
LAST_MODIFIED: "Kemmet da ziwezhañ"
LAST_MODIFIED_HELP: "Arventennañ an talbenn kemmet da ziwezhañ a c'hall skoazell da wellaat ar proksi ha krubuilh ar merdeer"
ETAG: "ETag"
ETAG_HELP: "Arventennañ an talbenn etag evit skoazell da c'houzout peur eo bet kemmet ur bajenn"
VARY_ACCEPT_ENCODING: "Vary accept encoding"
VARY_ACCEPT_ENCODING_HELP: "Arventennañ a ra an talbenn `Vary: Accept Encoding` evit skoazell gant ar proksi hag ar c'hrubuilh CDN"
MARKDOWN_EXTRA_HELP: "Gweredekaat ar skor dre ziouer evit Markdown Ectra - https://michelf.ca/projects/php-markdown/extra/"
AUTO_LINE_BREAKS: "Tremen d'al linenn ent emgefreek"
AUTO_LINE_BREAKS_HELP: "Gweredekaat skor tremen al linenn ent emgefreek e Markdown"
AUTO_URL_LINKS: "Ereoù URL emgefreek"
AUTO_URL_LINKS_HELP: "Gweredekaat amdroadur emgefreek an URLoù da ereoù HTML"
ESCAPE_MARKUP: "Gwareziñ an HTML"
ESCAPE_MARKUP_HELP: "Gwareziñ ar c'hlavioù e elfennoù HTML"
CACHING_HELP: "Trec'haoler hollek evit (di)weredekaat krubuilh Grav"
CACHE_CHECK_METHOD: "Hentenn gwiriekaat ar c'hrubuilh"
CACHE_CHECK_METHOD_HELP: "Dibab an hentenn arveret gant Grav evit gwiriekaat m'eo bet kemmer ar restoù pajenn."
CACHE_DRIVER: "Sturier Krubuilh"
CACHE_DRIVER_HELP: "Dibab pe sturier krubuilh a zo arveret Grav. 'Dinoiñ emgefreek' a glask kavout pe zoare a zo an hini gwellañ"
CACHE_PREFIX: "Rakger ar c'hrubuilh"
CACHE_PREFIX_HELP: "Lodenn naoudi an alc'hwez Grav. Na gemmit anezhi ma n'ouzit ket petra rit."
CACHE_PREFIX_PLACEHOLDER: "Deveret eus an URL diazez (flastret en un enkañ ur chadenn dargouezhek)"
LIFETIME: "Padelezh buhez"
LIFETIME_HELP: "Arventennañ padelezh ar c'hrubuilh e eilennoù. 0 = anvevenn"
GZIP_COMPRESSION: "Koazhadur Gzip"
GZIP_COMPRESSION_HELP: "Gweredekaat koazhadur Gzip ar bajenn Grav evit kreskiñ an digonusted."
TWIG_TEMPLATING: "Patromiñ Twig"
TWIG_CACHING: "Krubuilh Twig"
TWIG_CACHING_HELP: "Reoliañ wikefre krubuilh Twig. Laoskit gweredekaet evit an digonusted gwellañ."
TWIG_DEBUG: "Diveugañ Twig"
TWIG_DEBUG_HELP: "Aotren an dibarzh evit chom hep kargañ an askouezh diveugañ Twig"
DETECT_CHANGES: "Dinoiñ ar c'hemmoù"
DETECT_CHANGES_HELP: "Adkempunet e vo krubuilh Twig ent emgefreek ma vez dinoet kemmoù er patromoù Twig"
AUTOESCAPE_VARIABLES: "Gwareziñ an argemennoù ent emgefreek"
AUTOESCAPE_VARIABLES_HELP: "Gwareziñ an holl argemennoù ent emgefreek. Moarvat e torro ho lec'hienn"
ASSETS: "Madoù"
CSS_PIPELINE: "Arrevellañ CSS"
CSS_PIPELINE_HELP: "Arrevellañ ar CSS a zo unvanadur meur a loaz CSS en ur restr hepken"
CSS_PIPELINE_INCLUDE_EXTERNALS: "Ebarzhiñ restroù estren en arrevellañ CSS"
CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "URLoù diavaez a zo gant daveoù restroù daveel a-wechoù ha ne rankont ket bezañ arrevellet"
CSS_PIPELINE_BEFORE_EXCLUDES: "Deoueziñ an arrevellañ CSS da gentañ"
CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Deoueziñ an arrevellañ CSS a-raok kement dave CSS all ha n'int ket enkorfet"
CSS_MINIFY: "Bihanadur CSS"
CSS_MINIFY_HELP: "Bihanaat ar CSS e-pad an arrevellañ"
CSS_MINIFY_WINDOWS_OVERRIDE: "Amsaviñ bihanadur ar CSS Windows"
CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Amsaviñ ar bihanadur evit savennoù Windows. Faos dre ziouer abalamour da ThreadStackSize"
CSS_REWRITE: "Adskrivañ CSS"
CSS_REWRITE_HELP: "Adskrivañ kement URL daveel CSS e-pad an arrevellañ"
JAVASCRIPT_PIPELINE: "Arrevellañ Javascript"
JAVASCRIPT_PIPELINE_HELP: "An arrevellañ JS a zo unvanadur meur a restr JS en ur restr hepken"
JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Enkorfañ ar JS diavaez evit an arrevellañ"
JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "Urloù diavaez o deus daveoù restroù daveel a-wechoù ha ne rankont ket bezañ arrevellet"
JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Arrevellañ JS da gentañ"
JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Deoueziñ an arrevellañ JS a-raok kement dave JS all ha n'int ket enkorfet"
JAVASCRIPT_MINIFY: "Bihanat ar javascript"
JAVASCRIPT_MINIFY_HELP: "Bihanaat ar JS e-pad an arrevellañ"
ENABLED_TIMESTAMPS_ON_ASSETS: "Gweredekaat ar boneg-amzer war al loazioù"
ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Gweredekaat bonegoù-amzer al loazioù"
COLLECTIONS: "Dastumadegoù"
ERROR_HANDLER: "Dornataour fazioù"
DISPLAY_ERRORS: "Skrammañ ar fazioù"
DISPLAY_ERRORS_HELP: "Skrammañ ur bajenn fazi gant munudoù"
LOG_ERRORS: "Kerzhlevr ar fazioù"
LOG_ERRORS_HELP: "Lakaat kerzhlevr ar fazioù en teuliad /logs"
DEBUGGER: "Diveuger"
DEBUGGER_HELP: "Gweredekaat diveuger Grav hag an arventennoù da heul"
DEBUG_TWIG: "Diveugañ Twig"
DEBUG_TWIG_HELP: "Gweredekaat diveugañ ar patromoù Twig"
SHUTDOWN_CLOSE_CONNECTION: "Shutdown a serr ar c'hennask"
SHUTDOWN_CLOSE_CONNECTION_HELP: "Serriñ ar c'hennask a-raok gervel onShutdown(). 'false' evit diveugañ"
DEFAULT_IMAGE_QUALITY: "Perzhded skeudenn dre ziouer"
DEFAULT_IMAGE_QUALITY_HELP: "Perzhded skeudenn dre ziouer da arverañ e-pad adstandilhonañ ar skeudennoù (85%)"
CACHE_ALL: "Lakaat an holl skeudennoù er c'hrubuilh"
CACHE_ALL_HELP: "Lakaat an holl skeudennoù da dremen dre reizhiad krubuilh Grav zoken ma n'o deus dornatadur media ebet"
IMAGES_DEBUG: "Rouedigell diveugañ ar skeudenn"
IMAGES_DEBUG_HELP: "Diskouez un diflugell a-us d'ar skeudennoù a ziskouez an donder piksel pa labourer war Retina da skouer"
UPLOAD_LIMIT: "Bevenn ment ar restroù da bellgas"
UPLOAD_LIMIT_HELP: "Lakaat ar ment restroù uhelañ e eizhbitoù (0 a zo anvevenn)"
ENABLE_MEDIA_TIMESTAMP: "Gweredekaat ar boneg-amzer war ar media"
ENABLE_MEDIA_TIMESTAMP_HELP: "Ouzhpennañ ur boneg-amzer diazezet war an deiziad kemmadur evit pep elfenn media"
SESSION: "Estez"
SESSION_ENABLED_HELP: "Gweredekaat skor an estez evit Grav"
SESSION_NAME_HELP: "Un naoudi arveret da stummañ anv toupin an estez"
ABSOLUTE_URLS: "URL dizave"
ABSOLUTE_URLS_HELP: "URLoù dizave pe daveel evit 'base_url'"
PARAMETER_SEPARATOR: "Disranner arventenn"
PARAMETER_SEPARATOR_HELP: "An disranner evit an arventennoù tremenet a c'hall bezañ kemmet evit Apache war Windows"
TASK_COMPLETED: "Trevell echuet"
EVERYTHING_UP_TO_DATE: "Pep tra a zo hizivaet"
UPDATES_ARE_AVAILABLE: "hizivadennoù hegerz"
IS_AVAILABLE_FOR_UPDATE: "a zo gant un hizivadenn hegerz"
IS_NOW_AVAILABLE: "a zo hegerz"
CURRENT: "Bremanel"
UPDATE_GRAV_NOW: "Hizivaat Grav bremañ"
GRAV_SYMBOLICALLY_LINKED: "Gant un ere arouezel eo staliet Grav. Dihegerz eo an hizivadenn"
UPDATING_PLEASE_WAIT: "Oc'h hizivaat... gortozit, emañ o pellgargañ"
OF_THIS: "eus an"
OF_YOUR: "eus ho"
HAVE_AN_UPDATE_AVAILABLE: "en deus un hizivadenn hegerz"
SAVE_AS: "Enrollañ evel"
MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Sur oc'h e fell deoc'h dilemel ar bajenn-mañ hag holl he bugale? M'eo troet ar bajenn en ur yezh all e vo miret an troidigezhioù a rankout a reot o dilemel en un doare distag. E mod all e vo dilamet teuliad ar bajenn gant an is-pajennoù. N'haller ket dizober ar gwered-mañ."
AND: "ha"
UPDATE_AVAILABLE: "Hizivadenn hegerz"
METADATA_KEY: "Alc'hwez (sk. 'Gerioù-alc'hwez')"
METADATA_VALUE: "Gwerzh (sk. 'Blog, Grav')"
USERNAME_HELP: "Etre 3 ha 16 arouezenn e rank an anv arveriad bezañ o kontañ al lizherennoù bihan, an niverennoù, an islinennoù hag ar barrennigoù. N'eo ket aotreet al lizherennoù bras, an esaouennoù hag an arouezennoù arbennik"
FULLY_UPDATED: "Hizivaet"
SAVE_LOCATION: "Lec'hiadur enrollañ"
PAGE_FILE: "Patrom pajenn"
PAGE_FILE_HELP: "Anv restr patrom ar bajenn, ha patrom skrammañ ar bajenn dre ziouer"
NO_USER_ACCOUNTS: "Kont arveriad ebet kavet, krouit unan da gentañ..."
REDIRECT_TRAILING_SLASH: "Adheñchañ ar veskell dibenn"
REDIRECT_TRAILING_SLASH_HELP: "Ober un adheñchañ 301 e-lerc'h merañ an beskell dibenn an URI en un doare treuzwelus."
DEFAULT_DATE_FORMAT: "Mentrezh deiziad ar bajenn"
DEFAULT_DATE_FORMAT_HELP: "Mentrezh deiziad ar bajenn arveret gant Grav. Dre ziouer, Grav a glask divinout mentrezh an deiziad met gallout a rit erspizañ unan gant kevreadur deiziad PHP (sk.: Y-m-d H:i)"
DEFAULT_DATE_FORMAT_PLACEHOLDER: "Divinout en emgefreek"
IGNORE_FILES: "Leuskel restroù a-gostez"
IGNORE_FILES_HELP: "Restroù da leuskel a-gostez e-pad keweriañ ar pajennoù"
IGNORE_FOLDERS: "Leuskel teuliadoù a-gostez"
IGNORE_FOLDERS_HELP: "Teuliadoù resis da leuskel a-gostez e-pad keweriañ ar pajennoù"
HTTP_ACCEPT_LANGUAGE: "Lakaat yezh ar merdeer"
HTTP_ACCEPT_LANGUAGE_HELP: "Gallout a rit klask arventennañ ar yezh gant hini ar talbenn `http_accept_language` ar merdeer"
OVERRIDE_LOCALE: "Flastrañ ar yezh"
OVERRIDE_LOCALE_HELP: "Flastrañ arventenn yezh PHP diazezet war ar yezh vremanel"
REDIRECT: "Adheñchañ ar bajenn"
REDIRECT_HELP: "Enankit hent ur bajenn pe un URL diavaez da adheñchañ ar bajenn. Sk. '/un/hent' pe 'http://ulload.bzh'"
PLUGIN_STATUS: "Stad an elugellad"
INCLUDE_DEFAULT_LANG: "Enkorfañ ar yezh dre ziouer"
INCLUDE_DEFAULT_LANG_HELP: "Ouzhpennañ a raio ar yezh dre ziouer en holl URLoù er yezh dre ziouer. Sk. '/br/blog/post'"
ALLOW_URL_TAXONOMY_FILTERS: "URL siloù rummad"
ALLOW_URL_TAXONOMY_FILTERS_HELP: "Dastumadegoù pajennoù a aotren ac'hanoc'h da silañ dre '/rummad:gwerzh'."
REDIRECT_DEFAULT_CODE: "Boneg adheñchan dre ziouer"
REDIRECT_DEFAULT_CODE_HELP: "Boneg stad HTTP da arverañ evit adheñchañ"
IGNORE_HIDDEN: "Leuskel ar re kuzhet a-gostez"
IGNORE_HIDDEN_HELP: "Leuskel an holl restroù ha teuliadoù a grog gant ur POENT"
WRAPPED_SITE: "Lec'hienn enkorfet"
WRAPPED_SITE_HELP: "Evit ma ouife an neuzioù/enlugelladoù m'eo enkorfet Grav en ur savenn all"
FALLBACK_TYPES: "Aotren doareoù fallback"
FALLBACK_TYPES_HELP: "Doareoù restr aotreet a c'hall bezañ kavet m'int haezet dre hent ar bajenn. An holl zoareoù media skoret dre ziouer."
INLINE_TYPES: "Doareoù fallback enkorfet"
INLINE_TYPES_HELP: "Ur roll doareoù restroù a rank bezañ skrammet en un doare enkorfet kentoc'h eget pellgarget"
APPEND_URL_EXT: "Ouzhpennañ an astenn d'an URL"
APPEND_URL_EXT_HELP: "Ouzhpennañ a raio un astenn personelaet da URL ar bajenn. Talvezout a ra e glasko Grav ur patrom anvet `<patrom>.<astenn>.twig`"
PAGE_MODES: "Modoù pajenn"
PAGE_TYPES: "Doareoù pajenn"
ACCESS_LEVELS: "Liveoù haeziñ"
GROUPS: "Strolladoù"
GROUPS_HELP: "Roll ar strolladoù gant an arveriad enno"
ADMIN_ACCESS: "Haeziñ ardoer"
SITE_ACCESS: "Haeziñ d'al lec'hienn"
INVALID_SECURITY_TOKEN: "Reveziadenn diogelroez didalvoudek"
ACTIVATE: "Gweredekaat"
TWIG_UMASK_FIX: "Ratreadur Umask"
TWIG_UMASK_FIX_HELP: "Twig a grou ar restroù krubuilh gant 0755 dre ziouer, ar ratreañ a lak anezho da 0755"
CACHE_PERMS: "Aotreoù ar c'hrubuilh"
CACHE_PERMS_HELP: "Aotreoù dre ziouer teuliad ar c'hrubuilh. 0755 pe 0775 peurvuiañ, hervez ar c'hefluniadur"
REMOVE_SUCCESSFUL: "Dilamet gant berzh"
REMOVE_FAILED: "C'hwitadenn war an dilemel"
HIDE_HOME_IN_URLS: "Kuzhat hent ar pennbajenn en URL"
HIDE_HOME_IN_URLS_HELP: "Gwiriekaat a raio n'eo ket daveet hent skoueriek an degemer gant hentoù dre ziouer ar pajennoù dindan an degemer"
TWIG_FIRST: "Keweriañ an Twig da gentañ"
TWIG_FIRST_HELP: "M'ho peus gweredekaat keweriañ ar bajenn Twig e c'hallit kefluniañ Twig evit e geweriañ a-raok pe goude ar Markdown"
SESSION_SECURE: "Diogel"
SESSION_SECURE_HELP: "M'eo gwir, diskouez a ra eo ret d'ar c'hehentiñ evit an toupin-mañ bezañ graet war un treuzkas diogel. DIWALLIT: Gweredekait an dra-se war lec'hiennoù e HTTPS nemetken"
SESSION_HTTPONLY: "HTTP nemetken"
SESSION_HTTPONLY_HELP: "M'eo gwir, diskouez a ra eo ret d'ar c'hehentiñ evit an toupin-mañ bezañ graet war un treuzkas HTTP ha n'eo ket aotreet kemmañ ar Javascript"
REVERSE_PROXY: "Proksi en tu-gin"
REVERSE_PROXY_HELP: "Gweredekait an dra-se m'hoc'h a-dreñv ur proksi en tu-gin hag ho peus diaesterioù gant an URLoù oc'h enderc'hel ur porzh didalvoudek"
INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmatter didalvoudek, n'haller ket enrollan"
ADD_FOLDER: "Ouzhpennañ un teuliad"
PROXY_URL: "URL ar proksi"
PROXY_URL_HELP: "Enankit HERBERC'HIER pe IP ar proksi hag ar PORZH"
NOTHING_TO_SAVE: "Netra da enrollañ"
FILE_ERROR_ADD: "Degouezhet ez eus bet ur fazi en ur glask enrollañ ar restr"
FILE_ERROR_UPLOAD: "Degouezhet ez eus bet ur fazi en ur glask pellgas ar restr"
FILE_UNSUPPORTED: "Doare restr anskor"
ADD_ITEM: "Ouzhpennañ un elfenn"
FILE_TOO_LARGE: "Re leden eo ar restr evit bezañ pellgaset. %s eo an uhelañ aotreet hervez <br> hoc'h arventennoù PHP. Kreskit an arventenn PHP`post_max_size`"
INSTALLING: "O staliañ"
LOADING: "O kargañ.."
DEPENDENCIES_NOT_MET_MESSAGE: "Ret eo deoc'h staliañ an amzalc'hoù da-heul a-raok:"
ERROR_INSTALLING_PACKAGES: "Fazi en ur staliañ ar pakad(où)"
INSTALLING_DEPENDENCIES: "O staliañ an amzalc'hoù..."
INSTALLING_PACKAGES: "O staliañ ar pakad(où).."
PACKAGES_SUCCESSFULLY_INSTALLED: "Pakad(où) staliet gant berzh."
READY_TO_INSTALL_PACKAGES: "Prest da staliañ ar pakad(où)"
PACKAGES_NOT_INSTALLED: "N'eo ket stalied ar pakadoù"
PACKAGES_NEED_UPDATE: "Staliet eo ar pakadoù endeo, met re gozh eo"
PACKAGES_SUGGESTED_UPDATE: "Staliet eo ar pakadoù endeo, dereat eo an handelv, met hizivaet e vint evit ma vefec'h en handelv diwezhañ"
REMOVE_THE: "Dilemel an %s"
CONFIRM_REMOVAL: "Sur oc'h e fell deoc'h dilemel %s?"
REMOVED_SUCCESSFULLY: "%s dilamet gant berzh"
ERROR_REMOVING_THE: "Fazi en ur zilemel %s"
ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "An amzalc'hoù da heul a zo azgoulennet gant %s, met n'eo ket azgoulennet gant ur pakad all. Ma ne arverit ket anezho e c'hallit o dilemel adalek amañ."
READY_TO_UPDATE_PACKAGES: "Prest da hizivaat ar pakad(où)"
ERROR_UPDATING_PACKAGES: "Fazi en ur hizivaat ar pakad(où)"
UPDATING_PACKAGES: "Oc'h hizivaat ar pakad(où).."
PACKAGES_SUCCESSFULLY_UPDATED: "Pakad(où) hizivaet gant berzh."
UPDATING: "Hizivaet"
GPM_RELEASES: "Ermaeziadennoù GPM"
GPM_RELEASES_HELP: "Dibabit 'Amprouiñ' evit staliañ an handelv beta pe amprouiñ"
AUTO: "Oto"
FOPEN: "fopen"
CURL: "cURL"
STABLE: "Stabil"
TESTING: "Amprouiñ"
FRONTMATTER_PROCESS_TWIG: "Keweriañ frontmatter Twig"
FRONTMATTER_PROCESS_TWIG_HELP: "P'eo oberiant e c'hallit arverañ argemennoù kefluniañ Twig e frontmatter ar bajenn"
FRONTMATTER_IGNORE_FIELDS: "Leuskel maeziennoù Frontmatter a-gostez"
FRONTMATTER_IGNORE_FIELDS_HELP: "Maeziennoù Frontmatter a c'hall enderc'hel Twig met ne rankont ket bezañ keweriet, evel 'forms'"
PACKAGE_X_INSTALLED_SUCCESSFULLY: "Pakad %s staliet gant berzh"
ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Urzh ar c'har, diweredekaet eo an urzhiañ"
ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Diwelus eo ar bajenn, diweredekaet eo an urzhiañ"
ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "N'eo ket skoret an urzhiañ dre an ardeiñ dre ma zo ouzhpenn 200 c'hoar"
CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "EVEZHIADENN: n'hallit ket ouzhpennañ restroù media evit enrollañ ar bajenn. Klikit war 'Enrollañ' a-us"
CANNOT_ADD_FILES_PAGE_NOT_SAVED: "EVEZHIADENN: ret eo enrollañ ar bajenn a-raok pellgas restroù dezhi."
DROP_FILES_HERE_TO_UPLOAD: "Lakait ho restroù amañ pe <strong>klikit amañ</strong>"
INSERT: "Enlakaat"
UNDO: "Dizober"
REDO: "Adober"
HEADERS: "Talbennoù"
BOLD: "Tev"
ITALIC: "Stouet"
STRIKETHROUGH: "Barrennet"
SUMMARY_DELIMITER: "Bonner diverrad"
LINK: "Liamm"
IMAGE: "Skeudenn"
BLOCKQUOTE: "Meneg"
UNORDERED_LIST: "Roll dizurzh"
ORDERED_LIST: "Roll urzhiet"
EDITOR: "Embanner"
PREVIEW: "Alberz"
FULLSCREEN: "Skramm a-bezh"
NON_ROUTABLE: "Dihentus"
NON_VISIBLE: "Diwelus"
NON_PUBLISHED: "Diembannet"
CHARACTERS: "arouezioù"
PUBLISHING: "Embann"
MEDIA_TYPES: "Doareoù media"
IMAGE_OPTIONS: "Opsionoù skeudenn"
MIME_TYPE: "Doare Mime"
THUMB: "Miniaturenn"
TYPE: "Doare"
FILE_EXTENSION: "Astenn ar fichenn"
LEGEND: "Alc'hwez ar bajenn"
MEMCACHE_SERVER: "Servijer Memcached"
MEMCACHE_SERVER_HELP: "Chomlec'h ar servijer Memcached"
MEMCACHE_PORT: "Porzh Memcached"
MEMCACHE_PORT_HELP: "Porzh ar servijer Memcached"
MEMCACHED_SERVER: "Servijer Memcached"
MEMCACHED_SERVER_HELP: "Chomlec'h ar servijer Memcached"
MEMCACHED_PORT: "Porzh Memcached"
MEMCACHED_PORT_HELP: "Porzh ar servijer Memcached"
REDIS_SERVER: "Servijer Redis"
REDIS_SERVER_HELP: "Chomlec'h ar servijer Redis"
REDIS_PORT: "Porzh Redis"
REDIS_PORT_HELP: "Porzh ar servijer Redis"
ALL: "Tout"
FROM: "eus"
TO: "da"
RESOURCE_FILTER: "Sil..."
FORCE_SSL: "Forsañ SSL"
DROPZONE_CANCEL_UPLOAD: 'Nullañ ar gargamant'
DROPZONE_REMOVE_FILE: "Dilemel ar fichenn"
PREMIUM_PRODUCT: "Premium"
ERROR_SIMPLE: "Fazi simpl"
ERROR_SYSTEM: "Fazi Sistem"
NOT_SET: "Pas termenet"
PERMISSIONS: "Permisionoù"
REINSTALL_PLUGIN: "Adstaliañ ar Plugin"
REINSTALL_THEME: "Adstaliañ an Tem"
REINSTALL_THE: "Adstaliañ ar %s"
REINSTALLATION_FAILED: "Adstaliañ c'hwitet"
TOOLS: "Ostilhoù"
DIRECT_INSTALL: "Staliañ war-eeun"
2FA_CODE_INPUT: "000000"
CONFIGURATION: "Kefluniadur"
TIMEOUT: "Diamzeriñ"
TIMEOUT_HELP: "Lakaat an amzer diamzeriñ e eilennoù"
DASHBOARD: "Taolenn labour"
NOTIFICATIONS: "Notifiadenn"

View File

@ -0,0 +1,643 @@
---
PLUGIN_ADMIN:
ADMIN_BETA_MSG: "Aquesta és una versió beta! Utilitza-la en producció sota el teu propi risc..."
ADMIN_REPORT_ISSUE: "Has trobat algun problema? Sisplau, reporta'l a GitHub."
EMAIL_FOOTER: "<a href=\"https://getgrav.org\">Funcionant amb Grav</a> - El CMS de fitxers plans modern"
LOGIN_BTN: "Inicia sessió"
LOGIN_BTN_FORGOT: "Ho he oblidat"
LOGIN_BTN_RESET: "Restablir contrasenya"
LOGIN_BTN_SEND_INSTRUCTIONS: "Envia instruccions pel reset"
LOGIN_BTN_CLEAR: "Netejar formulari"
LOGIN_BTN_CREATE_USER: "Crear usuari"
LOGIN_LOGGED_IN: "S'ha iniciat sessió correctament"
LOGIN_FAILED: "No s'ha pogut iniciar sessió"
LOGGED_OUT: "S'ha tancat la sessió"
RESET_NEW_PASSWORD: "Sisplau introdueix una nova contasenya &hellip;"
RESET_LINK_EXPIRED: "L'enllaç per a restablir contrasenya ha expirat, torna a provar"
RESET_PASSWORD_RESET: "S'ha restablert la contrasenya"
RESET_INVALID_LINK: "L'enllaç per a restablir contrasenya invàl·lid, torna a provar"
FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Les instruccions per a restablir la contrasenya s'han enviat per correu electrònic a %s"
FORGOT_FAILED_TO_EMAIL: "S'ha fallat al enviar les instruccions, sisplau torna-ho a provar"
FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "No es pot restablir la contrasenya per a %s, no té cap email assignat"
FORGOT_USERNAME_DOES_NOT_EXIST: "L'usuari <b>%s</b> no existeix"
FORGOT_EMAIL_NOT_CONFIGURED: "No es pot restablir la contrasenya. Aquest lloc no està configurat per enviar missatges de correu electrònic"
FORGOT_EMAIL_SUBJECT: "%s Petició de restabliment de contrasenya"
FORGOT_EMAIL_BODY: "<h1>Restabliment de contrasenya</h1><p>Benvolgut/da %1$s,</p><p>S'ha fet una petició a <b>%4$s</b> per a restablir la contrasenya.</p><p><br /><a href=\"%2$s\" class=\"btn-primary\">Fes clic aquí per a restablir la contrasenya</a><br /><br /></p><p>Altrament, copia el següent URL al teu navegador:</p><p>%2$s</p><p><br />Atentament,<br /><br />%3$s</p>"
MANAGE_PAGES: "Gestiona pàgines"
PAGES: "Pàgines"
PLUGINS: "Plugins"
PLUGIN: "Plugin"
THEMES: "Temes"
LOGOUT: "Tanca sessió"
BACK: "Enrere"
NEXT: "Següent"
PREVIOUS: "Anterior"
ADD_PAGE: "Afegeix pàgina"
MOVE: "Mou"
DELETE: "Esborra"
VIEW: "Visualitzar"
SAVE: "Desa"
NORMAL: "Normal"
EXPERT: "Expert"
EXPAND_ALL: "Expandeix tot"
COLLAPSE_ALL: "Col·lapsa tot"
ERROR: "Error"
CLOSE: "Tanca"
CANCEL: "Cancel·la"
CONTINUE: "Continua"
MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Confirmació requerida"
MODAL_CHANGED_DETECTED_TITLE: "Canvis detectats"
MODAL_CHANGED_DETECTED_DESC: "Tens canvis no desats. Estàs segur que vols sortir sense desar?"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Confirmació requerida"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Estàs segur que vols eliminar aquest fitxer? Aquesta acció no es pot desfer."
ADD_FILTERS: "Afegeix filtres"
SEARCH_PAGES: "Cerca pàgines"
VERSION: "Versió"
WAS_MADE_WITH: "Es va fer amb"
BY: "Per"
UPDATE_THEME: "Actualitza tema"
UPDATE_PLUGIN: "Actualitza plugin"
OF_THIS_THEME_IS_NOW_AVAILABLE: "d'aquest tema està disponible"
OF_THIS_PLUGIN_IS_NOW_AVAILABLE: "d'aquest plugin està disponible"
AUTHOR: "Autor/a"
HOMEPAGE: "Pàgina d'inici"
DEMO: "Demo"
BUG_TRACKER: "Rastrejador d'errors"
KEYWORDS: "Paraules clau"
LICENSE: "Llicència"
DESCRIPTION: "Descripció"
README: "Llegiu-me"
REMOVE_THEME: "Elimina tema"
INSTALL_THEME: "Instal·la tema"
THEME: "Tema"
BACK_TO_THEMES: "Torna a Temes"
BACK_TO_PLUGINS: "Torna a Plugins"
CHECK_FOR_UPDATES: "Cerca actualitzacions"
ADD: "Afegeix"
CLEAR_CACHE: "Neteja la memòria cache"
CLEAR_CACHE_ALL_CACHE: "Tota la cache"
CLEAR_CACHE_ASSETS_ONLY: "Només assets"
CLEAR_CACHE_IMAGES_ONLY: "Només imatges"
CLEAR_CACHE_CACHE_ONLY: "Només cache"
CLEAR_CACHE_TMP_ONLY: "Només tmp"
UPDATES_AVAILABLE: "Hi ha actualizacions disponibles"
DAYS: "Dies"
UPDATE: "Actualitza"
BACKUP: "Còpia de seguretat"
BACKUPS: "Còpia de seguretat"
BACKUP_NOW: "Fer còpia de seguretat ara"
BACKUPS_STATS: "Estadístiques de còpia de seguretat"
BACKUPS_HISTORY: "Historial de còpies de seguretat"
BACKUPS_PURGE_CONFIG: "Configuració de depuració de còpia de seguretat"
BACKUPS_PROFILES: "Perfils de còpia de seguretat"
BACKUPS_COUNT: "Nombre de còpies de seguretat"
BACKUPS_PROFILES_COUNT: "Nombre de perfils"
BACKUPS_TOTAL_SIZE: "Espai utilitzat"
STATISTICS: "Estadístiques"
TODAY: "Avui"
WEEK: "Setmana"
MONTH: "Mes"
LATEST_PAGE_UPDATES: "Últimes pàgines actualitzades"
MAINTENANCE: "Manteniment"
UPDATED: "Actualitzat"
MON: "Dl."
TUE: "Dt."
WED: "Dc."
THU: "Dj."
FRI: "Dv."
SAT: "Ds."
SUN: "Dg."
COPY: "Copia"
EDIT: "Edita"
CREATE: "Crea"
GRAV_ADMIN: "Administració Grav"
GRAV_OFFICIAL_PLUGIN: "Plugin oficial de Grav"
GRAV_OFFICIAL_THEME: "Tema oficial de Grav"
PLUGIN_SYMBOLICALLY_LINKED: "Aquest plugin està lligat simbòlicament. Les actualitzacions no seran detectades."
THEME_SYMBOLICALLY_LINKED: "Aquest tema està lligat simbòlicament. Les actualitzacions no seran detectades"
REMOVE_PLUGIN: "Elimina plugin"
INSTALL_PLUGIN: "Instal·la plugin"
AVAILABLE: "Disponible"
INSTALLED: "Instal·lat"
INSTALL: "Instal·la"
ACTIVE_THEME: "Tema actiu"
SWITCHING_TO: "Canviant a"
SWITCHING_TO_DESCRIPTION: "Al canviar a un tema diferent, no es garanteix que tots els estils de pàgina siguin compatibles, potencialment, pot haver-hi errors al intentar carregar aquestes pàgines."
SWITCHING_TO_CONFIRMATION: "Vols continuar i canviar de tema"
CREATE_NEW_USER: "Crea nou usuari"
REMOVE_USER: "Elimina usuari"
ACCESS_DENIED: "Accés denegat"
ACCOUNT_NOT_ADMIN: "el teu compte no té permisos d'administrador"
PHP_INFO: "Informació PHP"
INSTALLER: "Instal·lador"
AVAILABLE_THEMES: "Temes disponibles"
AVAILABLE_PLUGINS: "Plugins disponibles"
INSTALLED_THEMES: "Temes instal·lats"
INSTALLED_PLUGINS: "Plugins instal·lats"
BROWSE_ERROR_LOGS: "Examina registres d'errors"
SITE: "Lloc web"
INFO: "Info"
SYSTEM: "Sistema"
USER: "Usuari"
ADD_ACCOUNT: "Afegeix compte"
SWITCH_LANGUAGE: "Canvia l'idioma"
SUCCESSFULLY_ENABLED_PLUGIN: "El plugin s'ha activat correctament"
SUCCESSFULLY_DISABLED_PLUGIN: "El plugin s'ha desactivat correctament"
SUCCESSFULLY_CHANGED_THEME: "S'ha canviat el tema per defecte correctament"
INSTALLATION_FAILED: "La instal·lació ha fallat"
INSTALLATION_SUCCESSFUL: "Instal·lació satisfactòria"
UNINSTALL_FAILED: "Desinstal·lació fallida"
UNINSTALL_SUCCESSFUL: "Desinstal·lació satisfactòria"
SUCCESSFULLY_SAVED: "Desat satisfactòriament"
SUCCESSFULLY_COPIED: "Copiat satisfactòriament"
REORDERING_WAS_SUCCESSFUL: "Reordenació satisfactòria"
SUCCESSFULLY_DELETED: "Eliminat satisfactòriament"
SUCCESSFULLY_SWITCHED_LANGUAGE: "Idioma canviat satisfactòriament"
INSUFFICIENT_PERMISSIONS_FOR_TASK: "No tens permisos suficients per a la tasca"
CACHE_CLEARED: "Memòria cache esborrada"
METHOD: "Mètode"
ERROR_CLEARING_CACHE: "Error al esborrar cache"
AN_ERROR_OCCURRED: "S'ha produït un error"
YOUR_BACKUP_IS_READY_FOR_DOWNLOAD: "La teva còpia de seguretat està llesta per a descarregar"
DOWNLOAD_BACKUP: "Descarrega còpia de seguretat"
PAGES_FILTERED: "Pàgines filtrades"
NO_PAGE_FOUND: "No s'han trobat pàgines"
INVALID_PARAMETERS: "Els paràmetres són invàlids"
NO_FILES_SENT: "No s'han enviat fitxers"
EXCEEDED_FILESIZE_LIMIT: "S'ha excedit el límit de tamany de fitxer de la configuració de PHP"
UNKNOWN_ERRORS: "Hi ha hagut errors desconeguts"
EXCEEDED_GRAV_FILESIZE_LIMIT: "S'ha excedit el límit de tamany de fitxer de la configuració de Grav"
UNSUPPORTED_FILE_TYPE: "Tipus de fitxer no suportat"
FAILED_TO_MOVE_UPLOADED_FILE: "S'ha fallat al moure el fitxer carregat."
FILE_UPLOADED_SUCCESSFULLY: "S'ha carregat el fitxer amb èxit"
FILE_DELETED: "S'ha esborrat el fitxer"
FILE_COULD_NOT_BE_DELETED: "No s'ha pogut esborrar el fitxer"
FILE_NOT_FOUND: "No s'ha trobat el fitxer"
NO_FILE_FOUND: "No s'han trobat fitxers"
GRAV_WAS_SUCCESSFULLY_UPDATED_TO: "Grav ha estat actualitzat amb èxit a"
GRAV_UPDATE_FAILED: "Ha fallat l'actualització de Grav"
EVERYTHING_UPDATED: "Tot està actualitzat"
UPDATES_FAILED: "Han fallat les actualitzacions"
AVATAR_BY: "Avatar per"
LAST_BACKUP: "Última còpia de seguretat"
FULL_NAME: "Nom complet"
USERNAME: "Nom d'usuari"
EMAIL: "Email"
USERNAME_EMAIL: "Nom d'usuari o correu electrònic"
PASSWORD: "Contrasenya"
PASSWORD_CONFIRM: "Confirma contrasenya"
TITLE: "Títol"
LANGUAGE: "Llengua"
ACCOUNT: "Compte d'usuari"
EMAIL_VALIDATION_MESSAGE: "Ha de ser una adreça de correu electrònic vàl·lida"
PASSWORD_VALIDATION_MESSAGE: "La contrasenya ha de contenir almenys un número i una lletra majúscula i minúscula, i almenys 8 o més caràcters"
LANGUAGE_HELP: "Estableix la llengua preferida"
MEDIA: "Mèdia"
DEFAULTS: "Per defecte"
SITE_TITLE: "Títol del lloc"
SITE_TITLE_PLACEHOLDER: "Títol a tot el lloc"
SITE_TITLE_HELP: "Títol per defecte pel teu lloc, sovint utilitzat en els temes"
SITE_DEFAULT_LANG: "Llenguatge per defecte"
SITE_DEFAULT_LANG_PLACEHOLDER: "Llenguatge per defecte per a ser utilitzat per l'etiqueta del tema <HTML>"
SITE_DEFAULT_LANG_HELP: "Llenguatge per defecte per a ser utilitzat per l'etiqueta del tema <HTML>"
DEFAULT_AUTHOR: "Autor/a per defecte"
DEFAULT_AUTHOR_HELP: "Un nom d'autor/a per defecte, algunes vegades utilitzat en temes o contingut de les pàgines"
DEFAULT_EMAIL: "Email per defecte"
DEFAULT_EMAIL_HELP: "Un correu electrònic per referenciar en temes o pàgines"
TAXONOMY_TYPES: "Tipus de taxonomia"
TAXONOMY_TYPES_HELP: "Els tipus de taxonomia han de definir-se aquí si es desitja utilitzar-les en pàgines"
PAGE_SUMMARY: "Resum de pàgina"
ENABLED: "Habilitat"
ENABLED_HELP: "Habilita resum de pàgina (el resum retorna el mateix que el contingut de la pàgina)"
'YES': "Sí"
'NO': "No"
SUMMARY_SIZE: "Mida del resum"
SUMMARY_SIZE_HELP: "La quantitat de caràcters d'una pàgina a utilitzar com a resum del contingut"
FORMAT: "Format"
FORMAT_HELP: "curt = utilitza la primera ocurrència del delimitador o mida; llarg = s'ignorarà el delimitador del resum"
SHORT: "Curt"
LONG: "Llarg"
DELIMITER: "Delimitador"
DELIMITER_HELP: "El delimitador de resum (per defecte '===')"
METADATA: "Metadades"
METADATA_HELP: "Valors de metadades per defecte que es mostraran a cada pàgina excepte si es sobreescriuen a la pàgina"
NAME: "Nom"
CONTENT: "Contingut"
REDIRECTS_AND_ROUTES: "Redireccions i rutes"
CUSTOM_REDIRECTS: "Redireccions personalitzades"
CUSTOM_REDIRECTS_HELP: "rutes per redirigir a altres pàgines. Substitució de Regex estàndard és vàl·lida"
CUSTOM_REDIRECTS_PLACEHOLDER_KEY: "/el/teu/àlies"
CUSTOM_REDIRECTS_PLACEHOLDER_VALUE: "/la/teva/redirecció"
CUSTOM_ROUTES: "Rutes personalitzades"
CUSTOM_ROUTES_HELP: "rutes a àlies a altres pàgines. Substitució de Regex estàndard és vàl·lida"
CUSTOM_ROUTES_PLACEHOLDER_KEY: "/el/teu/àlies"
CUSTOM_ROUTES_PLACEHOLDER_VALUE: "/la/teva/ruta"
FILE_STREAMS: "Streams de fitxers"
DEFAULT: "Per defecte"
PAGE_MEDIA: "Contingut multimèdia de la pàgina"
OPTIONS: "Opcions"
PUBLISHED: "Publicat"
PUBLISHED_HELP: "Per defect, una pàgina es publica excepte que estableixis explícitament 'published': false o posant una publish_date al futur o una unpublish_date al passat"
DATE: "Data"
DATE_HELP: "La variable data permet definir específicament una data associada a aquesta pàgina."
PUBLISHED_DATE: "Data de publicació"
PUBLISHED_DATE_HELP: "Pots proporcionar una data per provocar automàticament la publicació."
UNPUBLISHED_DATE: "Data de despublicació"
UNPUBLISHED_DATE_HELP: "Pots proporcionar una data per provocar automàticament la despublicació."
ROBOTS: "Robots"
TAXONOMIES: "Taxonomies"
TAXONOMY: "Taxonomia"
ADVANCED: "Avançat"
SETTINGS: "Configuració"
FOLDER_NUMERIC_PREFIX: "Prefix numèric carpeta"
FOLDER_NUMERIC_PREFIX_HELP: "Prefix numèric que proporciona ordenació manual i implica la visibilitat"
FOLDER_NAME: "Nom de la carpeta"
FOLDER_NAME_HELP: "El nom de carpeta que s'emmagatzemarà al sistema de fitxers per a aquesta pàgina"
PARENT: "Pare"
DEFAULT_OPTION_ROOT: "-Root-"
DEFAULT_OPTION_SELECT: "- selecciona -"
DISPLAY_TEMPLATE: "Plantilla a mostrar"
DISPLAY_TEMPLATE_HELP: "El tipus de pàgina que es tradueix a la plantilla de Twig en què es renderitza la pàgina"
ORDERING: "Ordenació"
PAGE_ORDER: "Ordre de pàgines"
OVERRIDES: "Sobreescriu"
MENU: "Menú"
MENU_HELP: "El text a utilitzar en un menú. Si no s'estableix, s'utilitzarà el Títol."
SLUG: "Slug"
SLUG_HELP: "La variable slug permet definir específicament la part de l'URL que correspon a la pàgina"
SLUG_VALIDATE_MESSAGE: "L'slug ha de contenir només caràcters alfanumèrics en minúscules i guions"
PROCESS: "Processa"
PROCESS_HELP: "Control de com es processen les pàgines. Configurable per a pàgina enlloc de globalment"
DEFAULT_CHILD_TYPE: "Tipus de fill per defecte"
USE_GLOBAL: "Ús global"
ROUTABLE: "Accessible"
ROUTABLE_HELP: "Si aquesta pàgina és accessible des d'una URL"
CACHING: "Caching"
VISIBLE: "Visible"
VISIBLE_HELP: "Determina si una pàgina és visible en la navegació."
DISABLED: "Deshabilitat"
ITEMS: "Ítems"
ORDER_BY: "Ordena per"
ORDER: "Ordena"
FOLDER: "Carpeta"
ASCENDING: "Ascendent"
DESCENDING: "Descendent"
PAGE_TITLE: "Títol de pàgina"
PAGE_TITLE_HELP: "El títol de la pàgina"
PAGE: "Pàgina"
FRONTMATTER: "Frontmatter"
FILENAME: "Nom de fitxer"
PARENT_PAGE: "Pàgina pare"
HOME_PAGE: "Pàgina d'inici"
HOME_PAGE_HELP: "La pàgina que utilitzarà Grav com a destinació per defecte"
DEFAULT_THEME: "Tema per defecte"
DEFAULT_THEME_HELP: "Estableix el tema per defecte que utilitzarà Grav (per defecte és Antimatter)"
TIMEZONE: "Zona horària"
TIMEZONE_HELP: "Sobreescriu la zona horària del servidor"
SHORT_DATE_FORMAT: "Format de data curt"
SHORT_DATE_FORMAT_HELP: "Estableix el format de data curt que poden utilitzar els temes"
LONG_DATE_FORMAT: "Format de data llarg"
LONG_DATE_FORMAT_HELP: "Estableix el format de data llarg que poden utilitzar els temes"
DEFAULT_ORDERING: "Ordre per defecte"
DEFAULT_ORDERING_HELP: "Les pàgines de la llista seran generades utilitzant aquest ordre excepte que se sobreescrigui"
DEFAULT_ORDERING_DEFAULT: "Per defecte - basat en el nom de la carpeta"
DEFAULT_ORDERING_FOLDER: "Carpeta - basat en el nom de carpeta sense prefix"
DEFAULT_ORDERING_TITLE: "Títol - basat en el camp de títol de la capçalera"
DEFAULT_ORDERING_DATE: "Data - basat en el camp data de la capçalera"
DEFAULT_ORDER_DIRECTION: "Direcció d'ordre per defecte"
DEFAULT_ORDER_DIRECTION_HELP: "La direcció de les pàgines en una llista"
DEFAULT_PAGE_COUNT: "Compte de pàgina per defecte"
DEFAULT_PAGE_COUNT_HELP: "Nombre màxim de compte de pàgines en una llista"
DATE_BASED_PUBLISHING: "Publicació basada en la data"
DATE_BASED_PUBLISHING_HELP: "(Des)publicar automàticament posts basant-se en la seva data"
EVENTS: "Esdeveniments"
EVENTS_HELP: "Habilita o inhabilita esdeveniments concrets. Deshabilitant-los pot trencar plugins"
REDIRECT_DEFAULT_ROUTE: "Ruta de redirecció per defecte"
REDIRECT_DEFAULT_ROUTE_HELP: "Redirigir automàticament a la ruta per defecte d'una pàgina"
LANGUAGES: "Idiomes"
SUPPORTED: "Suportat"
SUPPORTED_HELP: "Llista separada per comes de codis d'idioma de 2 lletres (per exemple 'en,fr,de')"
TRANSLATIONS_FALLBACK: "Fallback de traduccions"
TRANSLATIONS_FALLBACK_HELP: "Fallback en traduccions suportades si l'idioma actiu no existeix"
ACTIVE_LANGUAGE_IN_SESSION: "Idioma actiu a la sessió"
ACTIVE_LANGUAGE_IN_SESSION_HELP: "Emmagatzema l'idioma actiu a la sessió"
HTTP_HEADERS: "Capçaleres HTTP"
EXPIRES: "Caduca"
EXPIRES_HELP: "Estableix la capçalera d'expiració. El valor és en segons."
LAST_MODIFIED: "Darrera modificació"
LAST_MODIFIED_HELP: "Estableix la capçalera de darrera modificació que pot optimitzar el proxy i la cache del navegador"
ETAG: "ETag"
ETAG_HELP: "Estableix la capçalera d'etag per ajudar a identificar quan una pàgina ha estat modificada"
VARY_ACCEPT_ENCODING: "Variar accept encoding"
VARY_ACCEPT_ENCODING_HELP: "Estableix la capçalera 'Vary: Accept Encoding' per ajudar amb el proxy i la cache CDN"
MARKDOWN_EXTRA_HELP: "Habilita suport per defecte per a Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
AUTO_LINE_BREAKS: "Salts de línia automàtics"
AUTO_LINE_BREAKS_HELP: "Habilita el suport per a salts de línia automàtics a Markdown"
AUTO_URL_LINKS: "Enllaços URL automàtics"
AUTO_URL_LINKS_HELP: "Habilita l'autoconversió d'URLs a hyperlinks HTML"
ESCAPE_MARKUP: "Escape markup"
ESCAPE_MARKUP_HELP: "Escape markup tags en entitats HTML"
CACHING_HELP: "Interruptor ON/OFF global per habilitar/deshabilitar cache de Grav"
CACHE_CHECK_METHOD: "Mètode de verificació de cache"
CACHE_CHECK_METHOD_HELP: "Seleccionar el mètode que utilitza Grav per comprovar si s'han modificat arxius de pàgina."
CACHE_DRIVER: "Contolador de cache"
CACHE_DRIVER_HELP: "Selecciona quin controlador de cache de Grav cal usar. 'Auto Detect' intenta trobar el millor per a tu"
CACHE_PREFIX: "Prefix de cache"
CACHE_PREFIX_HELP: "Un identificador per part de la clau de Grav. No la canviïs excepte que sàpigues el que estàs fent."
CACHE_PREFIX_PLACEHOLDER: "Derivat de l'URL base (sobreescriu introduint strings aleatòris)"
LIFETIME: "Cicle de vida"
LIFETIME_HELP: "Defineix el cicle de vida de la cache en segons. 0 = infinit"
GZIP_COMPRESSION: "Compressió gzip"
GZIP_COMPRESSION_HELP: "Habilita la compressió GZip de la pàgina de Grav per augmentar el rendiment."
TWIG_TEMPLATING: "Plantilles de Twig"
TWIG_CACHING: "Cache de Twig"
TWIG_CACHING_HELP: "Controla el mecanisme de cache de Twig. Deixa'l habilitat per a millor rendiment."
TWIG_DEBUG: "Depuració de Twig"
TWIG_DEBUG_HELP: "Permet l'opció de no carregar l'extensió de depuració de Twig"
DETECT_CHANGES: "Detectar canvis"
DETECT_CHANGES_HELP: "Twig recompilarà automàticament la cache de Twig si detecta canvis a les plantilles de Twig"
AUTOESCAPE_VARIABLES: "Variables d'autoescape"
AUTOESCAPE_VARIABLES_HELP: "Autoescapa totes les variables. Això probablement trencarà el teu lloc"
ASSETS: "Assets"
CSS_PIPELINE: "CSS pipeline"
CSS_PIPELINE_HELP: "El CSS pipeline és l'unificació de diversos recursos CSS en un sol fitxer"
CSS_PIPELINE_INCLUDE_EXTERNALS: "Inclou fitxers externs en el CSS pipeline"
CSS_PIPELINE_INCLUDE_EXTERNALS_HELP: "A vegades, algunes URLs externes tenen referències relatives de fitxer i no s'hi hauria de fer pipelining"
CSS_PIPELINE_BEFORE_EXCLUDES: "Processar pimer el CSS pipeline"
CSS_PIPELINE_BEFORE_EXCLUDES_HELP: "Intrepreta el CSS pipeline abans de qualsevol altra referència CSS que no estigui inclosa"
CSS_MINIFY: "Minifica CSS"
CSS_MINIFY_HELP: "Minifica el CSS durant el pipelining"
CSS_MINIFY_WINDOWS_OVERRIDE: "Sobreesciu la minificació de CSS a Windows"
CSS_MINIFY_WINDOWS_OVERRIDE_HELP: "Sobreescriu la minificació en plataformes Windows. Fals per defecte degut a ThreadStackSize"
CSS_REWRITE: "Reescriptura CSS"
CSS_REWRITE_HELP: "Reescriu qualsevol URL relativa de CSS durant el pipelining"
JAVASCRIPT_PIPELINE: "JavaScript pipeline"
JAVASCRIPT_PIPELINE_HELP: "El JS pipeline és l'unificació de diversos recursos JS en un sol fitxer"
JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS: "Inclou fitxers externs en el JS pipeline"
JAVASCRIPT_PIPELINE_INCLUDE_EXTERNALS_HELP: "A vegades, les URLs externes tenen referències d'arxiu i no s'hi hauria de fer pipelining"
JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES: "Interpreta primer el JS pipeline"
JAVASCRIPT_PIPELINE_BEFORE_EXCLUDES_HELP: "Interpreta el JS pipeline abans de qualsevol altra referència JS que no estigui inclosa"
JAVASCRIPT_MINIFY: "Minificació JavaScript"
JAVASCRIPT_MINIFY_HELP: "Minifica el JS durant el pipelining"
ENABLED_TIMESTAMPS_ON_ASSETS: "Habilita les marques de temps als assets"
ENABLED_TIMESTAMPS_ON_ASSETS_HELP: "Habilita les marques de temps a assets"
COLLECTIONS: "Col·leccions"
ERROR_HANDLER: "Controlador d'errors"
DISPLAY_ERRORS: "Mostra errors"
DISPLAY_ERRORS_HELP: "Mostra pàgina d'error full backstrace-style"
LOG_ERRORS: "Registre d'errors"
LOG_ERRORS_HELP: "Registre d'errors a la carpeta /logs"
DEBUGGER: "Depurador"
DEBUGGER_HELP: "Habilita depurador de Grav i les configuracions següents"
DEBUG_TWIG: "Depuració de Twig"
DEBUG_TWIG_HELP: "Habilita la depuració de plantilles de Twig"
SHUTDOWN_CLOSE_CONNECTION: "Al apagar tanca la connexió"
SHUTDOWN_CLOSE_CONNECTION_HELP: "Tanca la connexió abans de cridar onShutdown(). False per a la depuració"
DEFAULT_IMAGE_QUALITY: "Qualitat d'imatge per defecte"
DEFAULT_IMAGE_QUALITY_HELP: "Qualitat d'imatge per defecte per a ser utilitzada quan es remostri o es guardi a cache les imatges (85%)"
CACHE_ALL: "Guardar totes les imatges a cache"
CACHE_ALL_HELP: "Guarda totes les imatges al sistema cache de Grav fins i tot si no tenen cap manipulació de mèdia"
IMAGES_DEBUG: "Marca d'aigua de depuració"
IMAGES_DEBUG_HELP: "Mostra un overlay sobre les imatges indicant la profunditat de píxels quan es treballa amb retina, per exemple"
UPLOAD_LIMIT: "Límit de tamany de fitxer"
UPLOAD_LIMIT_HELP: "Defineix el tamany màxim de càrrega en bytes (0 = il·limitat)"
ENABLE_MEDIA_TIMESTAMP: "Permet timestamps en fitxers multimèdia"
ENABLE_MEDIA_TIMESTAMP_HELP: "Afegeix un timestamp basat en la data d'última modificació a cada element multimèdia"
SESSION: "Sessió"
SESSION_ENABLED_HELP: "Habilita suport de sessions a Grav"
SESSION_NAME_HELP: "Un identificador usat per formar el nom de la galeta de sessió"
ABSOLUTE_URLS: "URLs absolutes"
ABSOLUTE_URLS_HELP: "URLs absolutes o relatives per a 'base_url'"
PARAMETER_SEPARATOR: "Separador de paràmetres"
PARAMETER_SEPARATOR_HELP: "Separador per a paràmetres passats que es poden canviar d'Apache a Windows"
TASK_COMPLETED: "Tasca completada"
EVERYTHING_UP_TO_DATE: "Tot està actualitzat"
UPDATES_ARE_AVAILABLE: "hi ha actualitzacions disponibles"
IS_AVAILABLE_FOR_UPDATE: "està disponible per a l'actualització"
IS_NOW_AVAILABLE: "ja està disponible"
CURRENT: "Actual"
UPDATE_GRAV_NOW: "Actualitza Grav ara"
GRAV_SYMBOLICALLY_LINKED: "Grav està lligat simbòlicament. Les actualitzacions no estaran disponibles"
UPDATING_PLEASE_WAIT: "Actualitzant... descarregant, espera sisplau"
OF_THIS: "d'aquest/a"
OF_YOUR: "del teu"
HAVE_AN_UPDATE_AVAILABLE: "té disponible una actualització"
SAVE_AS: "Desa com a"
MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_DESC: "Esteu segur que voleu suprimir aquesta pàgina i tots els seus fills? Si la pàgina es tradueix en altres llengües, les traduccions es mantindran i han de ser esborrades per separat. En cas contrari la carpeta de la pàgina serà eliminada juntament amb les seves subpàgines. Aquesta acció no es pot desfer."
AND: "i"
UPDATE_AVAILABLE: "Actualització disponible"
METADATA_KEY: "Clau (p. ex. 'paraules clau')"
METADATA_VALUE: "Valor (per exemple, \"Blog, Grav\")"
USERNAME_HELP: "El nom d'usuari ha de tenir entre 3 i 16 caràcters, incloent minúscules, números, guions i guions baixos. Lletres majúscules, espais i caràcters especials no estan permesos"
FULLY_UPDATED: "Completament actualitzat"
SAVE_LOCATION: "Desa a la ubicació"
PAGE_FILE: "Plantilla de pàgina"
PAGE_FILE_HELP: "Nom d'arxiu de la plantilla de pàgina i plantilla de visualització per defecte"
NO_USER_ACCOUNTS: "No s'han trobat comptes d'usuari, sisplau crea'n una..."
REDIRECT_TRAILING_SLASH: "Redirigeix barra final"
REDIRECT_TRAILING_SLASH_HELP: "Realitza una redirecció 301 en lloc de manteniment transparent de barra final."
DEFAULT_DATE_FORMAT: "Format de data de pàgina"
DEFAULT_DATE_FORMAT_HELP: "Format de data de pàgina utilitzat per Grav. Per defecte, Grav intenta endivinar el format de data, tot i això pots especificar un format utilitzant la sintaxi de data de PHP (p.e., Y-m-d H:i)"
DEFAULT_DATE_FORMAT_PLACEHOLDER: "Endevina automàticament"
IGNORE_FILES: "Ignora fitxers"
IGNORE_FILES_HELP: "Arxius específics a ignorar al processar pàgines"
IGNORE_FOLDERS: "Ignora carpetes"
IGNORE_FOLDERS_HELP: "Carpetes específiques a ignorar al processar pàgines"
HTTP_ACCEPT_LANGUAGE: "Estableix la llengua a partir del navegador"
HTTP_ACCEPT_LANGUAGE_HELP: "Pots optar per intentar establir la llengua basant-se en l'etiqueta de la capçalera 'http_accept_language' en el navegador"
OVERRIDE_LOCALE: "Sobreescriu la configuració local"
OVERRIDE_LOCALE_HELP: "Sobreescriu la configuració local en PHP basant-se en la llengua actual"
REDIRECT: "Redirecció de pàgina"
REDIRECT_HELP: "Escriu una ruta de pàgina o URL externa per aqueta pàgina a redirigir a per exemple, '/alguna/ruta' o 'https://algunlloc.com'"
PLUGIN_STATUS: "Estat del plugin"
INCLUDE_DEFAULT_LANG: "Inclou llengua predeterminada"
INCLUDE_DEFAULT_LANG_HELP: "Això sobreposarà totes les URLs amb la llengua per defecte. Per exemple, 'ca/blog/el-meu-post'"
ALLOW_URL_TAXONOMY_FILTERS: "Filtres de taxonomia d'URL"
ALLOW_URL_TAXONOMY_FILTERS_HELP: "Col·lecions basades en pàgines que et permeten filtrar per '/taxonomia:valor'."
REDIRECT_DEFAULT_CODE: "Codi de redirecció per defecte"
REDIRECT_DEFAULT_CODE_HELP: "El codi d'estat HTTP per utilitzar en redireccions"
IGNORE_HIDDEN: "Ignora ocults"
IGNORE_HIDDEN_HELP: "Ignora tots els fitxers i carpetes que comencin amb un punt"
WRAPPED_SITE: "Lloc encapsulat"
WRAPPED_SITE_HELP: "Per a que els temes/plugins sàpiguen si Grav està encapsulat en una altra plataforma"
FALLBACK_TYPES: "Tipus de fallback permesos"
FALLBACK_TYPES_HELP: "Tipus de fitxers permesos que es poden trobar si s'accedeix per la ruta de la pàgina. Per defecte, qualsevol tipus de mèdia compatible."
INLINE_TYPES: "Tipus de fallback en línia"
INLINE_TYPES_HELP: "Una llista de tipus d'arxius que han de ser mostrats en línia enlloc de descarregats"
APPEND_URL_EXT: "Afegeix extensió de direcció URL"
APPEND_URL_EXT_HELP: "Afegirà una extensió personalitzarà a l'URL de la pàgina. Tingues en compte que això farà que Grav busqui la plantilla '<template>'.<extension>.twig'"
PAGE_MODES: "Modes de pàgina"
PAGE_TYPES: "Tipus de pàgina"
ACCESS_LEVELS: "Nivells d'accés"
GROUPS: "Grups"
GROUPS_HELP: "Llista de grups dels quals l'usuari en forma part"
ADMIN_ACCESS: "Accés d'administrador"
SITE_ACCESS: "Accés al lloc"
INVALID_SECURITY_TOKEN: "Clau de seguretat invàl·lida"
ACTIVATE: "Activa"
TWIG_UMASK_FIX: "Corregeix Umask"
TWIG_UMASK_FIX_HELP: "Per defecte Twig crea arxius cache com a 0755, la correcció ho canvia a 0775"
CACHE_PERMS: "Permisos de cache"
CACHE_PERMS_HELP: "Permisos de carpeta de cache per defecte. Normalment 0755 o 0775 segons la configuració"
REMOVE_SUCCESSFUL: "Eliminació amb èxit"
REMOVE_FAILED: "No s'ha pogut eliminar"
HIDE_HOME_IN_URLS: "Amaga ruta de la pàgina inicial en les URLs"
HIDE_HOME_IN_URLS_HELP: "S'assegurarà que les rutes per defecte de qualsevol pàgina sota la pàgina inicial no facin referència a la ruta regular de la pàgina inicial"
TWIG_FIRST: "Processa Twig primer"
TWIG_FIRST_HELP: "Si el processament de pàgina de Twig està habilitat, es pot configurar que Twig faci el processament després de markdown"
SESSION_SECURE: "Segur"
SESSION_SECURE_HELP: "Si és cert, indica que la comunicació per aquesta cookie ha de ser sobre una connexió encriptada. ALERTA: Habilita aquesta opicó en llocs que funcionen exclusivament en HTTPS"
SESSION_HTTPONLY: "Només HTTP"
SESSION_HTTPONLY_HELP: "Si és cert, indica que les cookies hauran de ser utilitzades només sobre HTTP, i les modificacions de JavaScript no estan permeses"
REVERSE_PROXY: "Proxy invers"
REVERSE_PROXY_HELP: "Habilita-ho si estàs darrere d'un proxy invers i tens problemes amb URLs que continguin ports incorrectes"
INVALID_FRONTMATTER_COULD_NOT_SAVE: "Frontmatter invàlid, no s'ha pogut desar"
ADD_FOLDER: "Afegeix carpeta"
PROXY_URL: "URL del proxy"
PROXY_URL_HELP: "Escriu el HOST proxy o IP i PORt"
NOTHING_TO_SAVE: "Res a desar"
FILE_ERROR_ADD: "S'ha produït un error mentre s'intentava afegir el fitxer"
FILE_ERROR_UPLOAD: "S'ha produït un error mentre s'intentava carregar el fitxer"
FILE_UNSUPPORTED: "Tipus de fitxer no suportat"
ADD_ITEM: "Afegeix ítem"
FILE_TOO_LARGE: "El fitxers és massa gran per carregar-se, el màxim permès és %s segons<br>la teva configuració PHP. Incrementa la opció PHP 'post_max_size'"
INSTALLING: "Instal·lant"
LOADING: "Carregant..."
DEPENDENCIES_NOT_MET_MESSAGE: "Les següents dependències han de ser cobertes primer:"
ERROR_INSTALLING_PACKAGES: "S'ha produït un error mentre s'instal·lava el(s) paquet(s)"
INSTALLING_DEPENDENCIES: "Instal·lant dependències..."
INSTALLING_PACKAGES: "Instal·lant paquet(s)..."
PACKAGES_SUCCESSFULLY_INSTALLED: "Paquet(s) instal·lat(s) correctament."
READY_TO_INSTALL_PACKAGES: "Preparat per instal·lar el(s) paquet(s)"
PACKAGES_NOT_INSTALLED: "No s'han instal·lat els paquets"
PACKAGES_NEED_UPDATE: "Els paquets ja estaven instal·lats, però eren massa antics"
PACKAGES_SUGGESTED_UPDATE: "Els paquets ja estaven instal·lats, i la versió està bé, però s'actualitzaran per tal d'estar al dia"
REMOVE_THE: "Elimina el %s"
CONFIRM_REMOVAL: "Estàs segur que vols eliminar aquest %s?"
REMOVED_SUCCESSFULLY: "S'ha eliminat %s correctament"
ERROR_REMOVING_THE: "S'ha produït un error eliminant el %s"
ADDITIONAL_DEPENDENCIES_CAN_BE_REMOVED: "El %s requeria les següents dependències, les quals no són requerides per cap altre paquet instal·lat. Si no les estàs utilitzant, les pots eliminar directament des d'aquí."
READY_TO_UPDATE_PACKAGES: "Preparat per actualizar el(s) paquet(s)"
ERROR_UPDATING_PACKAGES: "S'ha produït un error al actualitzar el(s) paquet(s)"
UPDATING_PACKAGES: "Actualitzant paquet(s)..."
PACKAGES_SUCCESSFULLY_UPDATED: "Paquet(s) actualitzat(s) correctament."
UPDATING: "Actualitzant"
GPM_RELEASES: "Versions GPM"
GPM_RELEASES_HELP: "Escull 'Provant' per instal·lar versions beta o en proves"
GPM_METHOD: "Mètode de recuperació remota"
GPM_METHOD_HELP: "Quan es posa a Automàtic, Grav determinarà si fopen està disponible i l'utilitzarà, d'altra forma s'utilitzarà cURL. Per a forçar l'ús d'un o altre, canvia la configuració."
GPM_VERIFY_PEER: "Verificació a distància de peer (SSL)"
GPM_VERIFY_PEER_HELP: "Alguns proveïdors semblen fallar en la verificació del certificat SSL de getgrav.org, el qual causa que GPM no funcioni. Si aquest és el teu cas, desactivar aquesta configuració pot ajudar"
AUTO: "Automàtic"
FOPEN: "fopen"
CURL: "cURL"
STABLE: "Estable"
TESTING: "Provant"
FRONTMATTER_PROCESS_TWIG: "Processa frontmatter de Twig"
FRONTMATTER_PROCESS_TWIG_HELP: "Quan està habilitat, pots utilitzar les variables de configuració de Twig a la frontmatter de pàgina"
FRONTMATTER_IGNORE_FIELDS: "Ignora camps frontmatter"
FRONTMATTER_IGNORE_FIELDS_HELP: "Certs camps de frontmatter poden contenir Twig però no han de ser processats, com ara els formularis"
PACKAGE_X_INSTALLED_SUCCESSFULLY: "Paquet %s instal·lat correctament"
ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "L'ordre està definit pel pare, l'ordenament està deshabilitat"
ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "La pàgina no és visible, l'ordenament està deshabilitat"
ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "L'ordre a través de l'administrador no està suportat degut a que hi ha més de 200 elements"
CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "NOTA: No es poden afegir fitxers multimèdia fins que no s'hagi desat la pàgina. Fes clic a 'Desa' a la part superior"
CANNOT_ADD_FILES_PAGE_NOT_SAVED: "NOTA: Cal desar la pàgina haver de pujar-hi fitxers."
DROP_FILES_HERE_TO_UPLOAD: "Deixa anar els teus arxius aquí o <strong>fes clic en aquesta àrea</strong>"
INSERT: "Inserta"
UNDO: "Desfés"
REDO: "Refés"
HEADERS: "Capçaleres"
BOLD: "Negreta"
ITALIC: "Cursiva"
STRIKETHROUGH: "Tatxat"
SUMMARY_DELIMITER: "Delimitador del resum"
LINK: "Enllaç"
IMAGE: "Imatge"
BLOCKQUOTE: "Citació"
UNORDERED_LIST: "Llista sense ordre"
ORDERED_LIST: "Llista ordenada"
EDITOR: "Editor"
PREVIEW: "Vista prèvia"
FULLSCREEN: "Pantalla completa"
NON_ROUTABLE: "No accessible"
NON_VISIBLE: "No visible"
NON_PUBLISHED: "No publicat"
CHARACTERS: "caràcters"
PUBLISHING: "Publicació"
MEDIA_TYPES: "Tipus de multimèdia"
IMAGE_OPTIONS: "Opcions d'imatge"
MIME_TYPE: "Tipus Mime"
THUMB: "Miniatura"
TYPE: "Tipus"
FILE_EXTENSION: "Extensió de fitxer"
LEGEND: "Llegenda de pàgina"
MEMCACHE_SERVER: "Servidor Memcache"
MEMCACHE_SERVER_HELP: "L'adreça del servidor Memcache"
MEMCACHE_PORT: "Port Memcache"
MEMCACHE_PORT_HELP: "El port del servidor Memcache"
MEMCACHED_SERVER: "Servidor Memcached"
MEMCACHED_SERVER_HELP: "L'adreça del servidor Memcached"
MEMCACHED_PORT: "Port Memcached"
MEMCACHED_PORT_HELP: "El port del servidor Memcached"
REDIS_SERVER: "Servidor de Redis"
REDIS_SERVER_HELP: "L'adreça del servidor de Redis"
REDIS_PORT: "Port Redis"
REDIS_PORT_HELP: "El port del servidor Redis"
ALL: "Tot"
FROM: "de"
TO: "a"
RELEASE_DATE: "Data de llançament"
SORT_BY: "Ordena per"
RESOURCE_FILTER: "Filtrar..."
FORCE_SSL: "Força SSL"
FORCE_SSL_HELP: "Força SSL globalment, si està habilitat, quan el lloc web és accedir per HTTP, Grav envia un redireccionament a la pàgina HTTPS"
NEWS_FEED: "Canal de notícies"
EXTERNAL_URL: "URL externa"
CUSTOM_BASE_URL: "URL base personalitzada"
CUSTOM_BASE_URL_HELP: "Utilitza si vols reescriure el domini del lloc web o utilitzar una subcarpeta diferent que la que utilitza Grav. Per exemple: http://localhost"
FILEUPLOAD_PREVENT_SELF: 'No es pot utilitzar "%s" fora de les pàgines.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'No es pot pujar el fitxer %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'No es pot moure el fitxer %s to "%s"'
DROPZONE_CANCEL_UPLOAD: 'Cancel·la càrrega'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Estàs segur que vols cancel·lar aquesta càrrega?'
DROPZONE_DEFAULT_MESSAGE: 'Deixa anar els teus fitxers aquí o <strong>fes clic en aquesta àrea</strong>'
DROPZONE_FALLBACK_MESSAGE: 'El teu navegador no suporta arrosegar i deixar anar per a carregar fitxers.'
DROPZONE_FALLBACK_TEXT: 'Si us plau, utilitza el formulari clàssic inferior per carregar els teus fitxers com es feia abans.'
DROPZONE_FILE_TOO_BIG: 'El fitxer és molt gran ({{filesize}}MiB). Mida màxima de fitxer: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "No pots carregar fitxers d'aquest tipus."
DROPZONE_MAX_FILES_EXCEEDED: "No pots carregar més fitxers."
DROPZONE_REMOVE_FILE: "Esborra fitxer"
DROPZONE_RESPONSE_ERROR: "El servidor ha respost amb el codi {{statusCode}}."
PREMIUM_PRODUCT: "Premium"
DESTINATION_NOT_SPECIFIED: "Destinació no especificat"
UPLOAD_ERR_NO_TMP_DIR: "No es troba una carpeta temporal"
SESSION_SPLIT: "Separació de sessió"
SESSION_SPLIT_HELP: "Separació de sessions independent entre el lloc web i altres plugins (com ara l'administració)"
ERROR_FULL_BACKTRACE: "Error Complert Backtrace"
ERROR_SIMPLE: "Error simple"
ERROR_SYSTEM: "Error del sistema"
IMAGES_AUTO_FIX_ORIENTATION: "Fixar l'orientació automàtica"
IMAGES_AUTO_FIX_ORIENTATION_HELP: "Corregir automàticament l'orientació de la imatge basada en les dades Exif"
REDIS_SOCKET: "Socket Redis"
REDIS_SOCKET_HELP: "El Socket Redis"
NOT_SET: "No establert"
PERMISSIONS: "Permisos"
ALLOW_WEBSERVER_GZIP: "Permetre WebServer Gzip"
OFFLINE_WARNING: "La connexió a la GPM no es pot establir"
CONFIRM_REINSTALL: "Segur que vols re-instal·lar %s?"
REINSTALLED_SUCCESSFULLY: "%s re-instal·lat correctament"
TOOLS: "Eines"
GPM_OFFICIAL_ONLY: "Només GPM oficial"
SORTABLE_PAGES: "Pàgines ordenables:"
ADMIN_CHILDREN_DISPLAY_ORDER: "Ordre de visualització de descendents"
PWD_PLACEHOLDER: "cadena complexa de al menys 8 caràcters"
PWD_REGEX: "Regex clau"
USERNAME_REGEX: "RegEx nom d'usuari"
CONFIGURATION: "Configuració"
ADMIN_CACHING: "Habilita el cache de l'administració"
ADMIN_CACHING_HELP: "El cache a l'administració pot ser controlat independentment del de la pàgina front-end"
TIMEOUT: "Temps d'espera"
TIMEOUT_HELP: "Estableix el temps d'espera de la sessió en segons"
DASHBOARD: "Panell de control"
NOTIFICATIONS: "Notificacions"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,89 @@
---
PLUGIN_ADMIN:
ADMIN_BETA_MSG: "Mae hwn yn fersiwn beta! Defnyddio hwn yn cynhyrchu ar risg eich hun..."
ADMIN_REPORT_ISSUE: "Canfod problem? Rhowch wybod ar GitHub."
EMAIL_FOOTER: "<a href=\"https://getgrav.org\"> wedi'u pweru gan Grav</a>-ffeil fflat Modern CMS"
LOGIN_BTN: "Mewngofnodi"
LOGIN_BTN_FORGOT: "Anghofio"
LOGIN_BTN_RESET: "Ailosod cyfrinair"
LOGIN_BTN_SEND_INSTRUCTIONS: "Anfon cyfarwyddiadau ailosod"
LOGIN_BTN_CLEAR: "Ffurflen clir"
LOGIN_BTN_CREATE_USER: "Creu defnyddiwr"
LOGIN_LOGGED_IN: "Wedi eich mewngofnodi llwyddiannus"
LOGIN_FAILED: "Wedi methu mewngofnodi"
LOGGED_OUT: "Chi allgofnodi"
RESET_LINK_EXPIRED: "Ailosod cysylltiad wedi dod i ben, rhowch gynnig arall arni"
RESET_PASSWORD_RESET: "Mae wedi'i ailosod cyfrinair"
RESET_INVALID_LINK: "Mae annilys yn ailosod cyswllt a ddefnyddir, rhowch gynnig arni eto"
FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Mae cyfarwyddiadau i ailosod eich cyfrinair wedi'u hanfon drwy e-bost at %s"
FORGOT_FAILED_TO_EMAIL: "Wedi methu anfon e-bost cyfarwyddiadau, rhowch gynnig arall arni rywbryd eto"
FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Does dim modd ailosod cyfrinair %s, pennir nad oes cyfeiriad e-bost"
FORGOT_USERNAME_DOES_NOT_EXIST: "Nid yw'r defnyddiwr gyda'r enw defnyddiwr <b>%s</b> yn bodoli"
FORGOT_EMAIL_NOT_CONFIGURED: "Does dim modd ailosod cyfrinair. Nid yw'r safle hwn wedi'i ffurfweddu i anfon negeseuon e-bost"
FORGOT_EMAIL_SUBJECT: "Cais ailosod cyfrinair %s"
FORGOT_EMAIL_BODY: "<h1>Ailosod cyfrinair</h1> <p>%1$s Annwyl,</p> <p>Gwnaed cais ar <b>%4$s</b> i ailosod eich cyfrinair.</p> <p>< br / > <a href=\"%2$s\" class=\"btn-primary\"> hyn i ailosod eich cyfrinair cliciwch</a> < br / > < br / ></p> <p>Fel arall, gopïo URL canlynol i'r bar cyfeiriad eich porwr:</p> <p>%2$s</p> <p>< br / > Cofion, < br / > < br / >%3$s</p>"
MANAGE_PAGES: "Rheoli tudalennau"
PAGES: "Tudalennau"
PLUGINS: "Ategion"
PLUGIN: "Ategyn"
THEMES: "Themâu"
LOGOUT: "Allgofnodi"
BACK: "Yn ôl"
ADD_PAGE: "Ychwanegu Tudalen"
MOVE: "Symud"
DELETE: "Dileu"
SAVE: "Cadw"
NORMAL: "Arferol"
EXPERT: "Arbenigol"
EXPAND_ALL: "Ehangu'r cyfan"
COLLAPSE_ALL: "Crebachu'r cyfan"
ERROR: "Gwall"
CLOSE: "Cau"
CANCEL: "Canslo"
CONTINUE: "Yn parhau"
MODAL_DELETE_PAGE_CONFIRMATION_REQUIRED_TITLE: "Cadarnhad sydd yn ofynnol"
MODAL_CHANGED_DETECTED_TITLE: "Ganfod newidiadau"
MODAL_CHANGED_DETECTED_DESC: "Wedi ichi golli'r newidiadau. Ydych chi'n siŵr eich bod am adael heb arbed?"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_TITLE: "Cadarnhad sydd yn ofynnol"
MODAL_DELETE_FILE_CONFIRMATION_REQUIRED_DESC: "Ydych chi'n siŵr eich bod am ddileu'r ffeil hon? Ni ellir dad-wneud y cam gweithredu hwn."
ADD_FILTERS: "Ychwanegu hidlydd"
SEARCH_PAGES: "Tudalennau chwilio"
VERSION: "Fersiwn"
WAS_MADE_WITH: "Wedi greu hefo"
BY: "Gan"
AUTHOR: "Awdur"
HOMEPAGE: "Hafan"
KEYWORDS: "Allweddeiriau"
LICENSE: "Trwydded"
DESCRIPTION: "Disgrifiad"
THEME: "Thema"
BACK_TO_THEMES: "Ôl i themâu"
BACK_TO_PLUGINS: "Ôl i ategion"
CHECK_FOR_UPDATES: "Chwilio am ddiweddariadau"
ADD: "Ychwanegu"
CLEAR_CACHE: "Clirio storfa"
CLEAR_CACHE_ALL_CACHE: "Holl storfa"
CLEAR_CACHE_ASSETS_ONLY: "Asedau yn unig"
CLEAR_CACHE_IMAGES_ONLY: "Llyniau yn unig"
CLEAR_CACHE_CACHE_ONLY: "Storfa yn unig"
UPDATES_AVAILABLE: "Diweddariadau ar gael"
DAYS: "Diwrnod"
UPDATE: "Diweddaru"
STATISTICS: "Ystadegau"
TODAY: "Heddiw"
WEEK: "Wythnos"
MONTH: "Mis"
UPDATED: "Wedi'w ddiweddaru"
MON: "Llu"
TUE: "Maw"
WED: "Mer"
THU: "Iau"
FRI: "Gwe"
SAT: "Sad"
SUN: "Sul"
COPY: "Copi"
EDIT: "Golygu"
CREATE: "Creu"
AVAILABLE: "Ar gael"
CONFIGURATION: "Ffurfweddiad"
DASHBOARD: "Dangosfwrdd"

Some files were not shown because too many files have changed in this diff Show More