picocms / Pico

Pico is a stupidly simple, blazing fast, flat file CMS.
http://picocms.org/
MIT License
3.81k stars 616 forks source link

Relative Image Paths #620

Closed LetOpDrempels closed 2 years ago

LetOpDrempels commented 2 years ago

I am trying to understand what PhrozenByte wrote in #493. It is exactly what i am looking to do, replace ../../assets/cats.jpg with either %assets_url%/cats.jpg or %base_url%/assets/cats.jpg if that'd make more sense. Unluckily the description of the solution is very unclear to me, as to where and how i would have to play with the piece of code PhrozenByte wrote there.

What i did was "create" a plugin called ParsedownRelativePicoPlugin with the code. The image URLs in the html still look like ../../assets/cats.jpg. Now i am not sure if it's something to do with the code (i can't really understand it), or if i wasn't meant to make a plugin out of that code but something else. Could somebody please elaborate a bit more on what i may be doing wrong? Or how else i can make relative image paths work?

mayamcdougall commented 2 years ago

Hi there. 👋🏻

I guess @PhrozenByte is busy, so I'm gonna try my best with this. I'm not a PHP developer, so I don't really do much with Pico's core or Plugins.

I think the issue you're seeing is that the code that @PhrozenByte provided wasn't actually a complete plugin.

My assumption was that he did this intentionally to avoid direct copy-and-pasting of the untested code. The user who opened that issue also didn't come back, so he may have intended to debug or customize it further if the discussion had continued.

The example code seems to lack the opening <?php tag, the API_VERSION declaration, and the enabled variable.

If you change the beginning of the file to this:

<?php

class ParsedownRelativePicoPlugin extends AbstractPicoPlugin
{
    const API_VERSION = 3;

    protected $enabled = false;

    public function onParsedownRegistered(Parsedown &$parsedown)

and then add ParsedownRelativePicoPlugin.enabled: true to your config.yml, it should start working for you. (Alternatively, you can just make it protected $enabled = true; to hard-code it as always enabled.)

This seemed to make it work in my (very brief) testing.

Please keep in mind though that I absolutely cannot verify anything else about the code. He said it was untested, so I've got no idea what other issues might occur. 🤷🏻‍♀️

Hope this helps at least a little. 😅

LetOpDrempels commented 2 years ago

Thank you. I have not been able to make it work for me though. I tried it with a new pico installation. Tried just a ParsedownRelativePicoPlugin.php file in the pico/plugins folder, tried to put it in a ParsedownRelativePicoPlugin folder, tried both ways of enabling the plugin. I guess something must go over my head here.

mayamcdougall commented 2 years ago

Here's my full file if you want to try it. I double checked that it was working for me. I originally tested it with a development build of Pico 3, so I went back and tested it in Pico 2 just to make sure.

I didn't have any issue with it (minus some user error *cough* when I forgot to enable it 🤦🏻‍♀️).

Idk, maybe something went wrong with copying just the start of the file.

And of course it still needs the ParsedownRelativePicoPlugin.enabled: true in config.yml (🤦🏻‍♀️ 🤦🏻‍♀️)

It sounds like you've tested it pretty throughly, so other than double checking with a copy-and-paste of the full file, I'm not sure where the issue would be. 😓

@PhrozenByte Do you have any ideas here? (Ik, I've been tagging you on everything lately. Sorry! 😅😓)

ParsedownRelativePicoPlugin.php

<?php

class ParsedownRelativePicoPlugin extends AbstractPicoPlugin
{
    const API_VERSION = 3;

    protected $enabled = false;

    public function onParsedownRegistered(Parsedown &$parsedown)
    {
        $config = $this->getPico()->getConfig();

        $parsedown = new ParsedownRelative();
        $parsedown->setPico($this->getPico());
        $parsedown->setBreaksEnabled((bool) $config['content_config']['breaks']);
        $parsedown->setMarkupEscaped((bool) $config['content_config']['escape']);
        $parsedown->setUrlsLinked((bool) $config['content_config']['auto_urls']);
    }
}

class ParsedownRelative extends ParsedownExtra
{
    protected $pico;

    public function setPico(Pico $pico)
    {
        $this->pico = $pico;
    }

    public function getPico()
    {
        return $this->pico;
    }

    protected function inlineLink($excerpt)
    {
        $link = parent::inlineLink($excerpt);
        if ($link === null) {
            return;
        }

        $href = $link['element']['attributes']['href'];
        if (($href[0] !== '/') && !preg_match('#^[A-Za-z][A-Za-z0-9+\-.]*://#', $href)) {
            $basePath = dirname($this->getPico()->getRequestFile());
            $targetPath = $this->normalizePath($basePath . '/' . $href);

            $rootDir = $this->getPico()->getRootDir();
            $rootDirLength = strlen($rootDir);
            if (substr($targetPath, 0, $rootDirLength) === $rootDir) {
                $link['element']['attributes']['href'] = $this->getPico()->getBaseUrl() . substr($targetPath, $rootDirLength);
            }
        }

        return $link;
    }

    protected function normalizePath($path)
    {
        $absolutePath = '';
        if (DIRECTORY_SEPARATOR === '\\') {
            if (preg_match('/^(?>[a-zA-Z]:\\\\|\\\\\\\\)/', $path, $pathMatches) === 1) {
                $absolutePath = $pathMatches[0];
                $path = substr($path, strlen($absolutePath));
            }
        } else {
            if ($path[0] === '/') {
                $absolutePath = '/';
                $path = substr($path, 1);
            }
        }

        $path = str_replace('\\', '/', $path);
        $pathParts = explode('/', $path);

        $resultParts = array();
        foreach ($pathParts as $pathPart) {
            if (($pathPart === '') || ($pathPart === '.')) {
                continue;
            } elseif ($pathPart === '..') {
                array_pop($resultParts);
                continue;
            }
            $resultParts[] = $pathPart;
        }

        return $absolutePath . implode('/', $resultParts);
    }
}
LetOpDrempels commented 2 years ago

So it seems the problem was my local server environment, i uploaded the same pico installation to my webhost and there it works. Thank you!

mayamcdougall commented 2 years ago

Huh, weird.

Wish I had more insight for you on it, but I'm glad you got it working! 😁👍🏻

LetOpDrempels commented 2 years ago

So, i tried fresh local server installations with xampp and wamp. With neither i get the plugin to work. I have not checked wamp, but xampp was using php v8.1.2 - not sure if that matters.

I have now installed php itself and used the built in webserver, now the plugin is working. I use php v7.4.28, i also tried with v8.1.3 and the plugin worked there too, but pico gave me some "deprecated warnings" like Deprecated : Return type of Twig\Markup::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in D:\Documents\web\pico3\vendor\twig\twig\src\Markup.php on line 35

I got these warnings using pico 2 and 3 branch.

Anyway, i am happy to have it working locally now as well.

mayamcdougall commented 2 years ago

I have not checked wamp, but xampp was using php v8.1.2

Oh... wow, yeah, I forgot running a web server on Windows was even a thing. 😅

I have absolutely no experience with either, so I can't say much about them.

As far as Pico versions go though... Pico 2.0 doesn't support PHP 8.0+ due to it needing older dependencies.

If you used the Pico 3.0 branch, it's currently still under development, so I wouldn't recommend that. Also, since the plugin code above specified API_VERSION = 3 (and Pico 3 actually expects API_VERSION = 4), you'd also need the PicoDepreciated plugin (3.0-branch) for it to work (or change the API_VERSION to 4). So that may have been part of the issue.

Finally, there's the Pico 3.0 "alpha" package mentioned in the readme right now. It's perfectly stable to use, as it's basically just Pico 2 with some partially updated dependencies (for example Twig 2 instead of Twig 3, which should be in the final release). This will give you an environment as close to Pico 2 as possible while supporting PHP 8. Also, it still uses API_VERSION = 3, so don't change the plugin code like I mentioned before.

The best options right now are to either run Pico 2 on PHP 7 (despite it being out of support, so there could be some future security issue there), or to grab the Pico 3 Alpha bundle and use PHP 8.

Hope that insight helps a little with your setup.

Let me know if there's anything else I can help with. ❤️

github-actions[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in two days if no further activity occurs. Thank you for your contributions! :+1: