Question

This is a concept of my goal

concept image

I want to

be able to create files in the current opened directory by entering the complete file name and and extension in a text field, click create and use the AJAX process to run the file_put_contents() method.

I haven't tried any coding methods as I do not even have a starting point. After 18 hours of research, I have only found methods using add_theme_page() to add a page where all the custom coding can be done. While that's a solid option, I'd like to keep it all in the core file editor. It's just an overkill to create an entire file editor which does all the same tasks as the core, with the only diff being a text field.

Update

In an act of desperation, I have used the admin_notices action to insert the input field and button, and attached a function to show only on the specified theme, and run the file creation process. It's not the solution I desire so I don't think it's appropriate to post as an answer.

public static function makeFile($file, $data=null)
{
    if( !is_null(mb::getPost('makecss')) ) {
        file_put_contents(DEF_THEMEPATH.'/styles/'.$file.'.css', $data);
        
        wp_redirect( add_query_arg(['file'=>'styles/'.$file.'.css','theme'=>'thor'],admin_url('theme-editor.php')) );
        exit;
    }
    
    $newfile = '
    <div class="newfile-form">
    <form method="post" action="">
        <p>Create New CSS File</p>
        <span>File name: <input type="text" name="newfile" id="newfile" value="" /></span>
        <span><button type="submit" name="makecss" class="button button-primary">Create File</button></span>
    </form>
    </div>
    ';
    return $newfile;
}

action

if( strstr(mb::urlVar('theme'), 'thor') ) {
 add_action('admin_notices', function() {
    echo mb::makeFile(mb::getPost('newfile'));
 });
}

Result

result of method used

Note: mb represents the name of my php class

Note: Though the .css extension is shown in the image, I have since made it the default and only extension possible. A javascript method replaces any period and following string with null

Was it helpful?

Solution

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.

theme editor view


Returned view after creating CSS file. When the file is successfully created, the page reloads and open the new file in the code editor.

file created


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.

supporting plugin config


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!

Licensed under: CC-BY-SA with attribution
Not affiliated with wordpress.stackexchange
scroll top