I couldn't define an "accurate" method to implement the form I desired, so I have settled on the admin_notices
action as a viable solution.
The Result Illustrations
View when in Theme Editor of the associated theme. The default style.css
is selected, but is not editable. An error message is shown if Update File button is clicked.
Returned view after creating CSS file. When the file is successfully created, the page reloads and open the new file in the code editor.
The CSS file is available for selection in the theme's supporting plugin. If a stylesheet is selected, it will not be listed in the delete list within the Theme Editor.
The Coding
Form process function
public static function makeFile($file, $data=null)
{
if( !is_null(mb::getPost('makecss')) )
{
// suppress default error notice since it is not related
echo '<style>div#message.notice.notice-error {display: none;}</style>';
$file = esc_html($file);
file_put_contents(MBTHEMEDIR.'/styles/'.$file.'.css', $data);
mb::redirect(admin_url('theme-editor.php?file=styles/'.$file.'.css&theme=thor'));
}
//delete file
if( !is_null(mb::getPost('deletecss')) )
{
// suppress default error notice since it is not related
echo '<style>div#message.notice.notice-error {display: none;}</style>';
if( mb::getPost('csslist') != '' ) {
unlink(MBTHEMEDIR.'/styles/'.mb::getPost('csslist'));
mb::redirect(admin_url('theme-editor.php?theme=thor'));
}else{
echo '<div class="notice notice-warning is-dismissible"><p>No file was selected</p></div>';
}
}
$newfile = '
<div class="newfile-form">
<form action="" method="POST">
<p>Create New CSS File</p>
<span>File name: <input type="text" name="newfile" id="newfile" value="" placeholder="mystyle" /></span>
<span><button type="submit" name="makecss" class="button button-primary">Create File</button></span>
<span>
<select name="csslist">
<option value="">None</option>';
foreach(mb::filelist(MBTHEMEDIR.'/styles', 'css') as $css) {
$newfile .= '<option value="'.$css.'">'.$css.'</option>';
}
$newfile .= '</select>
</span>
<span><button type="submit" name="deletecss" class="button">Delete File</button></span>
</form>
</div>
';
return $newfile;
}
Action Hook
if( current_user_can('edit_files') )
{
// confirm that the specified theme is selected
if( strstr(mb::urlVar('theme'), 'thor') )
{
// suppress default WP missing file notice when file create request is sent
echo '<style>div#message.notice.notice-info {display: none;}</style>';
// implement form process action
add_action('admin_notices', function() {
echo mb::makeFile(sanitize_text_field(mb::getPost('newfile')));
});
// action to disable editing core files
add_action('load-theme-editor.php', function()
{
$file = filter_input(INPUT_GET, 'file', FILTER_SANITIZE_STRING);
if( in_array($file, ['style.css', '404.php','index.php']) ) {
wp_redirect(add_query_arg([],self_admin_url('theme-editor.php?theme=thor')));
exit;
}
});
}
}
I had to use a different page redirection method due to header sent errors when using wp_redirect()
public static function redirect($url) {
if( !headers_sent() ) {
wp_redirect(admin_url($url));
}else{
echo '<meta http-equiv="refresh" content="0; URL='.$url.'">';
}
}
The filelist()
method used to output the select options of the delete field. WP has a method for this but I couldn't get it to behave as desired, so I corrected it
public static function filelist($path, $filter=null, $getpath=false)
{
$files = new \DirectoryIterator($path);
$filelist=[];
foreach($files as $file)
{
if( $file->isFile() && !$file->isDot() )
{
// include only files in $filter
// methods: 'css' or 'css|txt' or starting with '^cat' or ending with '$er'
if( !empty($filter) && !preg_match(chr(1).$filter.chr(1), $file) ) {
continue;
}
$filelist[] = ($getpath == true ? $file->getPath().'/'.$file->getFilename() : $file->getFilename());
}
}
return $filelist;
}
The getPost()
method is just a set of global functions to check the form request values. The WP method to do the same task would be applicable.
The urlVar()
method is a set of global functions to check the URL string for query variables and their values. The WP method to do the same would be applicable.
That's my story and I'm sticking with it!