WordPress / wordpress-playground

Run WordPress in the browser via WebAssembly PHP
https://w.org/playground/
GNU General Public License v2.0
1.64k stars 257 forks source link

Define a PHP constant so PHP code can detect it is running in Playground #483

Closed eliot-akira closed 11 months ago

eliot-akira commented 1 year ago

Recently, there was an issue where the Create Block Theme plugin was throwing an error due to limited networking support in Playground.

Maybe we could disable Google fonts if the plugin is on a Playground instance for now.

If the Playground could define a PHP constant, like WORDPRESS_PLAYGROUND, then plugins/themes could detect it and modify their behavior accordingly.


Another possible use case: when running wp-now on the server, a locally installed wp-cli could detect and interact with it somehow.

we may just need to tell WP-CLI where to find the WordPress files, SQLite database, and project files. At the moment however, the knowledge of this only exists in the virtualized filesystem.

If wp-now could define a PHP constant, like WP_NOW, it could help wp-cli (and maybe other tools) to implement a deeper integration. How exactly the communication between them would work is a bigger question. :upside_down_face:


@adamziel's recommendation:

I recommend not detecting Playground because it will break your plugin in many contexts.

You could, however, explicitly create a constant via a Blueprint and then check for that:

https://playground.wordpress.net/#{%22constants%22:{%20%22MYPLUGIN_SETUP_DEMO_CONTENT%22:%22true%22}}

This way your plugin will only load the demo data when that's expected.

I understand why blanket Playground detection is tempting for setting up demo content. Playground, however, is more than just the app on https://playground.wordpress.net/. Here's a few contexts where loading the demo content would be harmful instead of useful:

If installing the plugin does something unexpected to the site content in either of these tools, the user will likely be surprised and assume the plugin is broken.

adamziel commented 1 year ago

Good idea @eliot-akira! A few thoughts:

eliot-akira commented 1 year ago

Some related ideas:

adamziel commented 1 year ago

@danielbachhuber I'll add a constant/something global in php_wasm.c so that it's universally available for all Playground apps

danielbachhuber commented 1 year ago

@adamziel Sounds good.

dmsnell commented 1 year ago

could this unintentionally push people to write code that behaves differently on the playground and then gets shipped to production and fails because it's only ever tested in this environment?

can we find a way to fix those errors in the Playground without making the PHP code aware that it's running inside of it?

eliot-akira commented 1 year ago

code that behaves differently on the playground

A possible use case is for a plugin to detect the Playground and adjust its behavior to be compatible with it, such as disabling Google Fonts. This kind of compatibility issue is probably better solved on Playground side, instead of per plugin.

Another use case is for a plugin or CLI tool to integrate with the Playground environment by design.

For example, theoretically WP-CLI could interact with a locally running Playground's files and SQLite database. Or, a plugin could provide a settings screen or custom post type edit screen, which optionally communicates with the Playground client and parent context, if any.

An idea in particular I'm exploring is to have two iframes connected to a Playground client: an admin edit screen and the site frontend as preview. When something changes on one side, I'd like for the plugin to send a message to the "host" so the preview pane can be refreshed. And this feature can be conditionally loaded when running inside the Playground.

There can be other ideas involving multiple iframes - maybe a responsive design tool - which could benefit from having environment-specific code to support bi-directional exchange between the host running the Playground client and the embedded website.

But then again, the host can always set such env variables for a specific project or plugin.

Maybe there's no need for Playground itself to provide them as part of the public API. I noticed PHP WASM sets a server constant SERVER_SOFTWARE as PHP.wasm. I suppose this is undocumented, but someone's code could start to depend on that particular value, just because it's there. It reminds me of browser user agent strings and why we'd want to avoid environment-specific code.

adamziel commented 1 year ago

Another way to resolve this would be to document how to do feature detection (network, message passing, pthreads, ...) instead of blanket Playground detection – this could make Playground-based applications portable by default. As for wp-cli, the SQLite integration was primarily meant for non-Playground WordPress so that would have to rely on feature detection anyway.

adamziel commented 1 year ago

@eliot-akira also, I can't find the original issue where you posted a dual-pane screenshots of Loops and Logic but just wanted to say it was super cool! ❤️

eliot-akira commented 1 year ago

Maybe it was this screenshot I posted in the "self-hosted" issue thread. I removed it later because I wasn't sure if the image was adding to the discussion, haha. :)

Screenshot 2023-05-31 at 20-09-09 WordPress Playground

On the left is the site frontend. On the right side is the template editor running in the WP admin, which I wanted to integrate better with the Playground host. I solved this by creating a mu-plugin from the host, and using plugin hooks, styles and scripts to modify the editor screen.

So it was possible and easy to communicate from the Playground host to the embedded site. (The other direction seemed tricky, but I imagine would be made simple with post_message_to_js.).

All that is to say, there was no need for a general-purpose PHP constant defined by Playground or PHP WASM. Any application that uses Playground can have their own project-specific protocol or convention to integrate with the embedded website and its plugins.

rene-hermenau commented 1 year ago

@adamziel Can you please let us know how we plugin devs can detect if our plugin runs in playground? Since playground is in public, we need to escalate this. I need a way to disable our plugin in playground and show a message to the user if they run it in playground. WP STAGING will never run in playground. Thank you.

rene-hermenau commented 1 year ago

Easiest option seems to be checking the site_url or the hostname of the iframe which is playground.wordpress.net We will try that for the moment.

adamziel commented 1 year ago

@rene-hermenau php_sapi_name() === "wasm" should do the trick at the moment, but it may change in the future so I'd be careful. As mentioned earlier in the discussion, Playground will evolve and eventually support things like networking (even incoming requests). I'd rely on feature detection instead, as in "can I make a HTTP request?" – this would be useful even in production environments, as in "let's make sure this hosting configuration can support my plugin"

rene-hermenau commented 1 year ago

Thanks @adamziel

adamziel commented 11 months ago

The technical ability to define PHP constants got added in https://github.com/WordPress/wordpress-playground/pull/748

The feature request to always define a Playground-related constant for environment detection is a wontfix for now.

Also, to add to the discussion:

A possible use case is for a plugin to detect the Playground and adjust its behavior to be compatible with it, such as disabling Google Fonts. This kind of compatibility issue is probably better solved on Playground side, instead of per plugin.

Google Fonts do work now so there's no need to disable that particular features anymore! 🎉

Mte90 commented 7 months ago

I see various PR and tickets closed but is not clear if at the end a constant was added for the detection.

Someone else found a way by PHP to detect if it is playground?

adamziel commented 7 months ago

@Mte90 no constant was added in the end. Would feature detection suffice in your use-case? Ideally, as Playground gains more and more parity with PHP, relying on feature detection will gradually start using those features.

eliot-akira commented 7 months ago

For the use case described in #656, feature detection doesn't seem suitable:

Can be used to prefill the website with demo data or with different banners like a tour

It sounds like they would like to have different behavior, not based on available features of the environment, but based on whether the plugin/site is running in the Playground.

Someone else found a way by PHP to detect if it is playground?

I get the feeling, if there is no official PHP constant provided, then users will implement their own constant (which might be fine as a solution), or find a way to detect the Playground in other ways.

Mte90 commented 7 months ago

Exactly, feature detection is not something that assure what is the environment. If I need to load demo data how can I be sure that is not happening to a customer website instead then playground as example?

bgrgicak commented 7 months ago

This PR will add a $playground_scope global variable that you could use to detect if the site is using Playground.

adamziel commented 7 months ago

I recommend not detecting Playground because it will break your plugin in many contexts.

You could, however, explicitly create a constant via a Blueprint and then check for that:

https://playground.wordpress.net/#{%22constants%22:{%20%22MYPLUGIN_SETUP_DEMO_CONTENT%22:%22true%22}}

This way your plugin will only load the demo data when that's expected.

I understand why blanket Playground detection is tempting for setting up demo content. Playground, however, is more than just the app on https://playground.wordpress.net/. Here's a few contexts where loading the demo content would be harmful instead of useful:

If installing the plugin does something unexpected to the site content in either of these tools, the user will likely be surprised and assume the plugin is broken.

adamziel commented 7 months ago

This PR will add a $playground_scope global variable that you could use to detect if the site is using Playground.

Any Playground-specific variable or constant is a private API and may get renamed or disappear without a warning. A long-term goal for Playground is to have 1:1 feature parity with native PHP and be indistinguishable from it.

Mte90 commented 7 months ago

@adamziel so detect if it is running in playground it is important and we can't use a feature detection.

There are tons of different hosting around, docker environments, SAAS and services that change a lot of things and we can't use that solution. A variable in the playground URL is not very suitable for various reasons, if the playground is added again to the wordpress repository there will be this variable added as example? Otherwise is useless. Also this require to parse the URL and in some cases the URL is not accessible in wordpress, like in CLI or in specific WP hooks. A constant is the only solution that can works everywhere in any case, wordpress does the same with the https://developer.wordpress.org/reference/functions/wp_get_environment_type/ that use a constant.

I think that if the playground there will be not have something that can confirm at 100% is playground, that works in all the playground scenario it will be a mess and no plugin developer will want to implement the support. We just sell a single plugin and I have to deal with hostings and cases where I have difficulties to detect a page builder, I can just imagine what it will be a feature detection for this. Also I think that a constant is more performant compared to a URL parameter detection like this, it will be required some regex as in php the # parameters are not readable easily (compared to JS).

adamziel commented 7 months ago

@Mte90 oh, I think my comment wasn't clear – should have mentioned how the # part works!

Playground is configured using Blueprints – JSON files with various setup instructions. One way to load a Blueprint is to pass it in the URL after the # sign. For example, if I have the following Blueprint:

{
  "constants": {
    "MYPLUGIN_SETUP_DEMO_CONTENT": "This constant is set automatically"
  },
  "steps": [
    {
      "step": "writeFile",
      "path": "/wordpress/wp-content/mu-plugins/0-test-constant.php",
      "data": "<?php var_dump(MYPLUGIN_SETUP_DEMO_CONTENT); die(); "
    }
  ]
}

I could load it using the following URL: https://playground.wordpress.net/#{%20%22constants%22:%20{%20%22MYPLUGIN_SETUP_DEMO_CONTENT%22:%20%22This%20constant%20is%20set%20automatically%22%20},%20%22steps%22:%20[%20{%20%22step%22:%20%22writeFile%22,%20%22path%22:%20%22/wordpress/wp-content/mu-plugins/0-test-constant.php%22,%20%22data%22:%20%22%3C?php%20var_dump(MYPLUGIN_SETUP_DEMO_CONTENT);%20die();%20%22%20}%20]%20}

If you go to that page, you'll see it says:

string(34) "This constant is set automatically"

This is because Playground sets up any constant you provide via the Blueprint.

WordPress Plugin Directory even offers a "live preview" feature for plugins that ship a Blueprint:

https://make.wordpress.org/meta/2023/11/22/plugin-directory-preview-button-revisited/

You can try it live in this plugin.

adamziel commented 7 months ago

@Mte90 I'd also love to learn more about your use-case. Which plugin are you maintaining? In what context would you like to set up the demo content? Is it for a live demo on your website?

Mte90 commented 7 months ago

With blueprints the only way to detect if it is in playground is just to move the user to a dedicated page in the blueprint file. The next step it will be save something in the db to keep this information that the user is in playground, that is not so very performant after all.

I didn't knew of them and it is interesting for sure but I am still of the idea that a constant is the best solution that works in any case.

In my case like the others it will be to load demo data and probably various banner to explain the users how to use it. Also simplify a lot the development a constant to turn on instead of something just for playground. WordPress has a config file, why not use it for a constant?

adamziel commented 7 months ago

at 100% is playground, that works in all the playground scenario

I understand where you're coming from, and at the same time detecting "all playground scenarios" would be misleading.

In web browsers alone, Playground could be:

Now, assume one of these plugins has an absolute check if ( PLAYGROUND ) { load_demo_content(); }. It would make that plugin unusable for anything else than a single standalone demo.

And there's a lot more "playground scenarios" than that. Playground works in web browsers, in Node.js, offline in the CLI, inside native mobile apps, docker, and there will only be more cases. There isn't any blanket statement to make about all these use-cases.

An offline, non-browser development environments like wp-now is a drop-in replacement for a regular PHP where everything is meant to work in the exact same way.

With blueprints the only way to detect if it is in playground is just to move the user to a dedicated page in the blueprint file.

I'm not sure I follow. With the following Blueprint, MY_CONST will be always defined in every PHP file and request within that Playground instance:

{
  "constants": {
    "MY_CONST": "true"
  }
}

I only used a writeFile with an mu-plugin in my previous example to provide a clickable demo.

The next step it will be save something in the db to keep this information that the user is in playground, that is not so very performant after all.

You don't need to keep anything in the database. The constant will be defined for every request – it is the exact feature you're asking for.


Furthermore, you can even use Blueprints directly to import the demo content and update site options, see the importFile step. Here's an example that imports theme testing content.

Mte90 commented 7 months ago

So if it is possible to use a constant I think that maybe playground can set a standard, so SDK and other solution will use the same variable and everything it will be fine.