wpzapp/installer

View on GitHub
src/Installer.php

Summary

Maintainability
A
3 hrs
Test Coverage
<?php
/**
 * WP-ZAPP installer plugin for Composer.
 *
 * @package WPZAPP\Installer
 * @license GPL-3.0
 * @link    https://wpzapp.org
 */

namespace WPZAPP\Installer;

use Composer\IO\IOInterface;
use Composer\Installer\LibraryInstaller;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use InvalidArgumentException;

/**
 * Installer class.
 *
 * Handles installation of WP-ZAPP packages.
 *
 * @since 1.0.0
 */
class Installer extends LibraryInstaller
{

    /** @var string Package type identifier. */
    const NAME = 'wpzapp';

    /** @var array Associative array of subpackage types and their locations. */
    const LOCATIONS = array(
        'lib'    => 'wp-content/mu-plugins/wpzapp-lib/{$name}/',
        'module' => 'wp-content/mu-plugins/wpzapp-modules/{$name}/'
    );

    /**
     * {@inheritDoc}
     *
     * @since 1.0.0
     */
    public function getInstallPath(PackageInterface $package)
    {
        $type = $package->getType();
        if (substr($type, 0, strlen(self::NAME)) !== self::NAME) {
            throw new InvalidArgumentException(
                'Sorry the package type of this package is not yet supported.'
            );
        }

        $subPackageType = substr($type, strlen(self::NAME) + 1);

        $prettyName = $package->getPrettyName();
        if (strpos($prettyName, '/') !== false) {
            list($vendor, $name) = explode('/', $prettyName);
        } else {
            $vendor = '';
            $name = $prettyName;
        }

        $extra = $package->getExtra();
        if (!empty($extra['installer-name'])) {
            $name = $extra['installer-name'];
        }

        $availableVars = compact('name', 'vendor', 'type');

        if ($this->composer->getPackage()) {
            $extra = $this->composer->getPackage()->getExtra();
            if (!empty($extra['installer-paths'])) {
                $customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type, $vendor);
                if (!empty($customPath)) {
                    return $this->templatePath($customPath, $availableVars);
                }
            }
        }

        $locations = self::LOCATIONS;
        if (!isset($locations[$subPackageType])) {
            throw new InvalidArgumentException(sprintf('Sub-package type "%s" is not supported', $type));
        }

        return $this->templatePath($locations[$subPackageType], $availableVars);
    }

    /**
     * {@inheritDoc}
     *
     * @since 1.0.0
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new InvalidArgumentException('Package is not installed: ' . $package);
        }

        $repo->removePackage($package);

        $installPath = $this->getInstallPath($package);

        $message = '<error>not deleted</error>';
        if ($this->filesystem->removeDirectory($installPath)) {
            $message = '<comment>deleted</comment>';
        }

        $this->io->write(sprintf('Deleting %s - %s', $installPath, $message));
    }

    /**
     * {@inheritDoc}
     *
     * @since 1.0.0
     */
    public function supports($packageType)
    {
        if (substr($packageType, 0, strlen(self::NAME)) !== self::NAME) {
            return false;
        }

        $locationPattern = $this->getLocationPattern();

        return preg_match('#' . self::NAME . '-' . $locationPattern . '#', $packageType) === 1;
    }

    /**
     * Replace placeholder variables in a path.
     *
     * @since 1.0.0
     *
     * @param string $path Path to replace variables in.
     * @param array  $vars Associative array of variable names and their values.
     *
     * @return string Path with placeholders replaced with values.
     */
    protected function templatePath(string $path, array $vars = array()): string
    {
        if (strpos($path, '{') !== false) {
            extract($vars);

            preg_match_all('@\{\$([A-Za-z0-9_]*)\}@i', $path, $matches);
            if (!empty($matches[1])) {
                foreach ($matches[1] as $var) {
                    $path = str_replace('{$' . $var . '}', $$var, $path);
                }
            }
        }

        return $path;
    }

    /**
     * Get the second part of the regular expression to check for support of a package type.
     *
     * @since 1.0.0
     *
     * @return string Location pattern.
     */
    protected function getLocationPattern(): string
    {
        return '(' . implode('|', array_keys(self::LOCATIONS)) . ')';
    }

    /**
     * Search through a paths array for a custom install path.
     *
     * @since 1.0.0
     *
     * @param array  $paths  Associative array of paths and their identifying names.
     * @param string $name   Vendor and package name of the current package.
     * @param string $type   Package type of the current package.
     * @param string $vendor Optional. Vendor name of the current package. Default empty string.
     *
     * @return string Custom installation path, or empty string if no match found.
     */
    protected function mapCustomInstallPaths(array $paths, string $name, string $type, string $vendor = ''): string
    {
        foreach ($paths as $path => $names) {
            if (in_array($name, $names) || in_array('type:' . $type, $names)) {
                return $path;
            }

            if (!empty($vendor) && in_array('vendor:' . $vendor, $names)) {
                return $path;
            }
        }

        return '';
    }
}