Created
March 11, 2020 15:52
-
-
Save earth3300/6817d7d6eb42be813d9f961c13c65f9e to your computer and use it in GitHub Desktop.
Prints an htaccess file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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