443 lines
14 KiB
PHP
443 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Drush hooks and include commands for Basic theme.
|
|
*/
|
|
|
|
/**
|
|
* Implements hook_drush_command().
|
|
*/
|
|
function basic_drush_command() {
|
|
|
|
$items['basic-install'] = array(
|
|
'description' => dt('Run through the installation process of the Basic theme.'),
|
|
'options' => array(
|
|
'destination' => array(
|
|
'description' => 'Path to which the theme will be placed. If you\'re providing a relative path, note it is relative to the drupal root.',
|
|
'example-value' => 'themes/',
|
|
),
|
|
),
|
|
'aliases' => array('binstall'),
|
|
);
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_drush_help().
|
|
*/
|
|
function basic_drush_help($section) {
|
|
switch ($section) {
|
|
case 'drush:basic-install':
|
|
return dt('Runs through an automated process for installing and renaming your theme to use as your own.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements drush_hook_COMMAND().
|
|
*/
|
|
function drush_basic_install() {
|
|
drush_print('Checking for npm...');
|
|
$npm_check_output = drush_shell_exec('npm version 2>&1');
|
|
if (strpos($npm_check_output, 'command not found')) {
|
|
drush_print('NPM could not be found. Please install npm (https://nodejs.org/en/) and try again.');
|
|
exit();
|
|
}
|
|
else {
|
|
drush_print('npm was found.');
|
|
}
|
|
|
|
// Prompt the user what we're doing.
|
|
drush_print('-----------------------------');
|
|
drush_print(dt('Welcome to the Basic Install!'));
|
|
drush_print('-----------------------------');
|
|
drush_print(dt('We will perform the automated tasks for you to use your new theme. We will create a copy of the Basic theme and store it in the standard theme directory or in spescified destination.'));
|
|
drush_confirm(dt('Do you wish to continue?'));
|
|
|
|
// Rebuild the theme data so that we can safely check for the existence of
|
|
// themes by using the information provided by list_themes().
|
|
$theme_handler = \Drupal::service('theme_handler');
|
|
$theme_handler->rebuildThemeData();
|
|
|
|
// Prompt for a theme name.
|
|
$name = drush_prompt(dt('Please enter the name of that you want for your theme'), 'My Theme');
|
|
|
|
// Try to generate a machine-readable name. If that fails, prompt for one.
|
|
if (!$machine_name = drush_basic_generate_theme_name($name)) {
|
|
drush_print(dt("Sorry, I couldn't generate a machine-readable name for @name", array(
|
|
'@name' => $name,
|
|
)));
|
|
}
|
|
// Prompt for a theme name using the automatically generated default if any.
|
|
drush_set_option('machine-name', drush_basic_require_valid_theme_name(dt('Please enter a machine-readable name for your new theme'), $machine_name));
|
|
|
|
// Try to generate a machine-readable name. If that fails, prompt for one.
|
|
if (!$machine_name = drush_get_option('machine-name', drush_basic_generate_theme_name($name))) {
|
|
drush_print(dt("Sorry, I couldn't generate a machine-readable name for @name. Please use the '--machine-name' option to specify it manually.", array(
|
|
'@name' => $name,
|
|
)));
|
|
}
|
|
|
|
$origin_path = $theme_handler->getTheme('basic')->getPath();
|
|
$temporary = \Drupal::config('system.file')->get('path.temporary');
|
|
$temp_path = $temporary . '/' . $machine_name;
|
|
$destination_path = drush_basic_get_destination($machine_name);
|
|
|
|
// Check whether the destination path exist and bail out if it does,
|
|
// so we don't delete any important data by accident.
|
|
if (file_exists($destination_path)) {
|
|
return drush_set_error('BASIC_COPY_PATH', dt('The path !path already exists.', array(
|
|
'!path' => $destination_path,
|
|
)));
|
|
}
|
|
|
|
if (is_dir($temp_path)) {
|
|
basic_rm_dir($temp_path);
|
|
}
|
|
|
|
// Copy into temp directory.
|
|
if (!basic_copy_dir($origin_path, $temp_path)) {
|
|
return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Failed to copy !origin_path to !temp_path.', array(
|
|
'!origin_path' => $origin_path,
|
|
'!temp_path' => $temp_path,
|
|
)));
|
|
}
|
|
|
|
// Recursively rewrite the file names and contents of all the files that are
|
|
// now in the subtheme's directory to represent the human- and
|
|
// machine-readable names of the subtheme.
|
|
$search = array(
|
|
'basic' => $machine_name,
|
|
'Basic' => $name,
|
|
);
|
|
|
|
if (!drush_basic_rewrite_recursive($temp_path, array_keys($search), array_values($search))) {
|
|
return drush_set_error('BASIC_COPY_GENERATE_THEME', dt('Failed to rewrite files and contents while generating theme.'));
|
|
}
|
|
|
|
// Copy from temp folder into new theme's folder.
|
|
if (!basic_copy_dir($temp_path, $destination_path)) {
|
|
return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Failed to copy !temp_path to !destination_path.', array(
|
|
'!temp_path' => $temp_path,
|
|
'!destination_path' => $destination_path,
|
|
)));
|
|
}
|
|
|
|
// Run npm install on the new theme folder.
|
|
drush_print(dt("Building node dependencies for the theme @theme. This could take a couple minutes.", array(
|
|
'@theme' => $machine_name,
|
|
)));
|
|
drush_op('chdir', DRUPAL_ROOT . '/' . $destination_path);
|
|
drush_shell_exec('npm install');
|
|
drush_print(dt("Node dependancies have been successfully built."));
|
|
|
|
// Notify user of successful installation.
|
|
drush_print(dt("------------------------------------------------------------------"));
|
|
drush_print(dt("The new theme, !theme, has been successfully derived from Basic and placed in !destination.", array(
|
|
'!theme' => $machine_name,
|
|
'!destination' => $destination_path,
|
|
)));
|
|
drush_print(dt("------------------------------------------------------------------"));
|
|
}
|
|
|
|
/**
|
|
* Generates a valid machine-readable name for a theme from any string.
|
|
*
|
|
* @param string $string
|
|
* The string to generate the machine-readable name from.
|
|
*
|
|
* @return string
|
|
* The generated machine-readable name.
|
|
*/
|
|
function drush_basic_generate_theme_name($string) {
|
|
// Lowercase to start.
|
|
$string = strtolower($string);
|
|
// Machine-readable names have to start with a lowercase letter.
|
|
// $string = preg_replace('/^[^a-z]+/', '', strtolower($string));
|
|
// Machine-readable names may only contain alphanumeric characters and
|
|
// underscores.
|
|
$string = preg_replace('/[^a-z0-9_]+/', '_', $string);
|
|
// Trim all trailing and leading underscores.
|
|
$string = trim($string, '_');
|
|
|
|
// Get list of current themes.
|
|
$theme_handler = \Drupal::service('theme_handler');
|
|
$themes = $theme_handler->listInfo();
|
|
|
|
if (isset($themes[$string])) {
|
|
$plain = $string;
|
|
$counter = 0;
|
|
|
|
while (isset($themes[$string])) {
|
|
// Make sure that the machine-readable name of the theme is unique.
|
|
$string = $plain . '_' . $counter++;
|
|
}
|
|
}
|
|
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Helper function that continuously prompts for a valid machine-readable name.
|
|
*
|
|
* @param string $message
|
|
* The message that should be displayed.
|
|
* @param string $default
|
|
* (Optional) The default theme name. Defaults to NULL.
|
|
*
|
|
* @return string
|
|
* A valid, unique machine-readable name.
|
|
*/
|
|
function drush_basic_require_valid_theme_name($message, $default = NULL) {
|
|
while (TRUE) {
|
|
// Keep prompting for a machine-name until we get an acceptable value.
|
|
$prompt = drush_prompt($message, $default);
|
|
|
|
if (!preg_match('/^[a-z][a-z0-9_]*$/', $prompt)) {
|
|
drush_print('The machine-readable name is invalid. It may only contain lowercase numbers, letters and underscores and must start with a letter.');
|
|
}
|
|
else {
|
|
|
|
// Get list of current themes.
|
|
$theme_handler = \Drupal::service('theme_handler');
|
|
$themes = $theme_handler->listInfo();
|
|
|
|
// Validate that the machine-readable name of the theme is unique.
|
|
if (isset($themes[$prompt])) {
|
|
drush_print(dt('A theme with the name @name already exists. The machine-readable name must be unique.', array(
|
|
'@name' => $prompt,
|
|
)));
|
|
}
|
|
else {
|
|
// The given machine-readable name is valid. Let's proceed.
|
|
return $prompt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to get proper destination for the new theme.
|
|
*
|
|
* @param string $machine_name
|
|
* A valid, unique machine-readable name for the new theme.
|
|
*
|
|
* @return string
|
|
* The path to the destination where the new theme should be placed.
|
|
*/
|
|
function drush_basic_get_destination($machine_name) {
|
|
// Find proper destination if no destination option is provided.
|
|
if ($destination = drush_get_option('destination')) {
|
|
$destination_path = drush_trim_path($destination) . '/' . $machine_name;
|
|
}
|
|
else {
|
|
$site_root = drush_get_context('DRUSH_DRUPAL_SITE_ROOT');
|
|
if ($site_root == 'sites/default') {
|
|
$destination_path = 'themes/' . $machine_name;
|
|
}
|
|
else {
|
|
$destination_path = $site_root . '/themes/' . $machine_name;
|
|
}
|
|
}
|
|
$destination_path = drush_normalize_path($destination_path);
|
|
return $destination_path;
|
|
}
|
|
|
|
/**
|
|
* Recursively rewrites (and renames) all files in a given path.
|
|
*
|
|
* @param string $path
|
|
* The path to rewrite all files in.
|
|
* @param string|array $search
|
|
* The string(s) to look for when replacing the file names and contents. Can
|
|
* be an array or a string.
|
|
* @param string|array $replace
|
|
* The string(s) to replace $search with. Can be an array or a string.
|
|
*
|
|
* @return bool
|
|
* TRUE if the operation succeeded, FALSE otherwise.
|
|
*
|
|
* @see omega_drush_replace_contents()
|
|
* @see str_replace()
|
|
*/
|
|
function drush_basic_rewrite_recursive($path, $search, $replace) {
|
|
if (!is_dir($path)) {
|
|
return drush_set_error('INVALID_PATH', dt('The given path !path is not a directory.', array(
|
|
'!path' => $path,
|
|
)));
|
|
}
|
|
|
|
// If the file actually is a directory, proceed with the recursion.
|
|
$directory = new DirectoryIterator($path);
|
|
|
|
foreach ($directory as $item) {
|
|
if ($item->isDot()) {
|
|
// Do not process '..' and '.'.
|
|
continue;
|
|
}
|
|
|
|
// Retrieve the path of the current item.
|
|
$pathname = $item->getPathname();
|
|
if ($item->isDir() && !drush_basic_rewrite_recursive($pathname, $search, $replace)) {
|
|
return FALSE;
|
|
}
|
|
elseif ($item->isFile()) {
|
|
// If it is a file, try to replace its contents.
|
|
$content = file_get_contents($pathname);
|
|
|
|
// Nothing to replace in empty files.
|
|
if (empty($content)) {
|
|
unlink($pathname);
|
|
continue;
|
|
}
|
|
|
|
if (($changed_content = str_replace($search, $replace, $content)) === NULL) {
|
|
return drush_set_error('REWRITE_FAILURE', dt('There was an error while trying to rewrite !path (!search to !replace)', array(
|
|
'!path' => $pathname,
|
|
'!search' => $search,
|
|
'!replace' => $replace,
|
|
)));
|
|
}
|
|
|
|
if ($content !== $changed_content) {
|
|
file_put_contents($pathname, $changed_content);
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Copy $src to $dest.
|
|
*
|
|
* @param string $src
|
|
* The directory to copy.
|
|
* @param string $dest
|
|
* The destination to copy the source to, including the new name of
|
|
* the directory. To copy directory "a" from "/b" to "/c", then
|
|
* $src = "/b/a" and $dest = "/c/a". To copy "a" to "/c" and rename
|
|
* it to "d", then $dest = "/c/d".
|
|
* @param int $overwrite
|
|
* Action to take if destination already exists.
|
|
* - FILE_EXISTS_OVERWRITE - completely removes existing directory.
|
|
* - FILE_EXISTS_ABORT - aborts the operation.
|
|
* - FILE_EXISTS_MERGE - Leaves existing files and directories in place.
|
|
*
|
|
* @return bool
|
|
* TRUE on success, FALSE on failure.
|
|
*/
|
|
function basic_copy_dir($src, $dest, $overwrite = FILE_EXISTS_ABORT) {
|
|
// Preflight based on $overwrite if $dest exists.
|
|
if (file_exists($dest)) {
|
|
if ($overwrite === FILE_EXISTS_OVERWRITE) {
|
|
drush_op('drush_delete_dir', $dest, TRUE);
|
|
}
|
|
elseif ($overwrite === FILE_EXISTS_ABORT) {
|
|
return drush_set_error('DRUSH_DESTINATION_EXISTS', dt('Destination directory !dest already exists.', array('!dest' => $dest)));
|
|
}
|
|
elseif ($overwrite === FILE_EXISTS_MERGE) {
|
|
// $overwrite flag may indicate we should merge instead.
|
|
drush_log(dt('Merging existing !dest directory', array('!dest' => $dest)));
|
|
}
|
|
}
|
|
// $src readable?
|
|
if (!is_readable($src)) {
|
|
return drush_set_error('DRUSH_SOURCE_NOT_EXISTS', dt('Source directory !src is not readable or does not exist.', array('!src' => $src)));
|
|
}
|
|
// $dest writable?
|
|
if (!is_writable(dirname($dest))) {
|
|
return drush_set_error('DRUSH_DESTINATION_NOT_WRITABLE', dt('Destination directory !dest is not writable.', array('!dest' => dirname($dest))));
|
|
}
|
|
// Try to do a recursive copy.
|
|
if (@drush_op('_basic_recursive_copy', $src, $dest)) {
|
|
return TRUE;
|
|
}
|
|
|
|
return drush_set_error('DRUSH_COPY_DIR_FAILURE', dt('Unable to copy !src to !dest.', array('!src' => $src, '!dest' => $dest)));
|
|
}
|
|
|
|
/**
|
|
* Internal function called by basic_copy_dir; do not use directly.
|
|
*/
|
|
function _basic_recursive_copy($src, $destination) {
|
|
static $machine_name;
|
|
if (!isset($machine_name)) {
|
|
$machine_name = drush_get_option('machine-name');
|
|
}
|
|
// All subdirectories and contents.
|
|
$ignore_directories = array(
|
|
'node_modules',
|
|
);
|
|
$no_mask = '/^' . implode('|', $ignore_directories) . '$/';
|
|
|
|
if (is_dir($src)) {
|
|
if (!drush_mkdir($destination, TRUE)) {
|
|
return FALSE;
|
|
}
|
|
$dir_handle = opendir($src);
|
|
while ($file = readdir($dir_handle)) {
|
|
if (($file[0] != '.' || $file == '.gitignore') && !preg_match($no_mask, $file)) {
|
|
if (_basic_recursive_copy("$src/$file", "$destination/$file") !== TRUE) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
closedir($dir_handle);
|
|
}
|
|
elseif (is_link($src)) {
|
|
symlink(readlink($src), $destination);
|
|
}
|
|
elseif (!copy($src, $destination)) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Rename files with basic.
|
|
if (strpos($destination, 'basic') !== FALSE) {
|
|
$renamed_destination = str_replace('basic', $machine_name, $destination);
|
|
rename($destination, $renamed_destination);
|
|
}
|
|
|
|
// Preserve file modification time.
|
|
touch($destination, filemtime($src));
|
|
|
|
// Preserve execute permission.
|
|
if (!is_link($src) && !drush_is_windows()) {
|
|
// Get execute bits of $src.
|
|
$execperms = fileperms($src) & 0111;
|
|
// Apply execute permissions if any.
|
|
if ($execperms > 0) {
|
|
$perms = fileperms($destination) | $execperms;
|
|
chmod($destination, $perms);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Recursively removes all files within the provided directory.
|
|
*
|
|
* USE WITH CAUTION.
|
|
*
|
|
* @param string $dir
|
|
* File path for the directory you want to delete.
|
|
*/
|
|
function basic_rm_dir($dir) {
|
|
if (is_dir($dir)) {
|
|
$objects = scandir($dir);
|
|
foreach ($objects as $object) {
|
|
if ($object != "." && $object != "..") {
|
|
if (is_dir($dir . "/" . $object)) {
|
|
basic_rm_dir($dir . "/" . $object);
|
|
}
|
|
else {
|
|
unlink($dir . "/" . $object);
|
|
}
|
|
}
|
|
}
|
|
rmdir($dir);
|
|
}
|
|
}
|