init
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.github/*
|
||||
.phan/*
|
||||
assets/*
|
||||
backup/*
|
||||
bin/*
|
||||
cache/*
|
||||
images/*
|
||||
system/*
|
||||
tmp*
|
||||
vendor/*
|
||||
webserver-configs/*
|
||||
78
.htaccess
Normal file
@@ -0,0 +1,78 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
## Begin RewriteBase
|
||||
# If you are getting 500 or 404 errors on subpages, you may have to uncomment the RewriteBase entry
|
||||
# You should change the '/' to your appropriate subfolder. For example if you have
|
||||
# your Grav install at the root of your site '/' should work, else it might be something
|
||||
# along the lines of: RewriteBase /<your_sub_folder>
|
||||
##
|
||||
|
||||
# RewriteBase /
|
||||
|
||||
## End - RewriteBase
|
||||
|
||||
## Begin - X-Forwarded-Proto
|
||||
# In some hosted or load balanced environments, SSL negotiation happens upstream.
|
||||
# In order for Grav to recognize the connection as secure, you need to uncomment
|
||||
# the following lines.
|
||||
#
|
||||
# RewriteCond %{HTTP:X-Forwarded-Proto} https
|
||||
# RewriteRule .* - [E=HTTPS:on]
|
||||
#
|
||||
## End - X-Forwarded-Proto
|
||||
|
||||
## Begin - Exploits
|
||||
# If you experience problems on your site block out the operations listed below
|
||||
# This attempts to block the most common type of exploit `attempts` to Grav
|
||||
#
|
||||
# Block out any script trying to use twig tags in URL.
|
||||
RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR]
|
||||
RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR]
|
||||
# Block out any script trying to base64_encode data within the URL.
|
||||
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
|
||||
# Block out any script that includes a <script> tag in URL.
|
||||
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
|
||||
# Block out any script trying to set a PHP GLOBALS variable via URL.
|
||||
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
|
||||
# Block out any script trying to modify a _REQUEST variable via URL.
|
||||
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
|
||||
# Return 403 Forbidden header and show the content of the root homepage
|
||||
RewriteRule .* index.php [F]
|
||||
#
|
||||
## End - Exploits
|
||||
|
||||
## Begin - Index
|
||||
# If the requested path and file is not /index.php and the request
|
||||
# has not already been internally rewritten to the index.php script
|
||||
RewriteCond %{REQUEST_URI} !^/index\.php
|
||||
# and the requested path and file doesn't directly match a physical file
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# and the requested path and file doesn't directly match a physical folder
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# internally rewrite the request to the index.php script
|
||||
RewriteRule .* index.php [L]
|
||||
## End - Index
|
||||
|
||||
## Begin - Security
|
||||
# Block all direct access for these folders
|
||||
RewriteRule ^(\.git|cache|bin|logs|backup|webserver-configs|tests)/(.*) error [F]
|
||||
# Block access to specific file types for these system folders
|
||||
RewriteRule ^(system|vendor)/(.*)\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]
|
||||
# Block access to specific file types for these user folders
|
||||
RewriteRule ^(user)/(.*)\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ error [F]
|
||||
# Block all direct access to .md files:
|
||||
RewriteRule \.md$ error [F]
|
||||
# Block all direct access to files and folders beginning with a dot
|
||||
RewriteRule (^|/)\.(?!well-known) - [F]
|
||||
# Block access to specific files in the root folder
|
||||
RewriteRule ^(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$ error [F]
|
||||
## End - Security
|
||||
|
||||
</IfModule>
|
||||
|
||||
# Begin - Prevent Browsing and Set Default Resources
|
||||
Options -Indexes
|
||||
DirectoryIndex index.php index.html index.htm
|
||||
# End - Prevent Browsing and Set Default Resources
|
||||
3918
CHANGELOG.md
Normal file
133
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[INSERT CONTACT METHOD].
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||
at [https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
138
CONTRIBUTING.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Contributing to Grav
|
||||
|
||||
:+1::tada: First, thanks for getting involved with Grav! :tada::+1:
|
||||
|
||||
Please take a moment to review this document in order to make the contribution
|
||||
process easy and effective for everyone involved.
|
||||
|
||||
Following these guidelines helps to communicate that you respect the time of
|
||||
the developers managing and developing this open source project. In return,
|
||||
they should reciprocate that respect in addressing your issue or assessing
|
||||
patches and features.
|
||||
|
||||
## Grav, Plugins, Themes and Skeletons
|
||||
|
||||
Grav is a large open source project — it's made up of over 100 repositories. When you initially consider contributing to Grav, you might be unsure about which of those 200 repositories implements the functionality you want to change or report a bug for.
|
||||
|
||||
[https://github.com/getgrav/grav](https://github.com/getgrav/grav) is the main Grav repository. The core of Grav is provided by this repo.
|
||||
|
||||
[https://github.com/getgrav/grav-plugin-admin](https://github.com/getgrav/grav-plugin-admin) is the Admin Plugin repository.
|
||||
|
||||
Every Plugin and Theme has its own repository. If you have a problem you think is specific to a Theme or Plugin, please report it in its corresponding repository. Please read the Plugin or Theme documentation to ensure the problem is not addressed there already.
|
||||
|
||||
Every Skeleton also has its own repository, so if an issue is not specific to a theme or plugin but rather to its usage in the skeleton, report it in the skeleton repository.
|
||||
|
||||
## Using the issue tracker
|
||||
|
||||
The issue tracker is the preferred channel for [bug reports](#bugs),
|
||||
[features requests](#features) and [submitting pull
|
||||
requests](#pull-requests), but please respect the following restrictions:
|
||||
|
||||
* Please **do not** use the issue tracker for support requests. Use
|
||||
[the Forum](http://getgrav.org/forum) or [the Chat](https://chat.getgrav.org/).
|
||||
|
||||
|
||||
<a name="bugs"></a>
|
||||
## Bug reports
|
||||
|
||||
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||
Good bug reports are extremely helpful - thank you!
|
||||
|
||||
Guidelines for bug reports:
|
||||
|
||||
1. **Check you satisfy the Grav requirements** — [http://learn.getgrav.org/basics/requirements](http://learn.getgrav.org/basics/requirements)
|
||||
|
||||
2. **Check this happens on a clean Grav install** — check if the issue happens on any Grav site, or just with a specific configuration of plugins / theme
|
||||
|
||||
3. **Use the GitHub issue search** — check if the issue has already been
|
||||
reported.
|
||||
|
||||
4. **Check if the issue is already being solved in a PR** — check the open Pull Requests to see if one already solves the problem you're having
|
||||
|
||||
5. **Check if the issue has been fixed** — try to reproduce it using the
|
||||
latest `develop` branch in the repository.
|
||||
|
||||
6. **Isolate the problem** — create a [reduced test
|
||||
case](http://css-tricks.com/reduced-test-cases/) and provide a step-by-step instruction set on how to recreate the problem. Include code samples, page snippets or yaml configurations if needed.
|
||||
|
||||
7. **Check the problem on Grav 1.1** — if you're using Grav 1.0, latest stable release, please also check if you can replicate the issue on Grav 1.1 RC as many bugs are already solved in the next Grav release.
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more
|
||||
information. Please try to be as detailed as possible in your report.
|
||||
|
||||
- What is your environment? Is it localhost, OSX, Linux, on a remote server? Same happening locally and or the server, or just locally or just on Linux?
|
||||
|
||||
- What steps will reproduce the issue? What browser(s) and OS experience the problem?
|
||||
|
||||
- What would you expect to be the outcome?
|
||||
|
||||
- Did the problem start happening recently (e.g. after updating to a new version of Grav) or was this always a problem?
|
||||
|
||||
- If the problem started happening recently, can you reproduce the problem in an older version of Grav? What's the most recent version in which the problem doesn't happen? You can download older versions of Grav from the releases page on Github.
|
||||
|
||||
- Can you reliably reproduce the issue? If not, provide details about how often the problem happens and under which conditions it normally happens.
|
||||
|
||||
|
||||
All these details will help contributors to fix any potential bugs.
|
||||
|
||||
Important: [include Code Samples in triple backticks](https://help.github.com/articles/github-flavored-markdown/#fenced-code-blocks) so that Github will provide a proper indentation. [Add the language name after the backticks](https://help.github.com/articles/github-flavored-markdown/#syntax-highlighting) to add syntax highlighting to the code snippets.
|
||||
|
||||
Example:
|
||||
|
||||
> Short and descriptive example bug report title
|
||||
>
|
||||
> A summary of the issue and the browser/OS environment in which it occurs. If
|
||||
> suitable, include the steps required to reproduce the bug.
|
||||
>
|
||||
> 1. This is the first step
|
||||
> 2. This is the second step
|
||||
> 3. Further steps, etc.
|
||||
>>
|
||||
> Any other information you want to share that is relevant to the issue being
|
||||
> reported. This might include the lines of code that you have identified as
|
||||
> causing the bug, and potential solutions (and your opinions on their
|
||||
> merits).
|
||||
|
||||
|
||||
<a name="features"></a>
|
||||
## Feature requests
|
||||
|
||||
Feature requests are welcome. But take a moment to find out whether your idea
|
||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||
case to convince the project's developers of the merits of this feature. Please
|
||||
provide as much detail and context as possible.
|
||||
|
||||
|
||||
<a name="pull-requests"></a>
|
||||
## Pull requests
|
||||
|
||||
Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**Please ask first** in [the Forum](http://getgrav.org/forum) or [the Chat](https://chat.getgrav.org/)
|
||||
before embarking on any significant pull request (e.g.
|
||||
implementing features, refactoring code..),
|
||||
otherwise you risk spending a lot of time working on something that the
|
||||
project's developers might not want to merge into the project.
|
||||
|
||||
Please adhere to the coding conventions used throughout the project (indentation,
|
||||
accurate comments, etc.) and any other requirements.
|
||||
|
||||
See [Using Pull Request](https://help.github.com/articles/using-pull-requests/) and [Fork a Repo](https://help.github.com/articles/fork-a-repo/) if you're not familiar with Pull Requests.
|
||||
|
||||
Any pull request should be based on the `develop` branch. We will not consider pull requests made to master.
|
||||
|
||||
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
|
||||
license your work under the same license as that used by the project.
|
||||
|
||||
<a name="translations"></a>
|
||||
### Translations
|
||||
Translations for Grav core and the Admin plugin are managed through Crowdin:
|
||||
|
||||
- Admin: https://crowdin.com/project/grav-admin
|
||||
- Core: https://crowdin.com/project/grav-core
|
||||
|
||||
Please do not post translations PRs for core or admin translations on GitHub, with the exception of fixes for the english language.
|
||||
|
||||
All other plugins and themes translations are handled directly in their GitHub repository, and the string are usually found in the `languages.yaml` file at the root of each project.
|
||||
21
LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 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.
|
||||
156
README.md
Normal file
@@ -0,0 +1,156 @@
|
||||
#  Grav
|
||||
|
||||
[](https://github.com/phpstan/phpstan)
|
||||
[](https://chat.getgrav.org)
|
||||
[](https://github.com/getgrav/grav/actions?query=workflow%3A%22PHP+Tests%22) [](#backers) [](#supporters) [](#sponsors)
|
||||
|
||||
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principles to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.
|
||||
|
||||
The underlying architecture of Grav is designed to use well-established and _best-in-class_ technologies to ensure that Grav is simple to use and easy to extend. Some of these key technologies include:
|
||||
|
||||
* [Twig Templating](https://twig.symfony.com/): for powerful control of the user interface
|
||||
* [Markdown](https://en.wikipedia.org/wiki/Markdown): for easy content creation
|
||||
* [YAML](https://yaml.org): for simple configuration
|
||||
* [Parsedown](https://parsedown.org/): for fast Markdown and Markdown Extra support
|
||||
* [Doctrine Cache](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/caching.html): layer for performance
|
||||
* [Pimple Dependency Injection Container](https://github.com/silexphp/Pimple): for extensibility and maintainability
|
||||
* [Symfony Event Dispatcher](https://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
|
||||
* [Symfony Console](https://symfony.com/doc/current/components/console/introduction.html): for CLI interface
|
||||
* [Gregwar Image Library](https://github.com/Gregwar/Image): for dynamic image manipulation
|
||||
|
||||
# Requirements
|
||||
|
||||
- PHP 7.3.6 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
|
||||
- Check the [Apache](https://learn.getgrav.org/basics/requirements#apache-requirements) or [IIS](https://learn.getgrav.org/basics/requirements#iis-requirements) requirements
|
||||
|
||||
# Documentation
|
||||
|
||||
The full documentation can be found from [learn.getgrav.org](https://learn.getgrav.org).
|
||||
|
||||
# QuickStart
|
||||
|
||||
These are the options to get Grav:
|
||||
|
||||
### Downloading a Grav Package
|
||||
|
||||
You can download a **ready-built** package from the [Downloads page on https://getgrav.org](https://getgrav.org/downloads)
|
||||
|
||||
### With Composer
|
||||
|
||||
You can create a new project with the latest **stable** Grav release with the following command:
|
||||
|
||||
```
|
||||
$ composer create-project getgrav/grav ~/webroot/grav
|
||||
```
|
||||
|
||||
### From GitHub
|
||||
|
||||
1. Clone the Grav repository from [https://github.com/getgrav/grav]() to a folder in the webroot of your server, e.g. `~/webroot/grav`. Launch a **terminal** or **console** and navigate to the webroot folder:
|
||||
```
|
||||
$ cd ~/webroot
|
||||
$ git clone https://github.com/getgrav/grav.git
|
||||
```
|
||||
|
||||
2. Install the **plugin** and **theme dependencies** by using the [Grav CLI application](https://learn.getgrav.org/advanced/grav-cli) `bin/grav`:
|
||||
```
|
||||
$ cd ~/webroot/grav
|
||||
$ bin/grav install
|
||||
```
|
||||
|
||||
Check out the [install procedures](https://learn.getgrav.org/basics/installation) for more information.
|
||||
|
||||
# Adding Functionality
|
||||
|
||||
You can download [plugins](https://getgrav.org/downloads/plugins) or [themes](https://getgrav.org/downloads/themes) manually from the appropriate tab on the [Downloads page on https://getgrav.org](https://getgrav.org/downloads), but the preferred solution is to use the [Grav Package Manager](https://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
|
||||
|
||||
```
|
||||
$ bin/gpm index
|
||||
```
|
||||
|
||||
This will display all the available plugins and then you can install one or more with:
|
||||
|
||||
```
|
||||
$ bin/gpm install <plugin/theme>
|
||||
```
|
||||
|
||||
# Updating
|
||||
|
||||
To update Grav you should use the [Grav Package Manager](https://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
|
||||
|
||||
```
|
||||
$ bin/gpm selfupgrade
|
||||
```
|
||||
|
||||
To update plugins and themes:
|
||||
|
||||
```
|
||||
$ bin/gpm update
|
||||
```
|
||||
|
||||
## Upgrading from older version
|
||||
|
||||
* [Upgrading to Grav 1.7](https://learn.getgrav.org/16/advanced/grav-development/grav-17-upgrade-guide)
|
||||
* [Upgrading to Grav 1.6](https://learn.getgrav.org/16/advanced/grav-development/grav-16-upgrade-guide)
|
||||
* [Upgrading from Grav <1.6](https://learn.getgrav.org/16/advanced/grav-development/grav-15-upgrade-guide)
|
||||
|
||||
# Contributing
|
||||
We appreciate any contribution to Grav, whether it is related to bugs, grammar, or simply a suggestion or improvement! Please refer to the [Contributing guide](CONTRIBUTING.md) for more guidance on this topic.
|
||||
|
||||
## Security issues
|
||||
If you discover a possible security issue related to Grav or one of its plugins, please email the core team at contact@getgrav.org and we'll address it as soon as possible.
|
||||
|
||||
# Getting Started
|
||||
|
||||
* [What is Grav?](https://learn.getgrav.org/basics/what-is-grav)
|
||||
* [Install](https://learn.getgrav.org/basics/installation) Grav in few seconds
|
||||
* Understand the [Configuration](https://learn.getgrav.org/basics/grav-configuration)
|
||||
* Take a peek at our available free [Skeletons](https://getgrav.org/downloads/skeletons)
|
||||
* If you have questions, jump on our [Discord Chat Server](https://chat.getgrav.org)!
|
||||
* Have fun!
|
||||
|
||||
# Exploring More
|
||||
|
||||
* Have a look at our [Basic Tutorial](https://learn.getgrav.org/basics/basic-tutorial)
|
||||
* Dive into more [advanced](https://learn.getgrav.org/advanced) functions
|
||||
* Learn about the [Grav CLI](https://learn.getgrav.org/cli-console/grav-cli)
|
||||
* Review examples in the [Grav Cookbook](https://learn.getgrav.org/cookbook)
|
||||
* More [Awesome Grav Stuff](https://github.com/getgrav/awesome-grav)
|
||||
|
||||
# Backers
|
||||
Support Grav with a monthly donation to help us continue development. [[Become a backer](https://opencollective.com/grav/contribute)]
|
||||
|
||||
<img src="https://opencollective.com/grav/tiers/backers.svg?avatarHeight=36&width=600" />
|
||||
|
||||
|
||||
# Supporters
|
||||
Support Grav with a monthly donation to help us continue development. [[Become a supporter](https://opencollective.com/grav/contribute)]
|
||||
|
||||
<img src="https://opencollective.com/grav/tiers/supporters.svg?avatarHeight=36&width=600" />
|
||||
|
||||
|
||||
# Sponsors
|
||||
Support Grav with a yearly donation to help us continue development. [[Become a sponsor](https://opencollective.com/grav/contribute)]
|
||||
|
||||
<img src="https://opencollective.com/grav/tiers/sponsors.svg?avatarHeight=36&width=600" />
|
||||
|
||||
# License
|
||||
|
||||
See [LICENSE](LICENSE.txt)
|
||||
|
||||
|
||||
[gitflow-model]: http://nvie.com/posts/a-successful-git-branching-model/
|
||||
[gitflow-extensions]: https://github.com/nvie/gitflow
|
||||
|
||||
# Running Tests
|
||||
|
||||
First install the dev dependencies by running `composer install` from the Grav root.
|
||||
|
||||
Then `composer test` will run the Unit Tests, which should be always executed successfully on any site.
|
||||
Windows users should use the `composer test-windows` command.
|
||||
You can also run a single unit test file, e.g. `composer test tests/unit/Grav/Common/AssetsTest.php`
|
||||
|
||||
To run phpstan tests, you should run:
|
||||
|
||||
* `composer phpstan` for global tests
|
||||
* `composer phpstan-framework` for more strict tests
|
||||
* `composer phpstan-plugins` to test all installed plugins
|
||||
21
SECURITY.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We are focusing our security updates on the following versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.7.x | :white_check_mark: |
|
||||
| 1.6.x | :warning: |
|
||||
| < 1.6 | :x: |
|
||||
|
||||
## :warning: Versions
|
||||
|
||||
Versions with :warning: will be supported for security issues, however you won't be able to update to them, you will need to manually update through the [`direct-install` command](https://learn.getgrav.org/17/admin-panel/tools).
|
||||
|
||||
If you cannot update to the latest stable version available because, for example, your server does not meet the minimum PHP requirements, you can manually install a previous version by downloading the package from our Releases directory (https://github.com/getgrav/grav/releases).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please contact security@getgrav.org with a detailed explaination of the security issue found and we will work with you to get it resolved as fast as possible.
|
||||
130
composer.json
Normal file
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"name": "getgrav/grav",
|
||||
"type": "project",
|
||||
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
|
||||
"keywords": [
|
||||
"cms",
|
||||
"flat-file cms",
|
||||
"flat cms",
|
||||
"flatfile cms",
|
||||
"php"
|
||||
],
|
||||
"homepage": "https://getgrav.org",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.3.6 || ^8.0",
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"symfony/polyfill-mbstring": "~1.23",
|
||||
"symfony/polyfill-iconv": "^1.23",
|
||||
"symfony/polyfill-php74": "^1.23",
|
||||
"symfony/polyfill-php80": "^1.23",
|
||||
"symfony/polyfill-php81": "^1.23",
|
||||
"psr/simple-cache": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"psr/container": "~1.1.0",
|
||||
"nyholm/psr7-server": "^1.0",
|
||||
"nyholm/psr7": "^1.3",
|
||||
"twig/twig": "~v1.44",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"erusev/parsedown-extra": "~0.8",
|
||||
"symfony/contracts": "~1.1",
|
||||
"symfony/yaml": "~4.4",
|
||||
"symfony/console": "~4.4",
|
||||
"symfony/event-dispatcher": "~4.4",
|
||||
"symfony/var-dumper": "~4.4",
|
||||
"symfony/process": "~4.4",
|
||||
"doctrine/cache": "^1.10",
|
||||
"doctrine/collections": "^1.6",
|
||||
"guzzlehttp/psr7": "^1.7",
|
||||
"filp/whoops": "~2.9",
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"monolog/monolog": "~1.25",
|
||||
"getgrav/image": "^3.0",
|
||||
"getgrav/cache": "^2.0",
|
||||
"donatj/phpuseragentparser": "~1.1",
|
||||
"pimple/pimple": "~3.5.0",
|
||||
"rockettheme/toolbox": "~1.5",
|
||||
"maximebf/debugbar": "~1.16",
|
||||
"league/climate": "^3.6",
|
||||
"miljar/php-exif": "^0.6",
|
||||
"composer/ca-bundle": "^1.2",
|
||||
"dragonmantank/cron-expression": "^1.2",
|
||||
"willdurand/negotiation": "^3.0",
|
||||
"itsgoingd/clockwork": "^5.0",
|
||||
"symfony/http-client": "^4.4",
|
||||
"composer/semver": "^1.4",
|
||||
"rhukster/dom-sanitizer": "^1.0",
|
||||
"multiavatar/multiavatar-php": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^4.1",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0",
|
||||
"phpunit/php-code-coverage": "~9.2",
|
||||
"getgrav/markdowndocs": "^2.0",
|
||||
"codeception/module-asserts": "^1.3",
|
||||
"codeception/module-phpbrowser": "^1.0",
|
||||
"symfony/service-contracts": "*"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "Recommended for better performance",
|
||||
"ext-iconv": "Recommended for better performance",
|
||||
"ext-zend-opcache": "Recommended for better performance",
|
||||
"ext-intl": "Recommended for multi-language sites",
|
||||
"ext-memcache": "Needed to support Memcache servers",
|
||||
"ext-memcached": "Needed to support Memcached servers",
|
||||
"ext-redis": "Needed to support Redis servers",
|
||||
"ext-exif": "Needed to use exif data from images."
|
||||
},
|
||||
"config": {
|
||||
"apcu-autoloader": true,
|
||||
"platform": {
|
||||
"php": "7.3.6"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grav\\": "system/src/Grav",
|
||||
"Twig\\": "system/src/Twig"
|
||||
},
|
||||
"files": [
|
||||
"system/defines.php",
|
||||
"system/src/DOMLettersIterator.php",
|
||||
"system/src/DOMWordsIterator.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PHPStan\\": "tests/phpstan/classes"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"exclude": [
|
||||
"VERSION"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"api-17": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.17.md",
|
||||
"post-create-project-cmd": "bin/grav install",
|
||||
"phpstan": "vendor/bin/phpstan analyse -l 2 -c ./tests/phpstan/phpstan.neon --memory-limit=720M system/src",
|
||||
"phpstan-framework": "vendor/bin/phpstan analyse -l 5 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src/Grav/Framework system/src/Grav/Events system/src/Grav/Installer",
|
||||
"phpstan-plugins": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/plugins.neon --memory-limit=400M user/plugins",
|
||||
"test": "vendor/bin/codecept run unit",
|
||||
"test-windows": "vendor\\bin\\codecept run unit"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-develop": "1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
6696
composer.lock
generated
Normal file
51
index.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav.Core
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav;
|
||||
|
||||
\define('GRAV_REQUEST_TIME', microtime(true));
|
||||
\define('GRAV_PHP_MIN', '7.3.6');
|
||||
|
||||
if (PHP_SAPI === 'cli-server') {
|
||||
$symfony_server = stripos(getenv('_'), 'symfony') !== false || stripos($_SERVER['SERVER_SOFTWARE'] ?? '', 'symfony') !== false || stripos($_ENV['SERVER_SOFTWARE'] ?? '', 'symfony') !== false;
|
||||
|
||||
if (!isset($_SERVER['PHP_CLI_ROUTER']) && !$symfony_server) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
die('Please run: <i>bin/grav install</i>');
|
||||
}
|
||||
|
||||
// Register the auto-loader.
|
||||
$loader = require $autoload;
|
||||
|
||||
// Set timezone to default, falls back to system if php.ini not set
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
// Set internal encoding.
|
||||
@ini_set('default_charset', 'UTF-8');
|
||||
mb_internal_encoding('UTF-8');
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
// Get the Grav instance
|
||||
$grav = Grav::instance(array('loader' => $loader));
|
||||
|
||||
// Process the page
|
||||
try {
|
||||
$grav->process();
|
||||
} catch (\Error|\Exception $e) {
|
||||
$grav->fireEvent('onFatalException', new Event(array('exception' => $e)));
|
||||
throw $e;
|
||||
}
|
||||
1
logs/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
/* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved. */
|
||||
9
logs/email.log
Normal file
@@ -0,0 +1,9 @@
|
||||
[2021-08-08 12:16:30] email.DEBUG: [] []
|
||||
[2021-08-08 12:16:44] email.DEBUG: [] []
|
||||
[2021-08-08 12:18:18] email.DEBUG: [] []
|
||||
[2021-08-08 12:47:06] email.DEBUG: [] []
|
||||
[2021-08-08 12:47:42] email.DEBUG: [] []
|
||||
[2021-08-08 17:16:53] email.DEBUG: [] []
|
||||
[2021-08-08 17:19:24] email.DEBUG: [] []
|
||||
[2021-09-07 16:29:07] email.DEBUG: ++ Starting Swift_SmtpTransport << 220 mail.infomaniak.com ESMTP ready >> EHLO [127.0.0.1] << 250-mail.infomaniak.com 250-PIPELINING 250-SIZE 250-ETRN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250-AUTH PLAIN LOGIN 250 STARTTLS >> STARTTLS << 220 2.0.0 Start TLS >> EHLO [127.0.0.1] << 250-mail.infomaniak.com 250-PIPELINING 250-SIZE 250-ETRN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250 AUTH PLAIN LOGIN >> AUTH LOGIN << 334 VXNlcm5hbWU6 >> aGVsbG9AYm95YXR6b24uY2g= << 334 UGFzc3dvcmQ6 >> RFJzaHFSOV8jLzM2MQ== << 235 2.0.0 OK ++ Swift_SmtpTransport started >> MAIL FROM:<hello@boyatzon.ch> >> RCPT TO:<hello@boyatzon.ch> >> DATA << 250 2.1.0 Ok << 250 2.1.5 Ok << 354 End data with <CR><LF>.<CR><LF> >> . << 250 2.0.0 Ok: queued as 4H3nhM5s9YzlhKRw [] []
|
||||
[2021-09-07 16:29:54] email.DEBUG: ++ Starting Swift_SmtpTransport << 220 mail.infomaniak.com ESMTP ready >> EHLO [127.0.0.1] << 250-mail.infomaniak.com 250-PIPELINING 250-SIZE 250-ETRN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250-AUTH PLAIN LOGIN 250 STARTTLS >> STARTTLS << 220 2.0.0 Start TLS >> EHLO [127.0.0.1] << 250-mail.infomaniak.com 250-PIPELINING 250-SIZE 250-ETRN 250-ENHANCEDSTATUSCODES 250-8BITMIME 250-DSN 250 AUTH PLAIN LOGIN >> AUTH LOGIN << 334 VXNlcm5hbWU6 >> aGVsbG9AYm95YXR6b24uY2g= << 334 UGFzc3dvcmQ6 >> RFJzaHFSOV8jLzM2MQ== << 235 2.0.0 OK ++ Swift_SmtpTransport started >> MAIL FROM:<hello@boyatzon.ch> >> RCPT TO:<hello@boyatzon.ch> >> DATA << 250 2.1.0 Ok << 250 2.1.5 Ok << 354 End data with <CR><LF>.<CR><LF> >> . << 250 2.0.0 Ok: queued as 4H3njG5TCGzlhNw6 [] []
|
||||
17
logs/grav.log
Normal file
4
now.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "*.php", "use": "@now/php" }]
|
||||
}
|
||||
16
robots.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
User-agent: *
|
||||
Disallow: /backup/
|
||||
Disallow: /bin/
|
||||
Disallow: /cache/
|
||||
Disallow: /grav/
|
||||
Disallow: /logs/
|
||||
Disallow: /system/
|
||||
Disallow: /vendor/
|
||||
Disallow: /user/
|
||||
Allow: /user/pages/
|
||||
Allow: /user/themes/
|
||||
Allow: /user/images/
|
||||
Allow: /
|
||||
Allow: *.css$
|
||||
Allow: *.js$
|
||||
Allow: /system/*.js$
|
||||
21
user/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 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.
|
||||
159
user/README.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Mediator Origins
|
||||
|
||||
The Mediator theme for Grav is a direct port of the [Mediator Theme for Jekyll](https://github.com/dirkfabisch/mediator) designed by [Dirk Fabisch](http://blog.base68.com/about/) which in turn was inspired by the [Readium 2.0 Theme for Ghost](http://www.svenread.com/readium-ghost-theme/).
|
||||
|
||||
A couple of minor tweaks and adjustments have been made to better take advantage of Grav features and functionality.
|
||||
|
||||
# Features
|
||||
|
||||
* Fully Responsive layout
|
||||
* Use header images in articles, if you want to (add tag "image" and url to the image in the front matter section of a post)
|
||||
* Minimal design
|
||||
* Featured article support
|
||||
* FontAwesome implemented for easy use of icons fonts
|
||||
* Free & Open Source Font usage
|
||||
|
||||
## Basic Setup for a new Grav site
|
||||
|
||||
The simplest way to install Mediator theme for Grav is to download and install the Mediator Skeleton package:
|
||||
|
||||
1. [Download Mediator Skeleton](http://getgrav.org/downloads/skeletons#extras)
|
||||
2. Simply unzip the package into your web root folder.
|
||||
3. Point your browser at the folder, job done!
|
||||
|
||||
**TIP:** Check out the [general Grav installation instructions](http://learn.getgrav.org/basics/installation) for more details on this process.
|
||||
|
||||
---
|
||||
|
||||
## Existing Grav site
|
||||
|
||||
It is possible to install just the theme, but page content will need to reference the Mediator theme's supported templates. It is strongly advised to at least install the Mediator Skeleton package to see the theme's capabilities in action.
|
||||
|
||||
To install **just** the theme:
|
||||
|
||||
```
|
||||
$ bin/gpm install mediator
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced GitHub-based installation
|
||||
|
||||
1. Download and install [Grav](https://github.com/getgrav/grav)
|
||||
2. Create a new sandbox site with: `bin/grav sandbox ~/public_html/mediator`
|
||||
3. Clone this repo into the `mediator` folder as `user`: `git clone: https://github.com/getgrav/grav-skeleton-mediator-site ~/public_html/mediator/user`
|
||||
4. Install the plugin and theme dependencies: `bin/grav install`
|
||||
|
||||
# Configuration
|
||||
|
||||
Most of the configuration of the theme is done in the `user/config/site.yaml` file:
|
||||
|
||||
```
|
||||
title: Mediator
|
||||
description: A Grav theme - Medium inspired
|
||||
taxonomies: [tag, featured]
|
||||
metadata:
|
||||
description: Grav is an easy to use, yet powerful, open source flat-file CMS
|
||||
|
||||
logo: /user/images/logo.jpg
|
||||
|
||||
date_long: 'd F Y'
|
||||
date_short: 'd M Y'
|
||||
|
||||
author:
|
||||
name: Bill Bloggs
|
||||
email: a-mail@mail.mail
|
||||
image: /user/images/avatar.jpg
|
||||
bio: I'm a blogger and a Grav-lover
|
||||
|
||||
social:
|
||||
- icon: twitter
|
||||
url: https://twitter.com/getgrav
|
||||
desc: Follow me on twitter
|
||||
share_url: http://twitter.com/share
|
||||
share_title: ?text=
|
||||
share_link: "&url="
|
||||
|
||||
- icon: facebook
|
||||
url: https://facebook.com/???
|
||||
desc: Connect with me facebook
|
||||
share_url: //www.facebook.com/sharer.php
|
||||
share_title: ?t=
|
||||
share_link: "&u="
|
||||
|
||||
- icon: github
|
||||
url: https://github.com/getgrav
|
||||
desc: Fork me on github
|
||||
share_url:
|
||||
share_title:
|
||||
share_link:
|
||||
|
||||
- icon: google-plus
|
||||
url: https://google.com/???
|
||||
desc: Add me on google+
|
||||
share_url:
|
||||
share_title:
|
||||
share_link:
|
||||
```
|
||||
|
||||
Main settings for the site
|
||||
|
||||
* **title**: name of your site
|
||||
* **description**: description of your site
|
||||
|
||||
* **logo**: small logo for the site (300x * 300x)
|
||||
* **long date**: date used in the `default` listing page
|
||||
* **short date**: date used in the `post` details page
|
||||
|
||||
* **author name**: name site owner
|
||||
* **author email**: mail address of the site owner
|
||||
* **author image**: small image of author (300x * 300px)
|
||||
* **author bio**: short one sentence biography
|
||||
|
||||
### Social
|
||||
|
||||
The template allows to add all major social platforms to your site.
|
||||
Fill the the form for each platform. If you leave the share_* entries empty, the sharing buttons below a post are not shown. If you leave the **url** and **desc** empty the icons are not shown on the index page, but the share icons on the article pages remains untouched (Thanks to [Phil](https://github.com/philsturgeon))
|
||||
|
||||
* **icon**: name of social platform (must match a name of [font-awsome](http://fortawesome.github.io/Font-Awesome/) icon set )
|
||||
* **url**: url of your account
|
||||
* **desc**: slogan of the platform
|
||||
* **share_url**: share url
|
||||
* **share_title**: first part of url for the title
|
||||
* **share_link**: second part of the share url for the link to the post
|
||||
|
||||
The Twig template engine will magical combine the different parts to a share url.
|
||||
|
||||
```
|
||||
http://twitter.com/share?text=post_title&url=post_url
|
||||
````
|
||||
|
||||
# Supported Page Types
|
||||
|
||||
The Mediator theme supports 3 page types via templates:
|
||||
|
||||
* **default**: the template used to display the default blog listing view
|
||||
* **post**: a full page of the blog post
|
||||
* **page**: similar to the post, but without author information or reading-time
|
||||
|
||||
# Adding / Changing images on Posts
|
||||
|
||||
The title image in a Post is handled in the page's metadata. To include a title image you need to use "Expert" mode when editing a page on Grav Admin panel. Or if you are using an IDE simply
|
||||
edit the header segment of the markdown file.
|
||||
|
||||
Then simply add a property for image e.g.
|
||||
```
|
||||
title: 'Your Post"
|
||||
taxonomy:
|
||||
tag:
|
||||
- foo
|
||||
- bar
|
||||
slug: your-post
|
||||
image: your-image.jpg
|
||||
|
||||
```
|
||||
|
||||
|
||||
# Licensing
|
||||
|
||||
[MIT](https://github.com/dirkfabisch/madiator/blob/master/LICENSE) with no added caveats, so feel free to use this on your site without linking back to me or using a disclaimer or anything silly like that.
|
||||
0
user/accounts/.gitkeep
Normal file
BIN
user/accounts/avatars/ru2bhyrgjlfwt7i.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
21
user/accounts/boyatzon.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
state: enabled
|
||||
email: hello@boyatzon.ch
|
||||
fullname: Boyatzon
|
||||
title: Auteur
|
||||
hashed_password: $2y$10$SJrmFFNpeoXDzzCaW7Gc6ubG302lU54KXBP.YJGXtfYY/dL78fxiq
|
||||
language: en
|
||||
content_editor: default
|
||||
twofa_enabled: false
|
||||
twofa_secret: 5BHMGA3RLFJ2CJB2EBKFMJV27YM3RAFH
|
||||
avatar:
|
||||
user/accounts/avatars/ru2bhyrgjlfwt7i.png:
|
||||
name: ru2bhyrgjlfwt7i.png
|
||||
type: image/png
|
||||
size: 1288
|
||||
path: user/accounts/avatars/ru2bhyrgjlfwt7i.png
|
||||
access:
|
||||
site:
|
||||
login: true
|
||||
admin:
|
||||
login: true
|
||||
super: true
|
||||
18
user/blueprints.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Mediator
|
||||
version: 1.0.2
|
||||
description: "Mediator theme by Dirk Fabisch ported to **Grav**"
|
||||
icon: bullseye
|
||||
author:
|
||||
name: Team Grav
|
||||
email: devs@getgrav.org
|
||||
url: http://getgrav.org
|
||||
homepage: https://github.com/getgrav/grav-theme-mediator
|
||||
demo: http://demo.getgrav.org/mediator-skeleton
|
||||
keywords: mediator, theme, modern, fast, responsive, blog
|
||||
bugs: https://github.com/getgrav/grav-theme-mediator/issues
|
||||
license: MIT
|
||||
|
||||
form:
|
||||
validation: strict
|
||||
fields:
|
||||
|
||||
1
user/cli/config/security.yaml
Normal file
@@ -0,0 +1 @@
|
||||
salt: UD6MsmYfxgZtlN
|
||||
0
user/config/media.yaml
Normal file
63
user/config/plugins/comments.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
enabled: true
|
||||
enable_on_routes:
|
||||
- /blog
|
||||
disable_on_routes:
|
||||
- /blog/blog-post-to-ignore
|
||||
- /ignore-this-route
|
||||
form:
|
||||
name: comments
|
||||
fields:
|
||||
-
|
||||
name: name
|
||||
label: PLUGIN_COMMENTS.NAME_LABEL
|
||||
placeholder: PLUGIN_COMMENTS.NAME_PLACEHOLDER
|
||||
autocomplete: 'on'
|
||||
type: text
|
||||
validate:
|
||||
required: true
|
||||
-
|
||||
name: email
|
||||
label: PLUGIN_COMMENTS.EMAIL_LABEL
|
||||
placeholder: PLUGIN_COMMENTS.EMAIL_PLACEHOLDER
|
||||
type: email
|
||||
validate:
|
||||
required: true
|
||||
-
|
||||
name: text
|
||||
label: PLUGIN_COMMENTS.MESSAGE_LABEL
|
||||
placeholder: PLUGIN_COMMENTS.MESSAGE_PLACEHOLDER
|
||||
type: textarea
|
||||
validate:
|
||||
required: true
|
||||
-
|
||||
name: date
|
||||
type: hidden
|
||||
process:
|
||||
fillWithCurrentDateTime: true
|
||||
-
|
||||
name: title
|
||||
type: hidden
|
||||
evaluateDefault: grav.page.header.title
|
||||
-
|
||||
name: lang
|
||||
type: hidden
|
||||
evaluateDefault: grav.language.getLanguage
|
||||
-
|
||||
name: path
|
||||
type: hidden
|
||||
evaluateDefault: grav.uri.path
|
||||
buttons:
|
||||
-
|
||||
type: submit
|
||||
value: PLUGIN_COMMENTS.SUBMIT_COMMENT_BUTTON_TEXT
|
||||
process:
|
||||
-
|
||||
email:
|
||||
subject: PLUGIN_COMMENTS.EMAIL_NEW_COMMENT_SUBJECT
|
||||
body: '{% include ''forms/data.html.twig'' %}'
|
||||
-
|
||||
addComment: null
|
||||
-
|
||||
message: PLUGIN_COMMENTS.THANK_YOU_MESSAGE
|
||||
-
|
||||
reset: true
|
||||
30
user/config/plugins/email.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
enabled: true
|
||||
from: hello@boyatzon.ch
|
||||
from_name: 'Site Boyatzon.ch'
|
||||
to: hello@boyatzon.ch
|
||||
to_name: Boyatzon.ch
|
||||
queue:
|
||||
enabled: false
|
||||
flush_frequency: '* * * * *'
|
||||
flush_msg_limit: 10
|
||||
flush_time_limit: 100
|
||||
mailer:
|
||||
engine: sendmail
|
||||
smtp:
|
||||
server: mail.infomaniak.com
|
||||
port: 465
|
||||
encryption: tls
|
||||
user: hello@boyatzon.ch
|
||||
password: 'DRshqR9_#/361'
|
||||
auth_mode: login
|
||||
sendmail:
|
||||
bin: '/usr/sbin/sendmail -bs'
|
||||
content_type: text/html
|
||||
debug: true
|
||||
charset: null
|
||||
cc: null
|
||||
cc_name: null
|
||||
bcc: null
|
||||
reply_to: null
|
||||
reply_to_name: null
|
||||
body: null
|
||||
7
user/config/plugins/feed.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
enabled: true
|
||||
limit: 20
|
||||
title: 'Boyatzon | Articles'
|
||||
description: 'Tous les articles publiés sur boyatzon.ch'
|
||||
length: 500
|
||||
enable_json_feed: false
|
||||
show_last_modified: false
|
||||
1
user/config/security.yaml
Normal file
@@ -0,0 +1 @@
|
||||
salt: epOUzDRybns0C8
|
||||
47
user/config/site.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
title: Boyatzon
|
||||
default_lang: fr
|
||||
author:
|
||||
name: Boyatzon
|
||||
email: hello@boyatzon.ch
|
||||
image: /user/images/avatar.jpg
|
||||
bio: 'Ingénieur et flemmard'
|
||||
taxonomies:
|
||||
- tag
|
||||
- featured
|
||||
metadata:
|
||||
description: 'Des idées, des concepts, des opinions'
|
||||
summary:
|
||||
enabled: true
|
||||
format: short
|
||||
size: 300
|
||||
delimiter: '==='
|
||||
redirects: null
|
||||
routes: null
|
||||
blog:
|
||||
route: /blog
|
||||
description: 'Des idées, des concepts, des opinions'
|
||||
logo: /user/images/logo.jpg
|
||||
date_long: 'd F Y'
|
||||
date_short: 'd M Y'
|
||||
social:
|
||||
-
|
||||
icon: 'fab fa-mastodon'
|
||||
url: 'https://mamot.fr/@boyatzon'
|
||||
desc: 'Follow me on mastodon'
|
||||
share_url: null
|
||||
share_title: null
|
||||
share_link: null
|
||||
-
|
||||
icon: 'fab fa-github'
|
||||
url: 'https://git.guilo.ch/explore'
|
||||
desc: 'Fork me on github'
|
||||
share_url: null
|
||||
share_title: null
|
||||
share_link: null
|
||||
-
|
||||
icon: 'fas fa-rss'
|
||||
url: /blog.rss
|
||||
desc: 'Suivre mon flux RSS'
|
||||
share_url: null
|
||||
share_title: null
|
||||
share_link: null
|
||||
217
user/config/system.yaml
Normal file
@@ -0,0 +1,217 @@
|
||||
absolute_urls: false
|
||||
timezone: Europe/Zurich
|
||||
param_sep: ':'
|
||||
wrapped_site: false
|
||||
reverse_proxy_setup: false
|
||||
force_ssl: false
|
||||
force_lowercase_urls: true
|
||||
custom_base_url: null
|
||||
username_regex: '^[a-z0-9_-]{3,16}$'
|
||||
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
|
||||
intl_enabled: true
|
||||
http_x_forwarded:
|
||||
protocol: true
|
||||
host: false
|
||||
port: true
|
||||
ip: true
|
||||
languages:
|
||||
supported:
|
||||
- fr
|
||||
default_lang: null
|
||||
include_default_lang: false
|
||||
include_default_lang_file_extension: true
|
||||
translations: true
|
||||
translations_fallback: true
|
||||
session_store_active: false
|
||||
http_accept_language: false
|
||||
override_locale: false
|
||||
pages_fallback_only: true
|
||||
home:
|
||||
alias: /blog
|
||||
hide_in_urls: false
|
||||
pages:
|
||||
type: regular
|
||||
theme: custom-mediator
|
||||
order:
|
||||
by: default
|
||||
dir: asc
|
||||
list:
|
||||
count: 20
|
||||
dateformat:
|
||||
default: 'd-m-Y H:i'
|
||||
short: 'jS M Y'
|
||||
long: 'F jS \a\t g:ia'
|
||||
publish_dates: true
|
||||
process:
|
||||
markdown: true
|
||||
twig: false
|
||||
twig_first: false
|
||||
never_cache_twig: false
|
||||
events:
|
||||
page: true
|
||||
twig: true
|
||||
markdown:
|
||||
extra: false
|
||||
auto_line_breaks: false
|
||||
auto_url_links: false
|
||||
escape_markup: false
|
||||
special_chars:
|
||||
'>': gt
|
||||
'<': lt
|
||||
valid_link_attributes:
|
||||
- rel
|
||||
- target
|
||||
- id
|
||||
- class
|
||||
- classes
|
||||
types:
|
||||
- html
|
||||
- htm
|
||||
- xml
|
||||
- txt
|
||||
- json
|
||||
- rss
|
||||
- atom
|
||||
append_url_extension: null
|
||||
expires: 604800
|
||||
cache_control: null
|
||||
last_modified: false
|
||||
etag: true
|
||||
vary_accept_encoding: false
|
||||
redirect_default_code: '302'
|
||||
redirect_trailing_slash: 1
|
||||
redirect_default_route: 0
|
||||
ignore_files:
|
||||
- .DS_Store
|
||||
ignore_folders:
|
||||
- .git
|
||||
- .idea
|
||||
ignore_hidden: true
|
||||
hide_empty_folders: false
|
||||
url_taxonomy_filters: true
|
||||
frontmatter:
|
||||
process_twig: false
|
||||
ignore_fields:
|
||||
- form
|
||||
- forms
|
||||
markdown_extra: true
|
||||
cache:
|
||||
enabled: true
|
||||
check:
|
||||
method: file
|
||||
driver: auto
|
||||
prefix: g
|
||||
purge_at: '0 4 * * *'
|
||||
clear_at: '0 3 * * *'
|
||||
clear_job_type: standard
|
||||
clear_images_by_default: true
|
||||
cli_compatibility: false
|
||||
lifetime: 604800
|
||||
gzip: false
|
||||
allow_webserver_gzip: false
|
||||
redis:
|
||||
socket: '0'
|
||||
password: null
|
||||
database: null
|
||||
server: null
|
||||
port: null
|
||||
memcache:
|
||||
server: null
|
||||
port: null
|
||||
memcached:
|
||||
server: null
|
||||
port: null
|
||||
twig:
|
||||
cache: true
|
||||
debug: true
|
||||
auto_reload: true
|
||||
autoescape: false
|
||||
undefined_functions: true
|
||||
undefined_filters: true
|
||||
safe_functions: { }
|
||||
safe_filters: { }
|
||||
umask_fix: false
|
||||
assets:
|
||||
css_pipeline: false
|
||||
css_pipeline_include_externals: true
|
||||
css_pipeline_before_excludes: true
|
||||
css_minify: true
|
||||
css_minify_windows: false
|
||||
css_rewrite: true
|
||||
js_pipeline: false
|
||||
js_pipeline_include_externals: true
|
||||
js_pipeline_before_excludes: true
|
||||
js_minify: true
|
||||
enable_asset_timestamp: false
|
||||
enable_asset_sri: false
|
||||
collections:
|
||||
jquery: 'system://assets/jquery/jquery-2.x.min.js'
|
||||
errors:
|
||||
display: 1
|
||||
log: true
|
||||
log:
|
||||
handler: file
|
||||
syslog:
|
||||
facility: local6
|
||||
debugger:
|
||||
enabled: true
|
||||
provider: clockwork
|
||||
censored: false
|
||||
shutdown:
|
||||
close_connection: true
|
||||
twig: true
|
||||
images:
|
||||
default_image_quality: 85
|
||||
cache_all: false
|
||||
cache_perms: '0755'
|
||||
debug: false
|
||||
auto_fix_orientation: true
|
||||
seofriendly: false
|
||||
cls:
|
||||
auto_sizes: false
|
||||
aspect_ratio: false
|
||||
retina_scale: '1'
|
||||
defaults:
|
||||
loading: auto
|
||||
media:
|
||||
enable_media_timestamp: false
|
||||
unsupported_inline_types: null
|
||||
allowed_fallback_types: null
|
||||
auto_metadata_exif: false
|
||||
upload_limit: 2097152
|
||||
session:
|
||||
enabled: true
|
||||
initialize: true
|
||||
timeout: 1800
|
||||
name: boyatzon
|
||||
uniqueness: path
|
||||
secure: false
|
||||
httponly: true
|
||||
samesite: Lax
|
||||
split: true
|
||||
domain: null
|
||||
path: null
|
||||
gpm:
|
||||
releases: stable
|
||||
proxy_url: null
|
||||
method: auto
|
||||
verify_peer: true
|
||||
official_gpm_only: true
|
||||
accounts:
|
||||
type: regular
|
||||
storage: file
|
||||
flex:
|
||||
cache:
|
||||
index:
|
||||
enabled: true
|
||||
lifetime: 60
|
||||
object:
|
||||
enabled: true
|
||||
lifetime: 600
|
||||
render:
|
||||
enabled: true
|
||||
lifetime: 600
|
||||
strict_mode:
|
||||
yaml_compat: false
|
||||
twig_compat: false
|
||||
blueprint_compat: false
|
||||
8
user/config/versions.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
core:
|
||||
grav:
|
||||
version: 1.7.36
|
||||
schema: 1.7.0_2020-11-20_1
|
||||
history:
|
||||
- { version: 1.7.18, date: '2021-08-06 10:17:55' }
|
||||
- { version: 1.7.20, date: '2021-09-07 14:22:26' }
|
||||
- { version: 1.7.36, date: '2022-09-29 21:19:04' }
|
||||
0
user/data/.gitkeep
Normal file
4
user/data/comments/fr/blog/theme-setup.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
title: 'Mediator Theme Setup'
|
||||
lang: fr
|
||||
comments:
|
||||
- { text: awd, date: 'Tue, 07 Sep 2021 16:29:07', author: awd, email: awd@g.ch }
|
||||
4
user/data/contact/contact-20210808-124706-735424.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Name: awd
|
||||
Email: awd@xn--ba-bja.cd
|
||||
Message: awd
|
||||
|
||||
4
user/data/contact/contact-20210808-124742-662139.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Name: awd
|
||||
Email: awd@xn--ba-bja.cd
|
||||
Message: awd
|
||||
|
||||
5
user/data/contact/contact-20210907-162954-660264.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Name: awd
|
||||
Email: loic.guibert@bluewin.ch
|
||||
Message: awd
|
||||
|
||||
|
||||
BIN
user/data/email-queue/4bIKlgC8Hh.message
Normal file
BIN
user/data/email-queue/Dbn4A79d20.message
Normal file
BIN
user/data/email-queue/H9a13A58Rx.message
Normal file
BIN
user/data/email-queue/MDCU3ZtZgw.message
Normal file
BIN
user/data/email-queue/j0DjvxfjZk.message
Normal file
BIN
user/data/email-queue/kCx3Ub5ghj.message
Normal file
BIN
user/data/email-queue/w1cWt1JZCB.message
Normal file
52
user/data/feed/17a4d176a17a068732653cf9cac204f8.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
last_checked: 1664486111
|
||||
data:
|
||||
-
|
||||
title: 'macOS 12.0 Monterey Apache Setup: LetsEncrypt SSL'
|
||||
url: 'https://getgrav.org/blog/macos-monterey-apache-ssl'
|
||||
date: 1635591780
|
||||
nicetime: '11 months ago'
|
||||
-
|
||||
title: 'macOS 12.0 Monterey Apache Setup: MySQL, Xdebug & More...'
|
||||
url: 'https://getgrav.org/blog/macos-monterey-apache-mysql-vhost-apc'
|
||||
date: 1635591600
|
||||
nicetime: '11 months ago'
|
||||
-
|
||||
title: 'macOS 12.0 Monterey Apache Setup: Multiple PHP Versions'
|
||||
url: 'https://getgrav.org/blog/macos-monterey-apache-multiple-php-versions'
|
||||
date: 1635501600
|
||||
nicetime: '11 months ago'
|
||||
-
|
||||
title: 'macOS 12.0 Monterey Apache Setup: Upgrading Homebrew'
|
||||
url: 'https://getgrav.org/blog/macos-monterey-apache-upgrade-homebrew'
|
||||
date: 1633694400
|
||||
nicetime: '12 months ago'
|
||||
-
|
||||
title: 'Skeleton Build Automation'
|
||||
url: 'https://getgrav.org/blog/skeletons-build-automation'
|
||||
date: 1614347940
|
||||
nicetime: '2 years ago'
|
||||
-
|
||||
title: 'Using Grav''s new built-in Web Server'
|
||||
url: 'https://getgrav.org/blog/using-builtin-webserver'
|
||||
date: 1612186320
|
||||
nicetime: '2 years ago'
|
||||
-
|
||||
title: 'Grav 1.7 CLI Self-Upgrade Bug'
|
||||
url: 'https://getgrav.org/blog/grav-170-cli-self-upgrade-bug'
|
||||
date: 1611222900
|
||||
nicetime: '2 years ago'
|
||||
-
|
||||
title: 'Grav 1.7 Released!'
|
||||
url: 'https://getgrav.org/blog/grav-1.7-released'
|
||||
date: 1611050100
|
||||
nicetime: '2 years ago'
|
||||
-
|
||||
title: 'Grav Premium Focus: NextGen Editor'
|
||||
url: 'https://getgrav.org/blog/premium-focus-nextgen-editor'
|
||||
date: 1610713140
|
||||
nicetime: '2 years ago'
|
||||
-
|
||||
title: 'Grav 1.7 Stable Release - One week away!'
|
||||
url: 'https://getgrav.org/blog/grav-17-stable-imminent'
|
||||
date: 1610457780
|
||||
nicetime: '2 years ago'
|
||||
9
user/data/flex/indexes/accounts.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '1.1'
|
||||
timestamp: 1628417664
|
||||
count: 1
|
||||
index:
|
||||
boyatzon:
|
||||
storage_key: boyatzon
|
||||
storage_timestamp: 1628417664
|
||||
key: boyatzon
|
||||
email: hello@boyatzon.ch
|
||||
1
user/data/flex/indexes/pages.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version":"1.5","timestamp":1664486109,"count":9,"index":{"":{"key":"","storage_key":"","template":null,"storage_timestamp":1664485893,"children":{"about":1664485445,"blog":1664484529,"contact":1628419659,"contact-temp":1664486102},"checksum":"8b18be8bb0bed5a37a993811af7ed8f6"},"about":{"key":"about","storage_key":"about","template":"page","storage_timestamp":1664485445,"markdown":{"":{"page":1664485445}},"checksum":"d00a05b265ac2947bd752e795336f7f1"},"blog":{"key":"blog","storage_key":"blog","template":"default","storage_timestamp":1664484529,"markdown":{"":{"default":1620148636}},"children":{"2014-02-05-theme-setup":1664484246,"2014-08-12-sample-link-post":1664484136,"2014-11-30-welcome-to-grav":1664484127},"checksum":"634a2c7431646c245c89c95d637f0062"},"blog\/2014-02-05-theme-setup":{"key":"blog\/2014-02-05-theme-setup","storage_key":"blog\/2014-02-05-theme-setup","template":"post","storage_timestamp":1664484246,"markdown":{"":{"post":1664484246}},"checksum":"c5d20ba18c197e9b821c23c204ff7fc1"},"blog\/2014-08-12-sample-link-post":{"key":"blog\/2014-08-12-sample-link-post","storage_key":"blog\/2014-08-12-sample-link-post","template":"post","storage_timestamp":1664484136,"markdown":{"":{"post":1664484136}},"checksum":"65130789f583f5053d67e69030fa213c"},"blog\/2014-11-30-welcome-to-grav":{"key":"blog\/2014-11-30-welcome-to-grav","storage_key":"blog\/2014-11-30-welcome-to-grav","template":"post","storage_timestamp":1664484127,"markdown":{"":{"post":1664484127}},"checksum":"efb663beee3cb068be7b78d30a19f5a2"},"contact":{"key":"contact","storage_key":"contact","template":"form","storage_timestamp":1628419659,"markdown":{"fr":{"form":1628419659}},"children":{"emailsent":1628419566},"checksum":"a4b22cc8f0a7f1c1ed16695c4caa724c"},"contact-temp":{"key":"contact-temp","storage_key":"contact-temp","template":"page","storage_timestamp":1664486102,"markdown":{"":{"page":1664486102}},"checksum":"31993ed999ba511a91f517edced9dd66"},"contact\/emailsent":{"key":"contact\/emailsent","storage_key":"contact\/emailsent","template":"page","storage_timestamp":1628419707,"markdown":{"fr":{"page":1628419707}},"checksum":"c5a3616ba5a2c24d3fde275eb973cba9"}}}
|
||||
0
user/data/licenses.yaml
Normal file
117
user/data/notifications/17a4d176a17a068732653cf9cac204f8.yaml
Normal file
@@ -0,0 +1,117 @@
|
||||
last_checked: 1664486110
|
||||
data:
|
||||
feed:
|
||||
-
|
||||
id: 28
|
||||
date: '2021-04-23 11:40'
|
||||
message: '🚨 Grav 1.7.13 released, please update!'
|
||||
link: 'https://getgrav.org/downloads'
|
||||
type: info
|
||||
location:
|
||||
- feed
|
||||
-
|
||||
id: 26
|
||||
date: '2020-02-21 00:01'
|
||||
message: '🚀 Grav Premium available. Turbo-charge your Grav site today.'
|
||||
link: 'https://getgrav.org/premium'
|
||||
type: info
|
||||
location:
|
||||
- feed
|
||||
-
|
||||
id: 25
|
||||
date: '2020-02-18 11:27'
|
||||
message: '🕵️♂️ Would you attend an Official Grav Conference? We need your help!'
|
||||
link: 'https://forms.gle/22NQJuARmT1SFmXS6'
|
||||
type: note
|
||||
location:
|
||||
- feed
|
||||
-
|
||||
id: 24
|
||||
date: '2019-10-03 11:27'
|
||||
message: '🙏 Thanks to our amazing community, Grav was voted <b>Best Flat File CMS</b> in the 2019 CMS Critics'' Awards!'
|
||||
link: 'https://www.cmscritic.com/awards/'
|
||||
type: info
|
||||
location:
|
||||
- feed
|
||||
-
|
||||
id: 23
|
||||
date: '2018-11-15 15:50'
|
||||
message: '🙊 Grav community chat has moved from Slack to <span class=''fa fa-comments''></span> Discord'
|
||||
link: 'https://chat.getgrav.org'
|
||||
type: info
|
||||
location:
|
||||
- feed
|
||||
-
|
||||
id: 12
|
||||
date: '2017-02-17 15:15'
|
||||
message: '☕️ Support Grav for the price of a <span class=''fa fa-coffee''></span> a month!'
|
||||
link: 'https://opencollective.com/grav'
|
||||
type: note
|
||||
location:
|
||||
- feed
|
||||
-
|
||||
id: 4
|
||||
date: '2016-08-05 02:23'
|
||||
message: '💌 Join the Grav mailing list to stay in the loop!'
|
||||
link: 'http://eepurl.com/b41_oP'
|
||||
type: info
|
||||
location:
|
||||
- feed
|
||||
-
|
||||
id: 3
|
||||
date: '2016-08-05 02:23'
|
||||
message: '🐥 Please follow us on Twitter'
|
||||
link: 'https://twitter.com/getgrav'
|
||||
type: note
|
||||
location:
|
||||
- feed
|
||||
-
|
||||
id: 2
|
||||
date: '2016-08-05 02:23'
|
||||
message: '🎖 Don''t forget to star Grav on GitHub!'
|
||||
link: 'https://github.com/getgrav/grav'
|
||||
type: info
|
||||
location:
|
||||
- feed
|
||||
dashboard:
|
||||
-
|
||||
id: 27
|
||||
date: '2020-02-21 00:01'
|
||||
message: "<style>\n.dashboard-notifications-container, .dashboard-notifications-container .alert, .themes-notifications-container, .themes-notifications-container .alert, .plugins-notifications-container, .plugins-notifications-container .alert {\n margin: 0;\n padding: 0; \n}\n.dashboard-notifications-container .alert.position-dashboard:before,\n.themes-notifications-container .alert.position-themes:before,\n.plugins-notifications-container .alert.position-plugins:before,\n.hide-after-load {\n display: none;\n}\n</style>\n\n<span class=\"hide-after-load\">Loading...</span>\n<link href=\"https://getgrav.org/notifications/grav-premium-notification.css\" type=\"text/css\" rel=\"stylesheet\">\n<script src=\"https://getgrav.org/notifications/grav-premium-notification.js\" async></script>\n<div class=\"gp-banner hidden\">\n <div class=\"gp-image\">\n <img src=\"https://getgrav.org/user/themes/planetoid/images/grav-premium-logo-white.svg\" />\n </div>\n <div class=\"gp-sentences\">\n <span>Turbo-charge your Grav site - from the creators of Grav</span>\n <span>Downloads Pro - Powerful download manager directly integrated with Grav</span>\n <span>Algolia Pro - Class-leading AI-powered search made easy for Grav</span>\n <span>SEO-Magic - Advanced SEO tool for Grav with automatic webshot generator</span>\n <span>Typhoon - The most powerful Grav theme ever built, based on Tailwind 3</span>\n <span>NextGen Editor - The most advanced WYSIWYM editor for Grav</span>\n <span>Cloudflare Manager - Configure and manage your domain right within the admin</span>\n <span>Lightbox Gallery - A light, versatile and mobile friendly Lightbox Gallery</span>\n </div>\n <a class=\"gp-learn-more button\" href=\"https://getgrav.org/premium\" target=\"_blank\">Learn more</a>\n</div>\n"
|
||||
type: notice
|
||||
link: 'https://getgrav.org/premium'
|
||||
location:
|
||||
- dashboard
|
||||
- plugins
|
||||
- themes
|
||||
plugins:
|
||||
-
|
||||
id: 27
|
||||
date: '2020-02-21 00:01'
|
||||
message: "<style>\n.dashboard-notifications-container, .dashboard-notifications-container .alert, .themes-notifications-container, .themes-notifications-container .alert, .plugins-notifications-container, .plugins-notifications-container .alert {\n margin: 0;\n padding: 0; \n}\n.dashboard-notifications-container .alert.position-dashboard:before,\n.themes-notifications-container .alert.position-themes:before,\n.plugins-notifications-container .alert.position-plugins:before,\n.hide-after-load {\n display: none;\n}\n</style>\n\n<span class=\"hide-after-load\">Loading...</span>\n<link href=\"https://getgrav.org/notifications/grav-premium-notification.css\" type=\"text/css\" rel=\"stylesheet\">\n<script src=\"https://getgrav.org/notifications/grav-premium-notification.js\" async></script>\n<div class=\"gp-banner hidden\">\n <div class=\"gp-image\">\n <img src=\"https://getgrav.org/user/themes/planetoid/images/grav-premium-logo-white.svg\" />\n </div>\n <div class=\"gp-sentences\">\n <span>Turbo-charge your Grav site - from the creators of Grav</span>\n <span>Downloads Pro - Powerful download manager directly integrated with Grav</span>\n <span>Algolia Pro - Class-leading AI-powered search made easy for Grav</span>\n <span>SEO-Magic - Advanced SEO tool for Grav with automatic webshot generator</span>\n <span>Typhoon - The most powerful Grav theme ever built, based on Tailwind 3</span>\n <span>NextGen Editor - The most advanced WYSIWYM editor for Grav</span>\n <span>Cloudflare Manager - Configure and manage your domain right within the admin</span>\n <span>Lightbox Gallery - A light, versatile and mobile friendly Lightbox Gallery</span>\n </div>\n <a class=\"gp-learn-more button\" href=\"https://getgrav.org/premium\" target=\"_blank\">Learn more</a>\n</div>\n"
|
||||
type: notice
|
||||
link: 'https://getgrav.org/premium'
|
||||
location:
|
||||
- dashboard
|
||||
- plugins
|
||||
- themes
|
||||
themes:
|
||||
-
|
||||
id: 27
|
||||
date: '2020-02-21 00:01'
|
||||
message: "<style>\n.dashboard-notifications-container, .dashboard-notifications-container .alert, .themes-notifications-container, .themes-notifications-container .alert, .plugins-notifications-container, .plugins-notifications-container .alert {\n margin: 0;\n padding: 0; \n}\n.dashboard-notifications-container .alert.position-dashboard:before,\n.themes-notifications-container .alert.position-themes:before,\n.plugins-notifications-container .alert.position-plugins:before,\n.hide-after-load {\n display: none;\n}\n</style>\n\n<span class=\"hide-after-load\">Loading...</span>\n<link href=\"https://getgrav.org/notifications/grav-premium-notification.css\" type=\"text/css\" rel=\"stylesheet\">\n<script src=\"https://getgrav.org/notifications/grav-premium-notification.js\" async></script>\n<div class=\"gp-banner hidden\">\n <div class=\"gp-image\">\n <img src=\"https://getgrav.org/user/themes/planetoid/images/grav-premium-logo-white.svg\" />\n </div>\n <div class=\"gp-sentences\">\n <span>Turbo-charge your Grav site - from the creators of Grav</span>\n <span>Downloads Pro - Powerful download manager directly integrated with Grav</span>\n <span>Algolia Pro - Class-leading AI-powered search made easy for Grav</span>\n <span>SEO-Magic - Advanced SEO tool for Grav with automatic webshot generator</span>\n <span>Typhoon - The most powerful Grav theme ever built, based on Tailwind 3</span>\n <span>NextGen Editor - The most advanced WYSIWYM editor for Grav</span>\n <span>Cloudflare Manager - Configure and manage your domain right within the admin</span>\n <span>Lightbox Gallery - A light, versatile and mobile friendly Lightbox Gallery</span>\n </div>\n <a class=\"gp-learn-more button\" href=\"https://getgrav.org/premium\" target=\"_blank\">Learn more</a>\n</div>\n"
|
||||
type: notice
|
||||
link: 'https://getgrav.org/premium'
|
||||
location:
|
||||
- dashboard
|
||||
- plugins
|
||||
- themes
|
||||
none:
|
||||
-
|
||||
id: 15
|
||||
date: '2017-11-16 12:15'
|
||||
message: 'We won! <b>Grav</b> voted "Best Flat File CMS" in the 2017 CMS Critic Awards!'
|
||||
link: 'https://getgrav.org/blog/cms-critic-award-2017'
|
||||
type: note
|
||||
location:
|
||||
- none
|
||||
1
user/data/notifications/boyatzon.yaml
Normal file
@@ -0,0 +1 @@
|
||||
27: 'Sun, 08 Aug 2021 11:27:10 +0200'
|
||||
BIN
user/images/avatar.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
user/images/logo.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
63
user/localhost/config/plugins/comments.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
enabled: true
|
||||
enable_on_routes:
|
||||
- /blog
|
||||
disable_on_routes:
|
||||
- /blog/blog-post-to-ignore
|
||||
- /ignore-this-route
|
||||
form:
|
||||
name: comments
|
||||
fields:
|
||||
-
|
||||
name: name
|
||||
label: PLUGIN_COMMENTS.NAME_LABEL
|
||||
placeholder: PLUGIN_COMMENTS.NAME_PLACEHOLDER
|
||||
autocomplete: 'on'
|
||||
type: text
|
||||
validate:
|
||||
required: true
|
||||
-
|
||||
name: email
|
||||
label: PLUGIN_COMMENTS.EMAIL_LABEL
|
||||
placeholder: PLUGIN_COMMENTS.EMAIL_PLACEHOLDER
|
||||
type: email
|
||||
validate:
|
||||
required: true
|
||||
-
|
||||
name: text
|
||||
label: PLUGIN_COMMENTS.MESSAGE_LABEL
|
||||
placeholder: PLUGIN_COMMENTS.MESSAGE_PLACEHOLDER
|
||||
type: textarea
|
||||
validate:
|
||||
required: true
|
||||
-
|
||||
name: date
|
||||
type: hidden
|
||||
process:
|
||||
fillWithCurrentDateTime: true
|
||||
-
|
||||
name: title
|
||||
type: hidden
|
||||
evaluateDefault: grav.page.header.title
|
||||
-
|
||||
name: lang
|
||||
type: hidden
|
||||
evaluateDefault: grav.language.getLanguage
|
||||
-
|
||||
name: path
|
||||
type: hidden
|
||||
evaluateDefault: grav.uri.path
|
||||
buttons:
|
||||
-
|
||||
type: submit
|
||||
value: PLUGIN_COMMENTS.SUBMIT_COMMENT_BUTTON_TEXT
|
||||
process:
|
||||
-
|
||||
email:
|
||||
subject: PLUGIN_COMMENTS.EMAIL_NEW_COMMENT_SUBJECT
|
||||
body: '{% include ''forms/data.html.twig'' %}'
|
||||
-
|
||||
addComment: null
|
||||
-
|
||||
message: PLUGIN_COMMENTS.THANK_YOU_MESSAGE
|
||||
-
|
||||
reset: true
|
||||
30
user/localhost/config/plugins/email.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
enabled: true
|
||||
from: hello@boyatzon.ch
|
||||
from_name: 'Site Boyatzon.ch'
|
||||
to: hello@boyatzon.ch
|
||||
to_name: Boyatzon.ch
|
||||
queue:
|
||||
enabled: false
|
||||
flush_frequency: '* * * * *'
|
||||
flush_msg_limit: 10
|
||||
flush_time_limit: 100
|
||||
mailer:
|
||||
engine: smtp
|
||||
smtp:
|
||||
server: mail.infomaniak.com
|
||||
port: 587
|
||||
encryption: tls
|
||||
user: hello@boyatzon.ch
|
||||
password: 'DRshqR9_#/361'
|
||||
auth_mode: login
|
||||
sendmail:
|
||||
bin: '/usr/sbin/sendmail -bs'
|
||||
content_type: text/html
|
||||
debug: true
|
||||
charset: null
|
||||
cc: null
|
||||
cc_name: null
|
||||
bcc: null
|
||||
reply_to: null
|
||||
reply_to_name: null
|
||||
body: null
|
||||
1
user/localhost/config/security.yaml
Normal file
@@ -0,0 +1 @@
|
||||
salt: NrM5o6sK2QtB9n
|
||||
13
user/pages/about/page.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: 'À propos'
|
||||
---
|
||||
|
||||
**Boyatzon, Boyatz** : *nom masculin. Bon garçon alcoolique malgré lui. "Tû le boyatz', tu prends une ou bien ?"* [Désencyclopédie](https://desencyclopedie.org/wiki/Valaisan_(langue)#B)
|
||||
|
||||
J'aime beaucoup écrire. Le problème, c'est que je suis très irrégulier. J'ai créé ce blog sans prétention, pour partager des idées, des concepts ou des opinions qui peuvent intéresser certains et certaines.
|
||||
|
||||
La forme de ce blog, et peut-être son fond, est sujette au changement, comme l'a très bien expliqué [Serveur 410](https://serveur410.com/ton-coin-de-web-tattends/). J'ai préféré publiere ce blog sous sa forme non-finalisée plutôt que de repousser sa publication, sinon je ne l'aurais jamais fait.
|
||||
|
||||
Je vais parler de technologies, de sujets plus sociétaux ou encore de ce qui me passe par la tête. Je vais essayer de ne pas me restreindre.
|
||||
|
||||
[Image de fond](https://www.firewatchgame.com/media/)
|
||||
152
user/pages/blog/2014-02-05-theme-setup/post.md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
title: 'Mediator Theme Setup'
|
||||
slug: theme-setup
|
||||
date: '05-02-2014 00:00'
|
||||
taxonomy:
|
||||
tag:
|
||||
- post
|
||||
- featured
|
||||
image: tools.jpg
|
||||
---
|
||||
|
||||
# Mediator Origins
|
||||
|
||||
The Mediator theme for Grav is a direct port of the [Mediator Theme for Jekyll](https://github.com/dirkfabisch/mediator) designed by [Dirk Fabisch](http://blog.base68.com/about/) which in turn was inspired by the [Readium 2.0 Theme for Ghost](http://www.svenread.com/readium-ghost-theme/).
|
||||
|
||||
A couple of minor tweaks and adjustments have been made to better take advantage of Grav features and functionality.
|
||||
|
||||
# Features
|
||||
|
||||
* Fully Responsive layout
|
||||
* Use header images in articles, if you want to (add tag "image" and url to the image in the front matter section of a post)
|
||||
* Minimal design
|
||||
* Featured article support
|
||||
* FontAwesome implemented for easy use of icons fonts
|
||||
* Free & Open Source Font usage
|
||||
|
||||
## Basic Setup for a new Grav site
|
||||
|
||||
The simplest way to install Mediator theme for Grav is to download and install the Mediator Skeleton package:
|
||||
|
||||
1. [Download Mediator Skeleton](http://getgrav.org/downloads/skeletons#extras)
|
||||
2. Simply unzip the package into your web root folder.
|
||||
3. Point your browser at the folder, job done!
|
||||
|
||||
**TIP:** Check out the [general Grav installation instructions](http://learn.getgrav.org/basics/installation) for more details on this process.
|
||||
|
||||
---
|
||||
|
||||
## Existing Grav site
|
||||
|
||||
It is possible to install just the theme, but page content will need to reference the Mediator theme's supported templates. It is strongly advised to at least install the Mediator Skeleton package to see the theme's capabilities in action.
|
||||
|
||||
To install **just** the theme:
|
||||
|
||||
```
|
||||
$ bin/gpm install mediator
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced GitHub-based installation
|
||||
|
||||
1. Download and install [Grav](https://github.com/getgrav/grav)
|
||||
2. Create a new sandbox site with: `bin/grav sandbox ~/public_html/mediator`
|
||||
3. Clone this repo into the `mediator` folder as `user`: `git clone: https://github.com/getgrav/grav-skeleton-mediator-site ~/public_html/mediator/user`
|
||||
4. Install the plugin and theme dependencies: `bin/grav install`
|
||||
|
||||
# Configuration
|
||||
|
||||
Most of the configuration of the theme is done in the `user/config/site.yaml` file:
|
||||
|
||||
```
|
||||
title: Mediator
|
||||
description: A Grav theme - Medium inspired
|
||||
taxonomies: [tag, featured]
|
||||
metadata:
|
||||
description: Grav is an easy to use, yet powerful, open source flat-file CMS
|
||||
|
||||
logo: /user/images/logo.jpg
|
||||
|
||||
date_long: 'd F Y'
|
||||
date_short: 'd M Y'
|
||||
|
||||
author:
|
||||
name: Bill Bloggs
|
||||
email: a-mail@mail.mail
|
||||
image: /user/images/avatar.jpg
|
||||
bio: I'm a blogger and a Grav-lover
|
||||
|
||||
social:
|
||||
- icon: twitter
|
||||
url: https://twitter.com/getgrav
|
||||
desc: Follow me on twitter
|
||||
share_url: http://twitter.com/share
|
||||
share_title: ?text=
|
||||
share_link: "&url="
|
||||
|
||||
- icon: facebook
|
||||
url: https://facebook.com/???
|
||||
desc: Connect with me facebook
|
||||
share_url: //www.facebook.com/sharer.php
|
||||
share_title: ?t=
|
||||
share_link: "&u="
|
||||
|
||||
- icon: github
|
||||
url: https://github.com/getgrav
|
||||
desc: Fork me on github
|
||||
share_url:
|
||||
share_title:
|
||||
share_link:
|
||||
|
||||
- icon: google-plus
|
||||
url: https://google.com/???
|
||||
desc: Add me on google+
|
||||
share_url:
|
||||
share_title:
|
||||
share_link:
|
||||
```
|
||||
|
||||
Main settings for the site
|
||||
|
||||
* **title**: name of your site
|
||||
* **description**: description of your site
|
||||
|
||||
* **logo**: small logo for the site (300x * 300x)
|
||||
* **long date**: date used in the `default` listing page
|
||||
* **short date**: date used in the `post` details page
|
||||
|
||||
* **author name**: name site owner
|
||||
* **author email**: mail address of the site owner
|
||||
* **author image**: small image of author (300x * 300px)
|
||||
* **author bio**: short one sentence biography
|
||||
|
||||
### Social
|
||||
|
||||
The template allows to add all major social platforms to your site.
|
||||
Fill the the form for each platform. If you leave the share_* entries empty, the sharing buttons below a post are not shown. If you leave the **url** and **desc** empty the icons are not shown on the index page, but the share icons on the article pages remains untouched (Thanks to [Phil](https://github.com/philsturgeon))
|
||||
|
||||
* **icon**: name of social platform (must match a name of [font-awsome](http://fortawesome.github.io/Font-Awesome/) icon set )
|
||||
* **url**: url of your account
|
||||
* **desc**: slogan of the platform
|
||||
* **share_url**: share url
|
||||
* **share_title**: first part of url for the title
|
||||
* **share_link**: second part of the share url for the link to the post
|
||||
|
||||
The Twig template engine will magical combine the different parts to a share url.
|
||||
|
||||
```
|
||||
http://twitter.com/share?text=post_title&url=post_url
|
||||
````
|
||||
|
||||
# Supported Page Types
|
||||
|
||||
The Mediator theme supports 3 page types via templates:
|
||||
|
||||
* **default**: the template used to display the default blog listing view
|
||||
* **post**: a full page of the blog post
|
||||
* **page**: similar to the post, but without author information or reading-time
|
||||
|
||||
# Licensing
|
||||
|
||||
[MIT](https://github.com/dirkfabisch/madiator/blob/master/LICENSE) with no added caveats, so feel free to use this on your site without linking back to me or using a disclaimer or anything silly like that.
|
||||
BIN
user/pages/blog/2014-02-05-theme-setup/tools.jpg
Normal file
|
After Width: | Height: | Size: 652 KiB |
11
user/pages/blog/2014-08-12-sample-link-post/post.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: 'Sample Link Post'
|
||||
slug: sample-link-post
|
||||
description: 'Example and code for using link posts.'
|
||||
date: '12-08-2014 00:00'
|
||||
comments: true
|
||||
link: 'http://getgrav.org'
|
||||
published: false
|
||||
---
|
||||
|
||||
This theme supports **link posts**, made famous by John Gruber. To use, just add `link: http://url-you-want-linked` to the post's YAML front matter and you're done.
|
||||
51
user/pages/blog/2014-11-30-welcome-to-grav/post.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: 'Welcome to Grav'
|
||||
slug: welcome-to-grav
|
||||
date: '30-11-2014 00:00'
|
||||
image: swim.jpg
|
||||
published: false
|
||||
---
|
||||
|
||||
#Mediator Formats and CSS features
|
||||
|
||||
Examples for different formats and css features
|
||||
|
||||
#Header Formats
|
||||
#Header1
|
||||
##Header2
|
||||
|
||||
#Blockquotes
|
||||
>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
|
||||
|
||||
#Lists
|
||||
##orderd lists
|
||||
1. one
|
||||
2. two
|
||||
3. three
|
||||
|
||||
##unorderd lists
|
||||
- Apple
|
||||
- Banana
|
||||
- Plum
|
||||
|
||||
#Links
|
||||
This is an [example link](http://example.com/ "With a Title").
|
||||
|
||||
#Images
|
||||

|
||||
|
||||
#Code
|
||||
```
|
||||
#container {
|
||||
float: left;
|
||||
margin: 0 -240px 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
#Combinations
|
||||
>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
|
||||
>
|
||||
> - Apple
|
||||
> - Banana
|
||||
> - Plum
|
||||
BIN
user/pages/blog/2014-11-30-welcome-to-grav/stuff.jpg
Normal file
|
After Width: | Height: | Size: 317 KiB |
BIN
user/pages/blog/2014-11-30-welcome-to-grav/swim.jpg
Normal file
|
After Width: | Height: | Size: 496 KiB |
19
user/pages/blog/default.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: 'Derniers articles'
|
||||
sitemap:
|
||||
changefreq: weekly
|
||||
priority: 1.03
|
||||
content:
|
||||
items: '@self.children'
|
||||
order:
|
||||
by: date
|
||||
dir: desc
|
||||
limit: 10
|
||||
pagination: true
|
||||
feed:
|
||||
description: 'Derniers articles'
|
||||
limit: 10
|
||||
pagination: true
|
||||
cover: firewatch-forest-mountains-minimalism-4k-hb.jpg
|
||||
---
|
||||
|
||||
BIN
user/pages/blog/firewatch-forest-mountains-minimalism-4k-hb.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
user/pages/blog/shore.jpg
Normal file
|
After Width: | Height: | Size: 397 KiB |
6
user/pages/contact-temp/page.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Contact
|
||||
published: true
|
||||
---
|
||||
|
||||
Je travaille sur les formulaires, en attendant vous pouvez [m'envoyer un mail](mailto:hello@boyatzon.ch).
|
||||
9
user/pages/contact/emailsent/page.fr.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: 'Mail envoyé'
|
||||
cache_enable: false
|
||||
process:
|
||||
twig: true
|
||||
---
|
||||
|
||||
Merci pour votre message !
|
||||
Je vous répondrai dans les plus brefs délais.
|
||||
53
user/pages/contact/form.fr.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Contact
|
||||
form:
|
||||
name: contact
|
||||
fields:
|
||||
name:
|
||||
label: Name
|
||||
placeholder: 'Votre nom'
|
||||
autocomplete: 'on'
|
||||
type: text
|
||||
validate:
|
||||
required: true
|
||||
email:
|
||||
label: Email
|
||||
placeholder: 'Votre adresse mail'
|
||||
type: email
|
||||
validate:
|
||||
required: true
|
||||
message:
|
||||
label: Message
|
||||
placeholder: 'Votre message'
|
||||
type: textarea
|
||||
validate:
|
||||
required: true
|
||||
g-recaptcha-response:
|
||||
label: Captcha
|
||||
type: captcha
|
||||
recaptcha_not_validated: 'Captcha not valid!'
|
||||
buttons:
|
||||
submit:
|
||||
type: submit
|
||||
value: Envoyer
|
||||
reset:
|
||||
type: reset
|
||||
value: Reset
|
||||
process:
|
||||
captcha: false
|
||||
save:
|
||||
fileprefix: contact-
|
||||
dateformat: Ymd-His-u
|
||||
extension: txt
|
||||
body: '{% include ''forms/data.txt.twig'' %}'
|
||||
email:
|
||||
subject: '[Site Contact Form] {{ form.value.name|e }}'
|
||||
body: '{% include ''forms/data.html.twig'' %}'
|
||||
message: 'Merci pour votre envoi !'
|
||||
display: emailsent
|
||||
published: true
|
||||
---
|
||||
|
||||
# Contact form
|
||||
|
||||
Some sample page content
|
||||
0
user/plugins/.gitkeep
Normal file
17
user/plugins/admin/.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# 4 space indentation
|
||||
[*.php]
|
||||
indent_size = 4
|
||||
8
user/plugins/admin/.gitattributes
vendored
Normal 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
user/plugins/admin/.github/FUNDING.yml
vendored
Normal 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
|
||||
17
user/plugins/admin/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
themes/grav/.sass-cache
|
||||
.DS_Store
|
||||
crowdin.yaml
|
||||
|
||||
# Node Modules
|
||||
**/node_modules/**
|
||||
themes/grav/js/admin.js
|
||||
themes/grav/js/vendor.js
|
||||
themes/grav/js/*.map
|
||||
.idea
|
||||
|
||||
tests/_output/*
|
||||
tests/_support/_generated/*
|
||||
tests/cache/*
|
||||
tests/error.log
|
||||
/crowdin.yaml
|
||||
.vscode
|
||||
2560
user/plugins/admin/CHANGELOG.md
Normal file
1
user/plugins/admin/CONTRIBUTING.md
Normal 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
user/plugins/admin/LICENSE
Normal 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
user/plugins/admin/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
# 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
user/plugins/admin/UPGRADE.md
Normal 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
user/plugins/admin/admin.php
Normal file
84
user/plugins/admin/admin.yaml
Normal 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'
|
||||
BIN
user/plugins/admin/assets/admin-dashboard.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
781
user/plugins/admin/blueprints.yaml
Normal file
@@ -0,0 +1,781 @@
|
||||
name: Admin Panel
|
||||
slug: admin
|
||||
type: plugin
|
||||
version: 1.10.36
|
||||
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
|
||||
43
user/plugins/admin/blueprints/admin/pages/copy.yaml
Normal 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
|
||||
52
user/plugins/admin/blueprints/admin/pages/modular_new.yaml
Normal 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
|
||||
104
user/plugins/admin/blueprints/admin/pages/modular_raw.yaml
Normal 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
|
||||
5
user/plugins/admin/blueprints/admin/pages/move.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
route:
|
||||
type: hidden
|
||||
62
user/plugins/admin/blueprints/admin/pages/new.yaml
Normal 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
|
||||
31
user/plugins/admin/blueprints/admin/pages/new_folder.yaml
Normal 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
|
||||
104
user/plugins/admin/blueprints/admin/pages/raw.yaml
Normal 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
|
||||
34
user/plugins/admin/blueprints/admin/pages/root_raw.yaml
Normal 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
|
||||
36
user/plugins/admin/blueprints/config/media.yaml
Normal 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
|
||||
|
||||
|
||||
2493
user/plugins/admin/classes/plugin/Admin.php
Normal file
1167
user/plugins/admin/classes/plugin/AdminBaseController.php
Normal file
3066
user/plugins/admin/classes/plugin/AdminController.php
Normal file
182
user/plugins/admin/classes/plugin/AdminForm.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Plugin\Admin
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
user/plugins/admin/classes/plugin/AdminFormFactory.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Plugin\Admin
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,637 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Plugin\Admin
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 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');
|
||||
}
|
||||
|
||||
// For testing only!
|
||||
//Admin::DEBUG && Admin::addDebugMessage(sprintf('Email sent to %s', $to), $body);
|
||||
|
||||
$this->setMessage($this->translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'));
|
||||
} catch (\RuntimeException|\Swift_SwiftException $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'];
|
||||
}
|
||||
}
|
||||
435
user/plugins/admin/classes/plugin/Gpm.php
Normal file
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
303
user/plugins/admin/classes/plugin/Popularity.php
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
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, []);
|
||||
}
|
||||
}
|
||||