Making It Permanent for File_Managed

The file_managed functionality comes with ajax capability to upload and remove file, however. While, it stores the file, it stores it temporarily,so unless we make it permanent, the files just uploaded will be removed after certain time. In this post, we demonstrate one of the many ways how to make the file upload via file_managed a permanent.

Hook Into File_Managed

First, we specify the callback for the form element ‘file_managed’ as following:

$form['backgrounds']['ds_theme_custom_background'] = array(
        '#title' => t('Custom Background'),
        '#type' => 'managed_file',
        '#description' => t('Upload a your custom background image, allowed extensions: jpg, jpeg, png, gif'),
        '#default_value' => isset($form_state['value']['ds_theme_custom_background']) ? $form_state['value']['ds_theme_custom_background'] : theme_get_setting('ds_theme_custom_background'),
        '#upload_location' => DESIGNSSQUARE_THEME.'/theme-settings',
        '#upload_validators' => array(
            'file_validate_extensions' => array('jpg jpeg png gif'),
            // Pass the maximum file size in bytes
            'file_validate_size' => array(MAX_SIZE_LIMIT_DS_THEME*1024*1024),
        ),
        '#process' => array('our_custom_callback'),
    );
}

By specifying ‘#process’, the custom callback – our_custom_callback is being called when ‘upload’, ‘remove’ button is clicked

Make it Permanent

Next, we will ensure file is stored permanently by updating it status as following:

function our_custom_callback($element, &$form_state, $form){
    $element = file_managed_file_process($element, $form_state, $form);

    //file upload
    if (isset($form_state['input']['_triggering_element_name']) && $form_state['input']['_triggering_element_name'] == $element['upload_button']['#name'] && !empty($element['#file'])) {
        //make file permanent
        $file = $element['#file'];
        // Change status to permanent.
        $file->status = FILE_STATUS_PERMANENT;

        //all permanent files need an entry in the 'file_usage' table
        //we also add variable name as the 'type' parameter, so we can export via Sample Data module
        file_usage_add($file, 'designssquare_lib', $element['#name'], '1');
        // Save.
        file_save($file);
    }

    //file removed
    if (isset($form_state['input']['_triggering_element_name']) && $form_state['input']['_triggering_element_name'] == $element['remove_button']['#name']) {
        //ensure file is removed
        if(isset($element['#file']->fid) && $file = file_load($element['#file']->fid)){
            //file exist, lets remove
            //the file is removed despite the count of references present
            db_delete('file_managed')->condition('fid', $file->fid)->execute();
            db_delete('file_usage')->condition('fid', $file->fid)->execute();
            entity_get_controller('file')->resetCache();
        }
    }

    return $element;
}

Here, we check whichever button is clicked – ‘upload’ or ‘remove’. If it is ‘upload’, then we change the status of the file and add entry in the table ‘file_usage’ both needed to make file permanently stored. If button ‘remove’ is clicked, then we ensure the file is removed from both tables – file_managed as well as file_usage. The last step shouldn’t be needed, however. It happens that file_managed functionality, sometimes, adds more than one reference to the same file into file_manage table(see ‘count’) resulting into situation that it doesn’t remove the file because it sees more than one reference. So, we ensure the file is removed no matter how many references listed in the table.

Troubleshooting

1. Fatal error: Call to undefined function – our_custom_callback

For Drupal 2.26,2.28 and, probably, many other version, there seems to be a bug on how the form processing functions are called before all the dependencies are loaded resulting in the error “Fatal error: Call to undefined function – our_custom_callback”. To avoid it, we end up hooking into the Drupal bootstrap process to load our custom function before callback is called via hook_init() as following:

function MODULE-NAME_init(){
    //load for processing file_managed on Ajax calls(i.e. upload, remove)
    module_load_include('inc', 'MODULE-NAME', 'inc/file_defining_custom_callback');
}

Here, we load the file containing the definition of our callback – our_custom_callback. Since the hook_init is called before Drupal is executed, our custom callback is defined that way solving the above problem.

Leave a Reply

Your email address will not be published. Required fields are marked *