Added experimental mention support
authorAlexander Ebert <ebert@woltlab.com>
Mon, 23 Nov 2015 23:10:56 +0000 (00:10 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Mon, 23 Nov 2015 23:10:56 +0000 (00:10 +0100)
Still buggy and anything but finished, more like a proof of concept than
a working implementation. Naming is still inconsistent and is something
I'm working on.

18 files changed:
com.woltlab.wcf/templates/htmlNodeWoltlabMention.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/wysiwyg.tpl
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabEvent.js
wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabMention.js [new file with mode: 0644]
wcfsetup/install/files/js/WCF.Assets.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Mention.js [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/input/HtmlInputProcessor.class.php
wcfsetup/install/files/lib/system/html/input/filter/MessageHtmlInputFilter.class.php
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabMention.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/input/node/IHtmlInputNode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/node/HtmlNodeProcessor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/output/HtmlOutputNodeProcessor.class.php
wcfsetup/install/files/lib/system/html/output/HtmlOutputProcessor.class.php
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeBlockquote.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabMention.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/html/output/node/QuoteHtmlOutputNode.class.php [deleted file]
wcfsetup/install/files/lib/system/session/SessionHandler.class.php

diff --git a/com.woltlab.wcf/templates/htmlNodeWoltlabMention.tpl b/com.woltlab.wcf/templates/htmlNodeWoltlabMention.tpl
new file mode 100644 (file)
index 0000000..ac2b3d7
--- /dev/null
@@ -0,0 +1,7 @@
+{if $userProfile === null}
+       {* user no longer exists, use plain output rather than using a broken link *}
+       @{$username}
+{else}
+       {* non-breaking space below to prevent wrapping of user avatar and username *}
+       <a href="{link controller='User' object=$userProfile->getDecoratedObject()}{/link}">{@$userProfile->getAvatar()->getImageTag(16)}&nbsp;{$userProfile->username}</a>
+{/if}
\ No newline at end of file
index 475c2235563617e00063d05dafb79d738a2d48db..272816bb3ce097fdb98fe32d523ac0479c75a542 100644 (file)
@@ -1,3 +1,12 @@
+<style>
+       woltlab-mention {
+               background-color: rgb(240, 248, 255);
+               border: 1px solid rgb(52, 152, 219);
+               display: inline-block;
+               margin: 0 3px;
+               padding: 0 2px;
+       }
+</style>
 <script data-relocate="true">
 (function() {
        var buttons = ['format', 'wcfSeparator', 'bold', 'italic', 'underline', 'deleted', 'wcfSeparator', 'lists', 'image', 'link'];
                        }
                };
                
+               // user mentions
+               if (elDataBool(element, 'support-mention')) {
+                       config.plugins.push('WoltLabMention');
+               }
+               
                $(element).redactor(config);
        });
                
@@ -30,6 +44,7 @@
                '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabButton.js?v={@LAST_UPDATE_TIME}',
                '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabDropdown.js?v={@LAST_UPDATE_TIME}', 
                '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabEvent.js?v={@LAST_UPDATE_TIME}',
+               '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabMention.js?v={@LAST_UPDATE_TIME}',
                '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabQuote.js?v={@LAST_UPDATE_TIME}'
                
                ], function() {
index 2dcedae7ddbe75cd25175bc4dbd7015dd3bbcf30..21d6e8a03d5466713cfbf2a4462497e7fb2228ce 100644 (file)
@@ -3,6 +3,9 @@ $.Redactor.prototype.WoltLabEvent = function() {
        
        return {
                init: function() {
+                       this._callbacks = [];
+                       this._elementId = this.$element[0].id;
+                       
                        require(['EventHandler'], this.WoltLabEvent._setEvents.bind(this));
                },
                
@@ -17,6 +20,41 @@ $.Redactor.prototype.WoltLabEvent = function() {
                                        editor: this.$editor[0]
                                });
                        }).bind(this);
+                       
+                       this.opts.callbacks.keyup = function(event) {
+                               var data = {
+                                       cancel: false,
+                                       event: event
+                               };
+                               
+                               EventHandler.fire('com.woltlab.wcf.redactor', 'keyup_' + elementId, data);
+                               
+                               return (data.cancel === false);
+                       };
+               },
+               
+               register: function(callbackName, callback) {
+                       require(['EventHandler'], (function(EventHandler) {
+                               if (this._callbacks.indexOf(callbackName) === -1) {
+                                       this.opts.callbacks[callbackName] = (function (event) {
+                                               var data = {
+                                                       cancel: false,
+                                                       event: event,
+                                                       redactor: this
+                                               };
+                                               
+                                               EventHandler.fire('com.woltlab.wcf.redactor2', callbackName + '_' + this.WoltLabEvent._elementId, data);
+                                               
+                                               return (data.cancel === false);
+                                       }).bind(this);
+                                       
+                                       this._callbacks.push(callbackName);
+                               }
+                               
+                               require(['EventHandler'], (function(EventHandler) {
+                                       EventHandler.add('com.woltlab.wcf.redactor2', callbackName + '_' + this.WoltLabEvent._elementId, callback);
+                               }).bind(this));
+                       }).bind(this));
                }
        };
 };
diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabMention.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabMention.js
new file mode 100644 (file)
index 0000000..94265c1
--- /dev/null
@@ -0,0 +1,13 @@
+$.Redactor.prototype.WoltLabMention = function() {
+       "use strict";
+       
+       return {
+               init: function() {
+                       var WoltLabMention = document.registerElement('woltlab-mention');
+                       
+                       require(['WoltLab/WCF/Ui/Redactor/Mention'], (function(UiRedactorMention) {
+                               new UiRedactorMention(this);
+                       }).bind(this));
+               }
+       };
+};
index 6e69b0b56453fdb84eeeada31d2fc0d876b523f2..f4baf65a9ba9625bc2882695760d8bdb4cdbfdf6 100644 (file)
@@ -42,3 +42,6 @@
  * http://flaviusmatis.github.com/license.html
  */
 (function(e){var t={init:function(){var t=["paddingTop","paddingRight","paddingBottom","paddingLeft","fontSize","lineHeight","fontFamily","width","fontWeight","border-top-width","border-right-width","border-bottom-width","border-left-width","-moz-box-sizing","-webkit-box-sizing","box-sizing"];return this.each(function(){function i(){for(var e=0;e<t.length;e++){r.css(t[e],n.css(t[e]))}}function c(){var e=n.val().replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/&/g,"&").replace(/\n/g,"<br/>");r.html(e+"&nbsp;");h()}function h(){var e=r.height();var t="hidden";var i=s?e+a+o:e+a;if(i>l){i=l;t="auto"}else if(i<f){i=f}if(n.height()!==i){n.css({overflow:t,height:i+"px"})}}if(this.type!=="textarea")return false;var n=e(this).css({resize:"none",overflow:"hidden"});var r=e("<div></div>").css({position:"absolute",display:"none","word-wrap":"break-word","white-space":"pre-wrap","border-style":"solid"}).appendTo(document.body);i();var s=n.css("box-sizing")=="border-box"||n.css("-moz-box-sizing")=="border-box"||n.css("-webkit-box-sizing")=="border-box";var o=parseInt(n.css("border-top-width"))+parseInt(n.css("padding-top"))+parseInt(n.css("padding-bottom"))+parseInt(n.css("border-bottom-width"));var u=parseInt(n.css("height"),10);var a=parseInt(n.css("line-height"),10)||parseInt(n.css("font-size"),10);var f=a*2>u?a*2:u;var l=parseInt(n.css("max-height"),10)>-1?parseInt(n.css("max-height"),10):Number.MAX_VALUE;n.bind("keyup change cut paste",function(){c()});e(window).bind("resize",function(){var e=parseInt(n.width(),10);if(r.width()!==e){r.css({width:e+"px"});c()}});n.bind("blur",function(){h()});n.bind("updateHeight",function(){i();c()});e(function(){c()})})}};e.fn.flexible=function(n){if(t[n]){return t[n].apply(this,Array.prototype.slice.call(arguments,1))}else if(typeof n==="object"||!n){return t.init.apply(this,arguments)}else{e.error("Method "+n+" does not exist on jQuery.flexible")}}})(jQuery);
+
+/*! (C) WebReflection Mit Style License */
+(function(e,t,n,r){"use strict";function rt(e,t){for(var n=0,r=e.length;n<r;n++)dt(e[n],t)}function it(e){for(var t=0,n=e.length,r;t<n;t++)r=e[t],nt(r,b[ot(r)])}function st(e){return function(t){j(t)&&(dt(t,e),rt(t.querySelectorAll(w),e))}}function ot(e){var t=e.getAttribute("is"),n=e.nodeName.toUpperCase(),r=S.call(y,t?v+t.toUpperCase():d+n);return t&&-1<r&&!ut(n,t)?-1:r}function ut(e,t){return-1<w.indexOf(e+'[is="'+t+'"]')}function at(e){var t=e.currentTarget,n=e.attrChange,r=e.attrName,i=e.target;Q&&(!i||i===t)&&t.attributeChangedCallback&&r!=="style"&&t.attributeChangedCallback(r,n===e[a]?null:e.prevValue,n===e[l]?null:e.newValue)}function ft(e){var t=st(e);return function(e){X.push(t,e.target)}}function lt(e){K&&(K=!1,e.currentTarget.removeEventListener(h,lt)),rt((e.target||t).querySelectorAll(w),e.detail===o?o:s),B&&pt()}function ct(e,t){var n=this;q.call(n,e,t),G.call(n,{target:n})}function ht(e,t){D(e,t),et?et.observe(e,z):(J&&(e.setAttribute=ct,e[i]=Z(e),e.addEventListener(p,G)),e.addEventListener(c,at)),e.createdCallback&&Q&&(e.created=!0,e.createdCallback(),e.created=!1)}function pt(){for(var e,t=0,n=F.length;t<n;t++)e=F[t],E.contains(e)||(F.splice(t,1),dt(e,o))}function dt(e,t){var n,r=ot(e);-1<r&&(tt(e,b[r]),r=0,t===s&&!e[s]?(e[o]=!1,e[s]=!0,r=1,B&&S.call(F,e)<0&&F.push(e)):t===o&&!e[o]&&(e[s]=!1,e[o]=!0,r=1),r&&(n=e[t+"Callback"])&&n.call(e))}if(r in t)return;var i="__"+r+(Math.random()*1e5>>0),s="attached",o="detached",u="extends",a="ADDITION",f="MODIFICATION",l="REMOVAL",c="DOMAttrModified",h="DOMContentLoaded",p="DOMSubtreeModified",d="<",v="=",m=/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,g=["ANNOTATION-XML","COLOR-PROFILE","FONT-FACE","FONT-FACE-SRC","FONT-FACE-URI","FONT-FACE-FORMAT","FONT-FACE-NAME","MISSING-GLYPH"],y=[],b=[],w="",E=t.documentElement,S=y.indexOf||function(e){for(var t=this.length;t--&&this[t]!==e;);return t},x=n.prototype,T=x.hasOwnProperty,N=x.isPrototypeOf,C=n.defineProperty,k=n.getOwnPropertyDescriptor,L=n.getOwnPropertyNames,A=n.getPrototypeOf,O=n.setPrototypeOf,M=!!n.__proto__,_=n.create||function vt(e){return e?(vt.prototype=e,new vt):this},D=O||(M?function(e,t){return e.__proto__=t,e}:L&&k?function(){function e(e,t){for(var n,r=L(t),i=0,s=r.length;i<s;i++)n=r[i],T.call(e,n)||C(e,n,k(t,n))}return function(t,n){do e(t,n);while((n=A(n))&&!N.call(n,t));return t}}():function(e,t){for(var n in t)e[n]=t[n];return e}),P=e.MutationObserver||e.WebKitMutationObserver,H=(e.HTMLElement||e.Element||e.Node).prototype,B=!N.call(H,E),j=B?function(e){return e.nodeType===1}:function(e){return N.call(H,e)},F=B&&[],I=H.cloneNode,q=H.setAttribute,R=H.removeAttribute,U=t.createElement,z=P&&{attributes:!0,characterData:!0,attributeOldValue:!0},W=P||function(e){J=!1,E.removeEventListener(c,W)},X,V=e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,10)},$=!1,J=!0,K=!0,Q=!0,G,Y,Z,et,tt,nt;O||M?(tt=function(e,t){N.call(t,e)||ht(e,t)},nt=ht):(tt=function(e,t){e[i]||(e[i]=n(!0),ht(e,t))},nt=tt),B?(J=!1,function(){var e=k(H,"addEventListener"),t=e.value,n=function(e){var t=new CustomEvent(c,{bubbles:!0});t.attrName=e,t.prevValue=this.getAttribute(e),t.newValue=null,t[l]=t.attrChange=2,R.call(this,e),this.dispatchEvent(t)},r=function(e,t){var n=this.hasAttribute(e),r=n&&this.getAttribute(e),i=new CustomEvent(c,{bubbles:!0});q.call(this,e,t),i.attrName=e,i.prevValue=n?r:null,i.newValue=t,n?i[f]=i.attrChange=1:i[a]=i.attrChange=0,this.dispatchEvent(i)},s=function(e){var t=e.currentTarget,n=t[i],r=e.propertyName,s;n.hasOwnProperty(r)&&(n=n[r],s=new CustomEvent(c,{bubbles:!0}),s.attrName=n.name,s.prevValue=n.value||null,s.newValue=n.value=t[r]||null,s.prevValue==null?s[a]=s.attrChange=0:s[f]=s.attrChange=1,t.dispatchEvent(s))};e.value=function(e,o,u){e===c&&this.attributeChangedCallback&&this.setAttribute!==r&&(this[i]={className:{name:"class",value:this.className}},this.setAttribute=r,this.removeAttribute=n,t.call(this,"propertychange",s)),t.call(this,e,o,u)},C(H,"addEventListener",e)}()):P||(E.addEventListener(c,W),E.setAttribute(i,1),E.removeAttribute(i),J&&(G=function(e){var t=this,n,r,s;if(t===e.target){n=t[i],t[i]=r=Z(t);for(s in r){if(!(s in n))return Y(0,t,s,n[s],r[s],a);if(r[s]!==n[s])return Y(1,t,s,n[s],r[s],f)}for(s in n)if(!(s in r))return Y(2,t,s,n[s],r[s],l)}},Y=function(e,t,n,r,i,s){var o={attrChange:e,currentTarget:t,attrName:n,prevValue:r,newValue:i};o[s]=e,at(o)},Z=function(e){for(var t,n,r={},i=e.attributes,s=0,o=i.length;s<o;s++)t=i[s],n=t.name,n!=="setAttribute"&&(r[n]=t.value);return r})),t[r]=function(n,r){p=n.toUpperCase(),$||($=!0,P?(et=function(e,t){function n(e,t){for(var n=0,r=e.length;n<r;t(e[n++]));}return new P(function(r){for(var i,s,o=0,u=r.length;o<u;o++)i=r[o],i.type==="childList"?(n(i.addedNodes,e),n(i.removedNodes,t)):(s=i.target,Q&&s.attributeChangedCallback&&i.attributeName!=="style"&&s.attributeChangedCallback(i.attributeName,i.oldValue,s.getAttribute(i.attributeName)))})}(st(s),st(o)),et.observe(t,{childList:!0,subtree:!0})):(X=[],V(function E(){while(X.length)X.shift().call(null,X.shift());V(E)}),t.addEventListener("DOMNodeInserted",ft(s)),t.addEventListener("DOMNodeRemoved",ft(o))),t.addEventListener(h,lt),t.addEventListener("readystatechange",lt),t.createElement=function(e,n){var r=U.apply(t,arguments),i=""+e,s=S.call(y,(n?v:d)+(n||i).toUpperCase()),o=-1<s;return n&&(r.setAttribute("is",n=n.toLowerCase()),o&&(o=ut(i.toUpperCase(),n))),Q=!t.createElement.innerHTMLHelper,o&&nt(r,b[s]),r},H.cloneNode=function(e){var t=I.call(this,!!e),n=ot(t);return-1<n&&nt(t,b[n]),e&&it(t.querySelectorAll(w)),t});if(-2<S.call(y,v+p)+S.call(y,d+p))throw new Error("A "+n+" type is already registered");if(!m.test(p)||-1<S.call(g,p))throw new Error("The type "+n+" is invalid");var i=function(){return f?t.createElement(l,p):t.createElement(l)},a=r||x,f=T.call(a,u),l=f?r[u].toUpperCase():p,c=y.push((f?v:d)+p)-1,p;return w=w.concat(w.length?",":"",f?l+'[is="'+n.toLowerCase()+'"]':l),i.prototype=b[c]=T.call(a,"prototype")?a.prototype:_(H),rt(t.querySelectorAll(w),s),i}})(window,document,Object,"registerElement");
\ No newline at end of file
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Mention.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Mention.js
new file mode 100644 (file)
index 0000000..e8e8c55
--- /dev/null
@@ -0,0 +1,345 @@
+define(['Ajax', 'Environment', 'EventHandler', 'Ui/Alignment'], function(Ajax, Environment, EventHandler, UiAlignment) {
+       "use strict";
+       
+       function UiRedactorMention(redactor) { this.init(redactor); }
+       UiRedactorMention.prototype = {
+               init: function(redactor) {
+                       this._active = false;
+                       this._caret = null;
+                       this._dropdownActive = false;
+                       this._dropdownMenu = null;
+                       this._itemIndex = 0;
+                       this._lineHeight = null;
+                       this._mentionStart = '';
+                       this._redactor = redactor;
+                       this._timer = null;
+                       
+                       redactor.WoltLabEvent.register('keydown', this._keyDown.bind(this));
+                       redactor.WoltLabEvent.register('keyup', this._keyUp.bind(this));
+               },
+               
+               _keyDown: function(data) {
+                       if (!this._dropdownActive) {
+                               return;
+                       }
+                       
+                       /** @var Event event */
+                       var event = data.event;
+                       
+                       switch (event.which) {
+                               // enter
+                               case 13:
+                                       this._setUsername(null, this._dropdownMenu.children[this._itemIndex].children[0]);
+                                       break;
+                               
+                               // arrow up
+                               case 38:
+                                       this._selectItem(-1);
+                                       break;
+                               
+                               // arrow down
+                               case 40:
+                                       this._selectItem(1);
+                                       break;
+                               
+                               default:
+                                       return;
+                                       break;
+                       }
+                       
+                       event.preventDefault();
+                       data.cancel = true;
+               },
+               
+               _keyUp: function(data) {
+                       /** @var Event event */
+                       var event = data.event;
+                       
+                       // ignore return key
+                       if (event.which === 13) {
+                               this._active = false;
+                               
+                               return;
+                       }
+                       
+                       var text = this._getTextLineInFrontOfCaret();
+                       if (text.length) {
+                               var match = text.match(/@([^,]{3,})$/);
+                               if (match) {
+                                       // if mentioning is at text begin or there's a whitespace character
+                                       // before the '@', everything is fine
+                                       if (!match.index || text[match.index - 1].match(/\s/)) {
+                                               this._mentionStart = match[1];
+                                               
+                                               if (this._timer !== null) {
+                                                       window.clearTimeout(this._timer);
+                                                       this._timer = null;
+                                               }
+                                               
+                                               this._timer = window.setTimeout((function() {
+                                                       Ajax.api(this, {
+                                                               parameters: {
+                                                                       data: {
+                                                                               searchString: this._mentionStart
+                                                                       }
+                                                               }
+                                                       });
+                                                       
+                                                       this._timer = null;
+                                               }).bind(this), 500);
+                                       }
+                               }
+                               else {
+                                       this._hideDropdown();
+                               }
+                       }
+                       else {
+                               this._hideDropdown();
+                       }
+               },
+               
+               _setUsername: function(event, item) {
+                       if (event) {
+                               event.preventDefault();
+                               item = event.currentTarget;
+                       }
+                       
+                       /*if (this._timer !== null) {
+                               this._timer.stop();
+                               this._timer = null;
+                       }
+                       this._proxy.abortPrevious();*/
+                       
+                       var selection = window.getSelection();
+                       
+                       // restore caret position
+                       selection.removeAllRanges();
+                       selection.addRange(this._caret);
+                       
+                       var orgRange = selection.getRangeAt(0).cloneRange();
+                       
+                       // allow redactor to undo this
+                       this._redactor.buffer.set();
+                       
+                       var startContainer = orgRange.startContainer;
+                       var startOffset = orgRange.startOffset - (this._mentionStart.length + 1);
+                       
+                       // navigating with the keyboard before hitting enter will cause the text node to be split
+                       if (startOffset < 0) {
+                               startContainer = startContainer.previousSibling;
+                               startOffset = startContainer.length - (this._mentionStart.length + 1) - (orgRange.startOffset - 1);
+                       }
+                       
+                       var newRange = document.createRange();
+                       newRange.setStart(startContainer, startOffset);
+                       newRange.setEnd(orgRange.startContainer, orgRange.startOffset);
+                       
+                       selection.removeAllRanges();
+                       selection.addRange(newRange);
+                       
+                       var range = getSelection().getRangeAt(0);
+                       range.deleteContents();
+                       range.collapse(true);
+                       
+                       var mention = elCreate('woltlab-mention');
+                       elAttr(mention, 'contenteditable', 'false');
+                       elData(mention, 'user-id', elData(item, 'user-id'));
+                       elData(mention, 'username', elData(item, 'username'));
+                       mention.textContent = elData(item, 'username');
+                       
+                       // U+200C = zero width non-joiner
+                       var text = document.createTextNode('\u200c');
+                       
+                       range.insertNode(text);
+                       range.insertNode(mention);
+                       
+                       newRange = document.createRange();
+                       newRange.selectNode(text);
+                       newRange.collapse(false);
+                       
+                       selection.removeAllRanges();
+                       selection.addRange(newRange);
+                       
+                       this._redactor.selection.save();
+                       
+                       this._hideDropdown();
+               },
+               
+               _getTextLineInFrontOfCaret: function() {
+                       /** @var Range range */
+                       var range = window.getSelection().getRangeAt(0);
+                       if (!range.collapsed) {
+                               return '';
+                       }
+                       
+                       // in Firefox, blurring and refocusing the browser creates separate text nodes
+                       if (Environment.browser() === 'firefox' && range.startContainer.nodeType === Node.TEXT_NODE) {
+                               range.startContainer.parentNode.normalize();
+                       }
+                       
+                       var text = range.startContainer.textContent.substr(0, range.startOffset);
+                       
+                       // remove unicode zero-width space and non-breaking space
+                       var textBackup = text;
+                       text = '';
+                       var hadSpace = false;
+                       for (var i = 0; i < textBackup.length; i++) {
+                               var byte = textBackup.charCodeAt(i).toString(16);
+                               if (byte !== '200b' && (!/\s/.test(textBackup[i]) || ((byte === 'a0' || byte === '20') && !hadSpace))) {
+                                       if (byte === 'a0' || byte === '20') {
+                                               hadSpace = true;
+                                       }
+                                       
+                                       if (textBackup[i] === '@' && i && /\s/.test(textBackup[i - 1])) {
+                                               hadSpace = false;
+                                               text = '';
+                                       }
+                                       
+                                       text += textBackup[i];
+                               }
+                               else {
+                                       hadSpace = false;
+                                       text = '';
+                               }
+                       }
+                       
+                       return text;
+               },
+               
+               _ajaxSetup: function() {
+                       return {
+                               data: {
+                                       actionName: 'getSearchResultList',
+                                       className: 'wcf\\data\\user\\UserAction',
+                                       interfaceName: 'wcf\\data\\ISearchAction',
+                                       parameters: {
+                                               data: {
+                                                       includeUserGroups: false
+                                               }
+                                       }
+                               }
+                       };
+               },
+               
+               _ajaxSuccess: function(data) {
+                       if (!Array.isArray(data.returnValues) || !data.returnValues.length) {
+                               this._hideDropdown();
+                               
+                               return;
+                       }
+                       
+                       if (this._dropdownMenu === null) {
+                               this._dropdownMenu = elCreate('ol');
+                               this._dropdownMenu.className = 'dropdownMenu';
+                               elById('dropdownMenuContainer').appendChild(this._dropdownMenu);
+                       }
+                       
+                       this._dropdownMenu.innerHTML = '';
+                       
+                       var callbackClick = this._setUsername.bind(this), link, listItem, user;
+                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
+                               user = data.returnValues[i];
+                               
+                               listItem = elCreate('li');
+                               link = elCreate('a');
+                               link.addEventListener('click', callbackClick);
+                               link.className = 'box16';
+                               link.innerHTML = '<span class="framed">' + user.icon + '</span> <span>' + user.label + '</span>';
+                               elData(link, 'user-id', user.objectID);
+                               elData(link, 'username', user.label);
+                               
+                               listItem.appendChild(link);
+                               this._dropdownMenu.appendChild(listItem);
+                       }
+                       
+                       this._dropdownMenu.classList.add('dropdownOpen');
+                       this._dropdownActive = true;
+                       
+                       this._updateDropdownPosition();
+               },
+               
+               _getDropdownMenuPosition: function() {
+                       this._redactor.selection.save();
+                       
+                       var selection = window.getSelection();
+                       var orgRange = selection.getRangeAt(0).cloneRange();
+                       
+                       // mark the entire text, starting from the '@' to the current cursor position
+                       var newRange = document.createRange();
+                       newRange.setStart(orgRange.startContainer, orgRange.startOffset - (this._mentionStart.length + 1));
+                       newRange.setEnd(orgRange.startContainer, orgRange.startOffset);
+                       
+                       selection.removeAllRanges();
+                       selection.addRange(newRange);
+                       
+                       // get the offsets of the bounding box of current text selection
+                       var rect = selection.getRangeAt(0).getBoundingClientRect();
+                       var offsets = {
+                               top: Math.round(rect.bottom) + document.body.scrollTop,
+                               left: Math.round(rect.left) + document.body.scrollLeft
+                       };
+                       
+                       if (this._lineHeight === null) {
+                               this._lineHeight = Math.round(rect.bottom - rect.top - document.body.scrollTop);
+                       }
+                       
+                       // restore caret position
+                       this._redactor.selection.restore();
+                       
+                       this._caret = orgRange;
+                       
+                       return offsets;
+               },
+               
+               _updateDropdownPosition: function() {
+                       try {
+                               var offset = this._getDropdownMenuPosition();
+                               offset.top += 7; // add a little vertical gap
+                               
+                               this._dropdownMenu.style.setProperty('left', offset.left + 'px', '');
+                               this._dropdownMenu.style.setProperty('top', offset.top + 'px', '');
+                               
+                               this._selectItem(0);
+                               
+                               if (offset.top + this._dropdownMenu.offsetHeight + 10 > window.innerHeight + document.body.scrollTop) {
+                                       this._dropdownMenu.classList.add('dropdownArrowBottom');
+                                       
+                                       this._dropdownMenu.style.setProperty('top', offset.top - this._dropdownMenu.offsetHeight - 2 * this._lineHeight + 7 + 'px', '');
+                               }
+                               else {
+                                       this._dropdownMenu.classList.remove('dropdownArrowBottom');
+                               }
+                       }
+                       catch (e) {
+                               console.debug(e);
+                               // ignore errors that are caused by pressing enter to
+                               // often in a short period of time
+                       }
+               },
+               
+               _selectItem: function(step) {
+                       // find currently active item
+                       var item = elBySel('.active', this._dropdownMenu);
+                       if (item !== null) {
+                               item.classList.remove('active');
+                       }
+                       
+                       this._itemIndex += step;
+                       if (this._itemIndex === -1) {
+                               this._itemIndex = this._dropdownMenu.childElementCount - 1;
+                       }
+                       else if (this._itemIndex === this._dropdownMenu.childElementCount) {
+                               this._itemIndex = 0;
+                       }
+                       
+                       this._dropdownMenu.children[this._itemIndex].classList.add('active');
+               },
+               
+               _hideDropdown: function() {
+                       if (this._dropdownMenu !== null) this._dropdownMenu.classList.remove('dropdownOpen');
+                       this._dropdownActive = false;
+               }
+       };
+       
+       return UiRedactorMention;
+});
index c930a17e2f496c622acef22675802cd3ac433d60..c7a96389e4f75710bf7c4e5899ab0d9f1e20cb03 100644 (file)
@@ -3,6 +3,7 @@ namespace wcf\system\html\input;
 
 use wcf\system\html\input\filter\IHtmlInputFilter;
 use wcf\system\html\input\filter\MessageHtmlInputFilter;
+use wcf\system\html\input\node\HtmlInputNodeProcessor;
 use wcf\system\WCF;
 
 class HtmlInputProcessor {
@@ -11,10 +12,20 @@ class HtmlInputProcessor {
         */
        protected $htmlInputFilter;
        
+       /**
+        * @var HtmlInputNodeProcessor
+        */
+       protected $htmlInputNodeProcessor;
+       
        public function process($html) {
                // filter HTML
-               return $this->getHtmlInputFilter()->apply($html);
+               $html = $this->getHtmlInputFilter()->apply($html);
                
+               // pre-parse HTML
+               $this->getHtmlInputNodeProcessor()->load($html);
+               $this->getHtmlInputNodeProcessor()->process();
+               
+               return $this->getHtmlInputNodeProcessor()->getHtml();
        }
        
        public function setHtmlInputFilter(IHtmlInputFilter $htmlInputFilter) {
@@ -22,7 +33,8 @@ class HtmlInputProcessor {
        }
        
        /**
-        * @return IHtmlInputFilter
+        * @return IHtmlInputFilter|MessageHtmlInputFilter
+        * @throws \DI\NotFoundException
         */
        public function getHtmlInputFilter() {
                if ($this->htmlInputFilter === null) {
@@ -31,4 +43,20 @@ class HtmlInputProcessor {
                
                return $this->htmlInputFilter;
        }
+       
+       public function setHtmlInputNodeProcessor(HtmlInputNodeProcessor $htmlInputNodeProcessor) {
+               $this->htmlInputNodeProcessor = $htmlInputNodeProcessor;
+       }
+       
+       /**
+        * @return HtmlInputNodeProcessor
+        * @throws \DI\NotFoundException
+        */
+       public function getHtmlInputNodeProcessor() {
+               if ($this->htmlInputNodeProcessor === null) {
+                       $this->htmlInputNodeProcessor = WCF::getDIContainer()->make(HtmlInputNodeProcessor::class);
+               }
+               
+               return $this->htmlInputNodeProcessor;
+       }
 }
index 546200151b7fe9292a4c8d7f64ca2d4fdff8cb5a..4ec647b2bae1ac3ff4e943820582a945fb05f769 100644 (file)
@@ -25,8 +25,14 @@ class MessageHtmlInputFilter implements IHtmlInputFilter {
        }
        
        protected function setAttributeDefinitions(\HTMLPurifier_Config $config) {
+               // TODO: move this into own PHP classes
                $definition = $config->getHTMLDefinition(true);
                $definition->addAttribute('blockquote', 'data-quote-title', 'Text');
                $definition->addAttribute('blockquote', 'data-quote-url', 'URI');
+               
+               $definition->addElement('woltlab-mention', 'Inline', 'Inline', '', [
+                       'data-user-id' => 'Number',
+                       'data-username' => 'Text'
+               ]);
        }
 }
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php b/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeProcessor.class.php
new file mode 100644 (file)
index 0000000..b2540e3
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+namespace wcf\system\html\input\node;
+
+use wcf\system\html\node\HtmlNodeProcessor;
+use wcf\system\WCF;
+
+class HtmlInputNodeProcessor extends HtmlNodeProcessor {
+       public function load($html) {
+               parent::load($html);
+               
+               $this->nodeData = [];
+       }
+       
+       public function process() {
+               $woltlabMention = WCF::getDIContainer()->get(HtmlInputNodeWoltlabMention::class);
+               $woltlabMention->process($this);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabMention.class.php b/wcfsetup/install/files/lib/system/html/input/node/HtmlInputNodeWoltlabMention.class.php
new file mode 100644 (file)
index 0000000..067491c
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\system\html\input\node;
+
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+
+class HtmlInputNodeWoltlabMention implements IHtmlInputNode {
+       /**
+        * @var MessageEmbeddedObjectManager
+        */
+       protected $messageEmbeddedObjectManager;
+       
+       public function __construct(MessageEmbeddedObjectManager $messageEmbeddedObjectManager) {
+               $this->messageEmbeddedObjectManager = $messageEmbeddedObjectManager;
+       }
+       
+       public function process(HtmlInputNodeProcessor $htmlInputNodeProcessor) {
+               $userIds = [];
+               
+               /** @var \DOMElement $mention */
+               foreach ($htmlInputNodeProcessor->getDocument()->getElementsByTagName('woltlab-mention') as $mention) {
+                       $userId = intval($mention->getAttribute('data-user-id'));
+                       if ($userId) {
+                               $userIds[] = $userId;
+                       }
+               }
+               
+               if (!empty($userIds)) {
+                       
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/html/input/node/IHtmlInputNode.class.php b/wcfsetup/install/files/lib/system/html/input/node/IHtmlInputNode.class.php
new file mode 100644 (file)
index 0000000..ce78ab9
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+namespace wcf\system\html\input\node;
+
+interface IHtmlInputNode {
+       public function process(HtmlInputNodeProcessor $htmlInputNodeProcessor);
+}
diff --git a/wcfsetup/install/files/lib/system/html/node/HtmlNodeProcessor.class.php b/wcfsetup/install/files/lib/system/html/node/HtmlNodeProcessor.class.php
new file mode 100644 (file)
index 0000000..a0cfb51
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+namespace wcf\system\html\node;
+
+class HtmlNodeProcessor {
+       /**
+        * @var \DOMDocument
+        */
+       protected $document;
+       
+       public function load($html) {
+               $this->document = new \DOMDocument();
+               
+               // convert entities as DOMDocument screws them up
+               $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
+               
+               // ignore all errors when loading the HTML string, because DOMDocument does not
+               // provide a proper way to add custom HTML elements (even though explicitly allowed
+               // in HTML5) and the input HTML has already been sanitized by HTMLPurifier
+               @$this->document->loadHTML($html);
+       }
+       
+       public function getHtml() {
+               $html = $this->document->saveHTML();
+               
+               // remove nuisance added by PHP
+               $html = preg_replace('~^<!DOCTYPE[^>]+>\s<html><body>~', '', $html);
+               $html = preg_replace('~</body></html>$~', '', $html);
+               
+               $html = mb_convert_encoding($html, 'UTF-8', 'HTML-ENTITIES');
+               
+               return $html;
+       }
+       
+       public function getDocument() {
+               return $this->document;
+       }
+       
+       public function renameTag(\DOMElement $element, $tagName) {
+               $newElement = $this->document->createElement($tagName);
+               $element->parentNode->insertBefore($newElement, $element);
+               while ($element->hasChildNodes()) {
+                       $newElement->appendChild($element->firstChild);
+               }
+               
+               $element->parentNode->removeChild($element);
+               
+               return $newElement;
+       }
+       
+       public function unwrapContent(\DOMElement $element) {
+               while ($element->hasChildNodes()) {
+                       $element->parentNode->insertBefore($element->firstChild, $element);
+               }
+               
+               $element->parentNode->removeChild($element);
+       }
+}
\ No newline at end of file
index 34123148403b4a40df41f3ab9f83e49d065c15e8..97468d3563ad11c4f533242cd857f9c25d50bb81 100644 (file)
@@ -1,33 +1,32 @@
 <?php
 namespace wcf\system\html\output;
 
+use wcf\system\html\node\HtmlNodeProcessor;
+use wcf\system\html\output\node\HtmlOutputNodeBlockquote;
+use wcf\system\html\output\node\HtmlOutputNodeWoltlabMention;
 use wcf\system\html\output\node\IHtmlOutputNode;
-use wcf\system\html\output\node\QuoteHtmlOutputNode;
 use wcf\system\WCF;
 
-class HtmlOutputNodeProcessor {
-       /**
-        * @var \DOMDocument
-        */
-       protected $document;
-       
+class HtmlOutputNodeProcessor extends HtmlNodeProcessor {
        protected $nodeData = [];
        
        public function load($html) {
-               $this->document = new \DOMDocument();
-               $this->document->loadHTML($html);
+               parent::load($html);
+               
                $this->nodeData = [];
        }
        
        public function process() {
-               $quoteNode = WCF::getDIContainer()->get(QuoteHtmlOutputNode::class);
+               // TODO: this should be dynamic to some extent
+               $quoteNode = WCF::getDIContainer()->get(HtmlOutputNodeBlockquote::class);
                $quoteNode->process($this);
                
-               $html = $this->document->saveHTML();
-               
-               // remove nuisance added by PHP
-               $html = preg_replace('~^<!DOCTYPE[^>]+>\s<html><body>~', '', $html);
-               $html = preg_replace('~</body></html>$~', '', $html);
+               $woltlabMentionNode = WCF::getDIContainer()->get(HtmlOutputNodeWoltlabMention::class);
+               $woltlabMentionNode->process($this);
+       }
+       
+       public function getHtml() {
+               $html = parent::getHtml();
                
                /** @var IHtmlOutputNode $obj */
                foreach ($this->nodeData as $data) {
@@ -44,10 +43,6 @@ class HtmlOutputNodeProcessor {
                return $html;
        }
        
-       public function getDocument() {
-               return $this->document;
-       }
-       
        public function addNodeData(IHtmlOutputNode $htmlOutputNode, $nodeIdentifier, array $data) {
                $this->nodeData[] = [
                        'data' => $data,
@@ -55,24 +50,4 @@ class HtmlOutputNodeProcessor {
                        'object' => $htmlOutputNode
                ];
        }
-       
-       public function renameTag(\DOMElement $element, $tagName) {
-               $newElement = $this->document->createElement($tagName);
-               $element->parentNode->insertBefore($newElement, $element);
-               while ($element->hasChildNodes()) {
-                       $newElement->appendChild($element->firstChild);
-               }
-               
-               $element->parentNode->removeChild($element);
-               
-               return $newElement;
-       }
-       
-       public function unwrapContent(\DOMElement $element) {
-               while ($element->hasChildNodes()) {
-                       $element->parentNode->insertBefore($element->firstChild, $element);
-               }
-               
-               $element->parentNode->removeChild($element);
-       }
 }
index b483afabb02083e79db03ad5feaf1c5491d850a2..667cfaab965ab76ffe2e811027aae6565df636b3 100644 (file)
@@ -1,8 +1,6 @@
 <?php
 namespace wcf\system\html\output;
 
-use wcf\system\WCF;
-
 class HtmlOutputProcessor {
        /**
         * @var HtmlOutputNodeProcessor
@@ -15,8 +13,8 @@ class HtmlOutputProcessor {
        
        public function process($html) {
                $this->htmlOutputNodeProcessor->load($html);
-               $html = $this->htmlOutputNodeProcessor->process();
+               $this->htmlOutputNodeProcessor->process();
                
-               return $html;
+               return $this->htmlOutputNodeProcessor->getHtml();
        }
 }
diff --git a/wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeBlockquote.class.php b/wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeBlockquote.class.php
new file mode 100644 (file)
index 0000000..eb28108
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+namespace wcf\system\html\output\node;
+
+use wcf\system\application\ApplicationHandler;
+use wcf\system\html\output\HtmlOutputNodeProcessor;
+use wcf\system\request\RouteHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+class HtmlOutputNodeBlockquote implements IHtmlOutputNode {
+       /**
+        * @var ApplicationHandler
+        */
+       protected $applicationHandler;
+       
+       public function __construct(ApplicationHandler $applicationHandler) {
+               $this->applicationHandler = $applicationHandler;
+       }
+       
+       public function process(HtmlOutputNodeProcessor $htmlOutputNodeProcessor) {
+               $elements = $htmlOutputNodeProcessor->getDocument()->getElementsByTagName('blockquote');
+               while ($elements->length) {
+                       /** @var \DOMElement $blockquote */
+                       $blockquote = $elements->item(0);
+                       
+                       if ($blockquote->getAttribute('class') === 'quoteBox') {
+                               $nodeIdentifier = StringUtil::getRandomID();
+                               $htmlOutputNodeProcessor->addNodeData($this, $nodeIdentifier, [
+                                       'title' => ($blockquote->hasAttribute('data-quote-title')) ? $blockquote->getAttribute('data-quote-title') : '',
+                                       'url' => ($blockquote->hasAttribute('data-quote-url')) ? $blockquote->getAttribute('data-quote-url') : ''
+                               ]);
+                               
+                               $htmlOutputNodeProcessor->renameTag($blockquote, 'wcfNode-' . $nodeIdentifier);
+                       }
+                       else {
+                               $htmlOutputNodeProcessor->unwrapContent($blockquote);
+                       }
+               }
+       }
+       
+       public function replaceTag(array $data) {
+               $externalQuoteLink = (!empty($data['url'])) ? !$this->applicationHandler->isInternalURL($data['url']) : false;
+               if (!$externalQuoteLink) {
+                       $data['url'] = preg_replace('~^https://~', RouteHandler::getProtocol(), $data['url']);
+               }
+               
+               $quoteAuthorObject = null;
+               /*
+                * TODO: how should the author object be resolved?
+                * 
+               if ($quoteAuthor && !$externalQuoteLink) {
+                       $quoteAuthorLC = mb_strtolower(StringUtil::decodeHTML($quoteAuthor));
+                       foreach (MessageEmbeddedObjectManager::getInstance()->getObjects('com.woltlab.wcf.quote') as $user) {
+                               if (mb_strtolower($user->username) == $quoteAuthorLC) {
+                                       $quoteAuthorObject = $user;
+                                       break;
+                               }
+                       }
+               }
+               */
+               
+               WCF::getTPL()->assign([
+                       'quoteLink' => $data['url'],
+                       'quoteAuthor' => $data['title'],
+                       'quoteAuthorObject' => $quoteAuthorObject,
+                       'isExternalQuoteLink' => $externalQuoteLink
+               ]);
+               return WCF::getTPL()->fetch('quoteMetaCode');
+       }
+}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabMention.class.php b/wcfsetup/install/files/lib/system/html/output/node/HtmlOutputNodeWoltlabMention.class.php
new file mode 100644 (file)
index 0000000..7f06d42
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+namespace wcf\system\html\output\node;
+
+use wcf\data\user\UserProfile;
+use wcf\data\user\UserProfileCache;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\html\output\HtmlOutputNodeProcessor;
+use wcf\system\request\RouteHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+class HtmlOutputNodeWoltlabMention implements IHtmlOutputNode {
+       /**
+        * @var ApplicationHandler
+        */
+       protected $applicationHandler;
+       
+       /**
+        * @var UserProfile[]
+        */
+       protected $userProfiles;
+       
+       /**
+        * @var UserProfileCache
+        */
+       protected $userProfileCache;
+       
+       public function __construct(ApplicationHandler $applicationHandler, UserProfileCache $userProfileCache) {
+               $this->applicationHandler = $applicationHandler;
+               $this->userProfileCache = $userProfileCache;
+       }
+       
+       public function process(HtmlOutputNodeProcessor $htmlOutputNodeProcessor) {
+               $this->userProfiles = [];
+               
+               $userIds = [];
+               $elements = $htmlOutputNodeProcessor->getDocument()->getElementsByTagName('woltlab-mention');
+               while ($elements->length) {
+                       /** @var \DOMElement $mention */
+                       $mention = $elements->item(0);
+                       
+                       $userId = ($mention->hasAttribute('data-user-id')) ? intval($mention->getAttribute('data-user-id')) : 0;
+                       $username = ($mention->hasAttribute('data-username')) ? StringUtil::trim($mention->getAttribute('data-username')) : '';
+                       
+                       if ($userId === 0 || $username === '') {
+                               $mention->parentNode->removeChild($mention);
+                               continue;
+                       }
+                       
+                       $userIds[] = $userId;
+                       $nodeIdentifier = StringUtil::getRandomID();
+                       $htmlOutputNodeProcessor->addNodeData($this, $nodeIdentifier, [
+                               'userId' => $userId,
+                               'username' => $username
+                       ]);
+                       
+                       $htmlOutputNodeProcessor->renameTag($mention, 'wcfNode-' . $nodeIdentifier);
+               }
+               
+               if (!empty($userIds)) {
+                       $this->userProfiles = $this->userProfileCache->getUserProfiles($userIds);
+               }
+       }
+       
+       public function replaceTag(array $data) {
+               WCF::getTPL()->assign([
+                       'username' => $data['username'],
+                       'userId' => $data['userId'],
+                       'userProfile' => $this->userProfiles[$data['userId']]
+               ]);
+               
+               return WCF::getTPL()->fetch('htmlNodeWoltlabMention');
+       }
+}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/html/output/node/QuoteHtmlOutputNode.class.php b/wcfsetup/install/files/lib/system/html/output/node/QuoteHtmlOutputNode.class.php
deleted file mode 100644 (file)
index 88efb3d..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-namespace wcf\system\html\output\node;
-
-use wcf\system\application\ApplicationHandler;
-use wcf\system\html\output\HtmlOutputNodeProcessor;
-use wcf\system\request\RouteHandler;
-use wcf\system\WCF;
-use wcf\util\StringUtil;
-
-class QuoteHtmlOutputNode implements IHtmlOutputNode {
-       /**
-        * @var ApplicationHandler
-        */
-       protected $applicationHandler;
-       
-       public function __construct(ApplicationHandler $applicationHandler) {
-               $this->applicationHandler = $applicationHandler;
-       }
-       
-       public function process(HtmlOutputNodeProcessor $htmlOutputNodeProcessor) {
-               $elements = $htmlOutputNodeProcessor->getDocument()->getElementsByTagName('blockquote');
-               while ($elements->length) {
-                       /** @var \DOMElement $blockquote */
-                       $blockquote = $elements->item(0);
-                       
-                       if ($blockquote->getAttribute('class') === 'quoteBox') {
-                               $nodeIdentifier = StringUtil::getRandomID();
-                               $htmlOutputNodeProcessor->addNodeData($this, $nodeIdentifier, [
-                                       'title' => ($blockquote->hasAttribute('data-quote-title')) ? $blockquote->getAttribute('data-quote-title') : '',
-                                       'url' => ($blockquote->hasAttribute('data-quote-url')) ? $blockquote->getAttribute('data-quote-url') : ''
-                               ]);
-                               
-                               $htmlOutputNodeProcessor->renameTag($blockquote, 'wcfNode-' . $nodeIdentifier);
-                       }
-                       else {
-                               $htmlOutputNodeProcessor->unwrapContent($blockquote);
-                       }
-               }
-       }
-       
-       public function replaceTag(array $data) {
-               $externalQuoteLink = (!empty($data['url'])) ? !$this->applicationHandler->isInternalURL($data['url']) : false;
-               if (!$externalQuoteLink) {
-                       $data['url'] = preg_replace('~^https://~', RouteHandler::getProtocol(), $data['url']);
-               }
-               
-               $quoteAuthorObject = null;
-               /*
-                * TODO: how should the author object be resolved?
-                * 
-               if ($quoteAuthor && !$externalQuoteLink) {
-                       $quoteAuthorLC = mb_strtolower(StringUtil::decodeHTML($quoteAuthor));
-                       foreach (MessageEmbeddedObjectManager::getInstance()->getObjects('com.woltlab.wcf.quote') as $user) {
-                               if (mb_strtolower($user->username) == $quoteAuthorLC) {
-                                       $quoteAuthorObject = $user;
-                                       break;
-                               }
-                       }
-               }
-               */
-               
-               WCF::getTPL()->assign(array(
-                       'quoteLink' => $data['url'],
-                       'quoteAuthor' => $data['title'],
-                       'quoteAuthorObject' => $quoteAuthorObject,
-                       'isExternalQuoteLink' => $externalQuoteLink
-               ));
-               return WCF::getTPL()->fetch('quoteMetaCode');
-       }
-}
\ No newline at end of file
index 3440a6366a8809c9a815d9cfe2ca3929ba083d43..6b07b360c89aaf979650240a32f77df4f5f8eb6b 100644 (file)
@@ -874,13 +874,13 @@ class SessionHandler extends SingletonFactory {
                        'requestMethod' => $this->requestMethod,
                        'lastActivityTime' => TIME_NOW
                );
-               if (!class_exists('wcf\system\CLIWCF', false) && PACKAGE_ID && RequestHandler::getInstance()->getActiveRequest() && RequestHandler::getInstance()->getActiveRequest()->getRequestObject() instanceof ITrackablePage && RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->isTracked()) {
+               /*if (!class_exists('wcf\system\CLIWCF', false) && PACKAGE_ID && RequestHandler::getInstance()->getActiveRequest() && RequestHandler::getInstance()->getActiveRequest()->getRequestObject() instanceof ITrackablePage && RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->isTracked()) {
                        $data['controller'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getController();
                        $data['parentObjectType'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getParentObjectType();
                        $data['parentObjectID'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getParentObjectID();
                        $data['objectType'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getObjectType();
                        $data['objectID'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getObjectID();
-               }
+               }*/
                if ($this->variablesChanged) {
                        $data['sessionVariables'] = serialize($this->variables);
                }