Skip to content

Instantly share code, notes, and snippets.

@earth3300
Created March 11, 2020 15:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save earth3300/6817d7d6eb42be813d9f961c13c65f9e to your computer and use it in GitHub Desktop.
Save earth3300/6817d7d6eb42be813d9f961c13c65f9e to your computer and use it in GitHub Desktop.
Prints an htaccess file.
<?php
/**
* Htaccess Printer
*
* Prints an `.htaccess` file.
*
* @package Earth3300\EC01
* @since 1.0.1
* @author Clarence J. Bos <cbos@tnoep.ca>
* @copyright Copyright (c) 2018, Clarence J. Bos
* @license https://www.gnu.org/licenses/gpl-3.0.en.html GPL v3.0
* @link https://gist.github.com/earth3300/6817d7d6eb42be813d9f961c13c65f9e
*
* File: printer.htac.php
* Created: 2019-03-14
* Updated: 2020-03-11
* Time: 11:52 AM EDT
* ID: ENG-ON-001
*/
/*
* Notes
*
* Jan 2020:
* Added support for ".well-known" server side script that was added by host.
* Updated PHP version supported.
*/
namespace Earth3300\EC01;
// Define No Direct Access
define('NDA', true);
/**
* Prints an .htaccess file.
*/
class HtacPrinter
{
/** @var array Default options. */
protected $opts = [
'title' => 'EC01 Htaccess Printer',
'domain' => [
'prod' => 'https://www.example.com',
'stage' => 'https://dev.example.com',
'local' => 'http://example.local',
],
'dir' => [
'in' => '', // '' Empty string refers to document root.
'root' => '/',
'main' => '/main',
'out' => '/main',
'page' => '/page',
'cache' => '/b/cache/simple-cache/',
'prod' => 'main', // No preceding slash.
'stage' => 'stage',
'local' => 'main',
'backup' => '/.backup',
'archive' => '/.archive',
], // /dir
'server' => [
'conf' => [
'root' => '/etc',
'name' => '/apache2',
'avail' => '/conf-available',
'prefix' => 'server',
'backup' => '/.script',
],
],
'file' => [
'print' => '/.htaccess', // replace with output.
'in' => '', // If empty, no file to input.
'out' => '/.htaccess',
],
'index' => [
'prod' => '/index.php', // Preceding slash.
'stage' => '/index.php',
'local' => '/index.php',
],
'www' => [
'use' => 1, // if true, redirect to 'www'...
],
'use' => [
'www' => 1,
'dir' => 0, // Use the !-d switch at the site root.
'notes' => 0,
],
// For security, specify the max allowed.
'max' => [
'files' => 00, // When picking up...
'len' => 500, // No more than 3MB please... :}
'cnt' => 1, // When putting together...
],
'message' => [
'na' => 'Not available.',
'write' => [
'denied' => 'Write permission denied.' ,
'success' => 'Write operation succeeded.',
'failure' => 'Write operation failed.',
],
],
// Integer Values
'cache' => [
'time' => 432000
],
// 3600 = 1 hour
// 86400 = 1 day
// 28800 = 3 days
// 432000 = 5 days
// 864000 = 10 days
// May not be able to use in .htaccess
'keep-alive' => 10,
// Booleans
'notes' => 0,
// Write to the root allowed (security feature).
'write' => [
'root' => 1,
],
'login' => [
'rewrite' => 1,
'wp' => [
'block' => 0,
'rewrite' => 0,
'redirect' => 0,
],
],
];
/**
* Construct
*
* Load the data values and replace the default values. The data is in a
* separate file and needs to be loaded before the construct is processed.
*
* @param array $page
*
* @return void
*/
function __construct()
{
$items = getHtacData();
$this->opts = array_replace_recursive( $this->opts, $items );
}
/**
* Init
*/
public function init()
{
if( $this->security() )
{
$this->authorize();
}
}
/**
* Security
*/
private function security()
{
if ( '127.0.0.1' == $_SERVER['REMOTE_ADDR']
&& file_exists( __DIR__ . '/.security' ) )
{
return true;
}
else
{
return false;
}
}
/**
* Authorize
*/
private function authorize()
{
if( isset( $_GET['unlock'] ) )
{
echo $this->run();
}
else
{
echo '<pre>Unauthorized.</pre>' . PHP_EOL;
}
}
/**
* Build the Htaccess File.
*
* Simply gets the strings from the various parts, concatenates them and then
* sends it to be printed.
*
* @return void
*/
private function run()
{
if( isset( $_GET['run'] ) )
{
$file = [];
$file = $this->getPaths( $file );
$text['stamp']['desc']['use'] = 1;
if ( $text['stamp']['desc']['use'] )
{
$text['stamp']['desc']['title'] = '';
$text['stamp']['desc']['type'] = 'stamp';
$text['stamp']['desc']['text'] = $this->getFileDesc();
$text['stamp']['desc']['desc'] = '';
}
$text['stamp']['time']['use'] = 1;
if ( $text['stamp']['time']['use'] )
{
$text['stamp']['time']['title'] = '';
$text['stamp']['time']['type'] = 'stamp';
$text['stamp']['time']['text'] = $this->getTimeStamp();
$text['stamp']['time']['desc'] = '';
}
// This Lines of Code Stamps adds a placeholder only.
$text['stamp']['loc']['use'] = 1;
if ( $text['stamp']['loc']['use'] )
{
$text['stamp']['loc']['title'] = '';
$text['stamp']['loc']['type'] = 'stamp';
$text['stamp']['loc']['text'] = $this->getLocStamp();
$text['stamp']['loc']['desc'] = '';
}
$text['stamp']['id']['use'] = 1;
if ( $text['stamp']['id']['use'] )
{
$text['stamp']['id']['title'] = '';
$text['stamp']['id']['type'] = 'stamp';
$text['stamp']['id']['text'] = $this->getIDStamp();
$text['stamp']['id']['desc'] = '';
}
$text['index']['order']['use'] = 1;
if ( $text['index']['order']['use'] )
{
$text['index']['order']['title'] = 'Order in which index files are retrieved';
$text['index']['order']['type'] = 'index';
$text['index']['order']['text'] = $this->getIndexOrder();
$text['index']['order']['desc'] = '';
}
$text['options']['index']['use'] = 1;
if ( $text['options']['index']['use'] )
{
$text['options']['index']['title'] = 'Automatic Server Indexing';
$text['options']['index']['type'] = 'index';
$text['options']['index']['text'] = $this->getOptionsIndex();
$text['options']['index']['desc'] = '';
}
$text['log']['debug']['use'] = 0;
if ( $text['log']['debug']['use'] )
{
$text['log']['debug']['title'] = 'Server Debug Levels';
$text['log']['debug']['type'] = 'debug';
$text['log']['debug']['text'] = $this->getLogLevelDebug();
$text['log']['debug']['desc'] = '';
}
$text['access']['deny']['use'] = 1;
if ( $text['access']['deny']['use'] )
{
$text['access']['deny']['title'] = 'Improve security by denying access to files that match these values';
$text['access']['deny']['type'] = 'security';
$text['access']['deny']['text'] = $this->getSecurityDenyFiles();
$text['access']['deny']['desc'] = '';
}
$text['access']['grant']['use'] = 1;
if ( $text['access']['grant']['use'] )
{
$text['access']['grant']['title'] = 'Improve security by granting access to files normally made public';
$text['access']['grant']['type'] = 'security';
$text['access']['grant']['text'] = $this->getSecurityGrantFileTypes();
$text['access']['grant']['desc'] = '';
}
$text['optimize']['deflate']['use'] = 1;
if ( $text['optimize']['deflate']['use'] )
{
$text['optimize']['deflate']['title'] = 'Optimize: Deflate certain file types';
$text['optimize']['deflate']['type'] = 'optimize';
$text['optimize']['deflate']['text'] = $this->getOptimizeDeflate();
$text['optimize']['deflate']['desc'] = '';
}
$text['optimize']['cache']['use'] = 1;
if ( $text['optimize']['cache']['use'] )
{
$text['optimize']['cache']['title'] = 'Optimize: Set cache control, revalidation and max age.';
$text['optimize']['cache']['type'] = 'optimize';
$text['optimize']['cache']['text'] = $this->getOptimizeCache();
$text['optimize']['cache']['desc'] = '';
}
$text['optimize']['etag']['use'] = 1;
if ( $text['optimize']['etag']['use'] )
{
$text['optimize']['etag']['title'] = 'Optimize: Set or Unset Etags for certain file types';
$text['optimize']['etag']['type'] = 'optimize';
$text['optimize']['etag']['text'] = $this->getOptimizeEtag();
$text['optimize']['etag']['desc'] = '';
}
$text['charset']['add']['use'] = 1;
if ( $text['charset']['add']['use'] )
{
$text['charset']['add']['title'] = 'Set the Character Set Explicitly for HTML files';
$text['charset']['add']['type'] = 'charset';
$text['charset']['add']['text'] = $this->getCharset();
$text['charset']['add']['desc'] = '';
}
$text['response']['codes']['use'] = 0;
if ( $text['response']['codes']['use'] )
{
$text['response']['codes']['title'] = 'Server Response Codes';
$text['response']['codes']['type'] = 'codes';
$text['response']['codes']['text'] = $this->getCodesResp();
$text['response']['codes']['desc'] = '';
}
$text['redirect']['301']['use'] = 0;
if ( $text['redirect']['301']['use'] )
{
$text['redirect']['301']['title'] = 'Redirect permanently to another directory (301)';
$text['redirect']['301']['type'] = 'rewrite';
$text['redirect']['301']['text'] = $this->getRedirect301();
$text['redirect']['301']['desc'] = '';
}
$text['rewrite']['root']['use'] = 1;
if ( $text['rewrite']['root']['use'] )
{
$text['rewrite']['root']['title'] = 'Rewrite explicitly to the root index file';
$text['rewrite']['root']['type'] = 'rewrite';
$text['rewrite']['root']['text'] = $this->getRewriteRootIndex();
$text['rewrite']['root']['desc'] = 'Rewrite request to root to /index.html, explicitly';
}
$text['redirect']['www']['use'] = 1;
if ( $text['redirect']['www']['use'] )
{
$text['redirect']['www']['title'] = 'Redirect to WWW if not already';
$text['redirect']['www']['type'] = 'rewrite';
$text['redirect']['www']['text'] = $this->getRedirectWWW();
$text['redirect']['www']['desc'] = 'Redirect to www (maybe).';
}
$text['redirect']['https']['use'] = 1;
if ( $text['redirect']['https']['use'] )
{
$text['redirect']['https']['title'] = 'Redirect to HTTPS if not local';
$text['redirect']['https']['type'] = 'rewrite';
$text['redirect']['https']['text'] = $this->getRedirectHttps();
$text['redirect']['https']['desc'] = 'Redirect to https (maybe).';
}
$text['redirect']['https-cond']['use'] = 0;
if ( $text['redirect']['https-cond']['use'] )
{
$text['redirect']['https-cond']['title'] = 'Redirect to Https (Conditional)';
$text['redirect']['https-cond']['type'] = 'rewrite';
$text['redirect']['https-cond']['text'] = $this->getRedirectHttpsCond();
$text['redirect']['https-cond']['desc'] = '';
}
$text['rewrite']['login']['use'] = 1;
if ( $text['rewrite']['login']['use'] )
{
$text['rewrite']['login']['title'] = 'Rewrite LOGIN (Security)';
$text['rewrite']['login']['type'] = 'rewrite';
$text['rewrite']['login']['text'] = $this->getRewriteLogin();
$text['rewrite']['login']['desc'] = 'Better secures the login.';
}
$text['redirect']['match']['use'] = 0;
if ( $text['redirect']['match']['use'] )
{
$text['redirect']['match']['title'] = 'Redirect Match';
$text['redirect']['match']['type'] = 'rewrite';
$text['redirect']['match']['text'] = $this->getRedirectMatch();
$text['redirect']['match']['desc'] = '';
}
$text['rewrite']['complex']['use'] = 1;
if ( $text['rewrite']['complex']['use'] )
{
$text['rewrite']['complex']['title'] = 'Rewrite CACHE';
$text['rewrite']['complex']['type'] = 'rewrite';
$text['rewrite']['complex']['text'] = $this->getRewriteCache();
$text['rewrite']['complex']['desc'] = '';
}
$text['rewrite']['main']['use'] = 1;
if ( $text['rewrite']['main']['use'] )
{
$text['rewrite']['main']['title'] = 'Rewrite to a Directory';
$text['rewrite']['main']['type'] = 'rewrite';
$text['rewrite']['main']['text'] = $this->getRewriteMainToSub();
$text['rewrite']['main']['desc'] = '';
}
$text['rewrite']['staging']['use'] = 0;
if ( $text['rewrite']['staging']['use'] )
{
$text['rewrite']['staging']['title'] = 'Rewrite Staging to a directory';
$text['rewrite']['staging']['type'] = 'rewrite';
$text['rewrite']['staging']['text'] = $this->getRewriteStagingToSub();
$text['rewrite']['staging']['desc'] = '';
}
$text['rewrite']['local']['use'] = 1;
if ( $text['rewrite']['local']['use'] )
{
$text['rewrite']['local']['title'] = 'Rewrite Main to a directory (local)';
$text['rewrite']['local']['type'] = 'rewrite';
$text['rewrite']['local']['text'] = $this->getRewriteMainLocal();
$text['rewrite']['local']['desc'] = '';
}
$text['rewrite']['standard']['use'] = 1;
if ( $text['rewrite']['standard']['use'] )
{
// This is the standard WordPress rewrite.
$text['rewrite']['standard']['title'] = '';
$text['rewrite']['standard']['type'] = 'rewrite';
$text['rewrite']['standard']['text'] = $this->getRewriteStandard();
$text['rewrite']['standard']['desc'] = '';
}
$text['handler']['php']['use'] = 1;
if ( $text['handler']['php']['use'] )
{
$text['handler']['php']['title'] = 'PHP Handler';
$text['handler']['php']['type'] = 'handler';
$text['handler']['php']['text'] = $this->getPhpHandler(); //Add PRN
$text['handler']['php']['desc'] = '';
}
$text['stamp']['notes']['use'] = 1;
if ( $text['stamp']['notes']['use'] )
{
$text['stamp']['notes']['title'] = 'Notes';
$text['stamp']['notes']['type'] = 'stamp';
$text['stamp']['notes']['text'] = $this->getNotes();
$text['stamp']['notes']['desc'] = '';
}
$text['stamp']['refs']['use'] = 1;
if ( $text['stamp']['refs']['use'] )
{
$text['stamp']['refs']['title'] = 'References';
$text['stamp']['refs']['type'] = 'stamp';
$text['stamp']['refs']['text'] = $this->getReferences();
$text['stamp']['refs']['desc'] = '';
}
// Convert the above array to a string (not incl. server level config).
$file = $this->genScriptMain( $file, $text );
// Generate the server wrap text.
$file = $this->getServerPaths( $file );
if ( 1 )
{
// Wrap the configuration text in the server text.
$file = $this->getServerConf( $file );
}
if ( 1 )
{
// Use $file['text'] to calculate file stats.
$file = $this->getLinesofCode( $file );
}
if ( 1 )
{
// Insert the "Lines of Code" values into the string.
$file = $this->setLocStamp( $file );
}
// Print the file to a storage medium.
$file = $this->printFile( $file );
// Get the Response Html.
$file['msg'] = $this->getRespHtml( $file );
// Get the Page Html.
$html = $this->getPageHTML( $file );
// Echo the Html to the browser screen.
echo $html;
}
else
{
echo '<pre>You wish to run the script?</pre>' . PHP_EOL;
}
}
/**
* Get Server Configuration
*
* Generate the text needed to print for the server level configuration.
* This will be (for now) simply ALL the directives that would otherwise
* be placed in the main .htaccess file (i.e. in the root of the site).
*
* @return string
*/
private function getServerConf( $file )
{
// Initialize.
$str = '';
$str .= PHP_EOL;
$str .= sprintf( '# %s', $file['server']['conf']['title'] ) . PHP_EOL;
$str .= $file['server']['conf']['begin'];
$str .= $file['text'];
$str .= $file['server']['conf']['end'];
$file['server']['text'] = $str;
return $file;
}
/**
* Generate the Script for the Main Domain
*
* Takes an array, cycles through it and adds string values to a string.
* then returns the array as an array with a key set to this concatenated
* string. The array being used is three levels deep. The third level
* contains the string values in a key labelled `text`.
*
* @param array $file
* @param array $text
*
* @return array $file
*/
private function genScriptMain( $file, $items )
{
// Initialize
$str = '';
$files['text'] = null;
if( is_array( $items ) )
{
foreach( $items as $item )
{
if( is_array( $item ) )
{
foreach( $item as $key )
{
if( isset( $key['text'] ) && is_string( $key['text'] ) )
{
if( isset( $key['use'] ) && $key['use'] )
{
if( strlen( $key['text'] ) > 0 )
{
// Add the title, if it is there.
if( isset( $key['title'] ) && $key['title'] )
{
$str .= sprintf( '# %s', $key['title'] ) . PHP_EOL;
}
if( 'rewrite' == $key['type'] )
{
// If the rewrite module check is not already in the text, add it.
if( strpos( $key['text'], 'mod_rewrite.c>' ) == false )
{
$str .= '<IfModule mod_rewrite.c>' . PHP_EOL;
$str .= ' RewriteEngine On' . PHP_EOL;
}
}
$str .= $key['text'];
if( 'rewrite' == $key['type'] )
{
// If the rewrite module check is not already in the text, add it.
if( strpos( $key['text'], 'mod_rewrite.c>' ) == false )
{
$str .= '</IfModule>' . PHP_EOL;
}
}
// Add an extra line for readability.
$str .= PHP_EOL;
}
}
}
}
}
}
}
else
{
$file['text'] = '';
}
if ( strlen ( $str ) > 0 )
{
$file['text'] = $str;
}
return $file;
}
/**
* Generate the Script for the Root of the Site
*
* By default, the main domain is routed to the root of the site. To prevent
* directory conflicts, the main domain files are moved to their own
* directory. The server configuration script (htaccess file) in the root
* properly routes only calls from the main domain to its directory.
*
* @param array $file
* @param array $text
*
* @return array $file
*/
private function genScriptRoot( $file, $items )
{
// Initialize
$str = '';
$files['text'] = null;
if( is_array( $items ) )
{
foreach( $items as $item )
{
if( is_array( $item ) )
{
foreach( $item as $key )
{
if( isset( $key['text'] ) && is_string( $key['text'] ) )
{
if( isset( $key['use'] ) && $key['use'] )
{
if( strlen( $key['text'] ) > 0 )
{
// Add the title, if it is there.
if( isset( $key['title'] ) && $key['title'] )
{
$str .= sprintf( '# %s', $key['title'] ) . PHP_EOL;
}
if( 'rewrite' == $key['type'] )
{
// If the rewrite module check is not already in the text, add it.
if( strpos( $key['text'], 'mod_rewrite.c>' ) == false )
{
$str .= '<IfModule mod_rewrite.c>' . PHP_EOL;
$str .= ' RewriteEngine On' . PHP_EOL;
}
}
$str .= $key['text'];
if( 'rewrite' == $key['type'] )
{
// If the rewrite module check is not already in the text, add it.
if( strpos( $key['text'], 'mod_rewrite.c>' ) == false )
{
$str .= '</IfModule>' . PHP_EOL;
}
}
// Add an extra line for readability.
$str .= PHP_EOL;
}
}
}
}
}
}
}
else
{
$file['text'] = '';
}
if ( strlen ( $str ) > 0 )
{
$file['text'] = $str;
}
return $file;
}
/**
* Get the Paths Used in the Current Project
*
* Uses options values if these are set. Modify on a per-project basis.
* Intended to be used to save and retrieve files, but can be used more
* generically as it sets and standardizes the document root and the relative
* path to the directory in which the containing file resides.
*
* Last updated: 2020-03-06
* No. of lines: 42
*
* @param array $file
*
* @return array
*/
private function getPaths( $file )
{
// Initialize
$file['path']['in'] = null;
$file['path']['dir'] = null;
$file['path']['out'] = null;
$file['path']['main'] = null;
$file['path']['root'] = null;
$file['path']['rel'] = null;
$file['server'] = null;
$file['path']['file']['root'] = null;
$file['path']['file']['main'] = null;
// $file['path']['file']['server'] = null;
$file['path']['file']['out'] = null;
$file['path']['file']['backup'] = null;
// The document root is the highest level accessible by the domain.
$file['path']['root'] = rtrim( $_SERVER['DOCUMENT_ROOT'], '/' );
// Remove the main directory, if present.
$file['path']['root'] = str_replace(
$this->opts['dir']['main'], '', $file['path']['root'] );
// Calculate the main directory, based on the root and the main dir given.
$file['path']['main'] = $file['path']['root'] . $this->opts['dir']['main'];
// The path to the directory this file is in.
$file['path']['dir'] = __DIR__;
// The difference between the root path and this directory (relative path).
$file['path']['rel'] = str_replace(
$file['path']['root'], '', $file['path']['dir'] );
// Used only if source files are needed.
$file['path']['in'] = null;
// Set the directory out explictly (from the root). Can be empty.
$file['dir']['out'] = $this->opts['dir']['out'];
// Set the path out explicitly.
$file['path']['out'] = $file['path']['root'];
$file['path']['out'] .= $file['path']['rel'];
// Set the file out explicitly (from the options).
$file['file']['out'] = $this->opts['file']['out'];
// Find the file name (no slashes).
$file['file']['name'] = ltrim( $this->opts['file']['out'], '/' );
// Set the path to the backup (with date appended).
$file['path']['file']['backup'] = $file['path']['out'];
$file['path']['file']['backup'] .= $this->opts['dir']['backup'];
$file['path']['file']['backup'] .= $file['file']['out'];
$file['path']['file']['backup'] .= '-' . date( 'Y-m-d' );
// Set the path to the file in the document root explicitly.
$file['path']['file']['root'] = $file['path']['root'];
$file['path']['file']['root'] .= $file['file']['out'];
// Set the path to the file in the main directory.
$file['path']['file']['main'] = $file['path']['main'];
$file['path']['file']['main'] .= $file['file']['out'];
// Set the path to the file out (Use only if it does not interfere).
$file['path']['file']['out'] = $file['path']['out'];
$file['path']['file']['out'] .= $file['file']['out'];
return $file;
}
/**
* Get Server Paths
*
* Generate the server directory directive in which to encapsulate the
* directives which otherwise would be placed in the main .htaccess file.
* The directory must have a preceding slash, but not a trailing slash.
* The server directory has: apache/conf-available. This would be a
* reasonable directory in which to write the file, *provided* that it did
* not override any other files. For this, it ought to be unique.
*
* i.e. /etc/apache2/conf-available
*
* 'server' => [
* 'config' => [
* 'root' => '/etc',
* 'name' => '/apache2',
* 'avail' => '/conf-available',
* ],
*
* @link https://httpd.apache.org/docs/2.4/mod/core.html#directory
*
* @return string
*/
private function getServerPaths( $file )
{
// Initialize.
$server['conf']['use'] = null;
$server['conf']['title'] = null;
$server['conf']['path'] = null;
$server['conf']['file'] = null;
$server['conf']['backup'] = null;
$server['conf']['begin'] = null;
$server['conf']['end'] = null;
$server['conf']['desc'] = null;
$server['conf']['use'] = 1;
$server['conf']['title'] = 'A specific directory on the server';
$server['conf']['begin'] = sprintf( '<Directory "%s">',
$file['path']['main'] ). PHP_EOL;
$server['conf']['end'] = '</Directory>' . PHP_EOL;
$server['conf']['path'] = $this->opts['server']['conf']['root'];
$server['conf']['path'] .= $this->opts['server']['conf']['name'];
$server['conf']['path'] .= $this->opts['server']['conf']['avail'];
$server['conf']['backup'] = $file['path']['out'];
$server['conf']['backup'] .= $this->opts['server']['conf']['backup'];
$server['conf']['backup'] .= sprintf( '/%s-', $this->opts['server']['conf']['prefix'] );
$server['conf']['backup'] .= ltrim( $this->opts['dir']['main'], '/' );
$server['conf']['backup'] .= '.conf';
// The server file is the path to the server, plus the main directory.
$server['conf']['file'] = $server['conf']['path'];
$server['conf']['file'] .= sprintf( '/%s-', $this->opts['server']['conf']['prefix'] );
$server['conf']['file'] .= ltrim( $this->opts['dir']['main'], '/' );
// Add the `.conf` extension.
$server['conf']['file'] .= '.conf';
$file['server'] = $server;
return $file;
}
//##########################################################################
/**
* Get Log Level Debug
*
* This may not work at the .htaccess level (may need to be one level
* higher.
*
* @return string
*/
private function getLogLevelDebug()
{
// Initialize.
$str = '';
// Currently empty.
return $str;
}
/**
* Index Order
*
* The default order is: index.html index.php default.html [check]
* This reverses them so that a non-standard index file will be used. This is
* so that it is not inadvertantly overwritten by other files or programs. We
* want it to be stable.
*
* @return string
*/
private function getIndexOrder()
{
// Initialize.
$str = '';
$str .= 'DirectoryIndex index.html index.php default.html' . PHP_EOL;
$str .= $this->opts['use']['notes'] ? '# Look for files in this order.' . PHP_EOL : '';
return $str;
}
/**
* Options Index
*
* @return string
*/
private function getOptionsIndex()
{
// Initialize.
$str = '';
$str .= $this->opts['use']['notes'] ? '# Do not allow the listing of directories by default' . PHP_EOL : '';
$str .= 'Options -Indexes' . PHP_EOL;
return $str;
}
/**
* Security: Deny Access to Files
*
* @return string
*/
private function getSecurityDenyFiles()
{
// Initialize.
$str = '';
// Deny access to file that contain these values.
$str .= '<FilesMatch "xmlrpc.php|config|error|debug|changelog|readme|license">' . PHP_EOL;
$str .= ' Require all denied' . PHP_EOL;
$str .= '</FilesMatch>' . PHP_EOL;
$str .= PHP_EOL;
$str .= '# Improve security by denying access to these hidden files' . PHP_EOL;
$str .= '<FilesMatch "\.(log|data|wpcli)$">' . PHP_EOL;
$str .= ' Require all denied' . PHP_EOL;
$str .= '</FilesMatch>' . PHP_EOL;
return $str;
}
/**
* Security: Grant Access to Files Types listed
*
* @return string
*/
private function getSecurityGrantFileTypes()
{
// Initialize.
$str = '';
$str .= '<FilesMatch "\.(html|css|js|jpg|png|gif|pdf|ico|mp3|mp4|webm)$">' . PHP_EOL;
$str .= ' Require all granted' . PHP_EOL;
$str .= '</FilesMatch>' . PHP_EOL;
return $str;
}
/**
* Optimization
*
* These optimization values will improve page speed scores.
*
* @return string
*/
private function getOptimizeDeflate()
{
// Initialize.
$str = '';
$str .= '<IfModule mod_deflate.c>' . PHP_EOL;
$str .= ' AddOutputFilterByType DEFLATE';
$str .= ' text/html text/css application/javascript image/x-icon' . PHP_EOL;
$str .= '</IfModule>' . PHP_EOL;
$str .= PHP_EOL;
}
/**
* Optimize Deflate
*
* These optimization values will improve page speed scores.
*
* @return string
*/
private function getOptimizeCache()
{
// Initialize.
$str = '';
$str .= '<IfModule mod_headers.c>' . PHP_EOL;
$str .= ' <FilesMatch "\.(html|jpg|png|gif|css|js|pdf|ico|mp3|mp4)$">' . PHP_EOL;
$str .= ' Header set Cache-Control';
$str .= sprintf(' "max-age=%s, public, must-revalidate"%s', $this->opts['cache']['time'], PHP_EOL );
$str .= ' </FilesMatch>' . PHP_EOL;
$str .= '</IfModule>' . PHP_EOL;
return $str;
}
/**
* Optimize Etags
*
* These optimization values will improve page speed scores.
*
* @return string
*/
private function getOptimizeEtag()
{
// Initialize.
$str = '';
$str .= '<IfModule mod_headers.c>' . PHP_EOL;
$str .= ' <FilesMatch "\.(html|jpg|png|gif|css|js|pdf|ico|mp3|mp4)$">' . PHP_EOL;
$str .= ' Header unset ETag' . PHP_EOL;
$str .= ' FileETag None' . PHP_EOL;
$str .= ' </FilesMatch>' . PHP_EOL;
$str .= '</IfModule>' . PHP_EOL;
return $str;
}
/**
* Set the Charset
*
* Set the charset expclicitly to UTF-8 for html files, unless another
* character set is needed.
*
* @return string
*/
private function getCharset()
{
// Initialize.
$str = '';
$str .= '<IfModule mod_headers.c>' . PHP_EOL;
$str .= ' AddCharset UTF-8 .html' . PHP_EOL;
$str .= '</IfModule>' . PHP_EOL;
return $str;
}
/**
* Keep Alive
*
* @return string
*/
private function getKeepAlive()
{
// Initialize
$str = '';
$str .= '<ifModule mod_headers.c>' . PHP_EOL;
$str .= ' Header set Connection keep-alive' . PHP_EOL;
$str .= ' KeepAliveTimeout 10' . PHP_EOL;
$str .= '</ifModule>' . PHP_EOL;
return $str;
}
/**
* Redirect 301 (Permanent)
*
* Give out easy to remember URLs and redirect them to the actual pages here.
* {@example /store /a/store/} (Note the addition of the trailing slash, which
* is required to denote a directory in a Unix like system). On the same host
* (domain), the domain name is needed.
*
* @return string
*/
private function getRedirect301()
{
// This function must exist in the data file.
$items = getRedirect301Data();
// Initialize.
$str = '';
$str .= $this->opts['use']['notes'] ? '# REDIRECT 301' . PHP_EOL : '';
foreach( $items as $item )
{
if( $item['use'] )
{
$str .= sprintf( 'Redirect 301 %s %s%s', $item['old'], $item['new'], PHP_EOL );
}
}
$str .= $this->opts['use']['notes'] ? '# /REDIRECT 301' . PHP_EOL : '';
return $str;
}
/**
* Response Codes
*
* Standard response codes that are often forgetten and can retrieve cool
* pages to stun your readers.
*
* @return string
*/
private function getCodesResp()
{
// Initialize.
$str = '';
0 ? $str .= 'ErrorDocument 401 /resp/401.html' . PHP_EOL : '';
1 ? $str .= 'ErrorDocument 403 /resp/403.html' . PHP_EOL : '';
1 ? $str .= 'ErrorDocument 404 /resp/404.html' . PHP_EOL : '';
1 ? $str .= 'ErrorDocument 500 /resp/500.html' . PHP_EOL : '';
0 ? $str .= 'ErrorDocument 503 /resp/503.html' . PHP_EOL : '';
return $str;
}
/**
* Redirect to HTTPS if off
*
* Always redirects to HTTPS, even if running locally.
* The following was added by Site Ground and is exactly same as script below.
*
* # HTTPS forced by SG-Optimizer
* <IfModule mod_rewrite.c>
* RewriteEngine On
* RewriteCond %{HTTPS} off
* RewriteRule ^(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
* </IfModule>
* # END HTTPS
*
* @return string
*/
private function getRedirectHttps()
{
// Initialize.
$str = '';
// If https is on AND the request is local, turn OFF.
$str .= ' RewriteCond %{HTTPS} on' . PHP_EOL;
$str .= ' RewriteCond %{SERVER_ADDR} 127.0.0.1' . PHP_EOL;
$str .= ' RewriteRule ^(.*)$ http://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]' . PHP_EOL;
// If https is off AND the request is NOT local, turn ON.
$str .= ' RewriteCond %{HTTPS} off' . PHP_EOL;
$str .= ' RewriteCond %{SERVER_ADDR} !=127.0.0.1' . PHP_EOL;
$str .= ' RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]' . PHP_EOL;
return $str;
}
/**
* Redirect to WWW
*
* @return string
*/
private function getRedirectWWW()
{
// Initialize.
$str = '';
// Set the environment variable to match the protocol.
$str .= ' RewriteCond %{HTTPS} on' . PHP_EOL; // if https IS on
$str .= ' RewriteRule ^(.*)$ - [ENV=ps:https]' . PHP_EOL; // => https
$str .= ' RewriteCond %{HTTPS} off' . PHP_EOL; // if https is NOT on.
$str .= ' RewriteRule ^(.*)$ - [ENV=ps:http]' . PHP_EOL; // => http
// If www is NOT present AND it is required, turn on (case insensitive).
$str .= ' RewriteCond %{HTTP_HOST} !^www\. [NC]' . PHP_EOL;
// If not local.
$str .= ' RewriteCond %{SERVER_ADDR} !=127.0.0.1' . PHP_EOL;
// Use the environment variable and the $1 is from the the (.*) capture group.
$str .= ' RewriteRule (.*) %{ENV:ps}://www.%{HTTP_HOST}/$1 [R=301,L]' . PHP_EOL;
return $str;
}
/**
* Redirect to HTTPS if off and not local.
*
* Contains the option to also redirect to 'www' (set in opts). Please note
* that the [OR] invokes an explicit OR condition, whereas the implicit
* condition (not written) is AND.
*
* <IfModule mod_rewrite.c>
* RewriteCond %{HTTPS} off [OR]
* RewriteCond %{HTTP_HOST} !^www\. [NC]
* RewriteCond %{HTTP_HOST} ^(.*)$ [NC]
* RewriteRule (.*) https://www.%1/$1 [R=301,L]
* </IfModule>
*
* RewriteCond %{HTTPS} =on
* RewriteRule ^(.*)$ - [env=ps:https]
* RewriteCond %{HTTPS} !=on
* RewriteRule ^(.*)$ - [env=ps:http]
*
* @link http://httpd.apache.org/docs/current/mod/mod_rewrite.html
* @link https://htaccessbook.com/htaccess-redirect-https-www/
*
* @return string
*/
private function getRedirectHttpsCond()
{
// Initialize.
$str = '';
// Use this only if server already added it. Double check it.
$str .= 1 ? $this->getDomainValidationScript() : '';
// If the server address is not local.
$str .= ' RewriteCond %{SERVER_ADDR} !=127.0.0.1' . PHP_EOL;
// If HTTPS is off.
$str .= ' RewriteCond %{HTTPS} off' . PHP_EOL;
// The %1 is the first %{group}. The $1 is from the the (.*) group (above).
$str .= ' RewriteRule (.*) https://%{HTTP_HOST}/$1 [R=301,L]' . PHP_EOL;
if ( $this->opts['use']['www'] )
{
// If the server address is not local.
$str .= ' RewriteCond %{SERVER_ADDR} !=127.0.0.1' . PHP_EOL;
// If www is not in the URL.
$str .= ' RewriteCond %{HTTP_HOST} !^www\. [NC]' . PHP_EOL;
// The $1 is from the the (.*) capture group.
$str .= ' RewriteRule (.*) https://www.%{HTTP_HOST}/$1 [R=301,L]' . PHP_EOL;
}
return $str;
}
/**
* Domain Validation Script
*
* Added automatically. Please refer to online discussion about this.
*/
private function getDomainValidationScript()
{
// Initialize.
$str = '';
$script['text'] = '';
$script['max']['len'] = 500;
$script['text'] = file_get_contents ( __DIR__ . '/.script/.well-known' );
if ( is_string( $script['text'] ) && strlen( $script['text'] ) < $script['max']['len'] )
{
$str .= $script['text'];
}
return $str;
}
/**
* Rewrite Root to Index
*
* Explicitly rewrite the request to the root to the index file.
*/
private function getRewriteRootIndex()
{
// Initialize.
$str = '';
// The request URI is for a directory (trailing slash only).
$str .= ' RewriteCond %{REQUEST_URI} /$' . PHP_EOL;
// The request file name is a directory (not a file).
$str .= ' RewriteCond %{REQUEST_FILENAME} -d' . PHP_EOL;
// Rewrite to the requested directory, with index.html appended.
$str .= ' RewriteRule . %{REQUEST_URI}/index.html [L]' . PHP_EOL;
return $str;
}
/**
* Cache Rewrite
*
* Rewrite to pick up cache files.
*
* @return string
*/
private function getRewriteCache()
{
$req = [];
$req['post'] = 1;
$req['query'] = 1;
$req['cookie'] = 1;
$req['cache'] = 1;
// Initialize.
$str = '';
$str .= ' RewriteBase /' . PHP_EOL;
// Request for a post.
if ( $req['post'] )
{
$str .= ' RewriteCond %{REQUEST_METHOD} !=POST' . PHP_EOL;
}
// Request for query
if ( $req['query'] )
{
$str .= ' RewriteCond %{QUERY_STRING} !.*=.*' . PHP_EOL;
// There is no attacment id (?). (Is this not the same as the previous?).
$str .= ' RewriteCond %{QUERY_STRING} !.*attachment_id=.*' . PHP_EOL;
}
// Request for cookie
if ( $req['cookie'] )
{
// There is no cookie that is a: comment, wordpress or wordpres password.
$str .= ' RewriteCond %{HTTP_COOKIE} !^.*(comment_author_|wordpress|wp-postpass_).*$' . PHP_EOL;
}
// Request for page
if ( $req['cache'] )
{
// If the page directory, plus the capture string, plus the index.html is a match, proceed.
$str .= ' RewriteCond %{DOCUMENT_ROOT}' . $this->opts['dir']['page'] . '/$1/index.html -f' . PHP_EOL;
// Take the capture string, and rewrite to the page directory, plus the capture string, plus index.html.
$str .= ' RewriteRule ^(.*) ' . $this->opts['dir']['page'] . '/$1/index.html [L]' . PHP_EOL;
}
return $str;
}
/**
* Rewrite Login (Important)
*
* Could incorporate into complex rewrite.
*/
private function getRewriteLogin()
{
// Initialize
$str = '';
// If wp-login.php is accessed directly, do nothing.
$str .= ' RewriteRule ^wp-login\.php$ - [L]' . PHP_EOL;
// Rewrite wp-login.php to /login (intended, working?).
$str .= ' RewriteRule ^wp-login\.php /login [R]' . PHP_EOL;
// If the request URI ends with /login (no trailing slash), proceed.
$str .= ' RewriteCond %{REQUEST_URI} ^/login$' . PHP_EOL;
// Rewrite to /wp-login.php (but don't show that in the URL).
$str .= ' RewriteRule ^login(.*) /wp-login.php [L,QSA]' . PHP_EOL;
return $str;
}
/**
* Rewrites the Main Domain to a Subdirectory.
*
* This is important at the root of the server.
*/
private function getRewriteMainToSub()
{
// Initialize
$str = '';
$str .= ' RewriteCond %{HTTP_HOST} ^' . $this->opts['domain']['prod'] . '$' . PHP_EOL;
$str .= ' RewriteCond %{REQUEST_URI} !^/' . $this->opts['dir']['prod'] . '/' . PHP_EOL;
$str .= ' RewriteCond %{REQUEST_FILENAME} !-f' . PHP_EOL;
$str .= $this->opts['use']['dir'] ?' RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL : '';
$str .= ' RewriteRule ^(.*)$ /' . $this->opts['dir']['prod'] . '/$1' . PHP_EOL; // Note the $1 at the end.
$str .= ' RewriteCond %{HTTP_HOST} ^' . $this->opts['domain']['prod'] . '$' . PHP_EOL;
$str .= ' RewriteRule ^(/)?$ ' . $this->opts['dir']['prod'] . $this->opts['index']['prod'] . ' [L]' . PHP_EOL;
return $str;
}
/**
* Rewrites the Staging Sub Domain to a Subdirectory.
*
* Assumes this can't be done at the server level.
*/
private function getRewriteStagingToSub()
{
// Initialize
$str = '';
$str .= ' RewriteCond %{HTTP_HOST} ^' . $this->opts['domain']['stage'] . '$' . PHP_EOL;
$str .= ' RewriteCond %{REQUEST_URI} !^/' . $this->opts['dir']['stage'] . '/' . PHP_EOL;
$str .= ' RewriteCond %{REQUEST_FILENAME} !-f' . PHP_EOL;
$str .= $this->opts['use']['dir'] ? ' RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL : '';
$str .= ' RewriteRule ^(.*)$ /' . $this->opts['dir']['stage'] . '/$1' . PHP_EOL; // Note the $1 at the end.
$str .= ' RewriteCond %{HTTP_HOST} ^' . $this->opts['domain']['stage'] . '$' . PHP_EOL;
$str .= ' RewriteRule ^(/)?$ ' . $this->opts['dir']['stage'] . $this->opts['index']['stage'] . ' [L]' . PHP_EOL;
return $str;
}
/**
* Rewrite Main to Local
*
* Assumes this can't be done at the server level.
*/
private function getRewriteMainLocal()
{
// Initialize
$str = '';
$str .= ' RewriteCond %{HTTP_HOST} ^' . $this->opts['domain']['local'] . '$' . PHP_EOL;
$str .= ' RewriteCond %{REQUEST_URI} !^/' . $this->opts['dir']['local'] . '/' . PHP_EOL;
$str .= ' RewriteCond %{REQUEST_FILENAME} !-f' . PHP_EOL;
$str .= 0 ?' RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL : '';
$str .= ' RewriteRule ^(.*)$ /' . $this->opts['dir']['local'] . '/$1' . PHP_EOL; // Note the $1 at the end.
$str .= ' RewriteCond %{HTTP_HOST} ^' . $this->opts['domain']['local'] . '$' . PHP_EOL;
$str .= ' RewriteRule ^(/)?$ ' . $this->opts['dir']['local'] . $this->opts['index']['local'] . ' [L]' . PHP_EOL;
return $str;
}
/**
* Get Redirect to www.
*
* @return string
*/
private function getRewriteCond03()
{
$str = '';
$str .= 'RewriteCond "%{HTTP_HOST}" "!^www\." [NC]' . PHP_EOL;
$str .= 0 ? 'RewriteCond "%{HTTP_HOST}" "!^$"' . PHP_EOL : '';
$str .= 'RewriteRule . /a/$1 [L,R,NE]' . PHP_EOL;
return $str;
}
/**
* Search for resource in more than one place...
*
* @link https://httpd.apache.org/docs/2.4/rewrite/remapping.html#multipledirs
*
* A particular resource might exist in one of several places, and we want to
* look in those places for the resource when it is requested. Perhaps we've
* recently rearranged our directory structure, dividing content into several
* locations.
*
* The following ruleset searches in two directories to find the resource,
* and, if not finding it in either place, will attempt to just serve it out
* of the location requested.
*
* RewriteEngine on
*
* # first try to find it in dir1/...
* # ...and if found stop and be happy:
* RewriteCond "%{DOCUMENT_ROOT}/dir1/%{REQUEST_URI}" -f
* RewriteRule "^(.+)" "%{DOCUMENT_ROOT}/dir1/$1" [L]
*
* # second try to find it in dir2/...
* # ...and if found stop and be happy:
* RewriteCond "%{DOCUMENT_ROOT}/dir2/%{REQUEST_URI}" -f
* RewriteRule "^(.+)" "%{DOCUMENT_ROOT}/dir2/$1" [L]
*
* # else go on for other Alias or ScriptAlias directives,
* # etc.
* RewriteRule "^" "-" [PT]
*/
private function getRewriteCond05()
{
$str = '';
$str .= ' RewriteCond "%{DOCUMENT_ROOT}/dir1/%{REQUEST_URI}" -f' . PHP_EOL;
$str .= ' RewriteRule "^(.+)" "%{DOCUMENT_ROOT}/dir1/$1" [L]' . PHP_EOL;
$str .= PHP_EOL;
$str .= ' RewriteCond "%{DOCUMENT_ROOT}/dir2/%{REQUEST_URI}" -f' . PHP_EOL;
$str .= ' RewriteRule "^(.+)" "%{DOCUMENT_ROOT}/dir2/$1" [L]' . PHP_EOL;
return $str;
}
/**
* WordPress Standard Rewrite (with Switch for Directory Check)
*
* @return string
*/
private function getRewriteStandard()
{
// Initialize.
$str = '';
$str .= '# BEGIN WordPress' . PHP_EOL;
$str .= '<IfModule mod_rewrite.c>' . PHP_EOL;
$str .= ' RewriteEngine On' . PHP_EOL;
$str .= ' RewriteBase /' . PHP_EOL;
$str .= ' RewriteRule ^index\.php$ - [L]' . PHP_EOL;
$str .= ' RewriteCond %{REQUEST_FILENAME} !-f' . PHP_EOL;
$str .= $this->opts['use']['dir'] ? ' RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL : '';
$str .= ' RewriteRule . /index.php [L]' . PHP_EOL;
$str .= '</IfModule>' . PHP_EOL;
$str .= PHP_EOL;
$str .= '# END WordPress' . PHP_EOL;
$str .= PHP_EOL;
return $str;
}
/**
* Redirect Match
*
* @return string
*/
private function getRedirectMatch()
{
$str = '';
$str .= 'RedirectMatch ^/a/(.*) /a/$1' . PHP_EOL;
return $str;
}
/**
* Get cPanel PHP Handler
*
* A PHP handler added by certain hosts. Use only if required.
* Note that the "x-httpd-ea-php##" and ".php .php#" refers to the latest
* edition of PHP supported.
*/
private function getPhpHandler()
{
$str = '';
$str .= '# php -- BEGIN cPanel-generated handler, do not edit' . PHP_EOL;
$str .= '# Set the “ea-php73” package as the default “PHP” programming language.' . PHP_EOL;
$str .= '<IfModule mime_module>' . PHP_EOL;
$str .= ' AddHandler application/x-httpd-ea-php73 .php .php7 .phtml' . PHP_EOL;
$str .= '</IfModule>' . PHP_EOL;
$str .= '# php -- END cPanel-generated handler, do not edit' . PHP_EOL;
$str .= PHP_EOL;
return $str;
}
/**
* Use a proxy to pass requests through to an image server, for example.
*
* RewriteRule provides the [P] flag to pass rewritten URIs through mod_proxy.
*
* RewriteRule "^/?images(.*)" "http://imageserver.local/images$1" [P]
*
* However, in many cases, when there is no actual pattern matching needed, as
* in the example shown above, the ProxyPass directive is a better choice. The
* example here could be rendered as:
*
* ProxyPass "/images/" "http://imageserver.local/images/"
*
* Note that whether you use RewriteRule or ProxyPass, you'll still need to
* use the ProxyPassReverse directive to catch redirects issued from the
* back-end server:
*
* ProxyPassReverse "/images/" "http://imageserver.local/images/"
*
* You may need to use RewriteRule instead when there are other RewriteRules
* in effect in the same scope, as a RewriteRule will usually take effect
* before a ProxyPass, and so may preempt what you're trying to accomplish.
*
* @link https://httpd.apache.org/docs/2.4/rewrite/avoid.html#proxy
*/
private function getProxy()
{
// RewriteRule "^/?images(.*)" "http://imageserver.local/images$1" [P]
$str = '';
$str .= 'ProxyPass "/images/" "http://imageserver.local/images/"' . PHP_EOL;
$str .= 'ProxyPassReverse "/images/" "http://imageserver.local/images/"' . PHP_EOL;
return $str;
}
/**
* Get Notes
*
* Include notes that may be relevant to include in an .htaccess file.
*
* @return string
*/
private function getNotes()
{
// Initialize.
$str = '';
// Currently empty.
return $str;
}
//##########################################################################
/**
* Write Files to the Storage Medium.
*
* If &backup is set, will print a backup to directory this file is in.
* If &root is set, will print to the root of the site.
*
* @param string $str
*
* @return bool
*/
private function printFile( $file )
{
$resp = [];
if ( isset( $file['text'] ) && strlen( $file['text'] ) > 0 )
{
if ( isset( $_POST['safety'] ) )
{
if ( isset( $_GET['print'] ) )
{
if ( isset( $_GET['backup'] ) )
{
$resp['backup']['text'] = sprintf(
'<b>Backup</b>: %s', $file['path']['file']['backup'] );
$resp['backup']['size'] = file_put_contents(
$file['path']['file']['backup'], $file['text'] );
$resp['backup']['text'] .= sprintf(
' (%s B)<br>%s', $resp['backup']['size'], PHP_EOL );
}
if ( isset( $_GET['root'] ) )
{
$resp['root']['text'] = sprintf(
'<b>File</b>: %s (', $file['path']['file']['root'] );
$resp['root']['size'] = file_put_contents(
$file['path']['file']['root'], $file['text'] );
$resp['root']['text'] .= sprintf(
' (%s B)<br>%s', $resp['root']['size'], PHP_EOL );
}
if ( isset( $_GET['main'] ) )
{
$resp['main']['text'] = sprintf(
'<b>File</b>: %s (', $file['path']['file']['main'] );
$resp['main']['size'] = file_put_contents(
$file['path']['file']['main'], $file['text'] );
$resp['main']['text'] .= sprintf(
' (%s B)<br>%s', $resp['main']['size'], PHP_EOL );
}
if ( isset( $_GET['server'] ) )
{
$resp['server']['text'] = sprintf(
'<b>File</b>: %s (', $file['server']['conf']['backup'] );
$resp['server']['size'] = file_put_contents(
$file['server']['conf']['backup'], $file['server']['text'] );
$resp['server']['text'] .= sprintf(
' (%s B)<br>%s', $resp['server']['size'], PHP_EOL );
}
if ( isset( $_GET['out'] ) )
{
$resp['out']['text'] = sprintf(
'<b>File</b>: %s (', $file['path']['file']['out'] );
$resp['out']['size'] = file_put_contents(
$file['path']['file']['out'], $file['text'] );
$resp['out']['text'] .= sprintf(
' (%s B)<br>%s', $resp['out']['size'], PHP_EOL );
}
}
}
}
if( isset( $_GET['display'] ) )
{
if( isset( $file['text'] ) && is_string( $file['text'] ) )
{
$resp['file']['text'] = '<pre>' . htmlspecialchars( $file['text'] ) . '</pre>';
}
}
$file['resp'] = $resp;
return $file;
}
/**
* Get Message
*
* @param array $file
* @param string $str
*
* @return string
*/
private function getRespHtml( $file )
{
//is_string( $str ) && strlen( $str ) > 0 && strlen( $str ) < 1000 )
if( is_array( $file ) && count( $file ) > 0 )
{
// Initialize.
$str = '';
$str .= sprintf( '<br><b>File</b>: %s<br>%s',
$file['path']['file']['backup'], PHP_EOL );
// Length of the files.
if ( isset( $file['resp'] ) )
{
if( is_array( $file['resp'] ) )
{
foreach ( $file['resp'] as $item )
{
if( isset( $item['text'] ) && is_string( $item['text'] ) )
{
$str .= $item['text'];
}
}
}
}
return $str;
}
else
{
return false;
}
}
/**
* File Description
*
* For use at the top of the file as needed.
*/
private function getFileDesc()
{
// Initialize
$str = '';
// Start the file with a blank line.
$str = PHP_EOL;
$str .= '# Generated automatically by: printer.htac.php' . PHP_EOL;
$str .= '# The PHP script that generated this file is' . PHP_EOL;
$str .= '# intended for use only on a local machine.' . PHP_EOL;
return $str;
}
/**
* Time Stamp (Including Created Date)
*
* @return string
*/
private function getTimeStamp()
{
// Initialize.
$str = '';
$str .= sprintf( '# Created: %s%s', '2019-03-14', PHP_EOL );
$str .= sprintf( '# Updated: %s%s', date( 'Y-m-d' ), PHP_EOL );
$str .= sprintf( '# Time: %s%s', date( 'H:i:s T' ), PHP_EOL );
return $str;
}
/**
* Lines of Code (LOC) Stamp
*
* This stamp includes placeholders only. The values are added afer the
* string is created.
*
* @return string
*/
private function getLocStamp()
{
// Initialize.
$str = '';
$str .= '# Lines: ' . PHP_EOL;
$str .= '# Length: ' . PHP_EOL;
$str .= '# Density: ' . PHP_EOL;
return $str;
}
/**
* Get Lines of Code
*
* Count the lines of code. This can be as simple as the total actual lines,
* or more complex and divide out comments from code. The following is
* for counting the lines, by counting special characters, such as: \n.
* Regex (simple): `^.*\S+.*$`
*
* @param array $file
*
* @return array $file
*/
private function getLinesofCode( $file )
{
// Initialize
$file['lines']['all'] = null;
$file['lines']['comment'] = null;
$file['lines']['logical'] = null;
$file['lines']['length'] = null;
$file['lines']['density'] = null;
if( isset( $file['text'] ) && is_string( $file['text'] ) )
{
$chars['density'] = 4;
$chars['other'] = 3;
// Count the number of line endings in the string.
$lines['count'] = substr_count( $file['text'], PHP_EOL );
// Calculate the length of the string.
$lines['length'] = strlen( $file['text'] );
// Add the number of characters counted, to the count.
$lines['length'] = $lines['length'] + strlen( $lines['length'] );
// Add the number of characters in the density to the count.
$lines['length'] = $lines['length'] + $chars['density'];
// Add unknown characters to make equal to file size.
$lines['length'] = $lines['length'] + $chars['other'];
// Calculate the density of the characters
$lines['density'] = $lines['length'] / $lines['count'];
// Format the density to the specified number of decimal places.
$lines['density'] = number_format( $lines['density'], 1 );
$file['lines'] = $lines;
}
return $file;
}
/**
* Set Lines of Code in the Stamp
*
* Set the lines of code in the stamp.
*
* @param array $file
*
* @return string
*/
private function setLocStamp( $file )
{
// Check
if( isset( $file['text'] ) && isset( $file['lines'] ) )
{
if( isset( $file['lines']['count'] ) )
{
$file['text'] = $this->strIns(
'# Lines: ', $file['lines']['count'], $file['text'] );
}
if( isset( $file['lines']['length'] ) )
{
$file['text'] = $this->strIns(
'# Length: ', $file['lines']['length'], $file['text'] );
}
if( isset( $file['lines']['density'] ) )
{
$file['text'] = $this->strIns(
'# Density: ', $file['lines']['density'], $file['text'] );
}
}
return $file;
}
/**
* Insert a String after a Given String Sequence
*
* Inserts a string after a given string sequence. For example, the string
* "Lines: " requires lines to be inserted AFTER the string is created.
* Thus the function needs to fine "Lines: " and then insert the integer
* value after that, without changing anything else.
*
* @param string $strFind
* @param string $strInsert
* @param string $strText
*
* @return string
*/
private function strIns( $strFind, $strInsert, $strText )
{
// Check
//if( is_string( $strText ) && is_string( $strFind ) && is_string( $strInsert ) )
if ( 1 )
{
$strText = str_replace( $strFind, $strFind . $strInsert, $strText );
}
return $strText;
}
/**
* ID Stamp
*
* @return string
*/
private function getIDStamp()
{
$str = '';
$str .= '# ID: ENG-ON-001' . PHP_EOL;
return $str;
}
/**
* References for Documentation.
*
* @return string
*/
private function getReferences()
{
// Initialize.
$str = '';
$str .= '# https://httpd.apache.org/docs/2.4/getting-started.html' . PHP_EOL;
return $str;
}
/**
* Get Form
*
* @return string
*/
private function getForm()
{
if( isset( $_GET['ctrls'] ) )
{
// Initialize
$str = '';
$str .= '<form action="" method="post">' . PHP_EOL;
$str .= '<div>' . PHP_EOL;
$str .= '<input type="checkbox" name="safety" id="safety" required> ' . PHP_EOL;
$str .= '<input type="submit" class="btn btn-primary" value="Run">' . PHP_EOL;
$str .= '</div>' . PHP_EOL;
$str .= '</form>' . PHP_EOL;
return $str;
}
else
{
return false;
}
}
/**
* Wrap the HTML in Page HTML `<!DOCTYPE html>`, etc.
*
* Use basic settings. Assume no SEO necessary. Bootstrap CSS only.
*
* @param string $html
*
* @return string|bool
*/
private function getPageHTML( $file )
{
$str = '<!DOCTYPE html>' . PHP_EOL;
$str .= '<html lang="en-CA" class="theme-dark dark">' . PHP_EOL;
$str .= '<head>' . PHP_EOL;
$str .= '<meta charset="UTF-8">' . PHP_EOL;
$str .= '<meta name="viewport" content="width=device-width, initial-scale=1"/>' . PHP_EOL;
$str .= sprintf( '<title>%s</title>%s', $this->opts['title'], PHP_EOL );
$str .= '<meta name="robots" content="noindex,nofollow" />' . PHP_EOL;
$str .= '<link rel=stylesheet href="/style.min.css">' . PHP_EOL;
$str .= '</head>' . PHP_EOL;
$str .= '<body>' . PHP_EOL;
$str .= '<main>' . PHP_EOL;
$str .= sprintf( '<h1 style="display:block;">%s</h1>%s',
$this->opts['title'], PHP_EOL );
$str .= $this->getForm();
$str .= isset( $file['msg'] ) ? $file['msg'] : '';
$str .= '</main>' . PHP_EOL;
$str .= '</html>' . PHP_EOL;
return $str;
}
} // End Class
if ( ! function_exists( 'pre_dump' ) ) {
/**
* Uses var_dump with <pre>...</pre> to dump and format an array or string when debugging (usually locally).
*
* Requires the debug flag to be set `?debug` AND a file named .security
* to be present in the same directory to function. This prevents it from
* inadvertangly working online UNLESS the `.security` file is present.
* Converts &, ", ', < and > to their corresponding HTML entities.
*
* @link https://www.php.net/manual/en/function.htmlspecialchars.php
*
* @param array $arr Any array
* @param integer $continue 0: exit after done; 1: continue after done.
* @return void
*/
function pre_dump( $arr, $continue = 1 ) {
if ( isset( $_GET['debug'] ) && file_exists( __DIR__ .'/.security' ) ) {
if ( is_string( $arr ) ) {
$arr = htmlspecialchars( $arr );
}
echo "<pre>" . PHP_EOL;
var_dump( $arr );
if ( isset( $_GET['exit'] ) || ! $continue ) {
exit( '<br/>' . 'Done.' );
}
echo "</pre>" . PHP_EOL;
}
}
} // pre_dump
/**
* Public Instantiation Function
*
* Loads the data file (if available), creates an instance of the class,
* then initializes it.
*
* @param array $args['dir']
*
* @return string HTML as a list of images, wrapped in the article element.
*/
function ec01_htac_printer()
{
if( file_exists( __DIR__ . '/data.htac.php' ) )
{
require_once( __DIR__ . '/data.htac.php' );
}
$printer = new HtacPrinter();
$printer->init();
}
ec01_htac_printer();
/**
* Notes
*
* The Apache documentation discourages the use of the .htaccess file for site
* specific control. Instead, the placement of the same directives in the
* server configuration files is recommended. The reason for this is that
* the server configuration files are loaded once, then cached. When
* AllowOverrides is on, the server must look through all the higher level
* directories on a site for each page load, compile them, and then process
* them. As this is obviously inefficient, ways should be found to avoid this.
*
* The simplest way to do this on a _per site_ basis is simply to wrap exactly
* the same directives in a virtual host element that matches the site to
* which these directives apply. Although, again, this is obviously inefficient
* for sites on the same host that use the same (or similar) directives, it
* would be the easiest way to move site configuration to the server level.
*/
/**
* Decentralized management (via ".htaccess" files).
*
* httpd allows for decentralized management of configuration via special files
* placed inside the web tree. The special files are usually called .htaccess,
* but any name can be specified in the AccessFileName directive. Directives
* placed in .htaccess files apply to the directory where you place the file,
* and all sub-directories. The .htaccess files follow the same syntax as the
* main configuration file.
*
* @link https://httpd.apache.org/docs/2.4/configuring.html#htaccess
*/
/**
* Virtual Host
*
* Contains directives that apply only to a specific hostname or IP address.
*
* <VirtualHost> and </VirtualHost> are used to enclose a group of directives
* that will apply only to a particular virtual host. Any directive that is
* allowed in a virtual host context may be used. When the server receives a
* request for a document on a particular virtual host, it uses the
* configuration directives enclosed in the <VirtualHost> section.
*
* @link https://httpd.apache.org/docs/2.4/mod/core.html#virtualhost
*/
/**
* Directory, Location, DirectoryMatch, LocationMatch
*
* The directory and location directives are specific. The directory match, etc.
* are non-specific for which a regex (regular expression) may be used.
*
* For example:
*
* <DirectoryMatch "^/www/(.+/)?[0-9]{3}/">
* # ...
* </DirectoryMatch>
*
* matches directories in /www/ (or any subdirectory thereof) that consist of
* three numbers.
*/
/**
* Directory
*
* The Directory directive refers to directories. The Location directive
* refers to locaion (URLs).
*
* If multiple (non-regular expression) <Directory> sections match the
* directory (or one of its parents) containing a document, then the directives
* are applied in the order of shortest match first, interspersed with the
* directives from the .htaccess files. For example, with
*
* <Directory "/">
* AllowOverride None
* </Directory>
*
* <Directory "/home">
* AllowOverride FileInfo
* </Directory>
*/
/**
* Location
*
* The Location directive refers to URLs. The Directory directory refers to
* directories.
*
* <Location> Directive
* Description: Applies the enclosed directives only to matching URLs
* Syntax: <Location URL-path|URL> ... </Location>
* Context: server config, virtual host
* Status: Core
* Module: core
*
* The <Location> directive limits the scope of the enclosed directives by URL.
* It is similar to the <Directory> directive, and starts a subsection which is
* terminated with a </Location> directive. <Location> sections are processed
* in the order they appear in the configuration file, after the <Directory>
* sections and .htaccess files are read, and after the <Files> sections.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment