[OJS 3.3] Hook ‘Schema::get::site’ is not triggerd

Describe the issue or problem
I am trying add some custom fields into site-wide schema by hook Schema::get::site however, this field is not added to site-wide schema.

My Plugin class

<?php




/**

 * @file plugins/generic/customSchemas/CustomSchemasPlugin.inc.php

 *

 * @class CustomSchemasPlugin

 * @ingroup plugins_generic_customSchemas

 *

 * @brief Custom Schemas plugin class

 */






import('lib.pkp.classes.plugins.GenericPlugin');




class CustomSchemasPlugin extends GenericPlugin

{

    public function isSitePlugin()

    {

        return true;

    }

    /**

     * @copydoc Plugin::register()

     */

    function register($category, $path, $mainContextId = null)

    {

        $success = parent::register($category, $path, $mainContextId);

        if ($success && $this->getEnabled($mainContextId)) {

            HookRegistry::register('Schema::get::site', array($this, 'addToSiteSchema'));




            HookRegistry::register('Form::config::before', array($this, 'addToForm'));

        }

        return $success;

    }




    function getDisplayName()

    {

        return 'Custom Schemas';

    }




    function getDescription()

    {

        return 'Custom Schemas';

    }




    public function addToSiteSchema($hookName, $args)

    {


        $schema = $args[0];

        $schema->properties->pageHeaderIconImage = (object) [

            'type' => 'object',

            'apiSummary' => true,

            'multilingual' => true,

            'validation' => ['nullable'],

            'properties' =>  [

                "temporaryFileId" => [

                    "type" => "integer",

                    "writeOnly" => true

                ],

                "name" => [

                    "type" => "string"

                ],

                "uploadName" => [

                    "type" => "string"

                ],

                "width" => [

                    "type" => "integer"

                ],

                "height" => [

                    "type" => "integer"

                ],

                "dateUploaded" => [

                    "type" => "string"

                ],

                "altText" => [

                    "type" => "string"

                ]

            ]

        ];




        $schema->properties->homepageImage = (object) [

            'type' => 'object',

            'apiSummary' => true,

            'multilingual' => true,

            'validation' => ['nullable'],

            'properties' =>  [

                "temporaryFileId" => [

                    "type" => "integer",

                    "writeOnly" => true

                ],

                "name" => [

                    "type" => "string"

                ],

                "uploadName" => [

                    "type" => "string"

                ],

                "width" => [

                    "type" => "integer"

                ],

                "height" => [

                    "type" => "integer"

                ],

                "dateUploaded" => [

                    "type" => "string"

                ],

                "altText" => [

                    "type" => "string"

                ]

            ]

        ];

        return false;

    }




    public function addtoForm($hookName, $form) {

        static $isAddedAdminFormSiteAppearance = false;




        // Don't do anything at the site-wide level

        $request = Application::get()->getRequest();

        $context = $request->getContext();

        if (!$context) {

            $site = $request->getSite();

            $dispatcher = $request->getDispatcher();

            $temporaryFileApiUrl = $dispatcher->url($request, ROUTE_API, CONTEXT_ID_ALL, 'temporaryFiles');

            import('classes.file.PublicFileManager');

            $publicFileManager = new PublicFileManager();

            $baseUrl = $request->getBaseUrl() . '/' . $publicFileManager->getSiteFilesPath();

            if (defined('FORM_SITE_APPEARANCE') && $form->id === FORM_SITE_APPEARANCE) {

                if ($isAddedAdminFormSiteAppearance) {

                    return false; // Nếu đã chạy rồi thì thoát ngay

                }

                

                $form->addField(new \PKP\components\forms\FieldUploadImage('pageHeaderIconImage', [

                    'label' => __('manager.setup.pageHeaderIconImage'),

                    'value' => $site->getData('pageHeaderIconImage'),

                    'isMultilingual' => true,

                    'baseUrl' => $baseUrl,

                    'options' => [

                        'url' => $temporaryFileApiUrl,

                    ],

                ]))

                ->addField(new \PKP\components\forms\FieldUploadImage('homepageImage', [

                    'label' => __('manager.setup.homepageImage'),

                    'tooltip' => __('manager.setup.homepageImage.description'),

                    'value' => $site->getData('homepageImage'),

                    'isMultilingual' => true,

                    'baseUrl' => $baseUrl,

                    'options' => [

                        'url' => $temporaryFileApiUrl,

                    ],

                ]));

                $isAddedAdminFormSiteAppearance = true; // Đánh dấu đã xử lý xong

                return false;

            }

            return false;

        }




        return false;

    }

}

What application are you using?
For example, OJS 3.3.0-x

Anyone who has encountered this error and can help me. Sincerely thank.

Hi @taphuocanh,

If you’re coding a new plugin that’s registering for hooks but those are never being called, it could be because the plugin hasn’t been properly registered with the system. There’s a command-line tool to help do that.

See e.g. this thread:

Regards,
Alec Smecher
Public Knowledge Project Team

I believe I registered it correctly, enabled it in the plugins tab, and if I replace this hook with the Schema::get::context hook, the callback function is triggered, along with the properties added to the context schema.

Hi @taphuocanh,

Did you try the command line tool I referenced above? Could you check what the plugin’s entry in the versions table looks like?

Regards,
Alec Smecher
Public Knowledge Project Team

I tried the command line tool but I did not receive any message for result.

image

And This is plugin’s entry in the versions table

Hi @taphuocanh,

That looks like it should.

I think the problem here is that you’re augmenting the site schema, but the site is loaded very early in the bootstrapping process – before the plugins are loaded – and if I remember correctly it’s cached in-memory for the duration of the request. So extending this with plugins might not be possible.

Can you describe a little bit more about your use case? I may be able to suggest a different approach that doesn’t require extending the site schema.

Regards,
Alec Smecher
Public Knowledge Project Team

Thank you for your support.

I had the same thought as you; it seems the site object is loaded (along with its schema) before the plugins are loaded. And just like other objects, specifically the context, the schema is cached, and I tried changing the context schema, then I had to reload the context schema with code to apply the changes. However, this method didn’t work for the site object.

In this case, I actually need to add some fields to include images and some information to the site to display in indexSite.tpl. To ensure that generic plugins are loaded before other plugins like themes, I separated that part into a custom plugin with the source code I sent above. But the problem still persists.

Currently, I’ve found a temporary solution: creating a separate schema file for the site, site.json, in the app’s schemas folder. Then, the attributes declared here will automatically be merged with the pkp library’s schema as the object’s schema load code dictates.

{

    "properties": {

        "pageHeaderIconImage": {

            "type": "object",

            "multilingual": true,

            "validation": [

                "nullable"

            ],

            "properties": {

                "temporaryFileId": {

                    "type": "integer",

                    "writeOnly": true

                },

                "name": {

                    "type": "string"

                },

                "uploadName": {

                    "type": "string"

                },

                "width": {

                    "type": "integer"

                },

                "height": {

                    "type": "integer"

                },

                "dateUploaded": {

                    "type": "string"

                },

                "altText": {

                    "type": "string"

                }

            }

        },

        

        "homepageImage": {

            "type": "object",

            "multilingual": true,

            "validation": [

                "nullable"

            ],

            "properties": {

                "temporaryFileId": {

                    "type": "integer",

                    "writeOnly": true

                },

                "name": {

                    "type": "string"

                },

                "uploadName": {

                    "type": "string"

                },

                "width": {

                    "type": "integer"

                },

                "height": {

                    "type": "integer"

                },

                "dateUploaded": {

                    "type": "string"

                },

                "altText": {

                    "type": "string"

                }

            }

        }

    }

}

A new problem arises here: although the site’s schema is updated, the site’s settings save function doesn’t correctly support saving the newly created properties, specifically image information. And so I have to implement a temporary solution: using the site Service’s protected method _saveFileParam and executing it inside a callback function of the Site::edit hook. This is really cumbersome and unsafe.

...

    public function register($category, $path, $mainContextId = null)
    {

		$success = parent::register($category, $path);
		if ($success && $this->getEnabled()) {
			// Use a hook to extend the site entity's schema
			HookRegistry::register('Schema::get::site', array($this, 'addToSiteSchema'));
			HookRegistry::register('Site::edit', array($this, 'siteEdit'));

			/
		}
		return $success;
	}

    public function siteEdit($hookName, $args)
	{

		// &$newSite, $site, $params, $request
		$newSite = $args[0];
		$site = $args[1];
		$params = $args[2];
		$supportedLocales = $site->getSupportedLocales();
		$siteService = \Services::get('site');
		$request = Application::get()->getRequest();
		$userId = $request->getUser() ? $request->getUser()->getId() : null;

		// 1. Định nghĩa closure với các tham số tương ứng
		$callSiteSaveFileParam = function ($site, $value, $settingName, $userId, $localeKey = '', $isImage = false) {
			// Bên trong này, $this chính là $instance
			return $this->_saveFileParam($site, $value, $settingName, $userId, $localeKey, $isImage);
		};

		if (array_key_exists('pageHeaderIconImage', $params)) {
			foreach ($supportedLocales as $localeKey) {
				if (!array_key_exists($localeKey, $params['pageHeaderIconImage'])) {
					continue;
				}
				$params['pageHeaderIconImage'][$localeKey] = $callSiteSaveFileParam->call($siteService, $site, $params['pageHeaderIconImage'][$localeKey], 'pageHeaderIconImage', $userId, $localeKey, true);

			}
		}
		if (array_key_exists('homepageImage', $params)) {
			foreach ($supportedLocales as $localeKey) {
				if (!array_key_exists($localeKey, $params['homepageImage']))      {
					continue;
				}
				$params['homepageImage'][$localeKey] = $callSiteSaveFileParam->call($siteService, $site, $params['homepageImage'][$localeKey], 'homepageImage', $userId, $localeKey, true);

			}
		}
		$newSite->_data = array_merge($newSite->_data, $params);
	}

...