Sample hook code for installing Wordpress theme from .zip file

Hi!
I’ve gotten some hooks for installing and removing plugins and themes to work very well. Was thinking I’d also automate installing a theme that’s not available on Wordpress.com/automated.

Was thinking maybe one of you guys have a sample of a script you’re using to (download and) install custom plugin/theme from a .zip file?

I’m using the php code examples from docs.apiscp.com, guess I can somehow modify this:

        [$hostname, $path, $opts] = $args;
        foreach (['divi'] as $theme) {
            $this->wordpress_install_theme($hostname, $path ?? '', $plugin);
        } 

I’m guessing I only need to figure out how to wget/curl a file in PHP (I don’t know much PHP at all) and then use that download path as $path

(I know I don’t need foreach for just one, but might leave room for installing multiple plugins maybe.)

Any help appreciated! :slight_smile:

There’s an outdated snippet from the File Manager that does this. Something like this would work inside the context of a hook:

$tempFile = tempnam($this->domain_fs_path("/tmp"), 'dl');
$localFile = "/tmp/" . basename($tempFile);
\Util_HTTP::download("https://some/url", $tempFile);
$this->file_endow_upload(basename($localFile));
$this->file_extract('/tmp/' . $localFile, $this->getAppRoot($hostname, $path ?? '') . '/wp-content/themes') && $this->file_delete($localFile); 

I don’t believe WP-CLI supports automatic updates for Divi. @anatoli knows more about this than I do.

1 Like

WP-CLI supports anything correctly implemented. If you have correctly licensed and activated your Divi, it should hook into update process handled by both Apis and WP-CLI and you should get automatic updates for it too.

2 Likes

Great help, thanks! :blush: :+1:

Seems there a couple of issues. I have some trouble knowing what’s important from all this debugging output.

I think these are the main errors:

ERROR   : DataStream::pipeline(): File_Module::endow_upload(): file `Divi.zipn049Kr' is not an uploaded file
WARNING: Undefined variable: hostname 
[/usr/local/apnscp/config/custom/wp-inst-divi-flips.php:13]

Seems the file is downloaded correctly (inside /home/virtual/my.domain.test/tmp):

# file Divi.zipn049Kr
Divi.zipn049Kr: Zip archive data, at least v2.0 to extract
# du -sh Divi.zipn049Kr
9.7M Divi.zipn049Kr

The hook/code:

 1  <?php
 2      \a23r::registerCallback('wordpress', 'valid', function ($ret, $args) {
 3          if (!$ret) {
 4              return;
 5          }
 6
 7          $tempFile = tempnam($this->domain_fs_path("/tmp"), 'Divi.zip');
 8          $localFile = "/tmp/" . basename($tempFile);
 9
10          \Util_HTTP::download("https://my.own.server/Divi.zip", $tempFile);
11          $this->file_endow_upload(basename($localFile));
12
13          $this->file_extract('/tmp/' . $localFile, $this->getAppRoot($hostname, $path ?? '') . '/wp-content/themes') && $this->file_delete($localFile);
14
15    });

Debug output:

DEBUG   : Callback fn install overwritten in wordpress - call cleanDynamicCompositions()
DEBUG   : Callback fn valid overwritten in wordpress - call cleanDynamicCompositions()
ERROR   : DataStream::pipeline(): File_Module::endow_upload(): file `Divi.zipn049Kr' is not an uploaded file
 0. Error_Reporter::merge_buffer([[message:"File_Module::endow_upload(): file `Divi.zipn049Kr' is not an uploaded file", severity:16, caller:"File_Module::endow_upload", bt:" 0B. Error_Reporter::add_error("file `Divi.zipn049Kr' is not an uploaded file", )[/usr/local/apnscp/lib/log_wrapper.php:62] 1B. error("file `Divi.zipn049Kr' is not an uploaded file")[/usr/local/apnscp/lib/modules/file.php:3009] 2B. File_Module->endow_upload(["Divi.zipn049Kr"])[/usr/local/apnscp/lib/Module/Skeleton/Standard.php:142] 3B. Module\Skeleton\Standard->_invoke("endow_upload", ["Divi.zipn049Kr"])[/usr/local/apnscp/lib/apnscpfunction.php:929] 4B. apnscpFunctionInterceptor->call("file_endow_upload", [..."]])
	[/usr/local/apnscp/lib/datastream.php:301]
 1. DataStream->unpack("")
	[/usr/local/apnscp/lib/datastream.php:394]
 2. DataStream->pipeline("")
	[/usr/local/apnscp/lib/datastream.php:383]
 3. DataStream->query("file_endow_upload", "Divi.zipn049Kr")
	[/usr/local/apnscp/lib/Module/Skeleton/Standard.php:197]
 4. Module\Skeleton\Standard->query("file_endow_upload", "Divi.zipn049Kr")
	[/usr/local/apnscp/lib/modules/file.php:2979]
 5. File_Module->endow_upload("Divi.zipn049Kr")
	[/usr/local/apnscp/lib/Module/Skeleton/Standard.php:142]
 6. Module\Skeleton\Standard->_invoke("endow_upload", ["Divi.zipn049Kr"])
	[/usr/local/apnscp/lib/apnscpfunction.php:929]
 7. apnscpFunctionInterceptor->call("file_endow_upload", ["Divi.zipn049Kr"])
	[/usr/local/apnscp/lib/apnscpFunctionInterceptorTrait.php:34]
 8. Module\Skeleton\Standard->__call("file_endow_upload", ["Divi.zipn049Kr"])
	[/usr/local/apnscp/config/custom/wp-inst-divi-flips.php:11]
 9. class@anonymous/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:1$34e->{closure}(true, ["my.domain.test"])
	[/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:17]
10. class@anonymous/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:1$34e->_invoke("valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/apnscpfunction.php:929]
11. apnscpFunctionInterceptor->call("wordpress_valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/CLI/cmd.php:62]
12. cli\__call("wordpress_valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/CLI/cmd.php:575]
13. cli\main()
	[/usr/local/apnscp/bin/cmd:7]

WARNING: Undefined variable: hostname 
[/usr/local/apnscp/config/custom/wp-inst-divi-flips.php:13]
WARNING: Undefined variable: hostname 
[/usr/local/apnscp/config/custom/wp-inst-divi-flips.php:13]

 0. Error_Reporter::handle_error(8, "Undefined variable: hostname", "/usr/local/apnscp/config/custom/wp-inst-divi-flips.php", 13, [ret:true, [["my.domain.test"], tempFile:"/home/virtual/site1/fst/tmp/Divi.zipn049Kr", localFile:"/tmp/Divi.zipn049Kr"]])
	[/usr/local/apnscp/config/custom/wp-inst-divi-flips.php:13]
 1. class@anonymous/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:1$34e->{closure}(true, ["my.domain.test"])
	[/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:17]
 2. class@anonymous/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:1$34e->_invoke("valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/apnscpfunction.php:929]
 3. apnscpFunctionInterceptor->call("wordpress_valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/CLI/cmd.php:62]
 4. cli\__call("wordpress_valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/CLI/cmd.php:575]
 5. cli\main()
	[/usr/local/apnscp/bin/cmd:7]
EXCEPTION: TypeError Argument 1 passed to Module\Support\Webapps::getAppRoot() must be of the type string, null given, called in /usr/local/apnscp/config/custom/wp-inst-divi-flips.php on line 13
 0. class@anonymous/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:1$34e->{closure}(true, ["my.domain.test"])
	[/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:17]
 1. class@anonymous/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:1$34e->_invoke("valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/apnscpfunction.php:929]
 2. apnscpFunctionInterceptor->call("wordpress_valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/CLI/cmd.php:62]
 3. cli\__call("wordpress_valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/CLI/cmd.php:575]
 4. cli\main()
	[/usr/local/apnscp/bin/cmd:7]

(TypeError) EXCEPTION: Argument 1 passed to Module\Support\Webapps::getAppRoot() must be of the type string, null given, called in /usr/local/apnscp/config/custom/wp-inst-divi-flips.php on line 13 
[/usr/local/apnscp/lib/Module/Support/Webapps.php:346]
EXCEPTION: Argument 1 passed to Module\Support\Webapps::getAppRoot() must be of the type string, null given, called in /usr/local/apnscp/config/custom/wp-inst-divi-flips.php on line 13 
[/usr/local/apnscp/lib/Module/Support/Webapps.php:346]

 0. Module\Support\Webapps->getAppRoot(null, "")
	[/usr/local/apnscp/config/custom/wp-inst-divi-flips.php:13]
 1. class@anonymous/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:1$34e->{closure}(true, ["my.domain.test"])
	[/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:17]
 2. class@anonymous/usr/local/apnscp/lib/apnscpfunction.php(604) : eval()'d code:1$34e->_invoke("valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/apnscpfunction.php:929]
 3. apnscpFunctionInterceptor->call("wordpress_valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/CLI/cmd.php:62]
 4. cli\__call("wordpress_valid", ["my.domain.test"])
	[/usr/local/apnscp/lib/CLI/cmd.php:575]
 5. cli\main()
	[/usr/local/apnscp/bin/cmd:7]

I’d offload that task to wp-cli instead. You can install plugins and themes given a URL too.

[$hostname, $path, $opts] = $args;
$this->wordpress_install_theme($hostname, $path ?? '', 'https://my.own.server/Divi.zip');
1 Like

Yay, much easier! :slight_smile:

Is there an option for wordpress_install_theme or wordpress_install_plugin for not activating it?

I tried running a separate

$this->wordpress_disable_theme($hostnaame, $path ?? '', 'divi');

… but it failed:

Debug (hooks): Processing hook "before_run_command" with 1 callbacks (0.201s)
Debug (hooks): On hook "before_run_command": WP_CLI\Bootstrap\RegisterDeferredCommands->add_deferred_commands() (0.201s)
Error: 'deactivate' is not a registered subcommand of 'theme'. See 'wp help theme' for available subcommands.
Did you mean 'activate'?

No big deal, though, I’m mostly curious, digging and testing. :slight_smile:

Hop on edge and give a try now later.

1 Like

Now :blush:

Calling wordpress:enable-theme($hostname, $path, $theme) would also disable the previously enabled theme.

1 Like

Thanks, I’m almost done here, as I also got that script working … but … I have some interesting issue … I changed all my hooks to run install time, changing the argument on line 2 from valid to install.

boot.php, which includes the next files at the bottom:

<?php
        \a23r::registerCallback('wordpress', 'install', function ($ret, $args) {
        if (!$ret) {
            return;
        }
        $approot = $args[0];
        if ($approot[0] !== '/') {
            // passed as $hostname, $path
            $approot = $this->getAppRoot($args[0], $args[1] ?? '');
        }
        $pairs = [
            'FS_METHOD'           => 'ssh2',
            'FTP_USER'            => $this->username . '@' . $this->domain,
            'FTP_HOST'            => 'localhost'
        ];
        return $this->updateConfiguration($approot, $pairs);
    });

    include 'wp-inst-plugins-flips.php';
    include 'wp-remove-themes-flips.php';
    include 'wp-remove-plugins-flips.php';
    include 'wp-inst-divi-flips.php';

Then all of the next 4 files have the same first lines:

<?php
        \a23r::registerCallback('wordpress', 'install', function ($ret, $args) {

… But somehow only the last include (wp-inst-divi-flips.php) is run, and the code in boot.php is not run either, FS_METHOD is still false after I do

env DEBUG=1 VERBOSE=-1 cpcmd -d mydomain.org wordpress_install test.mydomain.org

This probably makes sense, if I’m not to repeat the \a23r::register part. (I also tried copying all the script into the same file, not repeating this, but had trouble with that as well.)

When I built each module/include file, I used valid to trigger each, and they all worked.
PHP knowledge is really lacking, so I don’t know how to best get this to work.

Any hint on what to read or do is appreciated. :blush:

Each callback clobbers the previous one. Only wp-inst-divi-flips.php is run. You’ve got a hint in the debug log of this behavior: Callback fn install overwritten in wordpress - call cleanDynamicCompositions()

You could structure it like this by forwarding the call:

<?php
        \a23r::registerCallback('wordpress', 'install', function ($ret, $args) {
        if (!$ret) {
            return;
        }
        $approot = $args[0];
        if ($approot[0] !== '/') {
            // passed as $hostname, $path
            $approot = $this->getAppRoot($args[0], $args[1] ?? '');
        }
        $pairs = [
            'FS_METHOD'           => 'ssh2',
            'FTP_USER'            => $this->username . '@' . $this->domain,
            'FTP_HOST'            => 'localhost'
        ];
        
        $this->updateConfiguration($approot, $pairs);
        
        // do stuff in wp-inst-plugins-flips.php
        // do stuff in wp-remove-themes-flips.php...
        // OR
        Closure::fromCallable("installPlugins")($ret, $args);
    });

function installPlugins ($ret, $args) {
     echo "Ret: ", $ret;
}

Alternatively, I could also add something like chaining that would look like…

    \a23r::registerCallback('wordpress', 'install', function ($ret, $args) {
        if (!$ret) {
            return;
        }
        // continue call
        return true;   
    }, function ($ret, $args) {
        // called
        echo "Hello!";
        // break chain
        return;
    }, function ($ret, $args) {
        // not called
        echo "Never triggered";
    });
1 Like

Ah, thanks again!
Removing the return before this made it process all my code. Makes sense. :stuck_out_tongue:

Not sure if I should add a return at the end somehow. Seems to work fine anyways now:

----------------------------------------
MESSAGE SUMMARY
Reporter level: OK
INFO: created database `redacted'
INFO: added database backup task for `redacted'
INFO: autogenerated password `redacted'
INFO: setting admin user to `redacted'
INFO: WordPress installed - confirmation email with login info sent to redacted@mydomain.tld
INFO: installed plugin `easy-wp-smtp'
INFO: uninstalled plugin `akismet'
INFO: uninstalled plugin `hello'
INFO: uninstalled theme `twentynineteen'
INFO: uninstalled theme `twentytwenty'
INFO: installed theme `https://redacted.tld/Divi.zip'
----------------------------------------

None required. Hooks cannot interrupt flow (unless you called fatal() or threw an exception).