diff --git a/admin/_config.php b/admin/_config.php index 4ea5c221a..7b52790c7 100644 --- a/admin/_config.php +++ b/admin/_config.php @@ -34,6 +34,7 @@ HtmlEditorConfig::get('cms')->enablePlugins('media', 'fullscreen', 'inlinepopups HtmlEditorConfig::get('cms')->enablePlugins(array( 'ssbuttons' => sprintf('../../../%s/tinymce_ssbuttons/editor_plugin_src.js', THIRDPARTY_DIR) )); +HtmlEditorConfig::get('cms')->enablePlugins('advimagescale'); HtmlEditorConfig::get('cms')->insertButtonsBefore('formatselect', 'styleselect'); HtmlEditorConfig::get('cms')->addButtonsToLine(2, diff --git a/thirdparty/tinymce/plugins/advimagescale/editor_plugin.js b/thirdparty/tinymce/plugins/advimagescale/editor_plugin.js new file mode 100644 index 000000000..db7b32fc2 --- /dev/null +++ b/thirdparty/tinymce/plugins/advimagescale/editor_plugin.js @@ -0,0 +1 @@ +(function(){function e(e,t){var n=e.dom;var r=n.getAttrib(t,"mce_advimageresize_id");if(!e.originalDimensions[r]){e.originalDimensions[r]=e.lastDimensions[r]={width:n.getAttrib(t,"width",t.width),height:n.getAttrib(t,"height",t.height)}}return true}function t(t,n){var r=t.dom;var i=r.getAttrib(n,"mce_advimageresize_id");if(!i){var i=t.id+"_"+t.dom.uniqueId();r.setAttrib(n,"mce_advimageresize_id",i);e(t,n)}return i}function n(n,o,u){var l=n.dom;var c=t(n,o);var h=l.getAttrib(o,"width")!=n.lastDimensions[c].width||l.getAttrib(o,"height")!=n.lastDimensions[c].height;if(!h)return;if(l.getAttrib(o,"mce_noresize")||l.hasClass(o,n.getParam("advimagescale_noresize_class","noresize"))||n.getParam("advimagescale_noresize_all")){l.setAttrib(o,"width",n.lastDimensions[c].width);l.setAttrib(o,"height",n.lastDimensions[c].height);if(tinymce.isGecko)i(n);return}if(n.getParam("advimagescale_fix_border_glitch",true)){r(n,o);e(n,o)}var p=n.getParam("advimagescale_filter_src");if(p){var d=new RegExp(p);if(!o.src.match(d)){return}}var v=n.getParam("advimagescale_filter_class");if(v){if(!l.hasClass(o,v)){return}}var m={width:l.getAttrib(o,"width",o.width),height:l.getAttrib(o,"height",o.height)};if(n.getParam("advimagescale_maintain_aspect_ratio",true)){m=a(n,o,m.width,m.height)}m=f(n,o,m.width,m.height);var g=l.getAttrib(o,"width",o.width)!=m.width||l.getAttrib(o,"height",o.height)!=m.height;if(g){l.setAttrib(o,"width",m.width);l.setAttrib(o,"height",m.height);if(tinymce.isGecko)i(n)}if(n.getParam("advimagescale_append_to_url")){s(n,o,l.getAttrib(o,"width",o.width),l.getAttrib(o,"height",o.height))}if(n.lastDimensions[c].width!=l.getAttrib(o,"width",o.width)||n.lastDimensions[c].height!=l.getAttrib(o,"height",o.height)){if(n.getParam("advimagescale_resize_callback")){n.getParam("advimagescale_resize_callback")(n,o)}}n.lastDimensions[c]={width:l.getAttrib(o,"width",o.width),height:l.getAttrib(o,"height",o.height)}}function r(e,t){var n=e.dom;var r=n.getAttrib(t,"mce_advimageresize_id");var s=n.getAttrib(t,"width",t.width);var o=n.getAttrib(t,"height",t.height);var u=false;if(s!=e.lastDimensions[r].width){var a=0;a+=parseInt(n.getStyle(t,"borderLeftWidth","borderLeftWidth"));a+=parseInt(n.getStyle(t,"borderRightWidth","borderRightWidth"));if(a>0){n.setAttrib(t,"width",s-a);u=true}}if(o!=e.lastDimensions[r].height){var f=0;f+=parseInt(n.getStyle(t,"borderTopWidth","borderTopWidth"));f+=parseInt(n.getStyle(t,"borderBottomWidth","borderBottomWidth"));if(f>0){n.setAttrib(t,"height",o-f);u=true}}if(u&&tinymce.isGecko)i(e)}function i(e){e.execCommand("mceRepaint",false)}function s(e,t,n,r){var i=e.dom;var s=i.getAttrib(t,"src");var a=e.getParam("advimagescale_url_width_key","w");s=u(s,a,n);var f=e.getParam("advimagescale_url_height_key","h");s=u(s,f,r);if(s==i.getAttrib(t,"src")){return}if(e.getParam("advimagescale_loading_callback")){e.getParam("advimagescale_loading_callback")(t)}if(e.getParam("advimagescale_loaded_callback")){tinymce.dom.Event.add(t,"load",o,{el:t,ed:e})}i.setAttrib(t,"src",s)}function o(e){var t=this.el;var n=this.ed;var r=n.getParam("advimagescale_loaded_callback");r(t);tinymce.dom.Event.remove(t,"load",o)}function u(e,t,n){if(!e.match(/\?/))e+="?";if(!e.match(new RegExp("([?&])"+t+"="))){if(!e.match(/[&\?]$/))e+="&";e+=t+"="+escape(n)}else{e=e.replace(new RegExp("([?&])"+t+"=[^&]*"),"$1"+t+"="+escape(n))}return e}function a(e,t,n,r){var i=e.dom.getAttrib(t,"mce_advimageresize_id");var s=e.originalDimensions[i].width/e.originalDimensions[i].height;var o=e.lastDimensions[i].width;var u=e.lastDimensions[i].height;var a=Math.abs(o-n);var f=Math.abs(u-r);var l=Math.abs(a/o);var c=Math.abs(f/u);if(a||f){if(l>c){return{width:n,height:Math.round(n/s)}}else{return{width:Math.round(r*s),height:r}}}return{width:n,height:r}}function f(e,t,n,r){var i=e.dom.getAttrib(t,"mce_advimageresize_id");var s=e.getParam("advimagescale_max_width");var o=e.getParam("advimagescale_max_height");var u=e.getParam("advimagescale_min_width");var a=e.getParam("advimagescale_min_height");var f=e.getParam("advimagescale_maintain_aspect_ratio",true);var l=e.originalDimensions[i].width;var c=e.originalDimensions[i].height;var h=l/c;if(s&&n>s){n=s;r=f?Math.round(n/h):r}if(o&&r>o){r=o;n=f?Math.round(r*h):n}if(u&&n tag is inserted via mceInsertContent). + // So, catch all insertions using the editor's selection object + ed.onInit.add(function(ed) { + // http://wiki.moxiecode.com/index.php/TinyMCE:API/tinymce.dom.Selection/onSetContent + ed.selection.onSetContent.add(function(se, o) { + // @todo This seems to grab the entire editor contents - it works but could + // perform poorly on large documents + var currentNode = se.getNode(); + tinymce.each(ed.dom.select('img', currentNode), function (currentNode) { + // IF condition required as tinyMCE inserts 24x24 placeholders uner some conditions + if (currentNode.id != "__mce_tmp") + constrainSize(ed, currentNode); + }); + }); + }); + + /***************************** + * DISALLOW EXTERNAL IMAGE DRAG/DROPS + *****************************/ + // This is a hack. Listening for drag events wasn't working. + // + // Watches for mousedown and mouseup/dragdrop events within the editor. If a mouseup or + // dragdrop occurs in the editor without a preceeding mousedown, we assume it is an external + // dragdrop that should be rejected. + if (ed.getParam('advimagescale_reject_external_dragdrop', true)) { + + // catch mousedowns mouseups and dragdrops (which are basically mouseups too..) + ed.onMouseDown.add(function(e) { ed.edMouseDown = true; }); + ed.onMouseUp.add(function(e) { ed.edMouseDown = false; }); + ed.onInit.add(function(ed, o) { + tinymce.dom.Event.add(ed.getBody().parentNode, 'dragdrop', function(e) { ed.edMouseDown = false; }); + }); + + // watch for drag attempts + var evt = (tinymce.isIE) ? 'dragenter' : 'dragover'; // IE allows dragdrop reject on dragenter (more efficient) + ed.onInit.add(function(ed, o) { + // use parentNode to go above editor content, to cover entire editor area + tinymce.dom.Event.add(ed.getBody().parentNode, evt, function (e) { + if (!ed.edMouseDown) { + // disallow drop + return tinymce.dom.Event.cancel(e); + } + }); + }); + + } + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Advanced Image Resize Helper', + author : 'Marc Hodgins', + authorurl : 'http://www.hodginsmedia.com', + infourl : 'http://code.google.com/p/tinymce-plugin-advimagescale', + version : '1.1.3' + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('advimagescale', tinymce.plugins.AdvImageScale); + + /** + * Store image dimensions, pre-resize + * + * @param {object} el HTMLDomNode + */ + function storeDimensions(ed, el) { + var dom = ed.dom; + var elId = dom.getAttrib(el, 'mce_advimageresize_id'); + + // store original dimensions if this is the first resize of this element + if (!ed.originalDimensions[elId]) { + ed.originalDimensions[elId] = ed.lastDimensions[elId] = {width: dom.getAttrib(el, 'width', el.width), height: dom.getAttrib(el, 'height', el.height)}; + } + return true; + } + + /** + * Prepare image for resizing + * Check to see if we've seen this IMG tag before; does tasks such as adding + * unique IDs to image tags, saving "original" image dimensions, etc. + * @param {object} e is optional + */ + function prepareImage(ed, el) { + var dom = ed.dom; + var elId = dom.getAttrib(el, 'mce_advimageresize_id'); + + // is this the first time this image tag has been seen? + if (!elId) { + var elId = ed.id + "_" + ed.dom.uniqueId(); + dom.setAttrib(el, 'mce_advimageresize_id', elId); + storeDimensions(ed, el); + } + + return elId; + } + + /** + * Adjusts width and height to keep within min/max bounds and also maintain aspect ratio + * If mce_noresize attribute is set to image tag, then image resize is disallowed + */ + function constrainSize(ed, el, e) { + var dom = ed.dom; + var elId = prepareImage(ed, el); // also calls storeDimensions + var resized = (dom.getAttrib(el, 'width') != ed.lastDimensions[elId].width || dom.getAttrib(el, 'height') != ed.lastDimensions[elId].height); + + if (!resized) + return; // nothing to do + + // disallow image resize if mce_noresize or the noresize class is set on the image tag + if (dom.getAttrib(el, 'mce_noresize') || dom.hasClass(el, ed.getParam('advimagescale_noresize_class', 'noresize')) || ed.getParam('advimagescale_noresize_all')) { + dom.setAttrib(el, 'width', ed.lastDimensions[elId].width); + dom.setAttrib(el, 'height', ed.lastDimensions[elId].height); + if (tinymce.isGecko) + fixGeckoHandles(ed); + return; + } + + // Both IE7 and Gecko (as of FF3.0.03) has a "expands image by border width" bug before doing anything else + if (ed.getParam('advimagescale_fix_border_glitch', true /* default to true */)) { + fixImageBorderGlitch(ed, el); + storeDimensions(ed, el); // store adjusted dimensions + } + + // filter by regexp so only some images get constrained + var src_filter = ed.getParam('advimagescale_filter_src'); + if (src_filter) { + var r = new RegExp(src_filter); + if (!el.src.match(r)) { + return; // skip this element + } + } + + // allow filtering by classname + var class_filter = ed.getParam('advimagescale_filter_class'); + if (class_filter) { + if (!dom.hasClass(el, class_filter)) { + return; // skip this element, doesn't have the class we want + } + } + + // populate new dimensions object + var newDimensions = { width: dom.getAttrib(el, 'width', el.width), height: dom.getAttrib(el, 'height', el.height) }; + + // adjust w/h to maintain aspect ratio + if (ed.getParam('advimagescale_maintain_aspect_ratio', true /* default to true */)) { + newDimensions = maintainAspect(ed, el, newDimensions.width, newDimensions.height); + } + + // enforce minW/minH/maxW/maxH + newDimensions = checkBoundaries(ed, el, newDimensions.width, newDimensions.height); + + // was an adjustment made? + var adjusted = (dom.getAttrib(el, 'width', el.width) != newDimensions.width || dom.getAttrib(el, 'height', el.height) != newDimensions.height); + + // apply new w/h + if (adjusted) { + dom.setAttrib(el, 'width', newDimensions.width); + dom.setAttrib(el, 'height', newDimensions.height); + if (tinymce.isGecko) fixGeckoHandles(ed); + } + + if (ed.getParam('advimagescale_append_to_url')) { + appendToUri(ed, el, dom.getAttrib(el, 'width', el.width), dom.getAttrib(el, 'height', el.height)); + } + + // was the image resized? + if (ed.lastDimensions[elId].width != dom.getAttrib(el, 'width', el.width) || ed.lastDimensions[elId].height != dom.getAttrib(el, 'height', el.height)) { + // call "image resized" callback (if set) + if (ed.getParam('advimagescale_resize_callback')) { + ed.getParam('advimagescale_resize_callback')(ed, el); + } + } + + // remember "last dimensions" for next time + ed.lastDimensions[elId] = { width: dom.getAttrib(el, 'width', el.width), height: dom.getAttrib(el, 'height', el.height) }; + } + + /** + * Fixes IE7 and Gecko border width glitch + * + * Both "add" the border width to an image after the resize handles have been + * dropped. This reverses it by looking at the "previous" known size and comparing + * to the current size. If they don't match, then a resize has taken place and the browser + * has (probably) messed it up. So, we reverse it. Note, this will probably need to be + * wrapped in a conditional statement if/when each browser fixes this bug. + */ + function fixImageBorderGlitch(ed, el) { + var dom = ed.dom; + var elId = dom.getAttrib(el, 'mce_advimageresize_id'); + var currentWidth = dom.getAttrib(el, 'width', el.width); + var currentHeight = dom.getAttrib(el, 'height', el.height); + var adjusted = false; + + // if current dimensions do not match what we last saw, then a resize has taken place + if (currentWidth != ed.lastDimensions[elId].width) { + var adjustWidth = 0; + + // get computed border left/right widths + adjustWidth += parseInt(dom.getStyle(el, 'borderLeftWidth', 'borderLeftWidth')); + adjustWidth += parseInt(dom.getStyle(el, 'borderRightWidth', 'borderRightWidth')); + + // reset the width height to NOT include these amounts + if (adjustWidth > 0) { + dom.setAttrib(el, 'width', (currentWidth - adjustWidth)); + adjusted = true; + } + } + if (currentHeight != ed.lastDimensions[elId].height) { + var adjustHeight = 0; + + // get computed border top/bottom widths + adjustHeight += parseInt(dom.getStyle(el, 'borderTopWidth', 'borderTopWidth')); + adjustHeight += parseInt(dom.getStyle(el, 'borderBottomWidth', 'borderBottomWidth')); + + if (adjustHeight > 0) { + dom.setAttrib(el, 'height', (currentHeight - adjustHeight)); + adjusted = true; + } + } + if (adjusted && tinymce.isGecko) fixGeckoHandles(ed); + } + + /** + * Fix gecko resize handles glitch + */ + function fixGeckoHandles(ed) { + ed.execCommand('mceRepaint', false); + } + + /** + * Set image dimensions on into a uri as querystring params + */ + function appendToUri(ed, el, w, h) { + var dom = ed.dom; + var uri = dom.getAttrib(el, 'src'); + var wKey = ed.getParam('advimagescale_url_width_key', 'w'); + uri = setQueryParam(uri, wKey, w); + var hKey = ed.getParam('advimagescale_url_height_key', 'h'); + uri = setQueryParam(uri, hKey, h); + + // no need to continue if URL didn't change + if (uri == dom.getAttrib(el, 'src')) { + return; + } + + // trigger image loading callback (if set) + if (ed.getParam('advimagescale_loading_callback')) { + // call loading callback + ed.getParam('advimagescale_loading_callback')(el); + } + // hook image load(ed) callback (if set) + if (ed.getParam('advimagescale_loaded_callback')) { + // hook load event on the image tag to call the loaded callback + tinymce.dom.Event.add(el, 'load', imageLoadedCallback, {el: el, ed: ed}); + } + + // set new src + dom.setAttrib(el, 'src', uri); + } + + /** + * Callback event when an image is (re)loaded + * @param {object} e Event (use e.target or this.el to access element, this.ed to access editor instance) + */ + function imageLoadedCallback(e) { + var el = this.el; // image element + var ed = this.ed; // editor + var callback = ed.getParam('advimagescale_loaded_callback'); // user specified callback + + // call callback, pass img as param + callback(el); + + // remove callback event + tinymce.dom.Event.remove(el, 'load', imageLoadedCallback); + } + + /** + * Sets URL querystring parameters by appending or replacing existing params of same name + */ + function setQueryParam(uri, key, value) { + if (!uri.match(/\?/)) uri += '?'; + if (!uri.match(new RegExp('([\?&])' + key + '='))) { + if (!uri.match(/[&\?]$/)) uri += '&'; + uri += key + '=' + escape(value); + } else { + uri = uri.replace(new RegExp('([\?\&])' + key + '=[^&]*'), '$1' + key + '=' + escape(value)); + } + return uri; + } + + /** + * Returns w/h that maintain aspect ratio + */ + function maintainAspect(ed, el, w, h) { + var elId = ed.dom.getAttrib(el, 'mce_advimageresize_id'); + + // calculate aspect ratio of original so we can maintain it + var ratio = ed.originalDimensions[elId].width / ed.originalDimensions[elId].height; + + // decide which dimension changed more (percentage), because that's the + // one we'll respect (the other we'll adjust to keep aspect ratio) + var lastW = ed.lastDimensions[elId].width; + var lastH = ed.lastDimensions[elId].height; + var deltaW = Math.abs(lastW - w); // absolute + var deltaH = Math.abs(lastH - h); // absolute + var pctW = Math.abs(deltaW / lastW); // percentage + var pctH = Math.abs(deltaH / lastH); // percentage + + if (deltaW || deltaH) { + if (pctW > pctH) { + // width changed more - use that as the locked point and adjust height + return { width: w, height: Math.round(w / ratio) }; + } else { + // height changed more - use that as the locked point and adjust width + return { width: Math.round(h * ratio), height: h }; + } + } + + // nothing changed + return { width: w, height: h }; + } + + /** + * Enforce min/max boundaries + * + * Returns true if an adjustment was made + */ + function checkBoundaries(ed, el, w, h) { + + var elId = ed.dom.getAttrib(el, 'mce_advimageresize_id'); + var maxW = ed.getParam('advimagescale_max_width'); + var maxH = ed.getParam('advimagescale_max_height'); + var minW = ed.getParam('advimagescale_min_width'); + var minH = ed.getParam('advimagescale_min_height'); + var maintainAspect = ed.getParam('advimagescale_maintain_aspect_ratio', true); + var oW = ed.originalDimensions[elId].width; + var oH = ed.originalDimensions[elId].height; + var ratio = oW/oH; + + // max + if (maxW && w > maxW) { + w = maxW; + h = maintainAspect ? Math.round(w / ratio) : h; + } + if (maxH && h > maxH) { + h = maxH; + w = maintainAspect ? Math.round(h * ratio) : w; + } + + // min + if (minW && w < minW) { + w = minW; + h = maintainAspect ? Math.round(w / ratio) : h; + } + if (minH && h < minH) { + h = minH; + w = maintainAspect ? Math.round(h * ratio) : h; + } + + return { width: w, height:h }; + } + +})(); \ No newline at end of file