I'm not going to explain to you, what meta tags are and how important they are for SEO. Instead, I would like to start right now by implementing an extended functionality in Scriptor, that allows us to generate the metadata for each page and include it via meta tags in the template in the frontend.

We will use the Scriptor's hooking functions, which are available in version 1.4.6 and higher, to slightly customize the Pages class in the CMS backend.

The file structure of the PagesMetatags module:

PagesMetatags/
└───css/
│  └───styles.css
└───lang/  
│  └───en_US.php
└───PagesMetatags.php

To create our module, we simply create a PHP class called PagesMetatags which extends Module class. The file is located in the directory /site/modules/PagesMetatags/ and named like the module itself PagesMetatags.php:

<?php
namespace Scriptor;
use Imanager\Field;

class PagesMetatags extends Module {
    ...
}

Okay, let's add a method for installation in our class. The method should check if a field named Meta already exists when you open the page editor (Admin), otherwise it should be created first:

<?php
namespace Scriptor;
use Imanager\Field;

class PagesMetatags extends Module 
{
    public function install($event)
    {
        // Field name 
        $fName = 'meta';
        // Get the Pages object from the hooked class.
        $pages = $event->object->pages;
        // Check the field name already exists
        $exist = $pages->getField("name=$fName");
        // If the meta field already exists, display the corresponding message.
        if($exist) {
            $this->msgs[] = [
                'type' => 'success',
                'value' => 'A field with the name <strong>'.$fName.'</strong> already 
                exists in Scriptor\'s Page object. You can now remove the installation 
                request from the <strong>custom.scriptor-config.php</strong> file.'
            ];
            return;
        }
        // If the meta field does not exist yet, try to install it
        try {
            $field = new Field($pages->id);

            $field->set('name', $fName)
                ->set('type', 'array')
                ->set('label', 'Page Metadata')
                ->save();

            $this->msgs[] = [
                'type' => 'success',
                'value' => 'The Scriptor\'s Page object have been successfully '.
                    'extended with a new field <strong>'.$fName.'</strong>.'
            ];

        } catch (\Exception $e) {
            echo 'Exception: ' ,  $e->getMessage(), '<br>';
        }
    }
}

The install() method is only needed once to create the metafield, it should be called once and then commented out or removed from the PagesMetatags class.

However, the PagesMetatags module must first be made known to Scriptor. This can be done within your custom.scriptor-config.php file:

...
'modules' => [
    'PagesMetatags' => [
        'menu' => '',
        'position' => 100,
        'active' => true,
        'auth' => true,
        'autoinit' => true,
        'path' =>  'modules/PagesMetatags/PagesMetatags',
        'class' => 'PagesMetatags',
        'display_type' => [],
        'description' => 'Extension for Scriptor Editor for storing the page metadata.'
    ]
],

In the same file, we also install our hook. We attach our hook to the init() method of the Pages class. This is done by specifying only the class name Pages without a hooked method:

...
    'modules' => [
        'PagesMetatags' => [
            'menu' => '',
            'position' => 100,
            'active' => true,
            'auth' => true,
            'autoinit' => true,
            'path' =>  'modules/PagesMetatags/PagesMetatags',
            'class' => 'PagesMetatags',
            'display_type' => [],
            'description' => 'Extension for Scriptor Editor for storing the page metadata.'
        ]
    ],
    'hooks' => [
        'Pages' => [ // Hooked class without method
            [
                'module' => 'PagesMetatags', // Module
                'method' => 'install' // Method to call.
            ]
        ]
    ]

Now, start the installation by clicking on Pages menu in the editor and opening one of your pages in edit mode. If you have done everything properly, you should see this message:

Field created

After that, delete or comment out the install() method, so that it is no longer called when you are in the editor view:

/* 'Pages' => [ // Hooked class without method
       [
            'module' => 'PagesMetatags', // Module
            'method' => 'install' // Method to call.
       ]
    ], */

Next, two methods are required, one is used to display the fields in the editor view of the pages and the other is required to store the field values. Let us extend the our class with the following two methods, renderPageMetaFields and savePageMeta:

<?php
namespace Scriptor;
use Imanager\Field;

class PagesMetatags extends Module 
{
   /**
    * Generates and displays markup of meta fields in the page editor.
    * 
    * @param object $event - Hook event
    */
    public function renderPageMetaFields($event)
    {
        // Get current page
        $page = $event->object->page;
        // Embed custom style sheet
        $this->addHeaderResource('css', 
            dirname($this->siteUrl).'/site/modules/PagesMetatags/css/styles.css'
        );
        // Declare your meta field names
        $meta = [
            'title' => '',
            'description' => '',
            'keywords' => '',
        ];
        // Use metafield data of the current page, if available 
        if(isset($page->meta)) $meta = $page->meta;
        // Build meta fields markup
        ob_start(); ?>
        <div class="form-control">
            <fieldset>
                <legend><?php echo $this->i18n['metadata_legend']; ?></legend>
                <div class="form-control">
                    <label for="metatitle"><?php echo $this->i18n['metatitle_label']; ?></label>
                    <p class="info-text"><i class="fa fa-exclamation-triangle" 
                      aria-hidden="true"></i> <?php
                        echo $this->i18n['metatitle_field_infotext'] ?></p>
                    <input name="metatitle" id="metatitle" type="text" value="<?php 
                       echo $meta['title']; ?>">
                </div>

                <div class="form-control">
                    <label for="metadescription"><?php 
                      echo $this->i18n['metadescription_label']; ?></label>
                    <p class="info-text"><i class="fa fa-exclamation-triangle" 
                      aria-hidden="true"></i> <?php
                        echo $this->i18n['metadescription_field_infotext'] ?></p>
                    <input name="metadescription" id="metadescription" type="text" 
                        value="<?php echo $meta['description']; ?>">
                </div>

                <div class="form-control">
                    <label for="metakeywords"><?php 
                      echo $this->i18n['metakeywords_label']; ?></label>
                    <p class="info-text"><i class="fa fa-exclamation-triangle" 
                       aria-hidden="true"></i> <?php
                        echo $this->i18n['metakeywords_field_infotext'] ?></p>
                    <input name="metakeywords" id="metakeywords" type="text" value="<?php 
                        echo $meta['keywords']; ?>">
                </div>
            </fieldset>
        </div>
        <?php 
        // Add markup to the hooked method return
        $event->return .= ob_get_clean();
    }

    /**
     * Save the metadata if the page was saved successfully.
     * 
     * @param object $event - Hook event
     * 
     */
    public function savePageMeta($event)
    {
        // Check if saving the page was successful, if not just leave
        if(!$event->return) return;

        // Get the variables we use locally
        $sanitizer = $this->imanager->sanitizer;
        $page = $event->object->page;

        // Prepare meta before saving
        $meta = [
            'title' => $sanitizer->text($this->input->post->metatitle),
            'description' => $sanitizer->text($this->input->post->metadescription),
            'keywords' => $sanitizer->text($this->input->post->metakeywords)
        ];
        // Set meta and save 
        $event->return = $page->set('meta', $meta)->save();
        // Display error message if saving did not succeed
        if(!$event->return) {
            $this->msgs[] = array(
                'type' => 'error',
                'value' => $this->i18n['error_saving_meta']
            );
        }
    }

    public function install($event)
    {
        // Field name 
        $fName = 'meta';
        // Get the Pages object from the hooked class.
        $pages = $event->object->pages;
        // Check the field name already exists
        $exist = $pages->getField("name=$fName");

        // If the meta field already exists, display the corresponding message.
        if($exist) {
            $this->msgs[] = [
                'type' => 'success',
                'value' => 'A field with the name <strong>'.$fName.'</strong> already 
                exists in Scriptor\'s Page object. You can now remove the installation 
                request from the <strong>custom.scriptor-config.php</strong> file.'
            ];
            return;
        }
        // If the meta field does not exist yet, try to install it
        try {
            $field = new Field($pages->id);

            $field->set('name', $fName)
                ->set('type', 'array')
                ->set('label', 'Page Metadata')
                ->save();

            $this->msgs[] = [
                'type' => 'success',
                'value' => 'The Scriptor\'s Page object have been successfully '.
                    'extended with a new <strong>'.$fName.'</strong> field.'
            ];

        } catch (\Exception $e) {
            echo 'Exception: ' ,  $e->getMessage(), '<br>';
        }
    }
}

We also apply our methods to the Pages class by extending the custom.scriptor-config.php:

'hooks' => [
    /* 'Pages' => [
        [
            'module' => 'PagesMetatags',
            'method' => 'install'
        ]
   ], */
   'Pages::afterRenderEditorTemplateField' => [
        [
            'module' => 'PagesMetatags',
            'method' => 'renderPageMetaFields'
        ]
    ],
    'Pages::afterSavePage' => [
        [
            'module' => 'PagesMetatags',
            'method' => 'savePageMeta'
        ]
    ]
]

Pages::afterRenderEditorTemplateField causes the method renderPageMetaFields to be called after the template field has been rendered in the editor view.

Pages::afterSavePage causes the method savePageMeta to be called after the page has been saved.

As you can see, the renderPageMetaFields method is relatively simple. It creates HTML of the meta fields to be displayed and appends them to the return of the RenderEditorTemplateField method:

...
$event->return .= ob_get_clean();
...

The same applies to the savePageMeta method. This method checks if the page has been saved successfully and if so, it saves the entered metadata.

Now, we still need the styles so that we can adapt the look of the meta fields to the design of the page editor styles.css:

fieldset {
    box-sizing: inherit;
    padding: 20px;
    border: 3px solid rgba(0,0,0,.07);
    border-radius: 3px;
}

legend {
    font-weight: 700;
    padding: 0 5px;
}

Finally we create the file with translation strings, currently only en_US.php (you can extend it with your language):

<?php
return [
    'metadata_legend' => 'Meta Data',
    'metatitle_label' => 'Meta title',
    'metatitle_field_infotext' => 'Enter a meta title (also called title tag).',
    'metadescription_label' => 'Meta description',
    'metadescription_field_infotext' => 'Enter the meta description (also called a meta description attribute or tag).',
    'metakeywords_label' => 'Meta keywords',
    'metakeywords_field_infotext' => 'Enter your keywords separated by commas.',
    'error_saving_meta' => 'The metadata could not be saved!'
];

Open a page in editmode, the page should have an additional Meta Data field set:

Field created

How do you retrieve the stored meta values in frontend on the website

You can access the metadata of a page as follows:

echo $site->meta[~YOUR_FIELD_NAME~];

For example, to retrieve the meta title, insert this code into your template:

<title><?php echo $site->meta['title']; ?></title>

To get meta description use this code:

<meta name="description" content="<?php echo $site->meta['description']; ?>">

The same is also the case for the keywords:

<meta name="keywords" content="<?php echo $site->meta['keywords']; ?>">

However, you may forget to specify the meta title, since the page title is very important and should not be empty, the default page title should always be shown as an alternative:

<title><?php echo ($site->meta['title']) ? $site->meta['title'] : 
        $site->title; ?> - <?php echo $site->config['site_name']; ?></title>


Here you can download files used in this tutorial: PagesMetatags.zip