Adding a TinyMCE image browser the CakePHP way

by Braindead
If your cake app requieres some sort of WYSIWYG editor, integrating TinyMCE is in most cases the way to go. The only problem with TinyMCE is that there is no image browser included for free. So we have to build our own one.

Introduction

This article is meant as a follow up to this one http://bakery.cakephp.org/articles/view/using-tinymce-with-cakephp (see comment no. 4), as it will not explain how to integrate TinyMCE into your app, but how to integrate an image browser into your already working TinyMCE installation.

Please check the linked article to see how to integrate TinyMCE into your app.

For the sake of simplicity, all my images will reside in one folder. The user can browse the folder in form of a list and upload new images.

Step 1: Create the uploads folder

Create folder app/webroot/uploads and copy some image files into the new folder.

Step 2: Create the Image model

The image model will be responsible to read in all the images in the uploads folder and will also take care that only valid files can be uploaded.

Filename: app/models/image.php

Model Class:

<?php 
class Image extends AppModel {

    var 
$name 'Image';

    var 
$validate = array(
        
'image' => array(
            
'rule' => array(
                
'validFile',
                array(
                    
'required' => true,
                    
'extensions' => array(
                        
'jpg',
                        
'jpeg',
                        
'gif',
                        
'png'
                    
)
                )
            )
        )
    );

    var 
$useTable false;

    function 
readFolder($folderName null) {
        
$folder = new Folder($folderName);
        
$images $folder->read(
            
true,
            array(
                
'.',
                
'..',
                
'Thumbs.db'
            
),
            
true
        
);
        
$images $images[1]; // We are only interested in files

        // Get more infos about the images
        
$retVal = array();
        foreach (
$images as $the_image)
        {
            
$the_image = new File($the_image);
            
$retVal[] = array_merge(
                
$the_image->info(),
                array(
                    
'size' => $the_image->size(),
                    
'last_changed' => $the_image->lastChange()
                )
            );
        }

        return 
$retVal;
    }

    function 
upload($data null) {
        
$this->set($data);

        if(empty(
$this->data)) {
            return 
false;
        }

        
// Validation
        
if(!$this->validates()) {
            return 
false;
        }

        
// Move the file to the uploads folder
        
if(!move_uploaded_file($this->data['Image']['image']['tmp_name'], APP.WEBROOT_DIR.DS.'uploads'.DS.$this->data['Image']['image']['name'])) {
            return 
false;
        }

        return 
true;
    }



    function 
validFile($check$settings) {
        
$_default = array(
            
'required' => false,
            
'extensions' => array(
                
'jpg',
                
'jpeg',
                
'gif',
                
'png'
            
)
        );

        
$_settings array_merge(
            
$_default,
            
ife(
                
is_array($settings),
                
$settings,
                array()
            )
        );

        
// Remove first level of Array
        
$_check array_shift($check);

        if(
$_settings['required'] == false && $_check['size'] == 0) {
            return 
true;
        }

        
// No file uploaded.
        
if($_settings['required'] && $_check['size'] == 0) {
            return 
false;
        }

        
// Check for Basic PHP file errors.
        
if($_check['error'] !== 0) {
            return 
false;
        }

        
// Use PHPs own file validation method.
        
if(is_uploaded_file($_check['tmp_name']) == false) {
            return 
false;
        }

        
// Valid extension
        
return Validation::extension(
            
$_check,
            
$_settings['extensions']
        );
    }
}
?>

Step 3: Create the images controller

Filename: app/controllers/images_controller.php

Controller Class:

<?php 
class ImagesController extends AppController {

    var 
$name 'Images';

    var 
$uses = array('Image');

    var 
$helpers = array(
        
'Html',
        
'Form',
        
'Javascript',
        
'Number' // Used to show readable filesizes
    
);

    function 
index() {
        
$this->set(
            
'images',
            
$this->Image->readFolder(APP.WEBROOT_DIR.DS.'uploads')
        );
    }

    function 
upload() {
        
// Upload an image
        
if (!empty($this->data)) {
            
// Validate and move the file
            
if($this->Image->upload($this->data)) {
                
$this->Session->setFlash('The image was successfully uploaded.');
            } else {
                
$this->Session->setFlash('There was an error with the uploaded file.');
            }
            
            
$this->redirect(
                array(
                    
'action' => 'index'
                
)
            );
        } else {
            
$this->redirect(
                array(
                    
'action' => 'index'
                
)
            );
        }
    }
}
?>

Step 4: Create a view to show the images and upload new ones

Filename: app/views/images/index.ctp

View Template:


<?php
    
echo $javascript->codeBlock(
        
"function selectURL(url) {
            if (url == '') return false;

            url = '"
.Helper::url('/uploads/')."' + url;

            field = window.top.opener.browserWin.document.forms[0].elements[window.top.opener.browserField];
            field.value = url;
            if (field.onchange != null) field.onchange();
            window.top.close();
            window.top.opener.browserWin.focus();
        }"
    
);
?>

<?php
    
echo $form->create(
        
null,
        array(
            
'type' => 'file',
            
'url' => array(
                
'action' => 'upload'
            
)
        )
    );
    echo 
$form->label(
        
'Image.image',
        
'Upload image'
    
);
    echo 
$form->file(
        
'Image.image'
    
);    
    echo 
$form->end('Upload');
?>

<?php if(isset($images[0])) {
    
$tableCells = array();

    foreach(
$images As $the_image) {
        
$tableCells[] = array(
            
$html->link(
                
$the_image['basename'],
                
'#',
                array(
                    
'onclick' => 'selectURL("'.$the_image['basename'].'");'
                
)
            ),
            
$number->toReadableSize($the_image['size']),
            
date('m/d/Y H:i'$the_image['last_changed'])
        );
    }

    echo 
$html->tag(
        
'table',
        
$html->tableHeaders(
            array(
                
'File name',
                
'Size',
                
'Date created'
            
)
        ).
$html->tableCells(
            
$tableCells
        
)
    );
?>

If you now open the http://example.com/images you should see a list with all the files you copied into the uploads folder. You should also be able to upload a new image.

Step 5: Integrate the image browser into TinyMCE

Filename: app/views/elements/tinymce.ctp

View Template:


<?php echo $javascript->link("tiny_mce/tiny_mce.js"); ?>

<?php
    
echo $javascript->codeBlock(
        
"function fileBrowserCallBack(field_name, url, type, win) {
            browserField = field_name;
            browserWin = win;
            window.open('"
.Helper::url(array('controller' => 'images'))."', 'browserWindow', 'modal,width=600,height=400,scrollbars=yes');
        }"
    
);
?>

<?php
    
echo $javascript->codeBlock(
        
"tinyMCE.init({
            mode : 'textareas',
            theme : 'advanced',
            theme_advanced_buttons1 : 'forecolor, bold,italic,underline,|,justifyleft,justifycenter,justifyright,justifyfull,|,bullist,numlist,|,undo,redo,|,link,unlink,|,image,emotions,code',
            theme_advanced_buttons2 : '',
            theme_advanced_buttons3 : '',
            theme_advanced_toolbar_location : 'top',
            theme_advanced_toolbar_align : 'left',
            theme_advanced_path_location : 'bottom',
            extended_valid_elements : 'a[name|href|target|title|onclick],img[class|src|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name],hr[class|width|size|noshade],font[face|size|color|style],span[class|align|style]',
            file_browser_callback: 'fileBrowserCallBack',
            width: '620',
            height: '480',
            relative_urls : false
        });"
    
);
?>

Summary

As you could see, integrating the image browser into your TinyMCE installation is actually quiete easy. If you need a more advanced image browser, the view is first place you should tweak.

Happy baking!
Written by Markus Henke
http://braindead1.de

Report

More on Tutorials

Advertising

Comments

  • JacopKane.myopenid.com posted on 12/07/10 10:33:32 PM
    it helped me a lot, thanks
  • gveugen posted on 10/25/09 06:13:28 PM
    Hi,

    the lack of an imagebrowser was basically the only thing preventing me from using TinyMCE over FCKEditor. Now that this article has been published, I see no reason not to go for TinyMCE.

    However, this article is written with the assumption of using the TinyMCE Element. I however, have used the TinyMCE helper, explained at http://bakery.cakephp.org/articles/view/tinymce-helper-1
    I was trying to figure out how to add the image upload functionality to the helper, and it turned out to be quite simple. I'll describe the implementation below, assuming you have the helper up and running.

    First of all, copy the whole code provided at step 5 from this main article. Then, in every view you want the tinymce helper applied on textareas, insert the element into your view page

    <?php echo $this->element('your_element_name'); ?>
    Now, this will add the tinymce editor to all the textareas in your view. I can imagine that that isn't supposed to happen in most cases...

    This is caused by a parameter in the step 5 code. In the javascript TinyMCE.init() function, there is a parameter called mode, which can have the following 4 values: textareas, specific_textareas, exact and none. I'm not going to go too deep into this, it basically determines which textfields are affected by the javascript code.

    I haven't managed to fix this problem with the mode parameter, but I've found a workaround just as easy (or even easier). The "problem" is caused by the FormHelper::input method. This method generates an input field based on the datatype of the corresponding field in the database. TEXT, CHAR, and VARCHAR types will cause the formhelper to generate a textarea. This can be manipulated by determing the input type at the formhelper. It just involves adding a simple extra parameter, the 'type' parameter.

    So, to keep it simple:
    <?php echo $form->input('title', array('type' => 'text')); ?> will actually generate an input field instead of a textarea, whatever the database data type is. Thus, no editor will be applied to these kind of fields, only to textareas. Maybe not the right workaround, but certainly the easiest...
  • bambou713705 posted on 07/30/09 05:31:30 PM
    Hello,

    Thanks for this work, it exactly what I needed!

    Just one thing, this code can't display validation error messages (messages displayed close of fields)

    To display error messages :
    # in the controller :
    remove upload function, and modify index() with this one

    Controller Class:

    <?php 
        
    function index() {
            if (!empty(
    $this->data)) {
                
    // Validate and move the file
                
    if($this->Image->upload($this->data)) {
                    
    $this->Session->setFlash('The image was successfully uploaded.');
                } else {
                    
    $this->Session->setFlash('There was an error with the uploaded file');
                }
            }
        
            
    $this->set(
                
    'images',
                
    $this->Image->readFolder('your_upload_folder')
            );
        }
    ?>

    # in the view
    Set the action to index an remplace $form->file, by $form->input and specify type to file :

    View Template:


    <?php
    echo $form->create('Image',array('type' => 'file''url' => array('action' => 'index')));
    echo 
    $form->input('image', array('label' =>___('Upload image')." :"'type' => 'file'));
    echo 
    $form->end('Upload');
    ?>


    With this code you will be able to dissociate extention validation to the custom file validation and add other validation rules if needed :

    Model Class:

    <?php 
    <?php
        
    var $validate = array(
            
    'image' => array(
                
    'extension' => array('rule' => array( 'extension', array('jpg''jpeg''gif''png')),
                    
    'message' => 'Invalid file or extention (should be jpg, jpeg, png or gif'
                
    ),
                
    'validFile' => array('rule' => array('validFile'true), 'message' => 'Invalid file'))
            )
        );


            function 
    validFile($check$required true) {
            
    // Remove first level of Array
            
    $_check array_shift($check);

            
    // No file uploaded.
            
    if(($_check['size'] == 0) && $required) {
                return 
    false;
            }

            
    // Check for Basic PHP file errors.
            
    if($_check['error']) {
                return 
    false;
            }

            
    // Use PHPs own file validation method.
            
    if(!is_uploaded_file($_check['tmp_name'])) {
                return 
    false;
            }

            
    // Valid extension
            
    return true;
        }
    ?>
    ?>
login to post a comment.