Skip to content

Instantly share code, notes, and snippets.

@cvhau
Created October 28, 2022 07:24
Show Gist options
  • Save cvhau/f17fb3062ade95662dcddf885a179d19 to your computer and use it in GitHub Desktop.
Save cvhau/f17fb3062ade95662dcddf885a179d19 to your computer and use it in GitHub Desktop.
WordPress config file generator
#!/usr/bin/env php
<?php
namespace WpConfig;
use RuntimeException;
class ConfigOption
{
/**
* @var string
*/
private $longName;
/**
* @var string
*/
private $shortName;
/**
* @var boolean
*/
private $required;
/**
* @var mixed
*/
private $value;
/**
* @var string
*/
private $valueType;
/**
* @var string
*/
private $description;
/**
* Init a ConfigOption instance.
*
* @param string $name "long" name, "short" name or combined "long|short" name.
* @param mixed $value Default value.
* @param boolean $required This argument is required or not.
* @param string $description Description text.
*/
public function __construct($name, $value = null, $required = false, $description = null)
{
list($this->longName, $this->shortName) = $this->parseName($name);
$this->value = $value;
$this->valueType = gettype($value);
$this->required = $required;
$this->description = $description;
}
/**
* Parse input name to longName and shortName.
*
* @param string $name
* @return array [longName, shortName]
*/
protected function parseName($name)
{
$longName = null;
$shortName = null;
$names = array_map('trim', explode('|', trim($name)));
foreach ($names as $name) {
$nameLength = strlen($name);
if ($nameLength > 1) {
$longName = $name;
} elseif ($nameLength > 0) {
$shortName = $name;
}
}
return [$longName, $shortName];
}
/**
* @return string
*/
public final function getLongName()
{
return $this->longName;
}
/**
* @return string
*/
public final function getShortName()
{
return $this->shortName;
}
/**
* @return mixed
*/
public final function getValue()
{
return $this->value;
}
/**
* @param mixed $value
* @return void
*/
public final function setValue($value)
{
if (in_array($this->valueType, ['boolean', 'integer', 'double', 'string', 'array', 'object'])) {
settype($value, $this->valueType);
} else {
$this->valueType = gettype($value);
}
$this->value = $value;
}
/**
* @return boolean
*/
public final function isRequired()
{
return $this->required;
}
/**
* @return string
*/
public final function getDescription()
{
return $this->description;
}
/**
* Get CMN argument name by given an option name
*
* @param string $name
* @return string
*/
protected function getCmdOption($name)
{
if (!empty($name)) {
if ($this->required) {
//
// Parameter requires value.
//
return "{$name}:";
} elseif (is_null($this->value)) {
//
// Parameter does not accept any value
//
return $name;
} else {
//
// Optional value
//
return "{$name}::";
}
}
return '';
}
/**
* Get long option of command line argument
*
* @return string
*/
public function getCmdLongOption()
{
return $this->getCmdOption($this->longName);
}
/**
* Get short option of command line argument
*
* @return string
*/
public function getCmdShortOption()
{
return $this->getCmdOption($this->shortName);
}
/**
* Determines of the given name (long or short) is belong to current argument.
*
* @param string $name Long name or short name to check
* @return boolean
*/
public function hasName($name)
{
return (strcmp($this->longName, $name) === 0 || strcmp($this->shortName, $name) === 0);
}
}
abstract class WpConfigOption extends ConfigOption
{
/**
* Get generate WordPress config options
* that follow the config option.
*
* @return array Array of key=>value pairs.
*/
abstract function getWpConfigOptions(): array;
/**
* Get an array of WordPress "define('CONSTANT', value);" statements.
*
* @return array
*/
public function getWpConfigDefineStatements()
{
$configs = $this->getWpConfigOptions();
$defineStatements = [];
foreach ($configs as $key => $value) {
$defineStatements[] = self::generateDefineStatement($key, $value);
}
return $defineStatements;
}
/**
* Generate "define('CONSTANT', value);" statement.
*
* @param string $key
* @param mixed $value
* @return string
*/
public static function generateDefineStatement(string $key, $value)
{
$validatedKey = preg_replace('/[^a-zA-Z_]+/', '', $key);
$validatedValue = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$validatedValue = str_replace('"', '\'', $validatedValue);
return sprintf('define(\'%s\', %s);', $validatedKey, $validatedValue);
}
}
class WpDbConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('db', 'test');
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
return [
'DB_NAME' => $this->getValue(),
];
}
}
class WpDbUserConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('dbUser', 'dev');
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
return [
'DB_USER' => $this->getValue(),
];
}
}
class WpDbPassConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('dbPass', '0123456789');
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
return [
'DB_PASSWORD' => $this->getValue(),
];
}
}
class WpDbHostConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('dbHost', 'localhost');
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
return [
'DB_HOST' => $this->getValue(),
];
}
}
class WpDbCharsetConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('dbCharset', 'utf8mb4');
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
return [
'DB_CHARSET' => $this->getValue(),
];
}
}
class WpDbCollateConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('dbCollate', 'utf8mb4_unicode_ci');
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
return [
'DB_COLLATE' => $this->getValue(),
];
}
}
class WpDebugConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('debug', 1);
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
$value = $this->getValue();
if ($value <= 0) {
$enabledDebug = false;
$displayDebug = false;
} elseif ($value === 1) {
// Recommend for development.
$enabledDebug = true;
$displayDebug = true;
} else {
// Recommend for production.
$enabledDebug = true;
$displayDebug = false;
}
return [
'WP_DEBUG' => $enabledDebug,
'WP_DEBUG_DISPLAY' => $displayDebug,
];
}
}
class WpDebugLogConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('debugLog', '/var/log/php/wp-debug.log');
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
$logFile = $this->getValue();
$wpOptions = [];
if (!empty($logFile)) {
$wpOptions['WP_DEBUG_LOG'] = $logFile;
}
return $wpOptions;
}
}
class WpAppUrlConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('appUrl', 'http://localhost');
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
$appUrl = $this->getValue();
$wpOptions = [];
if (!empty($appUrl)) {
// code-spell-checker: disable-next-line
$wpOptions['WP_SITEURL'] = $appUrl;
$wpOptions['WP_HOME'] = $appUrl;
}
return $wpOptions;
}
}
class WpPostRevisionsConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('postRevisions', 0);
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
$numRevisions = $this->getValue();
$wpOptions = [
'WP_POST_REVISIONS' => $numRevisions,
];
if ($numRevisions > 0) {
// code-spell-checker: disable-next-line
$wpOptions['AUTOSAVE_INTERVAL'] = 60;
}
return $wpOptions;
}
}
class WpDisFileModsConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('disFileMods', false);
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
return [
'DISALLOW_FILE_MODS' => $this->getValue(),
];
}
}
class WpCronConfigOption extends WpConfigOption
{
public function __construct()
{
parent::__construct('wpCron', true);
}
/**
* @inherits
*/
public function getWpConfigOptions(): array
{
$enabledCron = $this->getValue();
$wpOptions = [
'DISABLE_WP_CRON' => !$enabledCron,
];
if (!$enabledCron) {
$wpOptions['ALTERNATE_WP_CRON'] = true;
}
return $wpOptions;
}
}
class DotEnvLoader
{
/**
* Dot ENV file to load
*
* @var string
*/
private $envFile;
/**
* Used default Dot ENV file path or not
*
* @var boolean
*/
private $usedDefault = false;
/**
* All of ENV options
*
* @var array
*/
private $envOptions;
private $loaded = false;
/**
* @param string $path
*/
public function __construct($path = '')
{
$path = trim($path);
if (empty($path)) {
$this->envFile = $this->defaultDotEnv();
$this->usedDefault = true;
} else {
$this->setEnvFile($path);
}
$this->envOptions = [];
$this->loaded = false;
}
/**
* Default .env file
*
* @return string
*/
public function defaultDotEnv()
{
$dotEnvFile = '.env';
$currentWorkingDir = getcwd();
if (!empty($currentWorkingDir)) {
$dotEnvFile = sprintf('%s/%s', rtrim($currentWorkingDir, DIRECTORY_SEPARATOR), $dotEnvFile);
}
return $dotEnvFile;
}
/**
* @return string
*/
public final function getEnvFile()
{
return $this->envFile;
}
public function setEnvFile($envPath)
{
if (is_dir($envPath)) {
$envPath = rtrim($envPath, '/') . '/' . '.env';
}
if (!is_file($envPath) || !is_readable($envPath)) {
throw new \InvalidArgumentException("The file '{$envPath}' does not exit or can not be read.", 1);
}
$this->envFile = $envPath;
$this->usedDefault = false;
$this->loaded = false;
}
/**
* @return boolean
*/
public final function isUsedDefault()
{
return $this->usedDefault;
}
/**
* @return array
*/
public final function getEnvOptions()
{
return $this->envOptions;
}
/**
* @return boolean
*/
public final function isLoaded()
{
return $this->loaded;
}
/**
* @return array
*/
public final function getEnvCamelCaseOptions()
{
$options = [];
foreach ($this->envOptions as $key => $value) {
$_key = strtolower($key);
$_keyWords = explode('_', $_key);
$len = count($_keyWords);
$i = 1;
if ($_keyWords[0] === '') {
$_keyWords[0] = '_';
$i++;
}
while ($i < $len) {
$_keyWords[$i] = ucfirst($_keyWords[$i]);
$i++;
}
$_key = implode('', $_keyWords);
$options[$_key] = $value;
}
return $options;
}
/**
* Load Dot ENV file.
*
* @param boolean $force
* @return \WpConfig\DotEnvLoader
*/
public function load($force = false)
{
try {
if (!$this->loaded || $force) {
$this->envOptions = [];
if (file_exists($this->envFile) && is_file($this->envFile)) {
if (is_readable($this->envFile)) {
// Read all lines
$lines = file($this->envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
// Validate
$lines = array_map('trim', $lines);
// Remove lines that are comment or empty
$lines = array_filter($lines, function ($line) {
return !(empty($line) || substr($line, 0, 1) === '#');
});
foreach ($lines as $line) {
$pairs = explode('=', $line, 2);
$key = trim($pairs[0]);
if (count($pairs) > 1) {
$value = trim($pairs[1]);
// If the value is an explicit string.
if (preg_match('/^(["\']).*\1$/m', $value)) {
$value = trim($value, '\'"');
} else {
// Capture NULL value before try to apply json decode
if (strcasecmp($value, 'null') === 0) {
$value = null;
} else {
$decodedValue = @json_decode($value);
// If can be decoded.
if (!is_null($decodedValue)) {
$value = $decodedValue;
}
// Otherwise, keep the original as a string value.
// Done.
}
}
} else {
$value = '';
}
$this->envOptions[$key] = $value;
}
} else {
throw new RuntimeException("The file '{$this->envFile}' can not be read.", 1);
}
}
}
} finally {
$this->loaded = true;
}
return $this;
}
}
class ConfigOptionManager
{
/**
* @var \WpConfig\DotEnvLoader
*/
private $dotEnvLoader;
/**
* @var \WpConfig\ConfigOption[]
*/
private $configOptions;
/**
* Rest value that passed from command line arguments via "rest_index".
* See more details at link below.
*
* @link https://www.php.net/manual/en/function.getopt
*
* @var string
*/
private $restValue;
/**
* @var boolean
*/
private $parsed = false;
/**
* @param DotEnvLoader|null $dotEnvLoader
* @param \WpConfig\ConfigOption[]|null $configOptions
*/
public function __construct(DotEnvLoader $dotEnvLoader = null, array $configOptions = null)
{
if (is_null($dotEnvLoader)) {
$dotEnvLoader = new DotEnvLoader();
}
if (empty($configOptions)) {
$configOptions = [
new WpDbConfigOption(),
new WpDbUserConfigOption(),
new WpDbPassConfigOption(),
new WpDbHostConfigOption(),
new WpDbCharsetConfigOption(),
new WpDbCollateConfigOption(),
new WpDebugConfigOption(),
new WpDebugLogConfigOption(),
new WpAppUrlConfigOption(),
new WpPostRevisionsConfigOption(),
new WpDisFileModsConfigOption(),
new WpCronConfigOption(),
new ConfigOption('env', ''),
];
}
$this->dotEnvLoader = $dotEnvLoader;
$this->configOptions = $configOptions;
$this->restValue = null;
$this->parsed = false;
}
/**
* @return \WpConfig\ConfigOption[]
*/
public final function getConfigOptions()
{
return $this->configOptions;
}
/**
* @return string|null
*/
public final function getRestValue()
{
return $this->restValue;
}
/**
* @return boolean
*/
public final function isParsed()
{
return $this->parsed;
}
/**
* Get ConfigOption object by given "long" or "short" name.
*
* @param string $optionName Long or short name
* @return \WpConfig\ConfigOption|null
*/
public function getConfigOptionByName($optionName)
{
foreach ($this->configOptions as $option) {
if ($option->hasName($optionName)) {
return $option;
}
}
return null;
}
/**
* Build short options for command line
*
* @return string
*/
public function buildCmdShortOptions()
{
$shortOptions = '';
foreach ($this->configOptions as $option) {
$shortOptions .= $option->getCmdShortOption();
}
return $shortOptions;
}
/**
* Build long options for command line
*
* @return array
*/
public function buildCmdLongOptions()
{
$longOptions = [];
foreach ($this->configOptions as $option) {
$longOptions[] = $option->getCmdLongOption();
}
return $longOptions;
}
/**
* Gets options from the command line arguments
*
* @return array
*/
public function getCommandLineOptions()
{
$shortOptions = $this->buildCmdShortOptions();
$longOptions = $this->buildCmdLongOptions();
$restIndex = null;
$options = getopt($shortOptions, $longOptions, $restIndex);
if (is_int($restIndex)) {
global $argv;
$this->restValue = @$argv[$restIndex] ?: null;
}
return $options ?: [];
}
/**
* Set config options by given an array of key=>value pairs.
*
* @param array $optionPairs Array of key=>value pair options.
* @return void
*/
public function setConfigOptions(array $optionPairs)
{
foreach ($optionPairs as $key => $value) {
$configOption = $this->getConfigOptionByName($key);
if ($configOption) {
$configOption->setValue($value);
}
}
}
/**
* @param boolean $force
* @return ConfigOptionManager
*/
public function parse($force = false)
{
try {
if (!$this->parsed || $force) {
$cmdOptions = $this->getCommandLineOptions();
//
// If has a Dot ENV file in command line argument list.
// Then pass it to Dot ENV Loader.
//
if (array_key_exists('env', $cmdOptions)) {
$envPath = $cmdOptions['env'];
unset($cmdOptions['env']);
$this->dotEnvLoader->setEnvFile($envPath);
}
//
// Load all options from Dot ENV file if available and overwrites
// all default values if present.
//
$envOptions = $this->dotEnvLoader->load()->getEnvCamelCaseOptions();
$this->setConfigOptions($envOptions);
//
// Overwrites all default values, dot env values
// by command line argument values if present.
//
$this->setConfigOptions($cmdOptions);
}
} finally {
$this->parsed = true;
}
return $this;
}
}
class ConfigGenerator
{
private static $instance = null;
/**
* @var \WpConfig\ConfigOptionManager
*/
private $optionManager = null;
/**
* @param ConfigOptionManager|null $optionManager
*/
public function __construct(ConfigOptionManager $optionManager = null)
{
if ($optionManager === null) {
$optionManager = new ConfigOptionManager();
}
$this->optionManager = $optionManager;
}
public static function getInstance()
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
public function phpOpenTagLines()
{
return [
'<?php',
];
}
public function getWpLastConfigLines()
{
$configLines = [
'$table_prefix = \'wp_\';',
"define('WP_ALLOW_REPAIR', false);",
"define('DISALLOW_FILE_EDIT', true);",
"define('AUTOMATIC_UPDATER_DISABLED', true);",
"define('FS_METHOD', 'direct');",
'if (empty($_SERVER[\'HTTPS\']) || $_SERVER[\'HTTPS\'] === \'off\') {',
'if (@$_SERVER[\'HTTP_X_FORWARDED_PROTO\'] == \'https\' || @$_SERVER[\'HTTP_X_FORWARDED_SSL\'] == \'on\') {',
'$_SERVER[\'HTTPS\'] = \'on\';}}',
'if (!defined(\'ABSPATH\')) { define(\'ABSPATH\', __DIR__ . \'/\');}',
'require_once ABSPATH . \'wp-settings.php\';',
];
return $configLines;
}
public function getWpSecretKeyLines()
{
$saltUrl = 'https://api.wordpress.org/secret-key/1.1/salt';
$context = stream_context_create([
'http' => [
'method' => 'GET',
'follow_location' => 0,
],
]);
$saltLines = null;
$tries = 10;
while ($tries > 0) {
$saltLines = file($saltUrl, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES, $context);
if (is_array($saltLines) && !empty($saltLines)) {
break;
}
sleep(1);
$tries--;
}
if (empty($saltLines)) {
error_log('Failed to get secret keys from WordPress.org secret-key service. Using fallback keys!');
// code-spell-checker:disable
$saltLines = [
'define(\'AUTH_KEY\', \'Ny-Hid3)a,txte@1{>1*ZV!$~*bv!?qy|gdgI}m866_d3KUU3]9X(5{%p~1:vR<M\');',
'define(\'SECURE_AUTH_KEY\', \'!$++)e`-in%%BI8p85j$7s_s| gNK]W5DDY;nvc*2cN+7 V5u?_-w0i##)j[`]56\');',
'define(\'LOGGED_IN_KEY\', \'~7 z?b)9#Hi aSO*m+oH{SO}<bX]3#3 xEz-v|fHc&=GWK8A`^dWrbGlx]:i|q3B\');',
'define(\'NONCE_KEY\', \'TT_j[UrR.<pbph5X*_]YqxaQQ1_X_l1&@-x-+7kzZg)P]T8f++-j13aY0Izpm0Kc\');',
'define(\'AUTH_SALT\', \'le)+1%+4gfPWEznArm+S`gKMJ=QEMJN)Jh:_r6_&a=9^!4l(YH jtv_yi|E?LED`\');',
'define(\'SECURE_AUTH_SALT\', \'z^~Ysj0_;]MBT1$C-QDP?+eCu4W+~cd~YAoK?nu-Xj,/cbPx#KN8^|[6mUWZpsV5\');',
'define(\'LOGGED_IN_SALT\', \'|xft)SI6OLY*Y+fi.+^i+8n/3xW!#]A_?djgjVN99#+e4b>d]%0*COz$H,q]l-.]\');',
'define(\'NONCE_SALT\', \'_c@o,|<mo;|ta`70BJC~{Fsu~h;S$Rj!HMNakU.|#%Lh}v,45AL62sP,6VWy7Cx@\');',
];
// code-spell-checker:enable
}
return $saltLines;
}
public function getWpConfigLines()
{
$configLines = [];
$configOptions = $this->optionManager->parse()->getConfigOptions();
foreach ($configOptions as $configOption) {
if ($configOption instanceof WpConfigOption) {
$configLines = array_merge($configLines, $configOption->getWpConfigDefineStatements());
}
}
return $configLines;
}
/**
* Get array of configuration file's lines
*
* @return array
*/
public function getConfigLines()
{
$configLines = array_merge(
$this->phpOpenTagLines(),
$this->getWpConfigLines(),
$this->getWpSecretKeyLines(),
$this->getWpLastConfigLines()
);
return $configLines;
}
/**
* Get target config file path to generate.
*
* @return string
*/
public function getConfigFile()
{
$configPath = $this->optionManager->getRestValue();
if (!empty($configPath)) {
if (is_dir($configPath)) {
$configPath = sprintf('%s/%s', rtrim($configPath, '/'), 'wp-config.php');
}
} else {
$configPath = $this->defaultConfigFile();
}
return $configPath;
}
/**
* Define fallback WordPress config file to generate
*
* @return string
*/
public function defaultConfigFile()
{
$configFile = 'wp-config.php';
$currentWorkingDir = getcwd();
if (!empty($currentWorkingDir)) {
$configFile = sprintf('%s/%s', rtrim($currentWorkingDir, DIRECTORY_SEPARATOR), $configFile);
}
return $configFile;
}
public function generate()
{
$configLines = $this->getConfigLines();
$configContent = implode(PHP_EOL, $configLines);
$configFile = $this->getConfigFile();
$written = file_put_contents($configFile, $configContent, LOCK_EX);
if ($written === false) {
throw new RuntimeException("Failed to generate configuration file '{$configFile}'.", 1);
}
}
}
ConfigGenerator::getInstance()->generate();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment