Вставка изображения из буфера обмена в редактор TinyMCE из песочницы

Некоторое время назад у нас на проекте возникла необходимость вставить картинки из буфера обмена прямо в редактор. Задача оказалась нетривиальной, и простых решений не имела. По факту поиска в интернете было найдено всего два пути решения проблемы – либо менять редактор целиком на флешовый, что привело бы к переписыванию большой части проекта, либо – ява-аплет. Собственно, о последнем и пойдет речь ниже.


Для решения проблемы мы использовали ява-аплет Supa.

Аплет без подписи работать не будет, соответственно его нужно подписать. Подписи выдают сертифицированные на это дело сервисы, которые требуют за это соответствующую плату. Но, для того чтобы оценить работоспособность, тратить деньги как то не хотелось. Поэтому был использован алгоритм, подсмотренный на сайте, указанном в конце статьи. Подпись дается на 8 мес, но для оценки эффективности работы – предостаточно.

Код для вставки аплета

<applet id="SupaApplet" archive="supa/Supa.jar" code="de.christophlinder.supa.SupaApplet" width="0" height="0">
  <param name="imagecodec" value="png"> </param>
  <param name="encoding" value="base64"> </param>
  <param name="previewscaler" value="fit to canvas"> </param>
  <param name="trace" value="true"> </param>
</applet>


Устанавливаем размер аплета 0х0 px, так как по-умолчанию аплет отображает изображение из буфера обмена без аплоуда на сервер, а нам нужно чтобы аплоуд происходил по нажатию кнопки в редакторе.

Интеграция в TinyMCE

Немного упростив код из примера для аплета получаем управляющую обертку на JS, состоящую из двух функций.

        function paste() 
        {
                var s = new supa();
                try 
                {
                var applet = document.getElementById( "SupaApplet" );
        
                if (!s.ping(applet)) throw "SupaApplet is not loaded (yet)";
                
                        var err = applet.pasteFromClipboard(); 
                        switch (err) 
                        {
                        case 0:
                        break;
                        case 1: 
                        case 2:
                        case 3:
                        case 4:
                        default:
                                return false;
                        }
                }
                catch (e) 
                {
                alert(e);
                throw e;
                }
                return upload();
        }       
        function upload() 
        {
                var s = new supa();
                var applet = document.getElementById("SupaApplet");
        
                try 
                { 
                        var result = s.ajax_post(applet, "supa/upload.php", "screenshot", "screenshot.jpg", 
                        { 
                                form: document.forms["form"] 
                        });
                        if (result.match("^OK")) 
                        {
                                var url = result.substr(3);
                                return url;
                        } 
                        else return false;
                } 
                catch (ex) { return false; }            
                return false;
        }


Добавление кнопки в TinyMCE

$(document).ready(function()
{
        $('#editor').tinymce(
        {
                script_url : '../js/tiny_mce/tiny_mce.js',
                theme : "advanced",
                plugins : "autolink,lists,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,advlist",
                theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,formatselect,fontselect,fontsizeselect",
                theme_advanced_buttons2 : "search,|,bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,image,|,forecolor,backcolor",
                theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup",
                theme_advanced_buttons4 : "insert_image",
                theme_advanced_toolbar_location : "top",
                theme_advanced_toolbar_align : "left",
                theme_advanced_statusbar_location : "bottom",
                theme_advanced_resizing : false,
                setup : function(ed) 
                {
                        ed.addButton('insert_image',
                        {
                                title: 'Insert Image',
                                image: 'images/add.png',
                                onclick: function()
                                {
                                        tmp = paste();
                                        if (tmp !== false)                                      
                                                ed.selection.setContent('img src="upload/' + tmp + '" /');
                                }
                        });
                }
        });
});
</script>


Код аплоуда на сервере

<?php
        define('FILESTORE_PATH', "../include/tcpdf/upload");
        define('FILESTORE_URLPREFIX', "upload"); 

        header('Content-Type: text/plain');

        if (!$_FILES['screenshot']) 
        {
                echo "ERROR: NO FILE (screenshot)";
                exit;
        }
        if ($_FILES['screenshot']['error']) 
        {
                echo "PHP upload error: " . $_FILES['screenshot']['error'];
                exit;
        }  
        $filename = uniqid() . '.jpg';  
        $file = FILESTORE_PATH . "/" . $filename;
        $fh = fopen($_FILES['screenshot']['tmp_name'], "r");
        if (!$fh) 
        {
                echo "ERROR: could not read temporary file";
        }
        $data = fread($fh, filesize($_FILES['screenshot']['tmp_name']));
        fclose($fh);
        $fh = fopen($file, "w");
        if (!$fh) 
        {
                echo "ERROR: could not open destination file";
                die();
        }
        fwrite($fh, base64_decode($data));
        fclose($fh);
        
        if (is_uploaded_file( $_FILES['screenshot']['tmp_name'])) 
        {
                unlink($_FILES['screenshot']['tmp_name']);
        }
        echo "OK:" . FILESTORE_URLPREFIX . "/" . $filename;
?>


Материалы:

Supa — supa.sourceforge.net

Подпись аплетов — тут.
+36
19 июля 2011, 18:19
78
dtcDev 13,1

комментарии (17)

0
Ockonal #
Без тегов ужасно, используйте же встроенный code, для подсветки.
0
dtcDev #
оу… куда то подевались. Сейчас поправлю
0
dtcDev #
Извините за наивный вопрос — каким макаром выделить код? тег code присутствует, тем не менее форматирование оставляет желать лучшего
0
Yashin #
Вам нужен тег source
0
dtcDev #
Спасибо, поправил
0
dtcDev #
Низкий поклон шлемом об асфальт тому кто угостил инвайтом. К сожалению, пока суть да дело, еще не разобрался, как найти благодетеля :) Если он увидит топик — спасибо тебе, мил человек!

И еще, прошу прощения за то, что пришлось убрать фигурные скобки из html разметки страницы — к сожалению, хабр воспринимал их слишком буквально.
+1
mark_ablov #
НЛО оставило инвайт.
так как на руках у юзеров мало инвайтов, то администрация в виде НЛО дарит инвайты.
+1
bolk #
> Прошу прощения — пришлось убрать теги из кода — хабр понимал их буквально :)
Не издевайтесь над читателями, используйте &lt; и &gt;.
+2
dtcDev #
спасибо, поправил
0
Cher #
не очень силен в java, но вы упомянули что можно такое релизовать на flash. А как? быстро погуглив я нашел:
«Метод System.setClipboard() разрешает SWF-файлу заменять содержимое буфера обмена строкой обычного текста. Это не представляет опасности. Соответствующий метод getClipboard() отсутствует, чтобы предотвратить риск несанкционированного получения паролей и других конфиденциальных данных, вырезанных или скопированных в буфер обмена.»
0
dtcDev #
В сторону флеша особенно не копали. Но данная технология, насколько мне известно, так же как и Silverlight, имеет доступ к памяти на машине клиента, и решение поставленной задачи было бы на порядок проще.
0
dmitry_dvp #
$fh = fopen($_FILES['screenshot']['tmp_name'], "r");
$data = fread($fh, filesize($_FILES['screenshot']['tmp_name']));
fclose($fh);

$data = file_get_contents($_FILES['screenshot']['tmp_name']);

$fh = fopen($file, "w");
fwrite($fh, base64_decode($data));
fclose($fh);

file_put_contents($file, base64_decode($data))

fopen, fclose, fread, fwrite имело бы смысл использовать только если вы рассчитываете на файл большого размера, сопоставимый размером памяти, выделенной для PHP, но даже в этом случае писать код следовало бы не так.

Ну а по теме — круто! возникло желание реализовать такое у себя
0
dtcDev #
Буду признателен, если Вы приведете альтернативное решение.

На истину в последней инстанции не претендуем, каждый волен перелопатить код так как ему заблагорассудится. :)
0
jMas #
Интересная реализация, спасибо!

Было бы интересней, если была бы реализация через флеш.
0
dtcDev #
сорри, промахнулся. ответ ниже
0
dtcDev #
Ответил выше, что в его сторону практически не копали. А проект сам по себе — анализ сеошных ресурсов, поддерживающий работу с шаблонами, автоматический сбор статистики и генерацию отчетов. Там на каждом шагу редактор, и переписывать все это счастье, где все завязано на редактор — удовольствие малоприятное, согласитесь :)
0
anatooly #
В статье ошибка в JS коде есть, в функции upload:

var result = s.ajax_post(
      "upload.php",
      applet.getEncodedString(),
      "screenshot",
      "screenshot.jpg", 
      { form: document.forms["form"] }
);

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.