X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;ds=sidebyside;f=plugins%2FTinyMCE%2FTinyMCEPlugin.php;h=fff313834749c86c5c2f2cb66e612bad171e3eef;hb=f110fc5c9a61ac666d88714b170b12f99d911b11;hp=92d31e51e6ce03432e3aa9ba25b209149c04b26f;hpb=771928c6e99a2eec29504b678ab3969048ae3807;p=quix0rs-gnu-social.git diff --git a/plugins/TinyMCE/TinyMCEPlugin.php b/plugins/TinyMCE/TinyMCEPlugin.php index 92d31e51e6..fff3138347 100644 --- a/plugins/TinyMCE/TinyMCEPlugin.php +++ b/plugins/TinyMCE/TinyMCEPlugin.php @@ -27,7 +27,6 @@ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ - if (!defined('STATUSNET')) { // This check helps protect against security problems; // your code file can't be executed directly from the web. @@ -39,6 +38,10 @@ if (!defined('STATUSNET')) { * * Converts the notice form in browser to a rich-text editor. * + * FIXME: this plugin DOES NOT load its static files from the configured + * plugin server if one exists. There are cross-server permissions errors + * if you try to do that (something about window.tinymce). + * * @category WYSIWYG * @package StatusNet * @author Evan Prodromou @@ -46,14 +49,18 @@ if (!defined('STATUSNET')) { * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ - class TinyMCEPlugin extends Plugin { var $html; + // By default, TinyMCE editor will be available to all users. + // With restricted on, only users who have been granted the + // "richedit" role get it. + public $restricted = false; + function onEndShowScripts($action) { - if (common_logged_in()) { + if (common_logged_in() && $this->isAllowedRichEdit()) { $action->script(common_path('plugins/TinyMCE/js/jquery.tinymce.js')); $action->inlineScript($this->_inlineScript()); } @@ -63,18 +70,21 @@ class TinyMCEPlugin extends Plugin function onEndShowStyles($action) { - $action->style('span#notice_data-text_container, span#notice_data-text_parent { float: left }'); + if ($this->isAllowedRichEdit()) { + $action->style('span#notice_data-text_container, span#notice_data-text_parent { float: left }'); + } return true; } function onPluginVersion(&$versions) { $versions[] = array('name' => 'TinyMCE', - 'version' => STATUSNET_VERSION, - 'author' => 'Evan Prodromou', - 'homepage' => 'http://status.net/wiki/Plugin:TinyMCE', - 'rawdescription' => - _m('Use TinyMCE library to allow rich text editing in the browser')); + 'version' => GNUSOCIAL_VERSION, + 'author' => 'Evan Prodromou', + 'homepage' => 'http://status.net/wiki/Plugin:TinyMCE', + 'rawdescription' => + // TRANS: Plugin description. + _m('Use TinyMCE library to allow rich text editing in the browser.')); return true; } @@ -86,10 +96,10 @@ class TinyMCEPlugin extends Plugin */ private function sanitizeHtml($raw) { - require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; + require_once INSTALLDIR . '/extlib/htmLawed/htmLawed.php'; $config = array('safe' => 1, - 'deny_attribute' => 'id,style,on*'); + 'deny_attribute' => 'id,style,on*'); return htmLawed($raw, $config); } @@ -102,55 +112,248 @@ class TinyMCEPlugin extends Plugin */ private function stripHtml($html) { - return str_replace("\n", " ", html_entity_decode(strip_tags($html))); + return str_replace("\n", " ", html_entity_decode(strip_tags($html), ENT_QUOTES, 'UTF-8')); } /** * Hook for new-notice form processing to take our HTML goodies; * won't affect API posting etc. - * + * * @param NewNoticeAction $action * @param User $user * @param string $content * @param array $options * @return boolean hook return */ - function onSaveNewNoticeWeb($action, $user, &$content, &$options) + function onStartSaveNewNoticeWeb($action, $user, &$content, &$options) + { + if ($action->arg('richedit') && $this->isAllowedRichEdit()) { + $html = $this->sanitizeHtml($content); + $options['rendered'] = $html; + $content = $this->stripHtml($html); + } + return true; + } + + /** + * Hook for new-notice form processing to process file upload appending... + * + * @param NewNoticeAction $action + * @param MediaFile $media + * @param string $content + * @param array $options + * @return boolean hook return + */ + function onStartSaveNewNoticeAppendAttachment($action, $media, &$content, &$options) { - $html = $this->sanitizeHtml($action->arg('status_textarea')); - $options['rendered'] = $html; - $content = $this->stripHtml($html); + if ($action->arg('richedit') && $this->isAllowedRichEdit()) { + // See if we've got a placeholder inline image; if so, fill it! + $dom = new DOMDocument(); + + if ($dom->loadHTML($options['rendered'])) { + $imgs = $dom->getElementsByTagName('img'); + foreach ($imgs as $img) { + if (preg_match('/(^| )placeholder( |$)/', $img->getAttribute('class'))) { + // Create a link to the attachment page... + $this->formatAttachment($img, $media); + } + } + $options['rendered'] = $this->saveHtml($dom); + } + + // The regular code will append the short URL to the plaintext content. + // Carry on and let it through... + } return true; } + /** + * Format the attachment placeholder img with the final version. + * + * @param DOMElement $img + * @param MediaFile $media + */ + private function formatAttachment($img, $media) + { + $parent = $img->parentNode; + $dom = $img->ownerDocument; + + $link = $dom->createElement('a'); + $link->setAttribute('href', $media->fileurl); + $link->setAttribute('title', File::url($media->filename)); + + if ($this->isEmbeddable($media)) { + // Fix the the attributes and wrap the link around it... + $this->insertImage($img, $media); + $parent->replaceChild($link, $img); //it dies in here?! + $link->appendChild($img); + } else { + // Not an image? Replace it with a text link. + $link->setAttribute('rel', 'external'); + $link->setAttribute('class', 'attachment'); + $link->setAttribute('id', 'attachment-' . $media->fileRecord->id); + $text = $dom->createTextNode($media->shortUrl()); + $link->appendChild($text); + $parent->replaceChild($link, $img); + } + } + + /** + * Is this media file a type we can display inline? + * + * @param MediaFile $media + * @return boolean + */ + private function isEmbeddable($media) + { + $showable = array('image/png', + 'image/gif', + 'image/jpeg'); + return in_array($media->mimetype, $showable); + } + + /** + * Rewrite and resize a placeholder image element to match the uploaded + * file. If the holder is smaller than the file, the file is scaled to fit + * with correct aspect ratio (but will be loaded at full resolution). + * + * @param DOMElement $img + * @param MediaFile $media + */ + private function insertImage($img, $media) + { + $img->setAttribute('src', $media->fileRecord->url); + + $holderWidth = intval($img->getAttribute('width')); + $holderHeight = intval($img->getAttribute('height')); + + $path = File::path($media->filename); + $imgInfo = getimagesize($path); + + if ($imgInfo) { + $origWidth = $imgInfo[0]; + $origHeight = $imgInfo[1]; + + list($width, $height) = $this->sizeBox( + $origWidth, $origHeight, + $holderWidth, $holderHeight); + + $img->setAttribute('width', $width); + $img->setAttribute('height', $height); + } + } + + /** + * + * @param int $origWidth + * @param int $origHeight + * @param int $holderWidth + * @param int $holderHeight + * @return array($width, $height) + */ + private function sizeBox($origWidth, $origHeight, $holderWidth, $holderHeight) + { + $holderAspect = $holderWidth / $holderHeight; + $origAspect = $origWidth / $origHeight; + if ($origAspect >= 1.0) { + // wide image + if ($origWidth > $holderWidth) { + return array($holderWidth, intval($holderWidth / $origAspect)); + } else { + return array($origWidth, $origHeight); + } + } else { + if ($origHeight > $holderHeight) { + return array(intval($holderWidth * $origAspect), $holderHeight); + } else { + return array($origWidth, $origHeight); + } + } + } + + private function saveHtml($dom) + { + $html = $dom->saveHTML(); + // hack to remove surrounding crap added to the dom + // all we wanted was a fragment + $stripped = preg_replace('/^.*]*>(.*)<\/body.*$/is', '$1', $html); + return $stripped; + } + function _inlineScript() { $path = common_path('plugins/TinyMCE/js/tiny_mce.js'); + $placeholder = common_path('plugins/TinyMCE/icons/placeholder.png'); // Note: the normal on-submit triggering to save data from // the HTML editor into the textarea doesn't play well with // our AJAX form submission. Manually moving it to trigger // on our send button click. $scr = <<'); + + form.find('.submit:first').click(function() { + tinymce.triggerSave(); + }); + + var origCounter = SN.U.CharacterCount; + SN.U.CharacterCount = function(form) { + var text = $(ed.getDoc()).text(); + return text.length; + }; + ed.onKeyUp.add(function (ed, e) { + SN.U.Counter(noticeForm); + }); + + form.find('input[type=file]').change(function() { + var img = ''; + var html = tinyMCE.activeEditor.getContent(); + ed.setContent(html + img); + }); + } }); - }); + }; + })(); END_OF_SCRIPT; return $scr; } -} + /** + * Does the current user have permission to use the rich-text editor? + * Always true unless the plugin's "restricted" setting is on, in which + * case it's limited to users with the "richedit" role. + * + * @fixme make that more sanely configurable :) + * + * @return boolean + */ + private function isAllowedRichEdit() + { + if ($this->restricted) { + $user = common_current_user(); + return !empty($user) && $user->hasRole('richedit'); + } else { + return true; + } + } +}