Cannot redeclare generateOgImage90210() (previously declared in /....site/config/config.php:184)

Hi there

I am at a loss and hope someone can help me out. Months ago, I created a function in the config.php file to generate an og image. It is triggered when a page is updated or the title changed. It worked spendidly for months.

Now I have made some additional changes, totally unrelated to that - yes, I have added two other routes but they should not matter.

Anyway, what happens now is that once I try to publish a page (blog-article), I get the following error:

Line 184 is the line where the function is declared (not in the below code because I remove some “sensitive” stuff) - and as you can see by the exotic name, it certainly is NOT declared somewhere else.

I tried to

  • remove the two added routes I added - no change
  • uncommented the call to the function in the two hooks - no change

What “worked” was removing the function/commenting it out.

I am really at a loss what causes this problem. Any ideas?

Here is my config.php:

<?php

/**
 * This is the general config file for the xxxxx website.
 * For environment specific config and sensitive data such as API keys,
 * please see the env.php file.
 * 
 * For all config options see https://getkirby.com/docs/reference/system/options
 * 
 */
return [
    'debug' => false,
    'home' => 'blog',
    //'languages.detect' => true, ## Lets enable automatic language detection
    'languages' => true, ## Lets enable I18N
    'smartypants' => true, // https://getkirby.com/docs/reference/system/options/smartypants
    'auth' => [
        'trials' => 3, // Number of wrong trials per timeout period  before login gets blocked for the current IP address and user
        'methods' => [
            'password' => ['2fa' => false], // Send second factor (Code via E-Mail) - current TOTP plugins all look dead/old
        ],
        'debug' => false, // We don't want to debug auth
    ],
    'panel' => [
        'css' => 'assets/css/custom-panel.css' // Lets add a custom design to our panel
    ],
    'routes' => [
        // Let's get rid of the "blog" in the URL
        [
            'pattern' => '(:any)',
            'language' => '*',
            'action'  => function($lang, $uid) {
                $page = page($uid);
                if(!$page) $page = page('blog/' . $uid);
                if(!$page) $page = site()->errorPage();
                return site()->visit($page, $lang); // activate the selected page and set a language
            }
        ],
        [
            'pattern' => 'blog/(:any)',
            'language' => '*',
            'action'  => function($lang, $uid) {
                go($uid);
            }
        ],
        // Lets add a multi-lingual sitemap - nobody really uses the weights, right? So lets ignore them
        [
            'pattern' => 'sitemap.xml',
            'action' => function () {
                // Define the pages to ignore
                $ignore = ['home'];
                $pages = site()->pages()->listed()->index();
                $languages = site()->translations() ?: [];
                $sitemap = '<?xml version="1.0" encoding="utf-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">';
                foreach ($pages as $page) {
                    if (in_array($page->uri(), $ignore)) continue;
                    $sitemap .= '<url>
                                    <loc>'.html($page->url()).'</loc>';
            
                        foreach ($languages as $lang) {
                            $sitemap .= '<xhtml:link rel="alternate" hreflang="'.$lang->code().'" href="'.html($page->url($lang->code())).'"/>';
                        }
            
                    $sitemap .= '</url>';
                }
            
                $sitemap .= '</urlset>';
            
                return new Kirby\Cms\Response($sitemap, 'application/xml');

            }
        ],
        // And make sure that we redirect /sitemap to /sitemap.xml with HTTP Code 301 - Moved Permanently
        [
            'pattern' => 'sitemap',
            'action'  => function() {
              return go('sitemap.xml', 301);
            }
        ],
    ],
    // We are adding hooks to maintain created/updated by/on fields and
    // to generate ogimages dynamically if the title of a page changes
    // Also, we want to allow for automation for published articles
    'hooks' => [
         'page.create:after' => function (Kirby\Cms\Page $page) {
            $createdOn = Kirby\Toolkit\Date::now();
            $user = kirby()->user();
            $page->update([
                'createdOn' => $createdOn,
                'createdBy' => $user,
              ]);

        },
        'page.update:after' => function (Kirby\Cms\Page $newPage, Kirby\Cms\Page $oldPage) {
            $updatedOn = Kirby\Toolkit\Date::now();
            $updatedBy = kirby()->user();
            //$ogImageURL = generateOgImage90210($newPage);
            $newPage->update([
                'lastUpdatedOn' => $updatedOn,
                'lastUpdatedBy' => $updatedBy,
                'Ogimage' => $ogImageURL,
              ]);
        },
        'page.changeTitle:after' => function (Kirby\Cms\Page $newPage, Kirby\Cms\Page $oldPage) {
           // $ogImageURL = generateOgImage90210($newPage);
            $newPage->update([
                'Ogimage' => $ogImageURL,
              ]);

        },
    ],
    // Matomo Plugin Config
    'sylvainjule.matomo.url'        => 'https:/xxxxxx.com',
    'sylvainjule.matomo.id'         => '1',
    'sylvainjule.matomo.disableCookies' => true,
    'sylvainjule.matomo.debug'          => false,
    // Content Security Policy Plugin
    'bnomei.securityheaders.enabled' => true,
    // Let's make sure we add the analytics subdomain dynamically
    'bnomei.securityheaders.setter' => function (\Bnomei\SecurityHeaders $instance) {
        // https://github.com/paragonie/csp-builder#build-a-content-security-policy-programmatically
        // Add a new source domain to the whitelist
        $csp = $instance->csp();
        $csp->addSource('script-src', 'https://xxxxxx');
        $csp->addSource('connect-src', 'https://xxxxxxx');
    },
    // SEO Plugin Config
    'tobimori.seo' => [
        'sitemap.active' => false,
        'canonicalIncludesWWW' => false,
        'robots' => [
            'active' => true,
            'content' => [
                '*' => [
                    'Allow' => ['/'],
                    'Disallow' => ['/kirby', '/panel', '/content', '/site', '/media', '/assets']
                ]
            ]
        ]
    ],
    // Translation Plugin Config
    //'tobiaswolf.machine-translation.deepl.authKey' => '', => saved in env.php
    // Lets make sure we show a nicely design error page for fatal errors not the ugly "This site is offline" OOB page
    'fatal' => function($kirby, $exception) {
        include $kirby->root('templates') . '/fatal.php';
      }
];

/**
 * This function generates dynamic opengraph images depending on the title
 * of a page. It then writes the UUID into the appropriate content-file of
 * each page for use in the tobimori.seo plugin
 *
 * @param Kirby\Cms\Page $page
 * @return void
 */
function generateOgImage90210(Kirby\Cms\Page $page){

        // Load the blank og image background as the image basis
        $baseImgPath = './assets/images/ogimage_blank.png';
        $canvas = imagecreatefrompng($baseImgPath);

        // Define colors
        $brandColor      = imagecolorallocate($canvas, 29, 5, 26);
        $textColor       = imagecolorallocate($canvas, 29, 17, 1);

        // Path to .ttf font file
        $fontFile = './assets/css/font/Montserrat-Bold.ttf';

        // Write page title to canvas
        $title  = $page->title()->toString();
        $title  = wordwrap($title, 25);
        $printedText = imagefttext($canvas, 50, 0, 150, 185, $textColor, $fontFile, $title);

        // Draw rectangle - relatively seen below the printed text
        imagefilledrectangle($canvas, $printedText[0], $printedText[1]+30, $printedText[0]+365, $printedText[1]+40, $brandColor);

        // Save image to the disk (temporarily)
        $tempImageFile = './content/'.$page->diruri().'/'.$page->slug().'.'.$page->translation()->code().'.png';
        $ogImageFile = $page->slug().'.'.$page->translation()->code().'.ogimage.png';

        // If writing the PNG is successful
        if(imagepng($canvas, $tempImageFile)){

            // Check if the ogImage already exists. If yes, replace it...
            if(F::exists(kirby()->root().'/content/'.$page->diruri().'/'. $ogImageFile)){

                $file = $page->file($ogImageFile)->replace($tempImageFile, true);

                return $file->uuid()->toString();

            } else {

                // ... if not, create a Kirby File object, save as ogimage and remove the original image file created via PHP
                $file = File::create(['filename' => $ogImageFile, 'parent' => $page, 'source' => $tempImageFile], true);

                return $file->uuid()->toString();
            }

        } else {

            // Generation did not work - lets not save any file reference then
            return '';
        }   

}

Have you done a full-text search to check the function is not declared twice?

If you check your php logs, can you find a strack trace for the error shown in the Panel?

What I forgot to mention is that two kirby websites are sharing on Kirby directory. So there is

and within both blog.mysite.com as well as mysite.com I have a symbolic link to the kirby dir. The config.php on the other kirby site also includes the function but with the original name generateOgImage. No matter how I rename it (generateOgImage90210 is not a name I would normally use ;)) the error appears.

And no, there is no second declaration in the blog.mysite.com directory:

image

The php error log shows the following:

[30-Jun-2024 14:53:50 Europe/Berlin] PHP Fatal error:  Cannot redeclare generateOgImage90210() (previously declared in /usr/www/users/xxxxxx/site/config/config.php:184) in /usr/www/users/xxxxxxx/site/config/config.php on line 184
[30-Jun-2024 14:53:50 Europe/Berlin] Whoops\Exception\ErrorException: Cannot redeclare generateOgImage90210() (previously declared in /usr/www/users/axxxxxxxx/site/config/config.php:184) in /usr/www/users/xxxxxxxxx/site/config/config.php:184
Stack trace:
#0 /usr/www/users/xxxxxxxxx/kirby/vendor/filp/whoops/src/Whoops/Run.php(510): Whoops\Run->handleError()
#1 [internal function]: Whoops\Run->handleShutdown()
#2 {main}

I am not quite sure when the error started appearing but it was around yesterday when I was implementing hooks and the URL rewrite of removing the “blog” part of the URL as per this thread.

… is it possible, that this has something to do with my article blueprint?

This is the (relevant?) beginning of article.yml:

title: Article
num: '{{ page.date.toDate("Y-m-d") }}'
icon: đź“–
create:
  fields:
    - date
  slug: "{{ page.date.toDate('Y-m-d') }}-{{ page.title.slug }}"
options:
  preview: "{{ page.parent.url }}/{{ page.slug }}"
tabs:
  article:
    status:
      draft:
        label: Draft
        type: pages
        parent: site.find("blog")
        text: The article is still in draft mode. It can only be seen by editors with panel access.
      unlisted:
        label: In Review
        type: pages
        parent: site.find("blog")
        text: The article is online and can be visited with the direct URL. The team must still give the final go to publish it.
      listed:
        label: Published
        type: pages
        parent: site.find("blog")
        text: The article is online and listed in the blog

Please move the function to a plugin and report if the error persists.

Thank you @texnixe , that did the trick. Do you know why?

For everyone else who might have the problem - just creating your plugin directory as per the docs and putting the function in the index.php will do, 5 second trick.

Probably down to load order. I think plugins get loaded after all else. So each sites plugin directory gets loaded after the core / your template code, which resolves the clash.

And forgive me… i mean nothing by this… you have two sites there which requires 2 Kirby liscences, despite the symlink. Just ensure you are liscened up properly :slight_smile: forgive me if you are :heart:

I guess it happens because the config is loaded again at some point. IMO, the config file is not the right place to store functions in general.

1 Like

I have bought my second license before I even created the dir on the server @jimbobrjames - that is the least I can do for this excellent piece of software and support provided :+1:

2 Likes

Kirby is freaking awesome :slight_smile: