Skip to content
Grav 2.0 is officially stable. Read the announcement →

Community guidelines

Please keep discussions civil and on-topic. Repeated violations may lead to a temporary ban.

Plugins

Create a guestbook in [Agency]

first-time plugins

Started by barnabécane 5 years ago · 13 replies · 5569 views
5 years ago

After trying to use the Guestbook plugin in my Agency themed site, witch result as too hard for me, lets try thing differently !

So now my next option is to try Comments plugin.
When searching for the keywords comments + agency, I get only my own post, so no feedback available on the forum for this combination : Did someone already experienced something similar ?

5 years ago

@citoyencandide, Sharing you requirements might be helpful because a 'guestbook' could be very simple, or more complex.

A very simple solution could be a 'contact'-like form that sends a notification email and saves the form into a file, like plugin Guestbook and Comments do. A small plugin can then read the file, pass the reviews into a Twig template that formats the list. No interface in Admin, just manually editing the file containing the feedback and set the 'approved' variable to true.

A more complex scenario is that an end-user must be able to approve / disapprove / delete reviews from Admin. Is pagination required for a long list of reviews?

Simple vs complex depends on your requirements.

5 years ago

Simple is beautiful :

A form where visitor submit a comment, witch is concatenated with previous comments.
Manual edition is fine to me to set the approved messages.
Pagination, you mean several columns ? It's a good idea ^^

But in my mind, a typical guest book is a blank notebook in which the comments are manually written one below the other ...

on internet, comments are more likely written one above the other, so it would be preferable, all on one column.

5 years ago

@citoyencandide,

Pagination, you mean several columns ?

No, pagination is often used in Blogs where there are many blog-items. By pagination only 5-10 blog items are shown at the same time. Using buttons one can see the next/previous set of 5-10 blog-items.

Ok, I've a had a little fun and tried to create a proof-of-concept of my own suggestion... Here is how to get a very simple and unpolished guestbook in theme Agency.

Setup:

  • Download and install skeleton Agency
  • Install Devtools to easily create themes/plugins: $ bin/gpm install devtools

Theme:

  • To prevent loss of modification when Agency gets updated, create an inheriting (child) theme of Agency: $ bin/plugin devtools new-theme.
    Name the theme "MyAgency"
  • Tell Grav in /user/config/system.yaml to use the new theme:
    YAML
    pages:
    theme: my-agency
    
  • In our child-theme, we are going to override template /user/themes/agency/templates/modular/form.html.twig, to fix a few things.
    • Copy file /user/themes/agency/templates/modular/form.html.twig into folder /user/themes/my-agency/templates/modular/
    • Above line 10, add the following:
      TWIG
      {% set form = forms(page.header.form.name) %}
      

      This will fix collisions with the form for the guestbook.

    • To fix non closing elements, add the following at the bottom of the file:
      HTML
      </div>
      </div>
      </section>
      

Plugin:

  • Create your own custom Guestbook plugin: $ bin/plugin devtools new-plugin.
    Name the plugin SimpleGuestbook. The name Guestbook is not possible because it already exists.
  • Add the following to the configuration file user/plugins/simple-guestbook/simple-guestbook.yaml:
    YAML
    enabled: true
    addCss: true      # true|false to add css to give Guestbook same styling as Contact
    routes:           # Add routes on which Guestbook should be displayed
    # - /             # Home page
    
  • Copy above config file into folder user/config/plugins/ and alter for your environment.
  • Add logic to user/plugins/simple-guestbook/simple-guestbook.php to retrieve the items from the Guestbook. Replace entire contents with the following code:

<details>
<summary>Content of simple-guestbook.php</summary>

PHP
  <?php

  namespace Grav\Plugin;

  use Composer\Autoload\ClassLoader;
  use Grav\Common\Plugin;
  use Grav\Framework\File\File;
  use Symfony\Component\Yaml\Yaml;

  /**
   * Class SimpleGuestbookPlugin
   * @package Grav\Plugin
   */
  class SimpleGuestbookPlugin extends Plugin
  {
    /**
     * @return array
     *
     * The getSubscribedEvents() gives the core a list of events
     *     that the plugin wants to listen to. The key of each
     *     array section is the event that the plugin listens to
     *     and the value (in the form of an array) contains the
     *     callable (or function) as well as the priority. The
     *     higher the number the higher the priority.
     */
    public static function getSubscribedEvents(): array
    {
      return [
        'onPluginsInitialized' => [
          // Uncomment following line when plugin requires Grav < 1.7
          // ['autoload', 100000],
          ['onPluginsInitialized', 0]
        ]
      ];
    }

    /**
     * Composer autoload
     *
     * @return ClassLoader
     */
    public function autoload(): ClassLoader
    {
      return require __DIR__ . '/vendor/autoload.php';
    }

    /**
     * Initialize the plugin
     */
    public function onPluginsInitialized(): void
    {
      // Don't proceed if we are in the admin plugin
      if ($this->isAdmin()) {
        return;
      }

      // Enable the main events we are interested in
      $this->enable([
        // Put your main events here
        'onAssetsInitialized' => ['onAssetsInitialized', 0],
        'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
        'onTwigSiteVariables' => ['onTwigSiteVariables', 0],
      ]);
    }

    public function isOnRoute(): bool
    {
      $uri = $this->grav['uri']->uri();
      $routes = $this->config->get("plugins.$this->name.routes", '');

      $enable = $routes && (
        (gettype($routes) === 'string' && $routes === $uri) ||
        (is_array($routes) && in_array($uri, $routes)));

      return $enable;
    }

    public function onTwigTemplatePaths(): void
    {
      $this->grav['twig']->twig_paths[] = "plugins://$this->name/templates";
    }

    public function onTwigSiteVariables(): void
    {
      $twig = $this->grav['twig'];
      $twig->twig_vars['simpleGuestbook'] = $this->readGuestbookEntries();
    }

    public function readGuestbookEntries(): array
    {
      /** @var File */
      $fileInstance = new File('user-data://simple-guestbook/guestbook.yaml');

      if (!$fileInstance->exists()) {
        $fileInstance->save('');
      }

      $content = $fileInstance->load();
      $comments = Yaml::parse($content) ?? [];

      $approved = array_filter($comments, function ($comment) {
        return $comment['approved'];
      });

      return $approved;
    }

    public function onAssetsInitialized()
    {
      if ($this->config->get('plugins.simple-guestbook.addCss', true)) {
        $this->grav['assets']->addCss("plugin://simple-guestbook/css/style.css");
      }
    }
  }

</details>

  • Create new template /user/plugins/simple-guestbook/templates/modular/guestbook-form.html.twig to display the Guestbook.

<details>
<summary>Content of guestbook-form.html.twig</summary>

TWIG
  <section id="simple-guestbook">
    <div class="container">

      <div class="row">
        <div class="col-lg-12 text-center">
          {{ content|raw }}
        </div>
      </div>

      {% if simpleGuestbook|count > 0 %}
        <div class="row">
          <div class="col-lg-12 text-center">
            {% include "partials/simple-guestbook-list.html.twig" %}
          </div>
        </div>
      {% endif %}

      <div class="row">

        {% set form = forms('simple-guestbook') %}

        {% if form is null %}
          {% set form = grav.session.getFlashObject('form') %}
        {% endif  %}

        {% include 'partials/form-messages.html.twig' %}

        {% set scope = scope ?: 'data.' %}
        {% set multipart = '' %}
        {% set method = form.method|upper|default('POST') %}

        {% for field in form.fields %}
          {% if (method == 'POST' and field.type == 'file') %}
            {% set multipart = ' enctype="multipart/form-data"' %}
          {% endif %}
        {% endfor %}

        {% set action = form.action ? base_url ~ form.action : base_url ~ page.route ~ uri.params %}

        {% if (action == base_url_relative) %}
          {% set action = base_url_relative ~ '/' ~ page.slug %}
        {% endif %}

        <form name="{{ form.name }}" action="{{ action }}" method="{{ method }}" {{ multipart }} {% if form.id %} id="{{ form.id }}" {% endif %} {% block form_classes %} {% if form.classes %} class="{{ form.classes }}" {% endif %} {% endblock %}> {% block inner_markup_fields_start %}{% endblock %}

          <div class="col-md-6">
            {% for field in form.fields %}
              {% if field.position == 'left' %}
                {% set value = form.value(field.name) %}
                <div class="form-group">
                  {% include "forms/fields/#{field.type}/#{field.type}.html.twig" ignore missing %}
                </div>
              {% endif %}
            {% endfor %}
          </div>
          <div class="col-md-6">
            {% for field in form.fields %}
              {% if field.position == 'right' %}
                {% set value = form.value(field.name) %}
                <div class="form-group">
                  {% include "forms/fields/#{field.type}/#{field.type}.html.twig" ignore missing %}
                </div>
              {% endif %}
            {% endfor %}
          </div>

          {% include "forms/fields/formname/formname.html.twig" %}

          {% block inner_markup_fields_end %}{% endblock %}

          {% block inner_markup_buttons_start %}
            <div class="buttons">
            {% endblock %}

            <div class="col-lg-12 text-center">
              <div class="form-group">
                {% for button in form.buttons %}
                  {% if button.outerclasses is defined %}
                    <div class="{{ button.outerclasses }}">
                    {% endif %}
                    {% if button.url %}
                      <a href="{{ button.url starts with 'http' ? button.url : url(button.url) }}">
                      {% endif %}
                      <button {% if button.id %} id="{{ button.id }}" {% endif %} {% block button_classes %} class="{{ button.classes|default('button') }}" {% endblock %} {% if button.disabled %} disabled="disabled" {% endif %} type="{{ button.type|default('submit') }}" {% if button.task %} name="task" value="{{ button.task }}" {% endif %}>
                        {{ button.value|t|default('Submit') }}
                      </button>
                      {% if button.url %}
                      </a>
                    {% endif %}
                    {% if button.outerclasses is defined %}
                    </div>
                  {% endif %}
                {% endfor %}
              </div>
            </div>

            {% block inner_markup_buttons_end %}
            </div>
          {% endblock %}

          {{ nonce_field('form', 'form-nonce')|raw }}
        </form>
      </div>
    </div>
  </section>

</details>

  • Create new template /user/plugins/simple-guestbook/templates/forms/data.yaml.twig to save the form in Yaml format.

<details>
<summary>Content of data.yaml.twig</summary>

TWIG
  {%- macro render_field(form, fields, scope) %}
    {%- import _self as self %}
    {{- "-\n" }}
    {%- for index, field in fields %}
      {%- set show_field = attribute(field, "input@") ?? field.store ?? true %}
      {%- if field.fields %}
        {%- set new_scope = field.nest_id ? scope ~ field.name ~ '.' : scope -%}
        {{- "  " }}{{- self.render_field(form, field.fields, new_scope) }}
      {%- else %}
        {%- if show_field %}
          {%- set value = form.value(scope ~ (field.name ?? index)) -%}
          {%- if value -%}
            {{- "  " ~ (field.name|t|e) ~ ": " }}
            {%- if field.type == "textarea" %}
              {{- (string(value is iterable ? value|json_encode : value)|e|replace({'\n': '<br>'})|raw) ~ "\n" }}
            {%- else %}
              {{- string(value is iterable ? value|json_encode : value) ~ "\n" }}
            {%- endif %}
          {%- endif -%}
        {%- endif %}
      {%- endif %}
    {%- endfor %}
    {{- "  approved: false" }}
  {%- endmacro %}

  {%- import _self as macro %}
  {%- autoescape false %}
    {{- macro.render_field(form, form.fields, '') ~ "\n" }}
  {%- endautoescape %}

</details>

  • Create new template user/plugins/simple-guestbook/templates/partials/simple-guestbook-list.html.twig to list the Guestbook entries from /user/data/simple-guestbook/guestbook.yaml:

<details>
<summary>Content of simple-guestbook-list.html.twig</summary>

TWIG
  <p class="count">{{ simpleGuestbook|count }} guest(s) has left a note</p>

  <ul>
    {% for item in simpleGuestbook %}
      <li>
        <div class="name">{{ item.name }}</div>
        <div class="email">{{ item.email }}</div>
        <div class="message">{{ item.message| raw }}</div>
      </li>
    {% endfor %}
  </ul>

</details>

  • Add css file user/plugins/simple-guestbook/css/style.css, to create same format as Contact form. Yes, I know, we should use scss...

<details>
<summary>Content of style.css</summary>

CSS
  #simple-guestbook h3,
  #simple-guestbook label {
    color: #999;
  }

  section#simple-guestbook {
    background-color: #222;
    background-image: url(../img/map-image.png);
    background-position: center;
    background-repeat: no-repeat;
  }

  section#simple-guestbook h2 {
    color: #fff;
  }

  section#simple-guestbook .form-group {
    margin-bottom: 25px;
  }

  section#simple-guestbook .form-group input,
  section#simple-guestbook .form-group textarea {
    padding: 20px;
  }

  section#simple-guestbook .form-group input.form-control {
    height: auto;
  }

  section#simple-guestbook .form-group textarea.form-control {
    height: 236px;
  }

  section#simple-guestbook .form-control:focus {
    border-color: #fed136;
    box-shadow: none;
  }

  section#simple-guestbook::-webkit-input-placeholder {
    text-transform: uppercase;
    font-family: Montserrat, 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-weight: 700;
    color: #bbb;
  }

  section#simple-guestbook:-moz-placeholder {
    text-transform: uppercase;
    font-family: Montserrat, 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-weight: 700;
    color: #bbb;
  }

  section#simple-guestbook::-moz-placeholder {
    text-transform: uppercase;
    font-family: Montserrat, 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-weight: 700;
    color: #bbb;
  }

  section#simple-guestbook:-ms-input-placeholder {
    text-transform: uppercase;
    font-family: Montserrat, 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-weight: 700;
    color: #bbb;
  }

  section#simple-guestbook .text-danger {
    color: #e74c3c;
  }

  section#simple-guestbook .count,
  section#simple-guestbook .name,
  section#simple-guestbook .email,
  section#simple-guestbook .message {
    color: #fff;
  }

</details>

Page:

  • Create a new guestbook page: /user/pages/01.home/_guestbook/guestbook-form.md, with the following content:

<details>
<summary>Content of guestbook-form.md</summary>

MARKDOWN
  ---
  title: Guestbook
  cache_enable: false

  form:
    name: simple-guestbook
    action: /#guestbook
    fields:
      - name: name
        label: Name
        classes: form-control
        placeholder: Enter your name
        autofocus: off
        autocomplete: on
        type: text
        position: left
        validate:
            required: true

      - name: email
        label: Email
        classes: form-control
        placeholder: Enter your email address
        type: email
        position: left
        validate:
            required: true

      - name: message
        label: Message
        placeholder: Enter your feedback
        type: textarea
        classes: form-control
        position: right
        validate:
            required: true

    buttons:
      - type: submit
        classes: 'btn btn-primary btn-lg'
        value: Submit

    process:
      - save:
            filename: guestbook.yaml
            body: '{% include "forms/data.yaml.twig" %}'
            operation: add
      - message: Thank you! Your feedback will be moderated shortly.
      - reset: true
  ---

  ## Feedback

  ### Lorem ipsum dolor sit amet consectetur.

</details>

  • Add menu-item for new page in user/config/site.yaml:
    YAML
    links:
    - ...
    - title: Guestbook
      url: '#simple-guestbook'
    

Todo:

  • Alter template user/plugins/simple-guestbook/templates/partials/simple-guestbook-list.html.twig to your own liking.
  • Add css to style the list of Guestbook entries.
  • And so much more can be done to polish it...
  • Fix errors/bugs I've created... :man_facepalming:
👍 2
last edited 11/12/21 by pamtbaau
5 years ago

hoawoo ! gratitude ! 😍 :heart_decoration:

your idea of pagination is great! I would love to have this !

But I'm getting blocked by the really second point :

@pamtbaau:
Install Devtools to easily create themes/plugins: $ bin/gpm install devtools

I don't own the server and only have a SFTP access.

5 years ago

@citoyencandide, You might really want to consider to setup a local development environment...

5 years ago

never done this, don't know how to do it neither where to find information on how-to ... is it difficult ?

feel like i'm entering the code zone and will become a coder someday ...

5 years ago

@citoyencandide, Depends on the OS you are using... But whichever OS, Google search is your friend!

5 years ago

I'm trying not to use g00gle services ... because it is definitively not my friend, I would say it's no one friends,!

anyway, there are other search engines ...

And the OS is ubuntu here ...

I've found this || alternate link but there are plenty of programming language and I guess they are not all useful for this project...

Let's have a look to the the grav github repository to find out what could be the useful language ... PHP at 99.7, and 0.3% for other, ok PHP only !

OK, new search, now found this

It say's :

This involves installing and configuring the PHP engine, a MySQL database, an Apache web server, and the XDebug debugger.

As mentioned above, I don't own the server : someone gave me access to only a part of his server, so I won't be root on it.

@pamtbaau I'm I wrong thinking the solution you offered me is not available for me ?

last edited 11/13/21 by barnabécane
5 years ago

@pamtbaau:
You might really want to consider to setup a local development environment…

This is an environment running PHP and Apache on a local machine i.e. laptop/desktop (Apple, Windows or Linux) and not a remote server over which one has no control.

Does Grav fit your needs?
Please note, Grav has been created with developers in mind. Grav is indeed a breeze to adapt and extend when one has experience with programming languages like PHP, Twig and, to a lesser extent, Javascript.

A non-developer, might use Grav successfully if one can except what Grav themes offer out-of-the-box, maybe extended with plugins which offer extra functionality. But as soon as that does not fit ones needs, experience with PHP, Twig, HTML, CSS/SCSS are essential.

If the skills are not available, maybe other environments like Wix ("Wix is user-friendly and makes it possible to build a professional website without knowing how to code.") , wordpress.com, or others may be a better choice.

5 years ago

Is it a subtle way of saying : you don't belong here ?

Anyway, right now, i'm setting up Netbeans, an Integrated Development Environment (IDE) ...

and it's too heavy for my old laptop...

back to commande line ...
upgrade of the disto ... been messing it out
now all thing corrected

stack overflow is my friend

Edit :

can't get the database working, either mySQL or Mariadb ...
been asking for help on ubuntu forum.

last edited 11/13/21 by barnabécane
5 years ago

I cannot change my previous answer, so I write a new one even if this is not the best practice with Discourse...

My laptop is up and running with a working IDE !

The learning curve is steep but continuous

4 years ago

It seems that this issue has been solved or isn't important any more. But as it's one of the first search results for 'grav guestbook', I wanted to add that integration of the guestbook plugin is quite simple if the desired theme already contains blog pages and the guestbook entries may look like blog entries.

I have just done that for the Future2021 theme without changing anything in the guestbook plugin itself.

Suggested topics

Topic Participants Replies Views Activity
Plugins · by Rene, 1 week ago
2 44 1 week ago
Plugins · by Xavier, 4 weeks ago
2 54 4 weeks ago
Plugins · by Luka Prinčič, 7 years ago
3 1181 1 month ago
Plugins · by Sebastian van de Meer, 1 month ago
1 48 1 month ago
Plugins · by PIERROT Alain, 2 months ago
3 73 2 months ago