Merged com.woltlab.wcf.bbcode into WCF
authorMarcel Werk <burntime@woltlab.com>
Mon, 20 May 2013 20:37:46 +0000 (22:37 +0200)
committerMarcel Werk <burntime@woltlab.com>
Mon, 20 May 2013 20:37:46 +0000 (22:37 +0200)
128 files changed:
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/bbcode.xml [new file with mode: 0644]
com.woltlab.wcf/coreObject.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/option.xml
com.woltlab.wcf/package.xml
com.woltlab.wcf/packageInstallationPlugin.xml
com.woltlab.wcf/smiley.xml [new file with mode: 0644]
com.woltlab.wcf/template/codeBBCodeTag.tpl [new file with mode: 0644]
com.woltlab.wcf/template/quoteBBCodeTag.tpl [new file with mode: 0644]
com.woltlab.wcf/template/spoilerBBCodeTag.tpl [new file with mode: 0644]
com.woltlab.wcf/userGroupOption.xml
wcfsetup/install/files/acp/templates/bbCodeSelectOptionType.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/bbcodeAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/bbcodeList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/bbcodeMediaProviderAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/bbcodeMediaProviderList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/smileyAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/smileyList.tpl [new file with mode: 0644]
wcfsetup/install/files/images/smilies/angry.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/attention.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/biggrin.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/blink.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/confused.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/cool.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/crying.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/cursing.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/evil.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/grin.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/heart.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/huh.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/kiss.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/love.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/mellow.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/pinch.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/question.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/rolleyes.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/sad.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/saint.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/scared.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/sick.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/sleeping.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/smile.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/thumbdown.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/thumbsup.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/thumbup.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/tired.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/tongue.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/unsure.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/w00t.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/wacko.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/whistling.png [new file with mode: 0644]
wcfsetup/install/files/images/smilies/wink.png [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/BBCodeAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/BBCodeEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/BBCodeMediaProviderAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/BBCodeMediaProviderEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/SmileyAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/SmileyCategoryAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/SmileyCategoryEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/SmileyEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/BBCodeListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/BBCodeMediaProviderListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/SmileyCategoryListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/SmileyListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/BBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/BBCodeAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/BBCodeCache.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/BBCodeEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/BBCodeList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttribute.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProvider.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/smiley/Smiley.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/smiley/SmileyAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/smiley/SmileyCache.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/smiley/SmileyEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/smiley/SmileyList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/smiley/category/SmileyCategory.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/AbstractBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/BBCodeHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/BBCodeParser.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/CodeBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/IBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/ImageBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/KeywordHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/ListBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/MediaBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/MessageParser.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/PreParser.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/QuoteBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/SimpleMessageParser.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/SpoilerBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/TableBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/URLBBCode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/BashHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/BrainfuckHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/CHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/CssHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/DiffHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/Highlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/HtmlHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/JavaHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/JsHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/PerlHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/PhpHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/PlainHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/PythonHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/SqlHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/TexHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/TplHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/highlighter/XmlHighlighter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cache/builder/BBCodeCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cache/builder/BBCodeMediaProviderCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cache/builder/SmileyCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/category/SmileyCategoryType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/option/user/group/BBCodeSelectUserGroupOptionType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/package/plugin/SmileyPackageInstallationPlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/style/bbcode.less [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index 616cbf171bede6f7f8c1ec719a58e8e8d0552ea2..8825023387b60a39169f05600d4c085c258e9ba1 100644 (file)
                        <parent>wcf.acp.menu.link.content</parent>
                </acpmenuitem>
                
+               <acpmenuitem name="wcf.acp.menu.link.bbcode">
+                       <parent>wcf.acp.menu.link.content</parent>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.bbcode.list">
+                       <controller><![CDATA[wcf\acp\page\BBCodeListPage]]></controller>
+                       <parent>wcf.acp.menu.link.bbcode</parent>
+                       <permissions>admin.content.bbcode.canManageBBCode</permissions>
+                       <showorder>1</showorder>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.bbcode.add">
+                       <controller><![CDATA[wcf\acp\form\BBCodeAddForm]]></controller>
+                       <parent>wcf.acp.menu.link.bbcode</parent>
+                       <permissions>admin.content.bbcode.canManageBBCode</permissions>
+                       <showorder>2</showorder>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.bbcode.mediaProvider.list">
+                       <controller><![CDATA[wcf\acp\page\BBCodeMediaProviderListPage]]></controller>
+                       <parent>wcf.acp.menu.link.bbcode</parent>
+                       <permissions>admin.content.bbcode.canManageBBCode</permissions>
+                       <showorder>3</showorder>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.bbcode.mediaProvider.add">
+                       <controller><![CDATA[wcf\acp\form\BBCodeMediaProviderAddForm]]></controller>
+                       <parent>wcf.acp.menu.link.bbcode</parent>
+                       <permissions>admin.content.bbcode.canManageBBCode</permissions>
+                       <showorder>4</showorder>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.smiley">
+                       <parent>wcf.acp.menu.link.content</parent>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.smiley.list">
+                       <controller><![CDATA[wcf\acp\page\SmileyListPage]]></controller>
+                       <parent>wcf.acp.menu.link.smiley</parent>
+                       <permissions>admin.content.smiley.canManageSmiley</permissions>
+                       <showorder>1</showorder>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.smiley.add">
+                       <controller><![CDATA[wcf\acp\form\SmileyAddForm]]></controller>
+                       <parent>wcf.acp.menu.link.smiley</parent>
+                       <permissions>admin.content.smiley.canManageSmiley</permissions>
+                       <showorder>2</showorder>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.smiley.category.list">
+                       <controller><![CDATA[wcf\acp\page\SmileyCategoryListPage]]></controller>
+                       <parent>wcf.acp.menu.link.smiley</parent>
+                       <permissions>admin.content.smiley.canManageSmiley</permissions>
+                       <showorder>3</showorder>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.smiley.category.add">
+                       <controller><![CDATA[wcf\acp\form\SmileyCategoryAddForm]]></controller>
+                       <parent>wcf.acp.menu.link.smiley</parent>
+                       <permissions>admin.content.smiley.canManageSmiley</permissions>
+                       <showorder>4</showorder>
+               </acpmenuitem>
+               
                <acpmenuitem name="wcf.acp.menu.link.attachment.list">
                        <controller><![CDATA[wcf\acp\page\AttachmentListPage]]></controller>
                        <parent>wcf.acp.menu.link.attachment</parent>
diff --git a/com.woltlab.wcf/bbcode.xml b/com.woltlab.wcf/bbcode.xml
new file mode 100644 (file)
index 0000000..503a402
--- /dev/null
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/bbcode.xsd">
+       <import>
+               <bbcode name="b">
+                       <htmlopen>b</htmlopen>
+                       <htmlclose>b</htmlclose>
+                       <allowedchildren>all^b</allowedchildren>
+               </bbcode>
+               <bbcode name="i">
+                       <htmlopen>i</htmlopen>
+                       <htmlclose>i</htmlclose>
+                       <allowedchildren>all^i</allowedchildren>
+               </bbcode>
+               <bbcode name="u">
+                       <htmlopen>span style="text-decoration: underline"</htmlopen>
+                       <htmlclose>span</htmlclose>
+                       <allowedchildren>all^u</allowedchildren>
+               </bbcode>
+               <bbcode name="s">
+                       <htmlopen>s</htmlopen>
+                       <htmlclose>s</htmlclose>
+                       <allowedchildren>all^s</allowedchildren>
+               </bbcode>
+               <bbcode name="sub">
+                       <htmlopen>sub</htmlopen>
+                       <htmlclose>sub</htmlclose>
+                       <allowedchildren>all^sub</allowedchildren>
+               </bbcode>
+               <bbcode name="sup">
+                       <htmlopen>sup</htmlopen>
+                       <htmlclose>sup</htmlclose>
+                       <allowedchildren>all^sup</allowedchildren>
+               </bbcode>
+               <bbcode name="email">
+                       <htmlopen>a</htmlopen>
+                       <htmlclose>a</htmlclose>
+                       <allowedchildren>none^img,b,i,u,s,sub,sup,color,size,font</allowedchildren>
+                       <attributes>
+                               <attribute name="0">
+                                       <html>href="mailto:%s"</html>
+                                       <required>1</required>
+                                       <usetext>1</usetext>
+                                       <validationpattern>^[^\s]+@[^\s]+$</validationpattern>
+                               </attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="color">
+                       <htmlopen>span</htmlopen>
+                       <htmlclose>span</htmlclose>
+                       <attributes>
+                               <attribute name="0">
+                                       <html>style="color: %s"</html>
+                                       <validationpattern>^(#([0-9a-f]{3}|[0-9a-f]{6})|[a-z]+)$</validationpattern>
+                                       <required>1</required>
+                               </attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="size">
+                       <htmlopen>span</htmlopen>
+                       <htmlclose>span</htmlclose>
+                       <attributes>
+                               <attribute name="0">
+                                       <html>style="font-size: %dpt"</html>
+                                       <validationpattern>^([89]{1}|[1-3]{1}[0-9]{1})$</validationpattern>
+                                       <required>1</required>
+                               </attribute>
+                       </attributes>
+                       <wysiwyg>1</wysiwyg>
+               </bbcode>
+               <bbcode name="font">
+                       <htmlopen>span</htmlopen>
+                       <htmlclose>span</htmlclose>
+                       <attributes>
+                               <attribute name="0">
+                                       <html>style="font-family: %s"</html>
+                                       <validationpattern>^[^"';}\(\)]*$</validationpattern>
+                                       <required>1</required>
+                               </attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="align">
+                       <htmlopen>div</htmlopen>
+                       <htmlclose>div</htmlclose>
+                       <attributes>
+                               <attribute name="0">
+                                       <html>style="text-align: %s"</html>
+                                       <validationpattern>^(left|right|center|justify)$</validationpattern>
+                                       <required>1</required>
+                               </attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="quote">
+                       <classname>wcf\system\bbcode\QuoteBBCode</classname>
+                       <attributes>
+                               <attribute name="0"></attribute>
+                               <attribute name="1"></attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="code">
+                       <classname>wcf\system\bbcode\CodeBBCode</classname>
+                       <allowedchildren>none</allowedchildren>
+                       <attributes>
+                               <attribute name="0">
+                                       <validationpattern>^\d+$</validationpattern>
+                               </attribute>
+                       </attributes>
+                       <sourcecode>1</sourcecode>
+               </bbcode>
+               <bbcode name="img">
+                       <classname>wcf\system\bbcode\ImageBBCode</classname>
+                       <allowedchildren>none</allowedchildren>
+                       <attributes>
+                               <attribute name="0">
+                                       <required>1</required>
+                                       <usetext>1</usetext>
+                                       <validationpattern>^.+$</validationpattern>
+                               </attribute>
+                               <attribute name="1">
+                                       <validationpattern>^(left|right)$</validationpattern>
+                               </attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="url">
+                       <classname>wcf\system\bbcode\URLBBCode</classname>
+                       <allowedchildren>none^img,b,i,u,s,sub,sup,color,size,font</allowedchildren>
+                       <attributes>
+                               <attribute name="0">
+                                       <required>1</required>
+                                       <usetext>1</usetext>
+                                       <validationpattern>^.+$</validationpattern>
+                               </attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="list">
+                       <classname>wcf\system\bbcode\ListBBCode</classname>
+                       <attributes>
+                               <attribute name="0">
+                                       <validationpattern>^(1|a|none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)$</validationpattern>
+                               </attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="table">
+                       <classname>wcf\system\bbcode\TableBBCode</classname>
+                       <allowedchildren>all^table</allowedchildren>
+               </bbcode>
+               <bbcode name="media">
+                       <classname>wcf\system\bbcode\MediaBBCode</classname>
+                       <sourcecode>1</sourcecode>
+               </bbcode>
+               <bbcode name="spoiler">
+                       <classname>wcf\system\bbcode\SpoilerBBCode</classname>
+                       <attributes>
+                               <attribute name="0"></attribute>
+                       </attributes>
+               </bbcode>
+               <bbcode name="tt">
+                       <htmlopen>code class="inlineCode"</htmlopen>
+                       <htmlclose>code</htmlclose>
+                       <allowedchildren>none</allowedchildren>
+                       <sourcecode>1</sourcecode>
+               </bbcode>
+       </import>
+</data>
\ No newline at end of file
index 9d10bf19284915d5429dfef41e92c167e332df1a..ed880434dc28f7bcac2898e339b8951c769987fc 100644 (file)
@@ -22,5 +22,8 @@
                <coreobject>
                        <objectname><![CDATA[wcf\system\MetaTagHandler]]></objectname>
                </coreobject>
+               <coreobject>
+                       <objectname><![CDATA[wcf\system\bbcode\BBCodeHandler]]></objectname>
+               </coreobject>
        </import>
 </data>
index f3f26309ee1e52679625c8d99ac48d13dae28127..8efe83b66f2d77e63066eb6e2e7153514f241dbb 100644 (file)
                        <name>com.woltlab.wcf.collapsibleSidebar</name>
                        <definitionname>com.woltlab.wcf.collapsibleContent</definitionname>
                </type>
+               
+               <type>
+                       <name>com.woltlab.wcf.bbcode.smiley</name>
+                       <definitionname>com.woltlab.wcf.category</definitionname>
+                       <classname>wcf\system\category\SmileyCategoryType</classname>
+               </type>
        </import>
 </data>
\ No newline at end of file
index 927f8e04c40fd4180138a4b2d51cbcba17d030b6..e69fab03f18e7ad243bfcb17d69709f96d046ea4 100644 (file)
                                <defaultvalue>1</defaultvalue>
                        </option>
                        
+                       <option name="module_smiley">
+                               <categoryname>module.content</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+                       
                        <!-- general.page -->
                        <option name="page_title">
                                <categoryname>general.page</categoryname>
index b2c2660ac7efa4168700ad59310ec92e4ee0303a..cdb0e9888fc321df2b4114e0a6d895b6e8faade4 100644 (file)
@@ -29,6 +29,8 @@
                <instruction type="objectType">objectType.xml</instruction>
                <instruction type="acpSearchProvider">acpSearchProvider.xml</instruction>
                <instruction type="style">woltlab-basic-style.tgz</instruction>
+               <instruction type="bbcode">bbcode.xml</instruction>
+               <instruction type="smiley">smiley.xml</instruction> 
        </instructions>
        
        <instructions type="update" fromversion="2.0.0 Alpha 1">
index 6a6fb4c6b17629b89b1a84bfd80a8ea42584aecf..70f2846832d515388de4dd7a7a9c96686a694882 100644 (file)
@@ -23,5 +23,7 @@
                <pip name="sitemap">wcf\system\package\plugin\SitemapPackageInstallationPlugin</pip>
                <pip name="acpSearchProvider">wcf\system\package\plugin\ACPSearchProviderPackageInstallationPlugin</pip>
                <pip name="style">wcf\system\package\plugin\StylePackageInstallationPlugin</pip>
+               <pip name="bbcode">wcf\system\package\plugin\BBCodePackageInstallationPlugin</pip>
+               <pip name="smiley">wcf\system\package\plugin\SmileyPackageInstallationPlugin</pip>
        </import>
 </data>
diff --git a/com.woltlab.wcf/smiley.xml b/com.woltlab.wcf/smiley.xml
new file mode 100644 (file)
index 0000000..1871bc2
--- /dev/null
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/smiley.xsd">
+       <import>
+               <smiley name=":)">
+                       <title>smile</title>
+                       <path>images/smilies/smile.png</path>
+                       <aliases><![CDATA[:-)
+(:]]></aliases>
+               </smiley>
+               <smiley name=":(">
+                       <title>sad</title>
+                       <path>images/smilies/sad.png</path>
+                       <aliases><![CDATA[:-(
+):]]></aliases>
+               </smiley>
+               <smiley name=";)">
+                       <title>wink</title>
+                       <path>images/smilies/wink.png</path>
+                       <aliases><![CDATA[;-)
+(;]]></aliases>
+               </smiley>
+               <smiley name=":P">
+                       <title>tongue</title>
+                       <path>images/smilies/tongue.png</path>
+                       <aliases><![CDATA[:-P]]></aliases>
+               </smiley>
+               <smiley name="^^">
+                       <title>grin</title>
+                       <path>images/smilies/grin.png</path>
+                       <aliases><![CDATA[^.^
+^_^]]></aliases>
+               </smiley>
+               <smiley name=":D">
+                       <title>biggrin</title>
+                       <path>images/smilies/biggrin.png</path>
+                       <aliases><![CDATA[:-D]]></aliases>
+               </smiley>
+               <smiley name=";(">
+                       <title>crying</title>
+                       <path>images/smilies/crying.png</path>
+                       <aliases><![CDATA[;-(]]></aliases>
+               </smiley>
+               <smiley name="X(">
+                       <title>angry</title>
+                       <path>images/smilies/angry.png</path>
+               </smiley>
+               <smiley name=":*">
+                       <title>kiss</title>
+                       <path>images/smilies/kiss.png</path>
+                       <aliases><![CDATA[:-*
+*:]]></aliases>
+               </smiley>
+               <smiley name=":|">
+                       <title>mellow</title>
+                       <path>images/smilies/mellow.png</path>
+               </smiley>
+               <smiley name="8o">
+                       <title>w00t</title>
+                       <path>images/smilies/w00t.png</path>
+               </smiley>
+               <smiley name="=O">
+                       <title>scared</title>
+                       <path>images/smilies/scared.png</path>
+               </smiley>
+               <smiley name="&lt;X">
+                       <title>sick</title>
+                       <path>images/smilies/sick.png</path>
+               </smiley>
+               <smiley name="||">
+                       <title>tired</title>
+                       <path>images/smilies/tired.png</path>
+               </smiley>
+               <smiley name=":/">
+                       <title>unsure</title>
+                       <path>images/smilies/unsure.png</path>
+               </smiley>
+               <smiley name=":S">
+                       <title>wacko</title>
+                       <path>images/smilies/wacko.png</path>
+               </smiley>
+               <smiley name="X/">
+                       <title>pinch</title>
+                       <path>images/smilies/pinch.png</path>
+               </smiley>
+               <smiley name="8)">
+                       <title>cool</title>
+                       <path>images/smilies/cool.png</path>
+               </smiley>
+               <smiley name="?(">
+                       <title>confused</title>
+                       <path>images/smilies/confused.png</path>
+               </smiley>
+               <smiley name=":huh:">
+                       <title>Huh</title>
+                       <path>images/smilies/huh.png</path>
+               </smiley>
+               <smiley name=":rolleyes:">
+                       <title>rolleyes</title>
+                       <path>images/smilies/rolleyes.png</path>
+               </smiley>
+               <smiley name=":love:">
+                       <title>love</title>
+                       <path>images/smilies/love.png</path>
+               </smiley>
+               <smiley name="8|">
+                       <title>blink</title>
+                       <path>images/smilies/blink.png</path>
+               </smiley>
+               <smiley name=":cursing:">
+                       <title>cursing</title>
+                       <path>images/smilies/cursing.png</path>
+               </smiley>
+               <smiley name=":thumbdown:">
+                       <title>thumbdown</title>
+                       <path>images/smilies/thumbdown.png</path>
+               </smiley>
+               <smiley name=":thumbsup:">
+                       <title>thumbsup</title>
+                       <path>images/smilies/thumbsup.png</path>
+               </smiley>
+               <smiley name=":thumbup:">
+                       <title>thumbup</title>
+                       <path>images/smilies/thumbup.png</path>
+               </smiley>
+               <smiley name=":sleeping:">
+                       <title>sleeping</title>
+                       <path>images/smilies/sleeping.png</path>
+               </smiley>
+               <smiley name=":whistling:">
+                       <title>whistling</title>
+                       <path>images/smilies/whistling.png</path>
+               </smiley>
+               <smiley name=":evil:">
+                       <title>evil</title>
+                       <path>images/smilies/evil.png</path>
+               </smiley>
+               <smiley name=":saint:">
+                       <title>saint</title>
+                       <path>images/smilies/saint.png</path>
+               </smiley>
+               <smiley name="&lt;3">
+                       <title>heart</title>
+                       <path>images/smilies/heart.png</path>
+               </smiley>
+               <smiley name=":!:">
+                       <title>attention</title>
+                       <path>images/smilies/attention.png</path>
+               </smiley>
+               <smiley name=":?:">
+                       <title>question</title>
+                       <path>images/smilies/question.png</path>
+               </smiley>
+       </import>
+</data>
\ No newline at end of file
diff --git a/com.woltlab.wcf/template/codeBBCodeTag.tpl b/com.woltlab.wcf/template/codeBBCodeTag.tpl
new file mode 100644 (file)
index 0000000..d2f6f25
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="container codeBox {$highlighter|get_class|substr:30|lcfirst}">
+       <div>
+               <div>
+                       <h3>{@$highlighter->getTitle()}{if $filename}: {@$filename}{/if}</h3>
+               </div>
+               
+               <ol start="{$startLineNumber}">
+                       {assign var='lineNumber' value=$startLineNumber}
+                       {foreach from=$content item=line}
+                               {if $lineNumbers[$lineNumber]|isset}
+                                       <li id="{@$lineNumbers[$lineNumber]}"><a href="{@$__wcf->getAnchor($lineNumbers[$lineNumber])}" class="lineAnchor"></a>{@$line}</li>
+                               {else}
+                                       <li>{@$line}</li>
+                               {/if}
+                               
+                               {assign var='lineNumber' value=$lineNumber+1}
+                       {/foreach}
+               </ol>
+       </div>
+</div>
diff --git a/com.woltlab.wcf/template/quoteBBCodeTag.tpl b/com.woltlab.wcf/template/quoteBBCodeTag.tpl
new file mode 100644 (file)
index 0000000..133a7de
--- /dev/null
@@ -0,0 +1,23 @@
+<blockquote class="container containerPadding quoteBox"{if $quoteLink} cite="{$quoteLink}"{/if}>
+       {if $quoteAuthor}
+               <header>
+                       <h3>
+                               {if $quoteLink}
+                                       <a href="{@$quoteLink}">{lang}wcf.bbcode.quote.title{/lang}</a>
+                               {else}
+                                       {lang}wcf.bbcode.quote.title{/lang}
+                               {/if}
+                       </h3>
+               </header>
+       {else}
+               <header class="invisible">
+                       <h3>
+                               {lang}wcf.bbcode.quote{/lang}
+                       </h3>
+               </header>
+       {/if}
+       
+       <div>
+               {@$content}
+       </div>
+</blockquote>
\ No newline at end of file
diff --git a/com.woltlab.wcf/template/spoilerBBCodeTag.tpl b/com.woltlab.wcf/template/spoilerBBCodeTag.tpl
new file mode 100644 (file)
index 0000000..515e214
--- /dev/null
@@ -0,0 +1,25 @@
+<div class="container containerPadding spoilerBox jsSpoilerBox">
+       <!-- begin:parser_nonessential -->
+       <header class="jsOnly">
+               <a class="button jsSpoilerToggle">{if $buttonTitle}{$buttonTitle}{else}{lang}wcf.bbcode.spoiler.show{/lang}{/if}</a>
+       </header>
+       <!-- end:parser_nonessential -->
+       
+       <div>
+               {@$content}
+       </div>
+</div>
+
+<!-- begin:parser_nonessential -->
+<script type="text/javascript">
+       //<![CDATA[
+       $(function() {
+               var $spoilerBox = $('.jsSpoilerBox').removeClass('jsSpoilerBox');
+               $spoilerBox.children('div').hide();
+               $spoilerBox.find('> header > .jsSpoilerToggle').click(function() {
+                       $(this).toggleClass('active').parent().next().slideToggle();
+               });
+       });
+       //]]>
+</script>
+<!-- end:parser_nonessential -->
\ No newline at end of file
index 93aeacc00036d9fce0d199d86b7aac4b19ed6248..a9bc1f2dd884e4fa9bb86b38e380574f1c72ad93 100644 (file)
                        <category name="admin.attachment">
                                <parent>admin.content</parent>
                        </category>
+                       <category name="admin.content.bbcode">
+                               <parent>admin.content</parent>
+                       </category>
+                       <category name="admin.content.smiley">
+                               <parent>admin.content</parent>
+                       </category>
+                       
                        <category name="admin.community">
                                <parent>admin</parent>
                        </category>
@@ -264,6 +271,20 @@ pdf]]></defaultvalue>
                                <defaultvalue>0</defaultvalue>
                                <admindefaultvalue>1</admindefaultvalue>
                        </option>
+                       
+                       <option name="admin.content.bbcode.canManageBBCode">
+                               <categoryname>admin.content.bbcode</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                       </option>
+                       
+                       <option name="admin.content.smiley.canManageSmiley">
+                               <categoryname>admin.content.smiley</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                       </option>
                </options>
        </import>
 </data>
diff --git a/wcfsetup/install/files/acp/templates/bbCodeSelectOptionType.tpl b/wcfsetup/install/files/acp/templates/bbCodeSelectOptionType.tpl
new file mode 100644 (file)
index 0000000..73c9395
--- /dev/null
@@ -0,0 +1,3 @@
+{foreach from=$bbCodes item='bbCode'}
+       <label><input type="checkbox" name="values[{$option->optionName}][]" value="{$bbCode}" {if $bbCode|in_array:$selectedBBCodes}checked="checked" {/if} /> {$bbCode}</label>
+{/foreach}
\ No newline at end of file
diff --git a/wcfsetup/install/files/acp/templates/bbcodeAdd.tpl b/wcfsetup/install/files/acp/templates/bbcodeAdd.tpl
new file mode 100644 (file)
index 0000000..9f1f8cd
--- /dev/null
@@ -0,0 +1,270 @@
+{include file='header' pageTitle='wcf.acp.bbcode.'|concat:$action}
+
+{capture assign='attributeTemplate'}
+       <fieldset>
+               <legend><span class="icon icon16 icon-remove pointer jsDeleteButton jsTooltip" title="{lang}wcf.global.button.delete{/lang}"></span> <span>{lang}wcf.acp.bbcode.attribute{/lang} {ldelim}#$attributeNo}</span></legend>
+               <dl>
+                       <dt><label for="attributes[{ldelim}@$attributeNo}][attributeHtml]">{lang}wcf.acp.bbcode.attribute.attributeHtml{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="attributes[{ldelim}@$attributeNo}][attributeHtml]" name="attributes[{ldelim}@$attributeNo}][attributeHtml]" value="" class="long" />
+                       </dd>
+               </dl>
+               
+               <dl>
+                       <dt><label for="attributes[{ldelim}@$attributeNo}][validationPattern]">{lang}wcf.acp.bbcode.attribute.validationPattern{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="attributes[{ldelim}@$attributeNo}][validationPattern]" name="attributes[{ldelim}@$attributeNo}][validationPattern]" value="" class="long" />
+                       </dd>
+               </dl>
+               
+               <dl>
+                       <dd>
+                               <label for="attributes[{ldelim}@$attributeNo}][required]"><input type="checkbox" id="attributes[{ldelim}@$attributeNo}][required]" name="attributes[{ldelim}@$attributeNo}][required]" value="1" /> {lang}{lang}wcf.acp.bbcode.attribute.required{/lang}{/lang}</label>
+                       </dd>
+               </dl>
+               
+               <dl>
+                       <dd>
+                               <label for="attributes[{ldelim}@$attributeNo}][useText]"><input type="checkbox" id="attributes[{ldelim}@$attributeNo}][useText]" name="attributes[{ldelim}@$attributeNo}][useText]" value="1" /> {lang}{lang}wcf.acp.bbcode.attribute.useText{/lang}{/lang}</label>
+                               <small>{lang}wcf.acp.bbcode.attribute.useText.description{/lang}</small>
+                       </dd>
+               </dl>
+               
+               {event name='attributeFields'}
+       </fieldset>
+{/capture}
+<script type="text/javascript">
+//<![CDATA[
+       $(function() {
+               $('.jsDeleteButton').click(function (event) {
+                       $(event.target).parent().parent().remove();
+               });
+               
+               var attributeNo = {if !$attributes|count}0{else}{assign var='lastAttribute' value=$attributes|end}{$lastAttribute->attributeNo+1}{/if};
+               var attributeTemplate = new WCF.Template('{@$attributeTemplate|encodeJS}');
+               
+               $('.jsAddButton').click(function (event) {
+                       var $html = $($.parseHTML(attributeTemplate.fetch({ attributeNo: attributeNo++ })));
+                       $html.find('.jsDeleteButton').click(function (event) {
+                               $(event.target).parent().parent().remove();
+                       });
+                       $('#attributeFieldset').append($html);
+               });
+               
+               var $buttonSettings = $('.jsButtonSetting');
+               var $showButton = $('#showButton');
+               function toggleButtonSettings() {
+                       if ($showButton.is(':checked')) {
+                               $buttonSettings.show();
+                       }
+                       else {
+                               $buttonSettings.hide();
+                       }
+               }
+               
+               $showButton.change(toggleButtonSettings);
+               toggleButtonSettings();
+       });
+//]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.bbcode.{$action}{/lang}</h1>
+</header>
+
+{if $action == 'add'}
+       <p class="info">{lang}wcf.acp.bbcode.add.userGroupOptionInfo{/lang}</p>
+{/if}
+
+{if $errorField}
+       <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+       <nav>
+               <ul>
+                       <li><a href="{link controller='BBCodeList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.bbcode.list{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtons'}
+               </ul>
+       </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='BBCodeAdd'}{/link}{else}{link controller='BBCodeEdit' object=$bbcode}{/link}{/if}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.global.form.data{/lang}</legend>
+                       
+                       <dl{if $errorField == 'bbcodeTag'} class="formError"{/if}>
+                               <dt><label for="bbcodeTag">{lang}wcf.acp.bbcode.bbcodeTag{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="bbcodeTag" name="bbcodeTag" value="{$bbcodeTag}" required="required" autofocus="autofocus" pattern="^[a-zA-Z0-9]+$" class="medium" />
+                                       {if $errorField == 'bbcodeTag'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.bbcode.bbcodeTag.error.{$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'htmlOpen'} class="formError"{/if}>
+                               <dt><label for="htmlOpen">{lang}wcf.acp.bbcode.htmlOpen{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="htmlOpen" name="htmlOpen" value="{$htmlOpen}" class="long" />
+                                       {if $errorField == 'htmlOpen'}
+                                               <small class="innerError">{lang}wcf.acp.bbcode.htmlOpen.error.{$errorType}{/lang}</small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'htmlClose'} class="formError"{/if}>
+                               <dt><label for="htmlClose">{lang}wcf.acp.bbcode.htmlClose{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="htmlClose" name="htmlClose" value="{$htmlClose}" class="long" />
+                                       {if $errorField == 'htmlClose'}
+                                               <small class="innerError">{lang}wcf.acp.bbcode.htmlClose.error.{$errorType}{/lang}</small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'allowedChildren'} class="formError"{/if}>
+                               <dt><label for="allowedChildren">{lang}wcf.acp.bbcode.allowedChildren{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="allowedChildren" name="allowedChildren" value="{$allowedChildren}" class="long" required="required" pattern="^((all|none)\^)?([a-zA-Z0-9]+,)*[a-zA-Z0-9]+$" />
+                                       {if $errorField == 'allowedChildren'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.bbcode.allowedChildren.error.{$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dd>
+                                       <label for="isSourceCode"><input type="checkbox" id="isSourceCode" name="isSourceCode" value="1"{if $isSourceCode} checked="checked"{/if} /> {lang}wcf.acp.bbcode.isSourceCode{/lang}</label>
+                                       <small>{lang}wcf.acp.bbcode.isSourceCode.description{/lang}</small>
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'className'} class="formError"{/if}>
+                               <dt><label for="className">{lang}wcf.acp.bbcode.className{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="className" name="className" value="{$className}" class="long" pattern="^\\?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\)*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$" />
+                                       {if $errorField == 'className'}
+                                               <small class="innerError">{lang}wcf.acp.bbcode.className.error.{$errorType}{/lang}</small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       {if $nativeBBCode|empty}
+                               <dl>
+                                       <dd>
+                                               <label><input type="checkbox" id="showButton" name="showButton" value="1"{if $showButton} checked="checked"{/if} /> {lang}wcf.acp.bbcode.showButton{/lang}</label>
+                                       </dd>
+                               </dl>
+                               
+                               <dl class="jsButtonSetting{if $errorField == 'buttonLabel'} formError{/if}">
+                                       <dt><label for="buttonLabel">{lang}wcf.acp.bbcode.buttonLabel{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="buttonLabel" name="buttonLabel" value="{$i18nPlainValues['buttonLabel']}" class="long" />
+                                               {if $errorField == 'buttonLabel'}
+                                                       <small class="innerError">
+                                                               {if $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {else}
+                                                                       {lang}wcf.acp.bbcode.buttonLabel.error.{@$errorType}{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                               
+                                               {include file='multipleLanguageInputJavascript' elementIdentifier='buttonLabel' forceSelection=false}
+                                       </dd>
+                               </dl>
+                               
+                               <dl class="jsButtonSetting{if $errorField == 'wysiwygIcon'} formError{/if}">
+                                       <dt><label for="wysiwygIcon">{lang}wcf.acp.bbcode.wysiwygIcon{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="wysiwygIcon" name="wysiwygIcon" value="{$wysiwygIcon}" class="long" />
+                                               {if $errorField == 'wysiwygIcon'}
+                                                       <small class="innerError">
+                                                               {if $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {else}
+                                                                       {lang}wcf.acp.bbcode.wysiwygIcon.error.{@$errorType}{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                       </dd>
+                               </dl>
+                       {/if}
+                       
+                       {event name='dataFields'}
+               </fieldset>
+               
+               <fieldset id="attributeFieldset">
+                       <legend><span class="icon icon16 icon-plus pointer jsAddButton jsTooltip" title="{lang}wcf.global.button.add{/lang}"></span> {lang}wcf.acp.bbcode.attributes{/lang}</legend>
+                       
+                       {foreach from=$attributes item='attribute'}
+                               <fieldset>
+                                       <legend><span class="icon icon16 icon-remove pointer jsDeleteButton jsTooltip" title="{lang}wcf.global.button.delete{/lang}"></span> <span>{lang}wcf.acp.bbcode.attribute{/lang} {#$attribute->attributeNo}</span></legend>
+                                       
+                                       <dl{if $errorField == 'attributeHtml'|concat:$attribute->attributeNo} class="formError"{/if}>
+                                               <dt><label for="attributes[{@$attribute->attributeNo}][attributeHtml]">{lang}wcf.acp.bbcode.attribute.attributeHtml{/lang}</label></dt>
+                                               <dd>
+                                                       <input type="text" id="attributes[{@$attribute->attributeNo}][attributeHtml]" name="attributes[{@$attribute->attributeNo}][attributeHtml]" value="{$attribute->attributeHtml}" class="long" />
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl{if $errorField == 'attributeValidationPattern'|concat:$attribute->attributeNo} class="formError"{/if}>
+                                               <dt><label for="attributes[{@$attribute->attributeNo}][validationPattern]">{lang}wcf.acp.bbcode.attribute.validationPattern{/lang}</label></dt>
+                                               <dd>
+                                                       <input type="text" id="attributes[{@$attribute->attributeNo}][validationPattern]" name="attributes[{@$attribute->attributeNo}][validationPattern]" value="{$attribute->validationPattern}" class="long" />
+                                                       {if $errorField == 'attributeValidationPattern'|concat:$attribute->attributeNo}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'notValid'}
+                                                                               {lang}wcf.acp.bbcode.attribute.error.validationPattern.notValid{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl{if $errorField == 'attributeRequired'|concat:$attribute->attributeNo} class="formError"{/if}>
+                                               <dd>
+                                                       <label for="attributes[{@$attribute->attributeNo}][required]"><input type="checkbox" id="attributes[{@$attribute->attributeNo}][required]" name="attributes[{@$attribute->attributeNo}][required]" value="1"{if $attribute->required} checked="checked"{/if} /> {lang}wcf.acp.bbcode.attribute.required{/lang}</label>
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl{if $errorField == 'attributeUseText'|concat:$attribute->attributeNo} class="formError"{/if}>
+                                               <dd>
+                                                       <label for="attributes[{@$attribute->attributeNo}][useText]"><input type="checkbox" id="attributes[{@$attribute->attributeNo}][useText]" name="attributes[{@$attribute->attributeNo}][useText]" value="1"{if $attribute->useText} checked="checked"{/if} /> {lang}wcf.acp.bbcode.attribute.useText{/lang}</label>
+                                                       <small>{lang}wcf.acp.bbcode.attribute.useText.description{/lang}</small>
+                                               </dd>
+                                       </dl>
+                                       
+                                       {event name='attributeFields'}
+                               </fieldset>
+                       {/foreach}
+               </fieldset>
+               
+               {event name='fieldsets'}
+       </div>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+       </div>
+</form>
+
+{include file='footer'}
\ No newline at end of file
diff --git a/wcfsetup/install/files/acp/templates/bbcodeList.tpl b/wcfsetup/install/files/acp/templates/bbcodeList.tpl
new file mode 100644 (file)
index 0000000..1a75dcc
--- /dev/null
@@ -0,0 +1,82 @@
+{include file='header' pageTitle='wcf.acp.bbcode.list'}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.bbcode.list{/lang}</h1>
+       
+       <script type="text/javascript">
+               //<![CDATA[
+               $(function() {
+                       new WCF.Action.Delete('wcf\\data\\bbcode\\BBCodeAction', '.jsBBCodeRow');
+                       new WCF.Action.Toggle('wcf\\data\\bbcode\\BBCodeAction', $('.jsBBCodeRow'));
+               });
+               //]]>
+       </script>
+</header>
+
+<div class="contentNavigation">
+       {pages print=true assign=pagesLinks controller="BBCodeList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+       
+       <nav>
+               <ul>
+                       <li><a href="{link controller='BBCodeAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.bbcode.add{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+
+{if $objects|count}
+       <div class="tabularBox tabularBoxTitle marginTop">
+               <header>
+                       <h2>{lang}wcf.acp.bbcode.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+               </header>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID columnBBCodeID{if $sortField == 'bbcodeID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='BBCodeList'}pageNo={@$pageNo}&sortField=bbcodeID&sortOrder={if $sortField == 'bbcodeID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle columnBBCode{if $sortField == 'bbcodeTag'} active {@$sortOrder}{/if}"><a href="{link controller='BBCodeList'}pageNo={@$pageNo}&sortField=bbcodeTag&sortOrder={if $sortField == 'bbcodeTag' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.bbcode.bbcodeTag{/lang}</a></th>
+                                       <th class="columnText columnClassName{if $sortField == 'className'} active {@$sortOrder}{/if}"><a href="{link controller='BBCodeList'}pageNo={@$pageNo}&sortField=className&sortOrder={if $sortField == 'className' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.bbcode.className{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item=bbcode}
+                                       <tr class="jsBBCodeRow">
+                                               <td class="columnIcon">
+                                                       <span class="icon icon16 icon-{if $bbcode->isDisabled}off{else}circle-blank{/if} jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if $bbcode->isDisabled}enable{else}disable{/if}{/lang}" data-object-id="{@$bbcode->bbcodeID}" data-disable-message="{lang}wcf.global.button.disable{/lang}" data-enable-message="{lang}wcf.global.button.enable{/lang}"></span>
+                                                       <a href="{link controller='BBCodeEdit' object=$bbcode}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
+                                                       <span class="icon icon16 icon-remove jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$bbcode->bbcodeID}" data-confirm-message="{lang}wcf.acp.bbcode.delete.sure{/lang}"></span>
+                                                       
+                                                       {event name='rowButtons'}
+                                               </td>
+                                               <td class="columnID">{@$bbcode->bbcodeID}</td>
+                                               <td class="columnTitle columnBBCode"><a href="{link controller='BBCodeEdit' object=$bbcode}{/link}">[{$bbcode->bbcodeTag}]</a></td>
+                                               <td class="columnText columnClassName">{$bbcode->className}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+               
+       </div>
+       
+       <div class="contentNavigation">
+               {@$pagesLinks}
+               
+               <nav>
+                       <ul>
+                               <li><a href="{link controller='BBCodeAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.bbcode.add{/lang}</span></a></li>
+                               
+                               {event name='contentNavigationButtonsBottom'}
+                       </ul>
+               </nav>
+       </div>
+{else}
+       <p class="info">{lang}wcf.acp.bbcode.noneAvailable{/lang}</p>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/bbcodeMediaProviderAdd.tpl b/wcfsetup/install/files/acp/templates/bbcodeMediaProviderAdd.tpl
new file mode 100644 (file)
index 0000000..12bb93e
--- /dev/null
@@ -0,0 +1,91 @@
+{include file='header' pageTitle='wcf.acp.bbcode.mediaProvider.'|concat:$action}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.bbcode.mediaProvider.{$action}{/lang}</h1>
+</header>
+
+{if $errorField}
+       <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+       <nav>
+               <ul>
+                       <li><a href="{link controller='BBCodeMediaProviderList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.bbcode.mediaProvider.list{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtons'}
+               </ul>
+       </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='BBCodeMediaProviderAdd'}{/link}{else}{link controller='BBCodeMediaProviderEdit' object=$mediaProvider}{/link}{/if}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.global.form.data{/lang}</legend>
+                       
+                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                               <dt><label for="title">{lang}wcf.acp.bbcode.mediaProvider.title{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="title" name="title" value="{$title}" required="required" autofocus="autofocus" class="long" />
+                                       {if $errorField == 'title'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.bbcode.mediaProvider.title.error.{$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'regex'} class="formError"{/if}>
+                               <dt><label for="regex">{lang}wcf.acp.bbcode.mediaProvider.regex{/lang}</label></dt>
+                               <dd>
+                                       <textarea id="regex" name="regex" cols="40" rows="10" required="required">{$regex}</textarea>
+                                       {if $errorField == 'regex'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.bbcode.mediaProvider.regex.error.{$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                                       <small>{lang}wcf.acp.bbcode.mediaProvider.regex.description{/lang}</small>
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'html'} class="formError"{/if}>
+                               <dt><label for="html">{lang}wcf.acp.bbcode.mediaProvider.html{/lang}</label></dt>
+                               <dd>
+                                       <textarea id="html" name="html" cols="40" rows="10" required="required">{$html}</textarea>
+                                       {if $errorField == 'html'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.bbcode.mediaProvider.html.error.{$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                                       <small>{lang}wcf.acp.bbcode.mediaProvider.html.description{/lang}</small>
+                               </dd>
+                       </dl>
+                       
+                       {event name='dataFields'}
+               </fieldset>
+               
+               {event name='fieldsets'}
+       </div>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+       </div>
+</form>
+
+{include file='footer'}
\ No newline at end of file
diff --git a/wcfsetup/install/files/acp/templates/bbcodeMediaProviderList.tpl b/wcfsetup/install/files/acp/templates/bbcodeMediaProviderList.tpl
new file mode 100644 (file)
index 0000000..fbfb184
--- /dev/null
@@ -0,0 +1,81 @@
+{include file='header' pageTitle='wcf.acp.bbcode.mediaProvider.list'}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.bbcode.mediaProvider.list{/lang}</h1>
+       
+       <script type="text/javascript">
+               //<![CDATA[
+               $(function() {
+                       new WCF.Action.Delete('wcf\\data\\bbcode\\media\\provider\\BBCodeMediaProviderAction', '.jsMediaProviderRow');
+               });
+               //]]>
+       </script>
+</header>
+
+<div class="contentNavigation">
+       {pages print=true assign=pagesLinks controller="BBCodeMediaProviderList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+       
+       <nav>
+               <ul>
+                       <li><a href="{link controller='BBCodeMediaProviderAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.bbcode.mediaProvider.add{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+
+{if $objects|count}
+       <div class="tabularBox tabularBoxTitle marginTop">
+               <header>
+                       <h2>{lang}wcf.acp.bbcode.mediaProvider.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+               </header>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID columnMediaProviderID{if $sortField == 'providerID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='BBCodeMediaProviderList'}pageNo={@$pageNo}&sortField=providerID&sortOrder={if $sortField == 'providerID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle columnMediaProviderTitle{if $sortField == 'title'} active {@$sortOrder}{/if}"><a href="{link controller='BBCodeMediaProviderList'}pageNo={@$pageNo}&sortField=title&sortOrder={if $sortField == 'title' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.bbcode.mediaProvider.title{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item='mediaProvider'}
+                                       <tr class="jsMediaProviderRow">
+                                               <td class="columnIcon">
+                                                       <a href="{link controller='BBCodeMediaProviderEdit' object=$mediaProvider}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
+                                                       <span class="icon icon16 icon-remove jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$mediaProvider->providerID}" data-confirm-message="{lang}wcf.acp.bbcode.mediaProvider.delete.sure{/lang}"></span>
+                                                       
+                                                       {event name='rowButtons'}
+                                               </td>
+                                               <td class="columnID">{@$mediaProvider->providerID}</td>
+                                               <td class="columnTitle columnMediaProviderTitle"><a href="{link controller='BBCodeMediaProviderEdit' object=$mediaProvider}{/link}">{$mediaProvider->title}</a></td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+       </div>
+       
+       <div class="contentNavigation">
+               {@$pagesLinks}
+               
+               <nav>
+                       <ul>
+                               <li><a href="{link controller='BBCodeMediaProviderAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.bbcode.mediaProvider.add{/lang}</span></a></li>
+                               
+                               {event name='contentNavigationButtonsBottom'}
+                       </ul>
+               </nav>
+       </div>
+{else}
+       <div class="container containerPadding">
+               <div>
+                       <p class="warning">{lang}wcf.acp.bbcode.mediaProvider.noneAvailable{/lang}</p>
+               </div>
+       </div>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/smileyAdd.tpl b/wcfsetup/install/files/acp/templates/smileyAdd.tpl
new file mode 100644 (file)
index 0000000..fd17355
--- /dev/null
@@ -0,0 +1,120 @@
+{include file='header' pageTitle='wcf.acp.smiley.'|concat:$action}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.smiley.{$action}{/lang}</h1>
+</header>
+
+{if $errorField}
+       <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.form.{$action}.success{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+       {hascontent}
+               <nav>
+                       <ul>
+                               {content}
+                                       <li><a href="{link controller='SmileyList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.smiley.list{/lang}</span></a></li>
+                                       {event name='contentNavigationButtons'}
+                               {/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='SmileyAdd'}{/link}{else}{link controller='SmileyEdit' id=$smiley->smileyID}{/link}{/if}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.global.form.data{/lang}</legend>
+                       
+                       <dl{if $errorField == 'smileyTitle'} class="formError"{/if}>
+                               <dt><label for="smileyTitle">{lang}wcf.acp.smiley.title{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="smileyTitle" name="smileyTitle" value="{$smileyTitle}" autofocus="autofocus" class="long" />
+                                       {if $errorField == 'smileyTitle'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.smiley.smileyTitle.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       {include file='multipleLanguageInputJavascript' elementIdentifier='smileyTitle' forceSelection=false}
+                       
+                       <dl{if $errorField == 'categoryID'} class="formError"{/if}>
+                               <dt><label for="categoryID">{lang}wcf.acp.smiley.categoryID{/lang}</label></dt>
+                               <dd>
+                                       <select id="categoryID" name="categoryID">
+                                               <option value="0"{if $categoryID === null} selected="selected"{/if}>{lang}wcf.acp.smiley.categoryID.default{/lang}</option>
+                                               {include file='categoryOptionList' maximumNestingLevel=0}
+                                       </select>
+                                       
+                                       {if $errorField == 'categoryID'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.smiley.categoryID.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'smileyCode'} class="formError"{/if}>
+                               <dt><label for="smileyCode">{lang}wcf.acp.smiley.smileyCode{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="smileyCode" name="smileyCode" value="{$smileyCode}" required="required" class="medium" />
+                                       {if $errorField == 'smileyCode'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.smiley.smileyCode.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'aliases'} class="formError"{/if}>
+                               <dt><label for="aliases">{lang}wcf.acp.smiley.aliases{/lang}</label></dt>
+                               <dd>
+                                       <textarea id="aliases" name="aliases" cols="40" rows="10">{$aliases}</textarea>
+                                       
+                                       {if $errorField == 'aliases'}
+                                               <small class="innerError">
+                                                       {lang}wcf.acp.smiley.smileyCode.error.{@$errorType}{/lang}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'showOrder'} class="formError"{/if}>
+                               <dt><label for="showOrder">{lang}wcf.acp.smiley.showOrder{/lang}</label></dt>
+                               <dd>
+                                       <input type="number" id="showOrder" name="showOrder" value="{$showOrder}" min="0" class="short" />
+                                       
+                                       {if $errorField == 'showOrder'}
+                                               <small class="innerError">
+                                                       {lang}wcf.acp.smiley.smileyCode.error.{@$errorType}{/lang}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+               </fieldset>
+       </div>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+       </div>
+</form>
+
+{include file='footer'}
\ No newline at end of file
diff --git a/wcfsetup/install/files/acp/templates/smileyList.tpl b/wcfsetup/install/files/acp/templates/smileyList.tpl
new file mode 100644 (file)
index 0000000..a9a543a
--- /dev/null
@@ -0,0 +1,69 @@
+{include file='header' pageTitle='wcf.acp.smiley.list'}
+
+{if $objects|count}
+       <script type="text/javascript">
+               //<![CDATA[
+               $(function() {
+                       new WCF.Action.Delete('wcf\\data\\smiley\\SmileyAction', $('.smileyRow'));
+                       new WCF.Sortable.List('smileyList', 'wcf\\data\\smiley\\SmileyAction', {@$startIndex});
+               });
+               //]]>
+       </script>
+{/if}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.smiley.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+       {pages print=true assign=pagesLinks controller="SmileyList" object=$category link="pageNo=%d"}
+       
+       <nav>
+               <ul>
+                       <li><a href="{link controller='SmileyAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.smiley.add{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+{if $smileyCount}
+       <div class="tabMenuContainer container containerPadding marginTop">
+               <nav class="menu">
+                       <ul>
+                               {foreach from=$categories item=categoryLoop}
+                                       <li{if (!$category && !$categoryLoop->categoryID) || ($category && $category->categoryID == $categoryLoop->categoryID)} class="ui-state-active"{/if}><a href="{if $categoryLoop->categoryID}{link controller='SmileyList' object=$categoryLoop}{/link}{else}{link controller='SmileyList'}{/link}{/if}">{$categoryLoop->title|language}</a></li>
+                               {/foreach}
+                       </ul>
+               </nav>
+               <section id="smileyList" class="sortableListContainer">
+                       {if $objects|count}
+                               <ol class="sortableList" data-object-id="0">
+                                       {foreach from=$objects item=smiley}
+                                               <li class="sortableNode sortableNoNesting smileyRow" data-object-id="{@$smiley->smileyID}">
+                                                       <span class="sortableNodeLabel">
+                                                               <a href="{link controller='SmileyEdit' id=$smiley->smileyID}{/link}"><img src="{$smiley->getURL()}" alt=""/ > {lang}{$smiley->smileyTitle}{/lang}</a> <span class="badge">{$smiley->smileyCode}</span>{foreach from=$smiley->getAliases() item='alias'} <span class="badge">{$alias}</span>{/foreach}
+                                                               
+                                                               <span class="statusDisplay sortableButtonContainer">
+                                                                       <a href="{link controller='SmileyEdit' id=$smiley->smileyID}{/link}"><span title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip icon icon16 icon-pencil" /></a>
+                                                                       <span title="{lang}wcf.global.button.delete{/lang}" class="jsDeleteButton jsTooltip icon icon16 icon-remove" data-object-id="{@$smiley->smileyID}" data-confirm-message="{lang}wcf.acp.smiley.delete.sure{/lang}" />
+                                                                       
+                                                                       {event name='itemButtons'}
+                                                               </span>
+                                                       </span>
+                                                       <ol class="sortableList" data-object-id="{@$smiley->smileyID}"></ol></li>
+                                               </li>
+                                       {/foreach}
+                               </ol>
+                               <div class="formSubmit">
+                                       <button class="button" data-type="submit">{lang}wcf.global.button.submit{/lang}</button>
+                               </div>
+                       {else}
+                               <p class="info">{lang}wcf.acp.smiley.category.noItems{/lang}</p>
+                       {/if}
+               </section>
+       </div>
+{else}
+       <p class="warning">{lang}wcf.acp.smiley.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/images/smilies/angry.png b/wcfsetup/install/files/images/smilies/angry.png
new file mode 100644 (file)
index 0000000..e16d826
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/angry.png differ
diff --git a/wcfsetup/install/files/images/smilies/attention.png b/wcfsetup/install/files/images/smilies/attention.png
new file mode 100644 (file)
index 0000000..e75f040
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/attention.png differ
diff --git a/wcfsetup/install/files/images/smilies/biggrin.png b/wcfsetup/install/files/images/smilies/biggrin.png
new file mode 100644 (file)
index 0000000..3772ea7
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/biggrin.png differ
diff --git a/wcfsetup/install/files/images/smilies/blink.png b/wcfsetup/install/files/images/smilies/blink.png
new file mode 100644 (file)
index 0000000..d566137
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/blink.png differ
diff --git a/wcfsetup/install/files/images/smilies/confused.png b/wcfsetup/install/files/images/smilies/confused.png
new file mode 100644 (file)
index 0000000..1a42f19
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/confused.png differ
diff --git a/wcfsetup/install/files/images/smilies/cool.png b/wcfsetup/install/files/images/smilies/cool.png
new file mode 100644 (file)
index 0000000..727de81
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/cool.png differ
diff --git a/wcfsetup/install/files/images/smilies/crying.png b/wcfsetup/install/files/images/smilies/crying.png
new file mode 100644 (file)
index 0000000..05aea3d
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/crying.png differ
diff --git a/wcfsetup/install/files/images/smilies/cursing.png b/wcfsetup/install/files/images/smilies/cursing.png
new file mode 100644 (file)
index 0000000..591bb65
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/cursing.png differ
diff --git a/wcfsetup/install/files/images/smilies/evil.png b/wcfsetup/install/files/images/smilies/evil.png
new file mode 100644 (file)
index 0000000..bf81060
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/evil.png differ
diff --git a/wcfsetup/install/files/images/smilies/grin.png b/wcfsetup/install/files/images/smilies/grin.png
new file mode 100644 (file)
index 0000000..8950626
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/grin.png differ
diff --git a/wcfsetup/install/files/images/smilies/heart.png b/wcfsetup/install/files/images/smilies/heart.png
new file mode 100644 (file)
index 0000000..4aa3120
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/heart.png differ
diff --git a/wcfsetup/install/files/images/smilies/huh.png b/wcfsetup/install/files/images/smilies/huh.png
new file mode 100644 (file)
index 0000000..ef3f6d9
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/huh.png differ
diff --git a/wcfsetup/install/files/images/smilies/kiss.png b/wcfsetup/install/files/images/smilies/kiss.png
new file mode 100644 (file)
index 0000000..e7bf282
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/kiss.png differ
diff --git a/wcfsetup/install/files/images/smilies/love.png b/wcfsetup/install/files/images/smilies/love.png
new file mode 100644 (file)
index 0000000..aa9dd52
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/love.png differ
diff --git a/wcfsetup/install/files/images/smilies/mellow.png b/wcfsetup/install/files/images/smilies/mellow.png
new file mode 100644 (file)
index 0000000..78c4c9f
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/mellow.png differ
diff --git a/wcfsetup/install/files/images/smilies/pinch.png b/wcfsetup/install/files/images/smilies/pinch.png
new file mode 100644 (file)
index 0000000..171f28a
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/pinch.png differ
diff --git a/wcfsetup/install/files/images/smilies/question.png b/wcfsetup/install/files/images/smilies/question.png
new file mode 100644 (file)
index 0000000..95bd6cb
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/question.png differ
diff --git a/wcfsetup/install/files/images/smilies/rolleyes.png b/wcfsetup/install/files/images/smilies/rolleyes.png
new file mode 100644 (file)
index 0000000..2e68be7
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/rolleyes.png differ
diff --git a/wcfsetup/install/files/images/smilies/sad.png b/wcfsetup/install/files/images/smilies/sad.png
new file mode 100644 (file)
index 0000000..17f9feb
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/sad.png differ
diff --git a/wcfsetup/install/files/images/smilies/saint.png b/wcfsetup/install/files/images/smilies/saint.png
new file mode 100644 (file)
index 0000000..f5fc8cc
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/saint.png differ
diff --git a/wcfsetup/install/files/images/smilies/scared.png b/wcfsetup/install/files/images/smilies/scared.png
new file mode 100644 (file)
index 0000000..62a6cae
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/scared.png differ
diff --git a/wcfsetup/install/files/images/smilies/sick.png b/wcfsetup/install/files/images/smilies/sick.png
new file mode 100644 (file)
index 0000000..2ef69f5
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/sick.png differ
diff --git a/wcfsetup/install/files/images/smilies/sleeping.png b/wcfsetup/install/files/images/smilies/sleeping.png
new file mode 100644 (file)
index 0000000..59897d1
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/sleeping.png differ
diff --git a/wcfsetup/install/files/images/smilies/smile.png b/wcfsetup/install/files/images/smilies/smile.png
new file mode 100644 (file)
index 0000000..34e0a9c
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/smile.png differ
diff --git a/wcfsetup/install/files/images/smilies/thumbdown.png b/wcfsetup/install/files/images/smilies/thumbdown.png
new file mode 100644 (file)
index 0000000..b46bc4a
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/thumbdown.png differ
diff --git a/wcfsetup/install/files/images/smilies/thumbsup.png b/wcfsetup/install/files/images/smilies/thumbsup.png
new file mode 100644 (file)
index 0000000..6f64862
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/thumbsup.png differ
diff --git a/wcfsetup/install/files/images/smilies/thumbup.png b/wcfsetup/install/files/images/smilies/thumbup.png
new file mode 100644 (file)
index 0000000..1ac0eb1
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/thumbup.png differ
diff --git a/wcfsetup/install/files/images/smilies/tired.png b/wcfsetup/install/files/images/smilies/tired.png
new file mode 100644 (file)
index 0000000..cf463ec
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/tired.png differ
diff --git a/wcfsetup/install/files/images/smilies/tongue.png b/wcfsetup/install/files/images/smilies/tongue.png
new file mode 100644 (file)
index 0000000..68f91ab
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/tongue.png differ
diff --git a/wcfsetup/install/files/images/smilies/unsure.png b/wcfsetup/install/files/images/smilies/unsure.png
new file mode 100644 (file)
index 0000000..2abb2be
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/unsure.png differ
diff --git a/wcfsetup/install/files/images/smilies/w00t.png b/wcfsetup/install/files/images/smilies/w00t.png
new file mode 100644 (file)
index 0000000..4a129ca
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/w00t.png differ
diff --git a/wcfsetup/install/files/images/smilies/wacko.png b/wcfsetup/install/files/images/smilies/wacko.png
new file mode 100644 (file)
index 0000000..a30bdf4
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/wacko.png differ
diff --git a/wcfsetup/install/files/images/smilies/whistling.png b/wcfsetup/install/files/images/smilies/whistling.png
new file mode 100644 (file)
index 0000000..f70f532
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/whistling.png differ
diff --git a/wcfsetup/install/files/images/smilies/wink.png b/wcfsetup/install/files/images/smilies/wink.png
new file mode 100644 (file)
index 0000000..87ebba3
Binary files /dev/null and b/wcfsetup/install/files/images/smilies/wink.png differ
diff --git a/wcfsetup/install/files/lib/acp/form/BBCodeAddForm.class.php b/wcfsetup/install/files/lib/acp/form/BBCodeAddForm.class.php
new file mode 100644 (file)
index 0000000..fdee91d
--- /dev/null
@@ -0,0 +1,287 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\bbcode\attribute\BBCodeAttributeAction;
+use wcf\data\bbcode\BBCode;
+use wcf\data\bbcode\BBCodeAction;
+use wcf\data\bbcode\BBCodeEditor;
+use wcf\data\package\PackageCache;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\Regex;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the bbcode add form.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class BBCodeAddForm extends AbstractForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.bbcode.add';
+       
+       /**
+        * allowed child bbcodes
+        * @var string
+        */
+       public $allowedChildren = 'all';
+       
+       /**
+        * list of attributes
+        * @var array<object>
+        */
+       public $attributes = array();
+       
+       /**
+        * tag name
+        * @var string
+        */
+       public $bbcodeTag = '';
+       
+       /**
+        * editor button label
+        * @var string
+        */
+       public $buttonLabel = '';
+       
+       /**
+        * class name
+        * @var string
+        */
+       public $className = '';
+       
+       /**
+        * closing html tag
+        * @var string
+        */
+       public $htmlClose = '';
+       
+       /**
+        * opening html tag
+        * @var string
+        */
+       public $htmlOpen = '';
+       
+       /**
+        * true, if bbcode contains source code
+        * @var boolean
+        */
+       public $isSourceCode = false;
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * @see wcf\page\AbstractPage::$templateName
+        */
+       public $templateName = 'bbcodeAdd';
+       
+       /**
+        * show editor button
+        * @var boolean
+        */
+       public $showButton = false;
+       
+       /**
+        * wysiwyg editor icon
+        * @var string
+        */
+       public $wysiwygIcon = '';
+       
+       /**
+        * @see wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               I18nHandler::getInstance()->register('buttonLabel');
+       }
+       
+       /**
+        * @see wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['allowedChildren'])) $this->allowedChildren = StringUtil::trim($_POST['allowedChildren']);
+               if (isset($_POST['attributes'])) $this->attributes = $_POST['attributes'];
+               if (isset($_POST['bbcodeTag'])) $this->bbcodeTag = StringUtil::trim($_POST['bbcodeTag']);
+               if (isset($_POST['className'])) $this->className = StringUtil::trim($_POST['className']);
+               if (isset($_POST['htmlClose'])) $this->htmlClose = StringUtil::trim($_POST['htmlClose']);
+               if (isset($_POST['htmlOpen'])) $this->htmlOpen = StringUtil::trim($_POST['htmlOpen']);
+               if (isset($_POST['isSourceCode'])) $this->isSourceCode = true;
+               if (isset($_POST['showButton'])) $this->showButton = true;
+               if (isset($_POST['wysiwygIcon'])) $this->wysiwygIcon = StringUtil::trim($_POST['wysiwygIcon']);
+               
+               // TODO: The code below violates every implicit convention of value reading and type casting
+               $attributeNo = 0;
+               foreach ($this->attributes as $key => $val) {
+                       $val['attributeNo'] = $attributeNo++;
+                       $val['required'] = (int) isset($val['required']);
+                       $val['useText'] = (int) isset($val['useText']);
+                       $this->attributes[$key] = (object) $val;
+               }
+               
+               I18nHandler::getInstance()->readValues();
+               if (I18nHandler::getInstance()->isPlainValue('buttonLabel')) $this->buttonLabel = I18nHandler::getInstance()->getValue('buttonLabel');
+       }
+       
+       /**
+        * @see wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               // tag name must not be empty
+               if (empty($this->bbcodeTag)) {
+                       throw new UserInputException('bbcodeTag');
+               }
+               
+               // tag may only contain alphanumeric chars
+               if (!Regex::compile('^[a-z0-9]+$', Regex::CASE_INSENSITIVE)->match($this->bbcodeTag)) {
+                       throw new UserInputException('bbcodeTag', 'notValid');
+               }
+               
+               // disallow the Pseudo-BBCodes all and none
+               if ($this->bbcodeTag == 'all' || $this->bbcodeTag == 'none') {
+                       throw new UserInputException('bbcodeTag', 'notValid');
+               }
+               
+               // check whether the tag is in use
+               $bbcode = BBCode::getBBCodeByTag($this->bbcodeTag);
+               if ((!isset($this->bbcode) && $bbcode->bbcodeID) || (isset($this->bbcode) && $bbcode->bbcodeID != $this->bbcode->bbcodeID)) {
+                       throw new UserInputException('bbcodeTag', 'inUse');
+               }
+               
+               // handle empty case first
+               if (empty($this->allowedChildren)) {
+                       throw new UserInputException('allowedChildren');
+               }
+               
+               // validate syntax of allowedChildren: Optional all|none^ followed by a comma-separated list of bbcodes
+               if (!empty($this->allowedChildren) && !Regex::compile('^(?:(?:all|none)\^)?(?:[a-zA-Z0-9]+,)*[a-zA-Z0-9]+$')->match($this->allowedChildren)) {
+                       throw new UserInputException('allowedChildren', 'notValid');
+               }
+               
+               // validate class
+               if (!empty($this->className) && !class_exists($this->className)) {
+                       throw new UserInputException('className', 'notFound');
+               }
+               
+               // validate attributes
+               foreach ($this->attributes as $attribute) {
+                       // Check whether the pattern is a valid regex
+                       if (!Regex::compile($attribute->validationPattern)->isValid()) {
+                               throw new UserInputException('attributeValidationPattern'.$attribute->attributeNo, 'notValid');
+                       }
+               }
+               
+               // button
+               if ($this->showButton) {
+                       // validate label
+                       if (!I18nHandler::getInstance()->validateValue('buttonLabel')) {
+                               throw new UserInputException('buttonLabel');
+                       }
+                       
+                       // validate image path
+                       if (empty($this->wysiwygIcon)) {
+                               throw new UserInputException('wysiwygIcon');
+                       }
+               }
+               else {
+                       $this->buttonLabel = '';
+               }
+       }
+       
+       /**
+        * @see wcf\form\IForm::save()
+        */
+       public function save() {
+               parent::save();
+               
+               // save bbcode
+               $this->objectAction = new BBCodeAction(array(), 'create', array('data' => array(
+                       'allowedChildren' => $this->allowedChildren,
+                       'bbcodeTag' => $this->bbcodeTag,
+                       'buttonLabel' => $this->buttonLabel,
+                       'className' => $this->className,
+                       'htmlOpen' => $this->htmlOpen,
+                       'htmlClose' => $this->htmlClose,
+                       'isSourceCode' => ($this->isSourceCode ? 1 : 0),
+                       'packageID' => PackageCache::getInstance()->getPackageID('com.woltlab.wcf.bbcode'),
+                       'showButton' => ($this->showButton ? 1 : 0),
+                       'wysiwygIcon' => $this->wysiwygIcon
+               )));
+               $returnValues = $this->objectAction->executeAction();
+               foreach ($this->attributes as $attribute) {
+                       $attributeAction = new BBCodeAttributeAction(array(), 'create', array('data' => array(
+                               'bbcodeID' => $returnValues['returnValues']->bbcodeID,
+                               'attributeNo' => $attribute->attributeNo,
+                               'attributeHtml' => $attribute->attributeHtml,
+                               'validationPattern' => $attribute->validationPattern,
+                               'required' => $attribute->required,
+                               'useText' => $attribute->useText,
+                       )));
+                       $attributeAction->executeAction();
+               }
+               
+               if ($this->showButton && !I18nHandler::getInstance()->isPlainValue('buttonLabel')) {
+                       $bbcodeID = $returnValues['returnValues']->bbcodeID;
+                       I18nHandler::getInstance()->save('buttonLabel', 'wcf.bbcode.buttonLabel'.$bbcodeID, 'wcf.bbcode', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.bbcode'));
+                       
+                       // update button label
+                       $bbcodeEditor = new BBCodeEditor($returnValues['returnValues']);
+                       $bbcodeEditor->update(array(
+                               'buttonLabel' => 'wcf.bbcode.buttonLabel'.$bbcodeID
+                       ));
+               }
+               
+               $this->saved();
+               
+               // reset values
+               $this->bbcodeTag = $this->htmlOpen = $this->htmlClose = $this->className = $this->buttonLabel = $this->wysiwygIcon = '';
+               $this->allowedChildren = 'all';
+               $this->attributes = array();
+               $this->isSourceCode = $this->showButton = false;
+               
+               I18nHandler::getInstance()->reset();
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'add',
+                       'allowedChildren' => $this->allowedChildren,
+                       'attributes' => $this->attributes,
+                       'bbcodeTag' => $this->bbcodeTag,
+                       'buttonLabel' => $this->buttonLabel,
+                       'className' => $this->className,
+                       'htmlOpen' => $this->htmlOpen,
+                       'htmlClose' => $this->htmlClose,
+                       'isSourceCode' => $this->isSourceCode,
+                       'showButton' => $this->showButton,
+                       'wysiwygIcon' => $this->wysiwygIcon
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/BBCodeEditForm.class.php b/wcfsetup/install/files/lib/acp/form/BBCodeEditForm.class.php
new file mode 100644 (file)
index 0000000..66c9a52
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\bbcode\attribute\BBCodeAttribute;
+use wcf\data\bbcode\attribute\BBCodeAttributeAction;
+use wcf\data\bbcode\BBCode;
+use wcf\data\bbcode\BBCodeAction;
+use wcf\data\package\PackageCache;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the bbcode edit form.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class BBCodeEditForm extends BBCodeAddForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.bbcode';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * bbcode id
+        * @var integer
+        */
+       public $bbcodeID = 0;
+       
+       /**
+        * bbcode object
+        * @var wcf\data\bbcode\BBCode
+        */
+       public $bbcode = null;
+       
+       /**
+        * list of native bbcodes
+        * @var array<string>
+        */
+       public static $nativeBBCodes = array('b', 'i', 'u', 's', 'sub', 'sup', 'list', 'align', 'color', 'size', 'font', 'url', 'img', 'email', 'table');
+       
+       /**
+        * @see wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->bbcodeID = intval($_REQUEST['id']);
+               $this->bbcode = new BBCode($this->bbcodeID);
+               if (!$this->bbcode->bbcodeID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @see wcf\form\IForm::save()
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               if ($this->showButton) {
+                       $this->buttonLabel = 'wcf.bbcode.buttonLabel'.$this->bbcode->bbcodeID;
+                       if (I18nHandler::getInstance()->isPlainValue('buttonLabel')) {
+                               I18nHandler::getInstance()->remove($this->buttonLabel);
+                               $this->buttonLabel = I18nHandler::getInstance()->getValue('buttonLabel');
+                       }
+                       else {
+                               I18nHandler::getInstance()->save('buttonLabel', $this->buttonLabel, 'wcf.bbcode', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.bbcode'));
+                       }
+               }
+               
+               // update bbcode
+               $this->objectAction = new BBCodeAction(array($this->bbcodeID), 'update', array('data' => array(
+                       'allowedChildren' => $this->allowedChildren,
+                       'bbcodeTag' => $this->bbcodeTag,
+                       'buttonLabel' => $this->buttonLabel,
+                       'className' => $this->className,
+                       'htmlClose' => $this->htmlClose,
+                       'htmlOpen' => $this->htmlOpen,
+                       'isSourceCode' => ($this->isSourceCode ? 1 : 0),
+                       'showButton' => ($this->showButton ? 1 : 0),
+                       'wysiwygIcon' => $this->wysiwygIcon
+               )));
+               $this->objectAction->executeAction();
+               
+               // clear existing attributes
+               $sql = "DELETE FROM     wcf".WCF_N."_bbcode_attribute
+                       WHERE           bbcodeID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($this->bbcodeID));
+               
+               foreach ($this->attributes as $attribute) {
+                       $attributeAction = new BBCodeAttributeAction(array(), 'create', array('data' => array(
+                               'bbcodeID' => $this->bbcodeID,
+                               'attributeNo' => $attribute->attributeNo,
+                               'attributeHtml' => $attribute->attributeHtml,
+                               'validationPattern' => $attribute->validationPattern,
+                               'required' => $attribute->required,
+                               'useText' => $attribute->useText,
+                       )));
+                       $attributeAction->executeAction();
+               }
+               
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       I18nHandler::getInstance()->setOptions('buttonLabel', 1, $this->bbcode->buttonLabel, 'wcf.bbcode.buttonLabel\d+');
+                       $this->buttonLabel = $this->bbcode->buttonLabel;
+                       
+                       $this->attributes = BBCodeAttribute::getAttributesByBBCode($this->bbcode);
+                       $this->bbcodeTag = $this->bbcode->bbcodeTag;
+                       $this->htmlOpen = $this->bbcode->htmlOpen;
+                       $this->htmlClose = $this->bbcode->htmlClose;
+                       $this->allowedChildren = $this->bbcode->allowedChildren;
+                       $this->isSourceCode = $this->bbcode->isSourceCode;
+                       $this->className = $this->bbcode->className;
+                       $this->showButton = $this->bbcode->showButton;
+                       $this->wysiwygIcon = $this->bbcode->wysiwygIcon;
+               }
+       }
+       
+       /**
+        * @see wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables(!empty($_POST));
+               
+               WCF::getTPL()->assign(array(
+                       'bbcode' => $this->bbcode,
+                       'action' => 'edit',
+                       'nativeBBCode' => (in_array($this->bbcode->bbcodeTag, self::$nativeBBCodes))
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/BBCodeMediaProviderAddForm.class.php b/wcfsetup/install/files/lib/acp/form/BBCodeMediaProviderAddForm.class.php
new file mode 100644 (file)
index 0000000..d2c6964
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\bbcode\media\provider\BBCodeMediaProviderAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\Regex;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the BBCode media provider add form.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class BBCodeMediaProviderAddForm extends AbstractForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.bbcode.mediaProvider.add';
+       
+       /**
+        * html value
+        * @var string
+        */
+       public $html = '';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * @see wcf\page\AbstractPage::$templateName
+        */
+       public $templateName = 'bbcodeMediaProviderAdd';
+       
+       /**
+        * title value
+        * @var string
+        */
+       public $title = '';
+       
+       /**
+        * regex value
+        * @var string
+        */
+       public $regex = '';
+       
+       /**
+        * @see wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['title'])) $this->title = StringUtil::trim($_POST['title']);
+               if (isset($_POST['regex'])) $this->regex = StringUtil::trim($_POST['regex']);
+               if (isset($_POST['html'])) $this->html = StringUtil::trim($_POST['html']);
+       }
+       
+       /**
+        * @see wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate fields
+               if (empty($this->title)) {
+                       throw new UserInputException('title');
+               }
+               if (empty($this->regex)) {
+                       throw new UserInputException('regex');
+               }
+               if (empty($this->html)) {
+                       throw new UserInputException('html');
+               }
+               
+               $lines = explode("\n", StringUtil::unifyNewlines($this->regex));
+               
+               foreach ($lines as $line) {
+                       if (!Regex::compile($line)->isValid()) throw new UserInputException('regex', 'notValid');
+               }
+       }
+       
+       /**
+        * @see wcf\form\IForm::save()
+        */
+       public function save() {
+               parent::save();
+               
+               // save media provider
+               $this->objectAction = new BBCodeMediaProviderAction(array(), 'create', array('data' => array(
+                       'title' => $this->title,
+                       'regex' => $this->regex,
+                       'html' => $this->html
+               )));
+               $this->objectAction->executeAction();
+               $this->saved();
+               
+               // reset values
+               $this->title = $this->regex = $this->html = '';
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'add',
+                       'title' => $this->title,
+                       'regex' => $this->regex,
+                       'html' => $this->html
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/BBCodeMediaProviderEditForm.class.php b/wcfsetup/install/files/lib/acp/form/BBCodeMediaProviderEditForm.class.php
new file mode 100644 (file)
index 0000000..d718419
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\bbcode\media\provider\BBCodeMediaProvider;
+use wcf\data\bbcode\media\provider\BBCodeMediaProviderAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Shows the BBCode media provider edit form.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class BBCodeMediaProviderEditForm extends BBCodeMediaProviderAddForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.bbcode';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * id of the edited media provider
+        * @var integer
+        */
+       public $providerID = 0;
+       
+       /**
+        * edited media provider object
+        * @var wcf\data\bbcode\media\provider\BBCodeMediaProvider
+        */
+       public $mediaProvider = null;
+       
+       /**
+        * @see wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->providerID = intval($_REQUEST['id']);
+               $this->mediaProvider = new BBCodeMediaProvider($this->providerID);
+               if (!$this->mediaProvider->providerID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @see wcf\form\IForm::save()
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               // update media-provider
+               $this->objectAction = new BBCodeMediaProviderAction(array($this->providerID), 'update', array('data' => array(
+                       'title' => $this->title,
+                       'regex' => $this->regex,
+                       'html' => $this->html
+               )));
+               $this->objectAction->executeAction();
+               
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       $this->title = $this->mediaProvider->title;
+                       $this->regex = $this->mediaProvider->regex;
+                       $this->html = $this->mediaProvider->html;
+               }
+       }
+       
+       /**
+        * @see wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'mediaProvider' => $this->mediaProvider,
+                       'action' => 'edit'
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/SmileyAddForm.class.php b/wcfsetup/install/files/lib/acp/form/SmileyAddForm.class.php
new file mode 100644 (file)
index 0000000..a3b07ff
--- /dev/null
@@ -0,0 +1,205 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\category\Category;
+use wcf\data\category\CategoryNodeTree;
+use wcf\data\package\PackageCache;
+use wcf\data\smiley\SmileyAction;
+use wcf\data\smiley\SmileyEditor;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the smiley add form.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class SmileyAddForm extends AbstractForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.smiley.add';
+       
+       /**
+        * @see wcf\page\AbstractPage::$templateName
+        */
+       public $templateName = 'smileyAdd';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.smiley.canManageSmiley');
+       
+       /**
+        * primary smiley code
+        * @var string
+        */
+       public $smileyCode = '';
+       
+       /**
+        * showorder value
+        * @var integer
+        */
+       public $showOrder = 0;
+       
+       /**
+        * categoryID value
+        * @var integer
+        */
+       public $categoryID = 0;
+       
+       /**
+        * smileyTitle
+        * @var string
+        */
+       public $smileyTitle = '';
+       
+       /**
+        * aliases value
+        * @var string
+        */
+       public $aliases = '';
+       
+       /**
+        * node tree with available smiley categories
+        * @var wcf\data\category\CategoryNodeTree
+        */
+       public $categoryNodeTree = null;
+       
+       /**
+        * @see wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'add',
+                       'smileyTitle' => $this->smileyTitle,
+                       'showOrder' => $this->showOrder,
+                       'categoryID' => $this->categoryID,
+                       'smileyCode' => $this->smileyCode,
+                       'aliases' => $this->aliases,
+                       'categoryNodeList' => $this->categoryNodeTree->getIterator()
+               ));
+       }
+       
+       /**
+        * @see wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->categoryNodeTree = new CategoryNodeTree('com.woltlab.wcf.bbcode.smiley', 0, true);
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               I18nHandler::getInstance()->register('smileyTitle');
+       }
+       
+       
+       /**
+        * @see wcf\page\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               I18nHandler::getInstance()->readValues();
+               
+               if (I18nHandler::getInstance()->isPlainValue('smileyTitle')) $this->smileyTitle = I18nHandler::getInstance()->getValue('smileyTitle');
+               
+               if (isset($_POST['showOrder'])) {
+                       $this->showOrder = intval($_POST['showOrder']);
+               }
+               if (isset($_POST['smileyCode'])) {
+                       $this->smileyCode = StringUtil::trim($_POST['smileyCode']);
+               }
+               if (isset($_POST['categoryID'])) {
+                       $this->categoryID = intval($_POST['categoryID']);
+               }
+               if (isset($_POST['aliases'])) {
+                       $this->aliases = StringUtil::unifyNewlines(StringUtil::trim($_POST['aliases']));
+               }
+       }
+       
+       /**
+        * @see wcf\page\IForm::save()
+        */
+       public function save() {
+               parent::save();
+               
+               $this->objectAction = new SmileyAction(array(), 'create', array(
+                       'data' => array(
+                               'smileyTitle' => $this->smileyTitle,
+                               'smileyCode' => $this->smileyCode,
+                               'showOrder' => $this->showOrder,
+                               'categoryID' => $this->categoryID ?: null,
+                               'packageID' => PackageCache::getInstance()->getPackageID('com.woltlab.wcf.bbcode'),
+                               'aliases' => $this->aliases
+                       )
+               ));
+               $this->objectAction->executeAction();
+               $returnValues = $this->objectAction->getReturnValues();
+               $smileyEditor = new SmileyEditor($returnValues['returnValues']);
+               $smileyID = $returnValues['returnValues']->smileyID;
+               
+               if (!I18nHandler::getInstance()->isPlainValue('smileyTitle')) {
+                       I18nHandler::getInstance()->save('smileyTitle', 'wcf.smiley.title'.$smileyID, 'wcf.smiley', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.bbcode'));
+                       
+                       // update title
+                       $smileyEditor->update(array(
+                               'title' => 'wcf.smiley.title'.$smileyID
+                       ));
+               }
+               
+               // reset values
+               $this->smileyCode = '';
+               $this->categoryID = 0;
+               $this->showOrder = 0;
+               
+               I18nHandler::getInstance()->reset();
+               
+               $this->saved();
+               
+               // show success message
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @see wcf\page\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate title
+               if (!I18nHandler::getInstance()->validateValue('smileyTitle')) {
+                       throw new UserInputException('smileyTitle');
+               }
+               
+               if ($this->categoryID !== 0) {
+                       $category = new Category($this->categoryID);
+                       if (!$category->categoryID) {
+                               throw new UserInputException('categoryID', 'notValid');
+                       }
+               }
+               
+               if ($this->smileyCode === '') {
+                       throw new UserInputException('smileyCode');
+               }
+               
+               // TODO: Validate uniqueness of smileyCode and aliases
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/SmileyCategoryAddForm.class.php b/wcfsetup/install/files/lib/acp/form/SmileyCategoryAddForm.class.php
new file mode 100644 (file)
index 0000000..3bb4948
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\acp\form;
+
+/**
+ * Shows the smiley category add form.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.category
+ * @category   Community Framework
+ */
+class SmileyCategoryAddForm extends AbstractCategoryAddForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.smiley.category.add';
+       
+       /**
+        * @see wcf\acp\form\AbstractCategoryAddForm::$objectTypeName
+        */
+       public $objectTypeName = 'com.woltlab.wcf.bbcode.smiley';
+       
+       /**
+        * @see wcf\acp\form\AbstractCategoryAddForm::$pageTitle
+        */
+       public $pageTitle = 'wcf.acp.smiley.category.add';
+}
diff --git a/wcfsetup/install/files/lib/acp/form/SmileyCategoryEditForm.class.php b/wcfsetup/install/files/lib/acp/form/SmileyCategoryEditForm.class.php
new file mode 100644 (file)
index 0000000..fc4c70f
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\acp\form;
+
+/**
+ * Shows the category edit form.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.category
+ * @category   Community Framework
+ */
+class SmileyCategoryEditForm extends AbstractCategoryEditForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.smiley.category.list';
+       
+       /**
+        * @see wcf\acp\form\AbstractCategoryAddForm::$objectTypeName
+        */
+       public $objectTypeName = 'com.woltlab.wcf.bbcode.smiley';
+       
+       /**
+        * @see wcf\acp\form\AbstractCategoryAddForm::$pageTitle
+        */
+       public $pageTitle = 'wcf.acp.smiley.category.edit';
+}
diff --git a/wcfsetup/install/files/lib/acp/form/SmileyEditForm.class.php b/wcfsetup/install/files/lib/acp/form/SmileyEditForm.class.php
new file mode 100644 (file)
index 0000000..4ecaa9c
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\package\PackageCache;
+use wcf\data\smiley\Smiley;
+use wcf\data\smiley\SmileyAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the smiley edit form.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class SmileyEditForm extends SmileyAddForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.smiley';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.smiley.canManageSmiley');
+       
+       /**
+        * smiley id
+        * @var integer
+        */
+       public $smileyID = 0;
+       
+       /**
+        * smiley object
+        * @var wcf\data\smiley\Smiley
+        */
+       public $smiley = null;
+       
+       /**
+        * @see wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->smileyID = intval($_REQUEST['id']);
+               $this->smiley = new Smiley($this->smileyID);
+               if (!$this->smiley->smileyID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @see wcf\form\IForm::save()
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               $this->smileyTitle = 'wcf.smiley.title'.$this->smiley->smileyID;
+               if (I18nHandler::getInstance()->isPlainValue('smileyTitle')) {
+                       I18nHandler::getInstance()->remove($this->smileyTitle);
+                       $this->smileyTitle = I18nHandler::getInstance()->getValue('smileyTitle');
+               }
+               else {
+                       I18nHandler::getInstance()->save('smileyTitle', $this->smileyTitle, 'wcf.smiley', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.bbcode'));
+               }
+               
+               // update bbcode
+               $this->objectAction = new SmileyAction(array($this->smileyID), 'update', array('data' => array(
+                       'smileyTitle' => $this->smileyTitle,
+                       'smileyCode' => $this->smileyCode,
+                       'showOrder' => $this->showOrder,
+                       'categoryID' => $this->categoryID ?: null,
+                       'aliases' => $this->aliases
+               )));
+               $this->objectAction->executeAction();
+               
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       I18nHandler::getInstance()->setOptions('smileyTitle', 1, $this->smiley->smileyTitle, 'wcf.smiley.title\d+');
+                       $this->smileyTitle = $this->smiley->smileyTitle;
+                       
+                       $this->smileyCode = $this->smiley->smileyCode;
+                       $this->aliases = $this->smiley->aliases;
+                       $this->showOrder = $this->smiley->showOrder;
+                       $this->categoryID = $this->smiley->categoryID;
+               }
+       }
+       
+       /**
+        * @see wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables(!empty($_POST));
+               
+               WCF::getTPL()->assign(array(
+                       'smiley' => $this->smiley,
+                       'action' => 'edit'
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/BBCodeListPage.class.php b/wcfsetup/install/files/lib/acp/page/BBCodeListPage.class.php
new file mode 100644 (file)
index 0000000..cfe049b
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Lists the available BBCodes.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.page
+ * @category   Community Framework
+ */
+class BBCodeListPage extends SortablePage {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.bbcode.list';
+       
+       /**
+        * @see wcf\page\SortablePage::$defaultSortField
+        */
+       public $defaultSortField = 'bbcodeTag';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * @see wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\bbcode\BBCodeList';
+       
+       /**
+        * @see wcf\page\AbstractPage::$templateName
+        */
+       public $templateName = 'bbcodeList';
+       
+       /**
+        * @see wcf\page\SortablePage::$validSortFields
+        */
+       public $validSortFields = array('bbcodeID', 'bbcodeTag', 'className');
+}
diff --git a/wcfsetup/install/files/lib/acp/page/BBCodeMediaProviderListPage.class.php b/wcfsetup/install/files/lib/acp/page/BBCodeMediaProviderListPage.class.php
new file mode 100644 (file)
index 0000000..3f927ac
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Lists the available media providers.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.page
+ * @category   Community Framework
+ */
+class BBCodeMediaProviderListPage extends SortablePage {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.bbcode.mediaProvider.list';
+       
+       /**
+        * @see wcf\page\SortablePage::$defaultSortField
+        */
+       public $defaultSortField = 'title';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * @see wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\bbcode\media\provider\BBCodeMediaProviderList';
+       
+       /**
+        * @see wcf\page\AbstractPage::$templateName
+        */
+       public $templateName = 'bbcodeMediaProviderList';
+       
+       /**
+        * @see wcf\page\SortablePage::$validSortFields
+        */
+       public $validSortFields = array('providerID', 'title');
+}
diff --git a/wcfsetup/install/files/lib/acp/page/SmileyCategoryListPage.class.php b/wcfsetup/install/files/lib/acp/page/SmileyCategoryListPage.class.php
new file mode 100644 (file)
index 0000000..b9a7f03
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\acp\page;
+
+/**
+ * Shows the smiley category list.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.category
+ * @category   Community Framework
+ */
+class SmileyCategoryListPage extends AbstractCategoryListPage {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.smiley.category.list';
+       
+       /**
+        * @see wcf\acp\page\AbstractCategoryListPage::$activeMenuItem
+        */
+       public $objectTypeName = 'com.woltlab.wcf.bbcode.smiley';
+
+       /**
+        * @see wcf\acp\page\AbstractCategoryListPage::$pageTitle
+        */
+       public $pageTitle = 'wcf.acp.smiley.category.list';
+}
diff --git a/wcfsetup/install/files/lib/acp/page/SmileyListPage.class.php b/wcfsetup/install/files/lib/acp/page/SmileyListPage.class.php
new file mode 100644 (file)
index 0000000..c33427e
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\category\Category;
+use wcf\data\smiley\SmileyCache;
+use wcf\page\MultipleLinkPage;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Lists the available smilies.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.page
+ * @category   Community Framework
+ */
+class SmileyListPage extends MultipleLinkPage {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.smiley.list';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.smiley.canManageSmiley');
+       
+       /**
+        * @see wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\smiley\SmileyList';
+       
+       /**
+        * @see wcf\page\AbstractPage::$templateName
+        */
+       public $templateName = 'smileyList';
+       
+       /**
+        * @see wcf\page\MultipleLinkPage::$sqlOrderBy
+        */
+       public $sqlOrderBy = 'showOrder ASC, smileyID ASC';
+       
+       /**
+        * category id
+        * @var integer
+        */
+       public $categoryID = 0;
+       
+       /**
+        * active category
+        * @var wcf\data\category\Category
+        */
+       public $category = null;
+       
+       /**
+        * available categories
+        * @var array<wcf\data\category\Category>
+        */
+       public $categories = array();
+       
+       /**
+        * @see wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) {
+                       $this->categoryID = intval($_REQUEST['id']);
+                       $this->category = new Category($this->categoryID);
+                       if (!$this->category->categoryID) {
+                               throw new IllegalLinkException();
+                       }
+               }
+       }
+       
+       /**
+        * @see wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'category' => $this->category,
+                       'categories' => $this->categories,
+                       'smileyCount' => count(SmileyCache::getInstance()->getSmilies())
+               ));
+       }
+       
+       /**
+        * @see wcf\page\MultipleLinkPage::initObjectList()
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               if ($this->categoryID) {
+                       $this->objectList->getConditionBuilder()->add('categoryID = ?', array($this->categoryID));
+               }
+               else {
+                       $this->objectList->getConditionBuilder()->add('categoryID IS NULL', array());
+               }
+       }
+       
+       /**
+        * @see wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->categories = SmileyCache::getInstance()->getCategories();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/BBCode.class.php b/wcfsetup/install/files/lib/data/bbcode/BBCode.class.php
new file mode 100644 (file)
index 0000000..7dd1f90
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+namespace wcf\data\bbcode;
+use wcf\data\ProcessibleDatabaseObject;
+use wcf\system\request\IRouteController;
+use wcf\system\WCF;
+
+/**
+ * Represents a bbcode.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode
+ * @category   Community Framework
+ */
+class BBCode extends ProcessibleDatabaseObject implements IRouteController {
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'bbcode';
+       
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableIndexName
+        */
+       protected static $databaseTableIndexName = 'bbcodeID';
+       
+       /**
+        * @see wcf\data\ProcessibleDatabaseObject::$processorInterface
+        */
+       protected static $processorInterface = 'wcf\system\bbcode\IBBCode';
+       
+       /**
+        * Returns the attributes of this bbcode.
+        * 
+        * @return      array<wcf\data\bbcode\attribute\BBCodeAttribute>
+        */
+       public function getAttributes() {
+               if ($this->attributes === null) {
+                       $this->data['attributes'] = BBCodeCache::getInstance()->getBBCodeAttributes($this->bbcodeTag);
+               }
+               
+               return $this->attributes;
+       }
+       
+       /**
+        * @see wcf\data\ITitledObject::getTitle()
+        */
+       public function getTitle() {
+               return $this->bbcodeTag;
+       }
+       
+       /**
+        * Returns BBCode object with the given tag.
+        * 
+        * @param       string          $tag
+        * @return      wcf\data\bbcode\BBCode
+        */
+       public static function getBBCodeByTag($tag) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_bbcode
+                       WHERE   bbcodeTag = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($tag));
+               $row = $statement->fetchArray();
+               if (!$row) $row = array();
+               
+               return new self(null, $row);
+       }
+       
+       /**
+        * Returns true if the given BBCode tag is allowed by the given list of
+        * BBCode tags. If the relevant BBCode should be globally disabled or non-
+        * existent, false is returned.
+        * 
+        * @param       string          $bbcodeTag
+        * @param       array<string>   $allowedBBCodeTags
+        * @return      boolean
+        */
+       public static function isAllowedBBCode($bbcodeTag, array $allowedBBCodeTags) {
+               // check if bbcode is unknown or disabled
+               if (BBCodeCache::getInstance()->getBBCodeByTag($bbcodeTag) === null) {
+                       return false;
+               }
+               
+               // all BBCodes are allowed
+               if (in_array('all', $allowedBBCodeTags)) {
+                       return true;
+               }
+               
+               // no BBCode are allowed
+               if (in_array('none', $allowedBBCodeTags)) {
+                       return false;
+               }
+               
+               return in_array($bbcodeTag, $allowedBBCodeTags);
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/BBCodeAction.class.php b/wcfsetup/install/files/lib/data/bbcode/BBCodeAction.class.php
new file mode 100644 (file)
index 0000000..2aeaa58
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+namespace wcf\data\bbcode;
+use wcf\data\user\group\UserGroup;
+use wcf\data\user\group\UserGroupEditor;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IToggleAction;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * Executes bbcode-related actions.
+ * 
+ * @author     Tim Duesterhus, Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode
+ * @category   Community Framework
+ */
+class BBCodeAction extends AbstractDatabaseObjectAction implements IToggleAction {
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$className
+        */
+       protected $className = 'wcf\data\bbcode\BBCodeEditor';
+       
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+        */
+       protected $permissionsDelete = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+        */
+       protected $permissionsUpdate = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::create()
+        */
+       public function create() {
+               $bbCode = parent::create();
+               
+               // add bbcode to BBCodeSelect user group options
+               $sql = "SELECT  optionID
+                       FROM    wcf".WCF_N."_user_group_option
+                       WHERE   optionType = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array('BBCodeSelect'));
+               
+               $optionIDs = array();
+               while ($optionID = $statement->fetchColumn()) {
+                       $optionIDs[] = $optionID;
+               }
+               
+               if (!empty($optionIDs)) {
+                       $conditionBuilder = new PreparedStatementConditionBuilder();
+                       $conditionBuilder->add("optionID IN (?)", array($optionIDs));
+                       $conditionBuilder->add("groupID IN (?)", array(UserGroup::getGroupIDsByType(array(UserGroup::EVERYONE))));
+                       $conditionBuilder->add("optionValue <> ?", array('all'));
+                       
+                       $sql = "SELECT  *
+                               FROM    wcf".WCF_N."_user_group_option_value
+                               ".$conditionBuilder;
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditionBuilder->getParameters());
+                       
+                       $sql = "UPDATE  wcf".WCF_N."_user_group_option_value
+                               SET     optionValue = ?
+                               WHERE   optionID = ?
+                                       AND groupID = ?";
+                       $updateStatement = WCF::getDB()->prepareStatement($sql);
+                       
+                       WCF::getDB()->beginTransaction();
+                       while ($row = $statement->fetchArray()) {
+                               if (!empty($row['optionValue'])) {
+                                       $row['optionValue'] .= ','.$bbCode->bbcodeTag;
+                               }
+                               else {
+                                       $row['optionValue'] = $bbCode->bbcodeTag;
+                               }
+                               
+                               $updateStatement->execute(array(
+                                       $row['optionValue'],
+                                       $row['optionID'],
+                                       $row['groupID']
+                               ));
+                       }
+                       WCF::getDB()->commitTransaction();
+                       
+                       // clear user group option cache
+                       UserGroupEditor::resetCache();
+               }
+               
+               return $bbCode;
+       }
+       
+       /**
+        * @see wcf\data\IToggleAction::validateToggle()
+        */
+       public function validateToggle() {
+               parent::validateUpdate();
+       }
+       
+       /**
+        * @see wcf\data\IToggleAction::toggle()
+        */
+       public function toggle() {
+               foreach ($this->objects as $bbcode) {
+                       $bbcode->update(array(
+                               'isDisabled' => $bbcode->isDisabled ? 0 : 1
+                       ));
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/BBCodeCache.class.php b/wcfsetup/install/files/lib/data/bbcode/BBCodeCache.class.php
new file mode 100644 (file)
index 0000000..987ccff
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+namespace wcf\data\bbcode;
+use wcf\system\cache\builder\BBCodeCacheBuilder;
+use wcf\system\SingletonFactory;
+
+/**
+ * Manages the bbcode cache.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode
+ * @category   Community Framework
+ */
+class BBCodeCache extends SingletonFactory {
+       /**
+        * cached bbcodes
+        * @var array<wcf\data\bbcode\BBCode>
+        */
+       protected $cachedBBCodes = array();
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               // get bbcode cache
+               $this->cachedBBCodes = BBCodeCacheBuilder::getInstance()->getData();
+       }
+       
+       /**
+        * Returns all bbcodes.
+        * 
+        * @return      array<wcf\data\bbcode\BBCode>
+        */
+       public function getBBCodes() {
+               return $this->cachedBBCodes;
+       }
+       
+       /**
+        * Returns the BBCode with the given tag or null if no such BBCode exists.
+        * 
+        * @param       string          $tag
+        * @return      wcf\data\bbcode\BBCode
+        */
+       public function getBBCodeByTag($tag) {
+               if (isset($this->cachedBBCodes[$tag])) {
+                       return $this->cachedBBCodes[$tag];
+               }
+               
+               return null;
+       }
+       
+       /**
+        * Returns all attributes of a bbcode.
+        * 
+        * @param       string          $tag
+        * @return      array<wcf\data\bbcode\attribute\BBCodeAttribute>
+        */
+       public function getBBCodeAttributes($tag) {
+               return $this->cachedBBCodes[$tag]->getAttributes();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/BBCodeEditor.class.php b/wcfsetup/install/files/lib/data/bbcode/BBCodeEditor.class.php
new file mode 100644 (file)
index 0000000..fcb8951
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\data\bbcode;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\builder\BBCodeCacheBuilder;
+
+/**
+ * Provides functions to edit bbcodes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode
+ * @category   Community Framework
+ */
+class BBCodeEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+       /**
+        * @see wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       public static $baseClass = 'wcf\data\bbcode\BBCode';
+       
+       /**
+        * @see wcf\data\IEditableCachedObject::resetCache()
+        */
+       public static function resetCache() {
+               BBCodeCacheBuilder::getInstance()->reset();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/BBCodeList.class.php b/wcfsetup/install/files/lib/data/bbcode/BBCodeList.class.php
new file mode 100644 (file)
index 0000000..153e0e0
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\bbcode;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of bbcodes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode
+ * @category   Community Framework
+ */
+class BBCodeList extends DatabaseObjectList {
+       /**
+        * @see wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\bbcode\BBCode';
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttribute.class.php b/wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttribute.class.php
new file mode 100644 (file)
index 0000000..7a0e1ea
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\data\bbcode\attribute;
+use wcf\data\bbcode\BBCode;
+use wcf\data\DatabaseObject;
+
+/**
+ * Represents a bbcode attribute.
+ * 
+ * @author     Tim Duesterhus, Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode.attribute
+ * @category   Community Framework
+ */
+class BBCodeAttribute extends DatabaseObject {
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'bbcode_attribute';
+       
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableIndexName
+        */
+       protected static $databaseTableIndexName = 'attributeID';
+       
+       /**
+        * Reads attributes by assigned bbcode.
+        * 
+        * @param       wcf\data\bbcode\BBCode          $bbcode
+        * @return      array<wcf\data\bbcode\attribute\BBCodeAttribute>
+        */
+       public static function getAttributesByBBCode(BBCode $bbcode) {
+               $attributeList = new BBCodeAttributeList();
+               $attributeList->sqlOrderBy = "bbcode_attribute.attributeNo ASC";
+               $attributeList->getConditionBuilder()->add('bbcode_attribute.bbcodeID = ?', array($bbcode->bbcodeID));
+               $attributeList->readObjects();
+               return $attributeList->getObjects();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeAction.class.php b/wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeAction.class.php
new file mode 100644 (file)
index 0000000..9eac962
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\bbcode\attribute;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes bbcode attribute-related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode.attribute
+ * @category   Community Framework
+ */
+class BBCodeAttributeAction extends AbstractDatabaseObjectAction {
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$className
+        */
+       protected $className = 'wcf\data\bbcode\attribute\BBCodeAttributeEditor';
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeEditor.class.php b/wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeEditor.class.php
new file mode 100644 (file)
index 0000000..9d3dcd7
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\bbcode\attribute;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit bbcode attributes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode.attribute
+ * @category   Community Framework
+ */
+class BBCodeAttributeEditor extends DatabaseObjectEditor {
+       /**
+        * @see wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       public static $baseClass = 'wcf\data\bbcode\attribute\BBCodeAttribute';
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeList.class.php b/wcfsetup/install/files/lib/data/bbcode/attribute/BBCodeAttributeList.class.php
new file mode 100644 (file)
index 0000000..0277aaa
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\bbcode\attribute;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of bbcode attribute.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode.attribute
+ * @category   Community Framework
+ */
+class BBCodeAttributeList extends DatabaseObjectList {
+       /**
+        * @see wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\bbcode\attribute\BBCodeAttribute';
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProvider.class.php b/wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProvider.class.php
new file mode 100644 (file)
index 0000000..58ccda0
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+namespace wcf\data\bbcode\media\provider;
+use wcf\data\DatabaseObject;
+use wcf\system\cache\builder\BBCodeMediaProviderCacheBuilder;
+use wcf\system\request\IRouteController;
+use wcf\system\Regex;
+use wcf\util\StringUtil;
+
+/**
+ * Represents a BBCode media provider.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode.media.provider
+ * @category   Community Framework
+ */
+class BBCodeMediaProvider extends DatabaseObject implements IRouteController {
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'bbcode_media_provider';
+       
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableIndexName
+        */
+       protected static $databaseTableIndexName = 'providerID';
+       
+       /**
+        * cached providers
+        * @var array<\wcf\data\bbcode\media\MediaProvider>
+        */
+       protected static $cache = null;
+       
+       /**
+        * Loads the provider cache.
+        * 
+        * @return      array<\wcf\data\bbcode\media\MediaProvider>
+        */
+       public static function getCache() {
+               if (self::$cache === null) {
+                       self::$cache = BBCodeMediaProviderCacheBuilder::getInstance()->getData();
+               }
+               
+               return self::$cache;
+       }
+       
+       /**
+        * Returns true if given URL is a media URL.
+        * 
+        * @param       string          $url
+        * @return      boolean
+        */
+       public static function isMediaURL($url) {
+               foreach (static::getCache() as $provider) {
+                       if ($provider->matches($url)) {
+                               return true;
+                       }
+               }
+               
+               return false;
+       }
+       
+       /**
+        * Checks whether this provider matches the given URL.
+        * 
+        * @param       string          $url
+        * @return      boolean
+        */
+       public function matches($url) {
+               $lines = explode("\n", StringUtil::unifyNewlines($this->regex));
+               
+               foreach ($lines as $line) {
+                       if (Regex::compile($line)->match($url)) return true;
+               }
+               
+               return false;
+       }
+       
+       /**
+        * Returns the html for this provider.
+        * 
+        * @param       string          $url
+        * @return      string
+        */
+       public function getOutput($url) {
+               $lines = explode("\n", StringUtil::unifyNewlines($this->regex));
+               
+               foreach ($lines as $line) {
+                       $regex = new Regex($line);
+                       if (!$regex->match($url)) continue;
+                       
+                       $output = $this->html;
+                       foreach ($regex->getMatches() as $name => $value) {
+                               $output = StringUtil::replace('{$'.$name.'}', $value, $output);
+                       }
+                       return $output;
+               }
+               
+               return '';
+       }
+       
+       /**
+        * @see wcf\data\ITitledObject::getTitle()
+        */
+       public function getTitle() {
+               return $this->title;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderAction.class.php b/wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderAction.class.php
new file mode 100644 (file)
index 0000000..9cdb76a
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+namespace wcf\data\bbcode\media\provider;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\SystemException;
+use wcf\system\Regex;
+use wcf\util\StringUtil;
+
+/**
+ * Executes BBCode media provider-related actions.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011-2012 Tim Duesterhus
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode.media.provider
+ * @category   Community Framework
+ */
+class BBCodeMediaProviderAction extends AbstractDatabaseObjectAction {
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$className
+        */
+       protected $className = 'wcf\data\bbcode\media\provider\BBCodeMediaProviderEditor';
+       
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+        */
+       protected $permissionsDelete = array('admin.content.bbcode.canManageBBCode');
+       
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+        */
+       protected $permissionsUpdate = array('admin.content.bbcode.canManageBBCode');
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderEditor.class.php b/wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderEditor.class.php
new file mode 100644 (file)
index 0000000..dd4c239
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\data\bbcode\media\provider;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\builder\BBCodeMediaProviderCacheBuilder;
+
+/**
+ * Provides functions to edit BBCode media providers.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode.media.provider
+ * @category   Community Framework
+ */
+class BBCodeMediaProviderEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+       /**
+        * @see wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       public static $baseClass = 'wcf\data\bbcode\media\provider\BBCodeMediaProvider';
+       
+       /**
+        * @see wcf\data\IEditableCachedObject::resetCache()
+        */
+       public static function resetCache() {
+               BBCodeMediaProviderCacheBuilder::getInstance()->reset();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderList.class.php b/wcfsetup/install/files/lib/data/bbcode/media/provider/BBCodeMediaProviderList.class.php
new file mode 100644 (file)
index 0000000..f794d4f
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\bbcode\media\provider;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of BBCode media providers.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011-2012 Tim Duesterhus
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.bbcode.media.provider
+ * @category   Community Framework
+ */
+class BBCodeMediaProviderList extends DatabaseObjectList {
+       /**
+        * @see wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\bbcode\media\provider\BBCodeMediaProvider';
+}
diff --git a/wcfsetup/install/files/lib/data/smiley/Smiley.class.php b/wcfsetup/install/files/lib/data/smiley/Smiley.class.php
new file mode 100644 (file)
index 0000000..2c14db0
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+namespace wcf\data\smiley;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Represents a smiley.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.smiley
+ * @category   Community Framework
+ */
+class Smiley extends DatabaseObject {
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'smiley';
+       
+       /**
+        * @see wcf\data\DatabaseObject::$databaseTableIndexName
+        */
+       protected static $databaseTableIndexName = 'smileyID';
+       
+       /**
+        * Returns the url to this smiley.
+        * 
+        * @return      string
+        */
+       public function getURL() {
+               return WCF::getPath().$this->smileyPath;
+       }
+       
+       /**
+        * Returns all aliases for this smiley.
+        * 
+        * @return      array<string>
+        */
+       public function getAliases() {
+               if (!$this->aliases) return array();
+               
+               return explode("\n", StringUtil::unifyNewlines($this->aliases));
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/smiley/SmileyAction.class.php b/wcfsetup/install/files/lib/data/smiley/SmileyAction.class.php
new file mode 100644 (file)
index 0000000..5ea2dae
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+namespace wcf\data\smiley;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\ISortableAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Executes smiley-related actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.smiley
+ * @category   Community Framework
+ */
+class SmileyAction extends AbstractDatabaseObjectAction implements ISortableAction {
+       /**
+        * @see wcf\data\AbstractDatabaseObjectAction::$className
+        */
+       protected $className = 'wcf\data\smiley\SmileyEditor';
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+        */
+       protected $permissionsDelete = array('admin.content.smiley.canManageSmiley');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+        */
+       protected $permissionsUpdate = array('admin.content.smiley.canManageSmiley');
+       
+       /**
+        * @see wcf\data\ISortableAction::validateUpdatePosition()
+        */
+       public function validateUpdatePosition() {
+               // validate permissions
+               if (is_array($this->permissionsUpdate) && count($this->permissionsUpdate)) {
+                       WCF::getSession()->checkPermissions($this->permissionsUpdate);
+               }
+               else {
+                       throw new PermissionDeniedException();
+               }
+               
+               if (!isset($this->parameters['data']['structure'])) {
+                       throw new UserInputException('structure');
+               }
+               
+               $this->readInteger('offset', true, 'data');
+       }
+       
+       /**
+        * @see wcf\data\ISortableAction::updatePosition()
+        */
+       public function updatePosition() {
+               $smileyList = new SmileyList();
+               $smileyList->readObjects();
+               
+               $i = $this->parameters['data']['offset'];
+               WCF::getDB()->beginTransaction();
+               foreach ($this->parameters['data']['structure'][0] as $smileyID) {
+                       $smiley = $smileyList->search($smileyID);
+                       if ($smiley === null) continue;
+                               
+                       $editor = new SmileyEditor($smiley);
+                       $editor->update(array('showOrder' => $i++));
+               }
+               WCF::getDB()->commitTransaction();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/smiley/SmileyCache.class.php b/wcfsetup/install/files/lib/data/smiley/SmileyCache.class.php
new file mode 100644 (file)
index 0000000..269ab6e
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+namespace wcf\data\smiley;
+use wcf\data\category\Category;
+use wcf\data\smiley\category\SmileyCategory;
+use wcf\system\cache\builder\SmileyCacheBuilder;
+use wcf\system\category\CategoryHandler;
+use wcf\system\SingletonFactory;
+
+/**
+ * Manages the smiley cache.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.smiley
+ * @category   Community Framework
+ */
+class SmileyCache extends SingletonFactory {
+       /**
+        * cached smilies
+        * @var array
+        */
+       protected $cachedSmilies = array();
+       
+       /**
+        * cached smiley categories
+        * @var array<wcf\data\smiley\category\SmileyCategory>
+        */
+       protected $cachedCategories = array();
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               // get smiley cache
+               $this->cachedSmilies = SmileyCacheBuilder::getInstance()->getData(array(), 'smilies');
+               $smileyCategories = CategoryHandler::getInstance()->getCategories('com.woltlab.wcf.bbcode.smiley');
+               
+               $this->cachedCategories[null] = new SmileyCategory(new Category(null, array(
+                       'categoryID' => null,
+                       'parentCategoryID' => 0,
+                       'title' => 'wcf.acp.smiley.categoryID.default',
+                       'description' => '',
+                       'showOrder' => -1,
+                       'isDisabled' => 0
+               )));
+               
+               foreach ($smileyCategories as $key => $smileyCategory) {
+                       $this->cachedCategories[$key] = new SmileyCategory($smileyCategory);
+               }
+       }
+       
+       /**
+        * Returns all smilies.
+        * 
+        * @return      array
+        */
+       public function getSmilies() {
+               return $this->cachedSmilies;
+       }
+       
+       /**
+        * Returns all smiley categories.
+        * 
+        * @return      array<wcf\data\smiley\category\SmileyCategory>
+        */
+       public function getCategories() {
+               return $this->cachedCategories;
+       }
+       
+       /**
+        * Returns all the smilies of a category.
+        * 
+        * @param       integer         $categoryID
+        * @return      array
+        */
+       public function getCategorySmilies($categoryID = null) {
+               if (isset($this->cachedSmilies[$categoryID])) return $this->cachedSmilies[$categoryID];
+               
+               return array();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/smiley/SmileyEditor.class.php b/wcfsetup/install/files/lib/data/smiley/SmileyEditor.class.php
new file mode 100644 (file)
index 0000000..f4618a2
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\data\smiley;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\builder\SmileyCacheBuilder;
+
+/**
+ * Provides functions to edit smilies.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.smiley
+ * @category   Community Framework
+ */
+class SmileyEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+       /**
+        * @see wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       public static $baseClass = 'wcf\data\smiley\Smiley';
+       
+       /**
+        * @see wcf\data\IEditableCachedObject::resetCache()
+        */
+       public static function resetCache() {
+               SmileyCacheBuilder::getInstance()->reset();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/smiley/SmileyList.class.php b/wcfsetup/install/files/lib/data/smiley/SmileyList.class.php
new file mode 100644 (file)
index 0000000..3abf397
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\smiley;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of smilies.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.smiley
+ * @category   Community Framework
+ */
+class SmileyList extends DatabaseObjectList {
+       /**
+        * @see wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\smiley\Smiley';
+}
diff --git a/wcfsetup/install/files/lib/data/smiley/category/SmileyCategory.class.php b/wcfsetup/install/files/lib/data/smiley/category/SmileyCategory.class.php
new file mode 100644 (file)
index 0000000..71321d7
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+namespace wcf\data\smiley\category;
+use wcf\data\category\AbstractDecoratedCategory;
+use wcf\data\smiley\SmileyCache;
+use wcf\data\ITraversableObject;
+use wcf\system\exception\SystemException;
+
+/**
+ * Represents a smiley category.
+ * 
+ * @author     Tim Duesterhus, Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage data.smiley.category
+ * @category   Community Framework
+ */
+class SmileyCategory extends AbstractDecoratedCategory implements \Countable, ITraversableObject {
+       /**
+        * current iterator index
+        * @var integer
+        */
+       protected $index = 0;
+       
+       /**
+        * list of index to object relation
+        * @var array<integer>
+        */
+       protected $indexToObject = null;
+       
+       /**
+        * list of assigned smilies
+        * @var array<wcf\data\smiley\Smiley>
+        */
+       public $smilies = null;
+       
+       /**
+        * Loads associated smilies from cache.
+        */
+       public function loadSmilies() {
+               if ($this->smilies === null) {
+                       $this->smilies = SmileyCache::getInstance()->getCategorySmilies($this->categoryID ?: null);
+                       $this->indexToObject = array_keys($this->smilies);
+               }
+       }
+       
+       /**
+        * @see \Countable::count()
+        */
+       public function count() {
+               return count($this->smilies);
+       }
+       
+       /**
+        * @see \Iterator::current()
+        */
+       public function current() {
+               $objectID = $this->indexToObject[$this->index];
+               return $this->smilies[$objectID];
+       }
+       
+       /**
+        * CAUTION: This methods does not return the current iterator index,
+        * rather than the object key which maps to that index.
+        *
+        * @see \Iterator::key()
+        */
+       public function key() {
+               return $this->indexToObject[$this->index];
+       }
+       
+       /**
+        * @see \Iterator::next()
+        */
+       public function next() {
+               ++$this->index;
+       }
+       
+       /**
+        * @see \Iterator::rewind()
+        */
+       public function rewind() {
+               $this->index = 0;
+       }
+       
+       /**
+        * @see \Iterator::valid()
+        */
+       public function valid() {
+               return isset($this->indexToObject[$this->index]);
+       }
+       
+       /**
+        * @see \SeekableIterator::seek()
+        */
+       public function seek($index) {
+               $this->index = $index;
+               
+               if (!$this->valid()) {
+                       throw new \OutOfBoundsException();
+               }
+       }
+       
+       /**
+        * @see wcf\data\ITraversableObject::seekTo()
+        */
+       public function seekTo($objectID) {
+               $this->index = array_search($objectID, $this->indexToObject);
+               
+               if ($this->index === false) {
+                       throw new SystemException("object id '".$objectID."' is invalid");
+               }
+       }
+       
+       /**
+        * @see wcf\data\ITraversableObject::search()
+        */
+       public function search($objectID) {
+               try {
+                       $this->seekTo($objectID);
+                       return $this->current();
+               }
+               catch (SystemException $e) {
+                       return null;
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/AbstractBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/AbstractBBCode.class.php
new file mode 100644 (file)
index 0000000..b1f9ecd
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Provides an abstract implementation for bbcodes.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+abstract class AbstractBBCode extends DatabaseObjectDecorator implements IBBCode {
+       /**
+        * @see wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\bbcode\BBCode';
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/BBCodeHandler.class.php b/wcfsetup/install/files/lib/system/bbcode/BBCodeHandler.class.php
new file mode 100644 (file)
index 0000000..1277dce
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\bbcode\BBCodeCache;
+use wcf\system\SingletonFactory;
+
+/**
+ * Handles BBCodes displayed as buttons within the WYSIWYG editor.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class BBCodeHandler extends SingletonFactory {
+       /**
+        * list of BBCodes displayed as buttons
+        * @var array<wcf\data\bbcode\BBCode>
+        */
+       protected $buttonBBCodes = array();
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               foreach (BBCodeCache::getInstance()->getBBCodes() as $bbcode) {
+                       if ($bbcode->showButton) {
+                               $this->buttonBBCodes[] = $bbcode;
+                       }
+               }
+       }
+       
+       /**
+        * Returns a list of BBCodes displayed as buttons.
+        * 
+        * @return      array<wcf\data\bbcode\BBCode>
+        */
+       public function getButtonBBCodes() {
+               return $this->buttonBBCodes;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/BBCodeParser.class.php b/wcfsetup/install/files/lib/system/bbcode/BBCodeParser.class.php
new file mode 100644 (file)
index 0000000..f7a60ac
--- /dev/null
@@ -0,0 +1,534 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\bbcode\attribute\BBCodeAttribute;
+use wcf\data\bbcode\BBCodeCache;
+use wcf\system\SingletonFactory;
+use wcf\util\StringUtil;
+
+/**
+ * Parses bbcode tags in text.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class BBCodeParser extends SingletonFactory {
+       /**
+        * list of bbcodes
+        * @var array<wcf\data\bbcode\BBCode>
+        */
+       protected $bbcodes = array();
+       
+       /**
+        * output type
+        * @var string
+        */
+       protected $outputType = 'text/html';
+       
+       /**
+        * source text
+        * @var string
+        */
+       protected $text = '';
+       
+       /**
+        * parsed text
+        * @var string
+        */
+       protected $parsedText = '';
+       
+       /**
+        * tag array
+        * @var array
+        */
+       protected $tagArray = array();
+       
+       /**
+        * text array
+        * @var array
+        */
+       protected $textArray = array();
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               // get bbcodes
+               $this->bbcodes = BBCodeCache::getInstance()->getBBCodes();
+       }
+       
+       /**
+        * Sets the output type of the parser.
+        * 
+        * @param       string          $outputType
+        */
+       public function setOutputType($outputType) {
+               $this->outputType = $outputType;
+       }
+       
+       /**
+        * Returns the current output type.
+        * 
+        * @return      string
+        */
+       public function getOutputType() {
+               return $this->outputType;
+       }
+       
+       /**
+        * Sets the text to be parsed.
+        * 
+        * @param       string          $text
+        */
+       public function setText($text) {
+               $this->text = $text;
+       }
+       
+       /**
+        * Parses the given text.
+        * 
+        * @param       string          $text
+        * @return      string          parsed text
+        */
+       public function parse($text) {
+               $this->setText($text);
+               $this->buildTagArray();
+               $this->buildXMLStructure();
+               $this->buildParsedString();
+               
+               return $this->parsedText;
+       }
+       
+       /**
+        * Builds a valid xml structure of bbcode tags.
+        * Inserts unclosed tags automatically.
+        */
+       public function buildXMLStructure() {
+               // stack for open tags
+               $openTagStack = $openTagDataStack = array();
+               $newTagArray = array();
+               $newTextArray = array();
+               
+               $i = -1;
+               foreach ($this->tagArray as $i => $tag) {
+                       if ($tag['closing']) {
+                               // closing tag
+                               if (in_array($tag['name'], $openTagStack) && $this->isAllowed($openTagStack, $tag['name'], true)) {
+                                       // close unclosed tags
+                                       $tmpOpenTags = array();
+                                       while (($previousTag = end($openTagStack)) != $tag['name']) {
+                                               $nextIndex = count($newTagArray);
+                                               $newTagArray[$nextIndex] = $this->buildTag('[/'.$previousTag.']');
+                                               if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                                               $newTextArray[$nextIndex] .= $this->textArray[$i];
+                                               $this->textArray[$i] = '';
+                                               $tmpOpenTags[] = end($openTagDataStack);
+                                               array_pop($openTagStack);
+                                               array_pop($openTagDataStack);
+                                       }
+                                       
+                                       $nextIndex = count($newTagArray);
+                                       $newTagArray[$nextIndex] = $tag;
+                                       array_pop($openTagStack);
+                                       array_pop($openTagDataStack);
+                                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                                       $newTextArray[$nextIndex] .= $this->textArray[$i];
+                                       
+                                       // open closed unclosed tags
+                                       while ($tmpTag = end($tmpOpenTags)) {
+                                               $nextIndex = count($newTagArray);
+                                               $newTagArray[$nextIndex] = $tmpTag;
+                                               if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                                               $openTagStack[] = $tmpTag['name'];
+                                               $openTagDataStack[] = $tmpTag;
+                                               array_pop($tmpOpenTags);
+                                       }
+                               }
+                               else {
+                                       // no such tag open
+                                       // handle as plain text
+                                       $this->textArray[$i] .= $tag['source'];
+                                       $last = count($newTagArray);
+                                       if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+                                       $newTextArray[$last] .= $this->textArray[$i];
+                               }
+                       }
+                       else {
+                               // opening tag
+                               if ($this->isAllowed($openTagStack, $tag['name']) && $this->isValidTag($tag)) {
+                                       $openTagStack[] = $tag['name'];
+                                       $openTagDataStack[] = $tag;
+                                       $nextIndex = count($newTagArray);
+                                       $newTagArray[$nextIndex] = $tag;
+                                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                                       $newTextArray[$nextIndex] .= $this->textArray[$i];
+                               }
+                               else {
+                                       // tag not allowed
+                                       $this->textArray[$i] .= $tag['source'];
+                                       $last = count($newTagArray);
+                                       if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+                                       $newTextArray[$last] .= $this->textArray[$i];
+                               }
+                       }
+               }
+               
+               $last = count($newTagArray);
+               if (!isset($newTextArray[$last])) $newTextArray[$last] = '';
+               $newTextArray[$last] .= $this->textArray[$i + 1];
+               
+               // close unclosed open tags
+               while (end($openTagStack)) {
+                       $nextIndex = count($newTagArray);
+                       $newTagArray[$nextIndex] = $this->buildTag('[/'.end($openTagStack).']');
+                       if (!isset($newTextArray[$nextIndex])) $newTextArray[$nextIndex] = '';
+                       array_pop($openTagStack);
+                       array_pop($openTagDataStack);
+               }
+               
+               $this->tagArray = $newTagArray;
+               $this->textArray = $newTextArray;
+       }
+       
+       /**
+        * Validates the attributes of a tag.
+        * 
+        * @param       array           $tag
+        * @return      boolean
+        */
+       protected function isValidTag(array $tag) {
+               if (isset($tag['attributes']) && count($tag['attributes']) > count($this->bbcodes[$tag['name']]->getAttributes())) {
+                       return false;
+               }
+               
+               foreach ($this->bbcodes[$tag['name']]->getAttributes() as $attribute) {
+                       if (!$this->isValidTagAttribute((isset($tag['attributes']) ? $tag['attributes'] : array()), $attribute)) {
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Validates an attributes of a tag.
+        * 
+        * @param       array                                   $tagAttributes
+        * @param       wcf\data\bbcode\BBCodeAttribute         $definedTagAttribute
+        * @return      boolean
+        */
+       protected function isValidTagAttribute(array $tagAttributes, BBCodeAttribute $definedTagAttribute) {
+               if ($definedTagAttribute->validationPattern && isset($tagAttributes[$definedTagAttribute->attributeNo])) {
+                       // validate attribute
+                       if (!preg_match('~'.$definedTagAttribute->validationPattern.'~i', $tagAttributes[$definedTagAttribute->attributeNo])) {
+                               return false;
+                       }
+               }
+                       
+               if ($definedTagAttribute->required && !$definedTagAttribute->useText && !isset($tagAttributes[$definedTagAttribute->attributeNo])) {
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Returns true if the text inside the given text needs to be buffered.
+        * 
+        * @param       array           $tag
+        * @return      boolean
+        */
+       protected function needBuffering(array $tag) {
+               // check for special bbcode class
+               if (!empty($this->bbcodes[$tag['name']]->className)) {
+                       return true;
+               }
+               
+               // search 'useText' attributes
+               foreach ($this->bbcodes[$tag['name']]->getAttributes() as $attribute) {
+                       if ($attribute->useText && !isset($tag['attributes'][$attribute->attributeNo])) {
+                               return true;
+                       }
+               }
+               
+               return false;
+       }
+       
+       /**
+        * Builds the opening tag.
+        * 
+        * @param       array           $tag
+        * @return      string
+        */
+       protected function buildOpeningTag(array $tag) {
+               // build attributes
+               $attributesString = '';
+               foreach ($this->bbcodes[$tag['name']]->getAttributes() as $attribute) {
+                       if (isset($tag['attributes'][$attribute->attributeNo])) {
+                               $atrributeString = '';
+                               if (!empty($attribute->attributeHtml)) {
+                                       $atrributeString = ' '.$attribute->attributeHtml;
+                               }
+                               
+                               if (!empty($atrributeString)) {
+                                       $attributesString .= sprintf($atrributeString, $tag['attributes'][$attribute->attributeNo]);
+                               }
+                       }
+               }
+               
+               // build tag
+               if (!empty($this->bbcodes[$tag['name']]->htmlOpen)) {
+                       return '<'.$this->bbcodes[$tag['name']]->htmlOpen.$attributesString.(empty($this->bbcodes[$tag['name']]->htmlClose) ? ' /' : '').'>';
+               }
+               
+               return '';
+       }
+       
+       /**
+        * Builds the closing tag.
+        * 
+        * @param       array           $tag
+        * @return      string
+        */
+       protected function buildClosingTag(array $tag) {
+               if (!empty($this->bbcodes[$tag['name']]->htmlClose)) {
+                       return '</'.$this->bbcodes[$tag['name']]->htmlClose.'>';
+               }
+               
+               return '';
+       }
+       
+       /**
+        * Returns true if the given tag is allowed in the given list of open tags.
+        * 
+        * @param       array           $openTags
+        * @param       string          $tag
+        * @param       boolean         $closing
+        * @return      boolean
+        */
+       protected function isAllowed(array $openTags, $tag, $closing = false) {
+               foreach ($openTags as $openTag) {
+                       if ($closing && $openTag == $tag) continue;
+                       if ($this->bbcodes[$openTag]->allowedChildren == 'all') continue;
+                       if ($this->bbcodes[$openTag]->allowedChildren == 'none') return false;
+                       
+                       $arguments = explode('^', $this->bbcodes[$openTag]->allowedChildren);
+                       if (!empty($arguments[1])) $tags = explode(',', $arguments[1]);
+                       else $tags = array();
+                       
+                       if ($arguments[0] == 'none' && !in_array($tag, $tags)) return false;
+                       if ($arguments[0] == 'all' && in_array($tag, $tags)) return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * Builds the parsed string.
+        */
+       public function buildParsedString() {
+               // reset parsed text
+               $this->parsedText = '';
+               
+               // create text buffer
+               $buffer =& $this->parsedText;
+               
+               // stack of buffered tags
+               $bufferedTagStack = array();
+               
+               // loop through the tags
+               $i = -1;
+               foreach ($this->tagArray as $i => $tag) {
+                       // append text to buffer
+                       $buffer .= $this->textArray[$i];
+                       
+                       if ($tag['closing']) {
+                               // get buffered opening tag
+                               $openingTag = end($bufferedTagStack);
+                               
+                               // closing tag
+                               if ($openingTag && $openingTag['name'] == $tag['name']) {
+                                       $hideBuffer = false;
+                                       // insert buffered content as attribute value
+                                       foreach ($this->bbcodes[$tag['name']]->getAttributes() as $attribute) {
+                                               if ($attribute->useText && !isset($openingTag['attributes'][$attribute->attributeNo])) {
+                                                       $openingTag['attributes'][$attribute->attributeNo] = $buffer;
+                                                       $hideBuffer = true;
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       // validate tag attributes again
+                                       if ($this->isValidTag($openingTag)) {
+                                               if ($this->bbcodes[$tag['name']]->getProcessor()) {
+                                                       // build tag
+                                                       $parsedTag = $this->bbcodes[$tag['name']]->getProcessor()->getParsedTag($openingTag, $buffer, $tag, $this);
+                                               }
+                                               else {
+                                                       // build tag
+                                                       $parsedTag = $this->buildOpeningTag($openingTag);
+                                                       $closingTag = $this->buildClosingTag($tag);
+                                                       if (!empty($closingTag) && $hideBuffer) $parsedTag .= $buffer.$closingTag;
+                                               }
+                                       }
+                                       else {
+                                               $parsedTag = $openingTag['source'].$buffer.$tag['source'];
+                                       }
+                                       
+                                       // close current buffer
+                                       array_pop($bufferedTagStack);
+                                       
+                                       // open previous buffer
+                                       if (count($bufferedTagStack) > 0) {
+                                               $bufferedTag =& $bufferedTagStack[count($bufferedTagStack) - 1];
+                                               $buffer =& $bufferedTag['buffer'];
+                                       }
+                                       else {
+                                               $buffer =& $this->parsedText;
+                                       }
+                                       
+                                       // append parsed tag
+                                       $buffer .= $parsedTag;
+                               }
+                               else {
+                                       $buffer .= $this->buildClosingTag($tag);
+                               }
+                       }
+                       else {
+                               // opening tag
+                               if ($this->needBuffering($tag)) {
+                                       // start buffering
+                                       $tag['buffer'] = '';
+                                       $bufferedTagStack[] = $tag;
+                                       $buffer =& $bufferedTagStack[(count($bufferedTagStack) - 1)]['buffer'];
+                               }
+                               else {
+                                       $buffer .= $this->buildOpeningTag($tag);
+                               }
+                       }
+               }
+               
+               if (isset($this->textArray[$i + 1])) $this->parsedText .= $this->textArray[$i + 1];
+       }
+       
+       /**
+        * Builds the tag array from the given text.
+        * 
+        * @param       boolean         $ignoreSoureCodes
+        */
+       public function buildTagArray($ignoreSoureCodes = true) {
+               // build tag pattern
+               $validTags = '';
+               if (!$ignoreSoureCodes) {
+                       $validTags = implode('|', array_keys($this->bbcodes));
+               }
+               else {
+                       foreach ($this->bbcodes as $tag => $bbcode) {
+                               if (!$bbcode->isSourceCode) {
+                                       // remove source codes
+                                       if (!empty($validTags)) $validTags .= '|';
+                                       $validTags .= $tag;
+                               }
+                       }
+               }
+               $pattern = '~\[(?:/(?:'.$validTags.')|(?:'.$validTags.')
+                       (?:=
+                               (?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*)
+                               (?:,(?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*))*
+                       )?)\]~ix';
+               
+               // get bbcode tags
+               preg_match_all($pattern, $this->text, $matches);
+               $this->tagArray = $matches[0];
+               unset($matches);
+               
+               // build tags
+               for ($i = 0, $j = count($this->tagArray); $i < $j; $i++) {
+                       $this->tagArray[$i] = $this->buildTag($this->tagArray[$i]);
+               }
+               
+               // get text
+               $this->textArray = preg_split($pattern, $this->text);
+       }
+       
+       /**
+        * Builds a bbcode tag.
+        * 
+        * @param       string          $string
+        * @return      array           bbcode tag data
+        */
+       protected function buildTag($string) {
+               $tag = array('name' => '', 'closing' => false, 'source' => $string);
+               
+               if (StringUtil::substring($string, 1, 1) == '/') {
+                       // closing tag
+                       $tag['name'] = StringUtil::toLowerCase(StringUtil::substring($string, 2, StringUtil::length($string) - 3));
+                       $tag['closing'] = true;
+               }
+               else {
+                       // opening tag
+                       // split tag and attributes
+                       preg_match("!^\[([a-z0-9]+)=?(.*)]$!si", $string, $match);
+                       $tag['name'] = StringUtil::toLowerCase($match[1]);
+                       
+                       // build attributes
+                       if (!empty($match[2])) {
+                               $tag['attributes'] = $this->buildTagAttributes($match[2]);
+                       }
+               }
+               
+               return $tag;
+       }
+       
+       /**
+        * Builds the attributes of a bbcode tag.
+        * 
+        * @param       string          $string
+        * @return      array           bbcode attributes
+        */
+       protected function buildTagAttributes($string) {
+               preg_match_all("~(?:^|,)('[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|[^,]*)~", $string, $matches);
+               
+               // remove quotes
+               for ($i = 0, $j = count($matches[1]); $i < $j; $i++) {
+                       if (StringUtil::substring($matches[1][$i], 0, 1) == "'" && StringUtil::substring($matches[1][$i], -1) == "'") {
+                               $matches[1][$i] = StringUtil::replace("\'", "'", $matches[1][$i]);
+                               $matches[1][$i] = StringUtil::replace("\\\\", "\\", $matches[1][$i]);
+                               
+                               $matches[1][$i] = StringUtil::substring($matches[1][$i], 1, -1);
+                       }
+               }
+               
+               return $matches[1];
+       }
+       
+       /**
+        * Validates the used BBCodes in the given text by the given allowed
+        * BBCodes and returns a list of used disallowed BBCodes.
+        * 
+        * @param       string                  $text
+        * @param       array<string>           $allowedBBCodes
+        * @return      array<string>
+        */
+       public function validateBBCodes($text, array $allowedBBCodes) {
+               // if all BBCodes are allowed, return directly
+               if (in_array('all', $allowedBBCodes)) {
+                       return array();
+               }
+               
+               $this->setText($text);
+               $this->buildTagArray(false);
+               
+               $usedDisallowedBBCodes = array();
+               foreach ($this->tagArray as $tag) {
+                       if (!in_array($tag['name'], $allowedBBCodes) && !isset($usedDisallowedBBCodes[$tag['name']])) {
+                               $usedDisallowedBBCodes[$tag['name']] = $tag['name'];
+                       }
+               }
+               
+               return $usedDisallowedBBCodes;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/CodeBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/CodeBBCode.class.php
new file mode 100644 (file)
index 0000000..9aaab31
--- /dev/null
@@ -0,0 +1,279 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\system\Regex;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Parses the [code] bbcode tag.
+ * 
+ * @author     Tim Duesterhus, Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class CodeBBCode extends AbstractBBCode {
+       /**
+        * code type attribute value
+        * @var string
+        */
+       protected $codeType = '';
+       
+       /**
+        * file name attribute value
+        * @var string
+        */
+       protected $filename = '';
+       
+       /**
+        * start line numer attribute value
+        * @var string
+        */
+       protected $startLineNumber = 1;
+       
+       /**
+        * already used ids for line numbers to prevent duplicate ids in the output
+        * @var array<string>
+        */
+       private static $codeIDs = array();
+       
+       /**
+        * @see wcf\system\bbcode\IBBCode::getParsedTag()
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               // encode html
+               $content = self::trim($content);
+               
+               // get attributes
+               $this->mapAttributes($openingTag);
+               
+               // fetch highlighter-classname
+               $className = '\wcf\system\bbcode\highlighter\PlainHighlighter';
+               if ($this->codeType) {
+                       $className = '\wcf\system\bbcode\highlighter\\'.StringUtil::firstCharToUpperCase(StringUtil::toLowerCase($this->codeType)).'Highlighter';
+                       
+                       switch (StringUtil::substring($className, strlen('\wcf\system\bbcode\highlighter\\'))) {
+                               case 'ShellHighlighter':
+                                       $className = '\wcf\system\bbcode\highlighter\BashHighlighter';
+                               break;
+                               
+                               case 'C++Highlighter':
+                                       $className = '\wcf\system\bbcode\highlighter\CHighlighter';
+                               break;
+                               
+                               case 'JavascriptHighlighter':
+                                       $className = '\wcf\system\bbcode\highlighter\JsHighlighter';
+                               break;
+                               
+                               case 'LatexHighlighter':
+                                       $className = '\wcf\system\bbcode\highlighter\TexHighlighter';
+                               break;
+                       }
+               }
+               else {
+                       // try to guess highlighter
+                       if (StringUtil::indexOf($content, '<?php') !== false) {
+                               $className = '\wcf\system\bbcode\highlighter\PhpHighlighter';
+                       }
+                       else if (StringUtil::indexOf($content, '<html') !== false) {
+                               $className = '\wcf\system\bbcode\highlighter\HtmlHighlighter';
+                       }
+                       else if (StringUtil::indexOf($content, '<?xml') === 0) {
+                               $className = '\wcf\system\bbcode\highlighter\XmlHighlighter';
+                       }
+                       else if (       StringUtil::indexOf($content, 'SELECT') === 0
+                                       ||      StringUtil::indexOf($content, 'UPDATE') === 0
+                                       ||      StringUtil::indexOf($content, 'INSERT') === 0
+                                       ||      StringUtil::indexOf($content, 'DELETE') === 0) {
+                               $className = '\wcf\system\bbcode\highlighter\SqlHighlighter';
+                       }
+                       else if (StringUtil::indexOf($content, 'import java.') !== false) {
+                               $className = '\wcf\system\bbcode\highlighter\JavaHighlighter';
+                       }
+                       else if (       StringUtil::indexOf($content, "---") !== false
+                                       &&      StringUtil::indexOf($content, "\n+++") !== false) {
+                               $className = '\wcf\system\bbcode\highlighter\DiffHighlighter';
+                       }
+                       else if (StringUtil::indexOf($content, "\n#include ") !== false) {
+                               $className = '\wcf\system\bbcode\highlighter\CHighlighter';
+                       }
+                       else if (StringUtil::indexOf($content, '#!/usr/bin/perl') === 0) {
+                               $className = '\wcf\system\bbcode\highlighter\PerlHighlighter';
+                       }
+                       else if (StringUtil::indexOf($content, 'def __init__(self') !== false) {
+                               $className = '\wcf\system\bbcode\highlighter\PythonHighlighter';
+                       }
+                       else if (Regex::compile('^#!/bin/(ba|z)?sh')->match($content)) {
+                               $className = '\wcf\system\bbcode\highlighter\BashHighlighter';
+                       }
+                       else if (StringUtil::indexOf($content, '\\documentclass') !== false) {
+                               $className = '\wcf\system\bbcode\highlighter\TexHighlighter';
+                       }
+                       else if (Regex::compile('[-\\+\\.,\\[\\]\\>\\<]{9}')->match($content)) {
+                               // 9 times a brainfuck char in a row -> seems to be brainfuck
+                               $className = '\wcf\system\bbcode\highlighter\BrainfuckHighlighter';
+                       }
+               }
+               
+               if (!class_exists($className)) {
+                       $className = '\wcf\system\bbcode\highlighter\PlainHighlighter';
+               }
+               
+               if ($parser->getOutputType() == 'text/html') {
+                       $highlightedContent = self::fixMarkup(explode("\n", $className::getInstance()->highlight($content)));
+                       
+                       // show template
+                       WCF::getTPL()->assign(array(
+                               'lineNumbers' => self::makeLineNumbers($content, $this->startLineNumber),
+                               'startLineNumber' => $this->startLineNumber,
+                               'content' => $highlightedContent,
+                               'highlighter' => $className::getInstance(),
+                               'filename' => $this->filename
+                       ));
+                       return WCF::getTPL()->fetch('codeBBCodeTag');
+               }
+               else if ($parser->getOutputType() == 'text/simplified-html') {
+                       return WCF::getLanguage()->getDynamicVariable('wcf.bbcode.code.text', array(
+                               'highlighterTitle' => $className::getInstance()->getTitle(),
+                               'lines' => substr_count($content, "\n") + 1
+                       ));
+               }
+       }
+       
+       /**
+        * Maps the arguments to the property they represent.
+        * 
+        * @param       array           $openingTag
+        */
+       protected function mapAttributes(array $openingTag) {
+               // reset default values
+               $this->codeType = '';
+               $this->startLineNumber = 1;
+               $this->filename = '';
+               
+               if (!isset($openingTag['attributes'])) {
+                       return;
+               }
+               
+               $attributes = $openingTag['attributes'];
+               switch (count($attributes)) {
+                       case 1:
+                               if (is_numeric($attributes[0])) {
+                                       $this->startLineNumber = intval($attributes[0]);
+                               }
+                               else if (StringUtil::indexOf($attributes[0], '.') === false) {
+                                       $this->codeType = $attributes[0];
+                               }
+                               else {
+                                       $this->filename = $attributes[0];
+                               }
+                       break;
+                       
+                       case 2:
+                               if (is_numeric($attributes[0])) {
+                                       $this->startLineNumber = intval($attributes[0]);
+                                       if (StringUtil::indexOf($attributes[1], '.') === false) {
+                                               $this->codeType = $attributes[1];
+                                       }
+                                       else {
+                                               $this->filename = $attributes[1];
+                                       }
+                               }
+                               else {
+                                       $this->codeType = $attributes[0];
+                                       $this->filename = $attributes[1];
+                               }
+                       break;
+                       
+                       default:
+                               $this->codeType = $attributes[0];
+                               $this->startLineNumber = intval($attributes[1]);
+                               $this->filename = $attributes[2];
+                       break;
+               }
+               
+               // correct illegal line number
+               if ($this->startLineNumber < 1) {
+                       $this->startLineNumber = 1;
+               }
+       }
+       
+       /**
+        * Returns a string with all line numbers
+        * 
+        * @param       string          $code
+        * @param       integer         $start
+        * @return      string
+        */
+       protected static function makeLineNumbers($code, $start, $split = "\n") {
+               $lines = explode($split, $code);
+               
+               $lineNumbers = array();
+               $i = -1;
+               // find an unused codeID
+               do {
+                       $codeID = StringUtil::substring(StringUtil::getHash($code), 0, 6).(++$i ? '_'.$i : '');
+               }
+               while (isset(self::$codeIDs[$codeID]));
+               
+               // mark codeID as used
+               self::$codeIDs[$codeID] = true;
+               
+               for ($i = $start, $j = count($lines) + $start; $i < $j; $i++) {
+                       $lineNumbers[$i] = 'codeLine_'.$i.'_'.$codeID;
+               }
+               return $lineNumbers;
+       }
+       
+       /**
+        * Removes empty lines from the beginning and end of a string.
+        * 
+        * @param       string          $string
+        * @return      string
+        */
+       protected static function trim($string) {
+               $string = preg_replace('/^\s*\n/', '', $string);
+               $string = preg_replace('/\n\s*$/', '', $string);
+               return $string;
+       }
+       
+       /**
+        * Fixes markup that every line has proper number of opening and closing tags
+        * 
+        * @param       array<string>   $lines
+        */
+       public static function fixMarkup(array $lines) {
+               static $spanRegex = null;
+               static $emptyTagRegex = null;
+               if ($spanRegex === null) {
+                       $spanRegex = new Regex('(?:<span(?: class="(?:[^"])*")?>|</span>)');
+                       $emptyTagRegex = new Regex('<span(?: class="(?:[^"])*")?></span>');
+               }
+               
+               $openTags = array();
+               foreach ($lines as &$line) {
+                       $spanRegex->match($line, true);
+                       // open all tags again
+                       $line = implode('', $openTags).$line;
+                       $matches = $spanRegex->getMatches();
+                       
+                       // parse opening and closing spans
+                       foreach ($matches[0] as $match) {
+                               if ($match === '</span>') array_pop($openTags);
+                               else {
+                                       array_push($openTags, $match);
+                               }
+                       }
+                       
+                       // close all tags
+                       $line .= str_repeat('</span>', count($openTags));
+                       
+                       // remove empty tags to avoid cluttering the output
+                       $line = $emptyTagRegex->replace($line, '');
+               }
+               unset($line);
+               return $lines;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/IBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/IBBCode.class.php
new file mode 100644 (file)
index 0000000..b78bc9f
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\IDatabaseObjectProcessor;
+
+/**
+ * Any special bbcode class should implement this interface.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf..bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+interface IBBCode extends IDatabaseObjectProcessor {
+       /**
+        * Returns the parsed bbcode tag.
+        * 
+        * @param       array                                   $openingTag
+        * @param       string                                  $content
+        * @param       array                                   $closingTag
+        * @param       wcf\system\bbcode\BBCodeParser          $parser
+        * @return      string
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser);
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/ImageBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/ImageBBCode.class.php
new file mode 100644 (file)
index 0000000..5d2c371
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\util\StringUtil;
+
+/**
+ * Parses the [img] bbcode tag.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class ImageBBCode extends AbstractBBCode {
+       /**
+        * @see wcf\system\bbcode\IBBCode::getParsedTag()
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               $src = '';
+               if (isset($openingTag['attributes'][0])) {
+                       $src = $openingTag['attributes'][0];
+               }
+               
+               if ($parser->getOutputType() == 'text/html') {
+                       $float = '';
+                       if (isset($openingTag['attributes'][1])) {
+                               $float = $openingTag['attributes'][1];
+                       }
+                       
+                       return '<img src="'.$src.'" class="jsResizeImage" alt=""'.($float ? ('style="float: '.$float.'; margin: ' . ($float == 'left' ? '0 15px 7px 0' : '0 0 7px 15px') . '"') : '').' />';
+               }
+               else if ($parser->getOutputType() == 'text/simplified-html') {
+                       $src = StringUtil::decodeHTML($src);
+                       return StringUtil::getAnchorTag($src);
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/KeywordHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/KeywordHighlighter.class.php
new file mode 100644 (file)
index 0000000..f30bce7
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\system\SingletonFactory;
+use wcf\util\ArrayUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Highlights keywords in text messages.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class KeywordHighlighter extends SingletonFactory {
+       /**
+        * search keywords
+        * @var array<string>
+        */
+       protected $keywords = array();
+       
+       /**
+        * search query parameters
+        * @var array<string>
+        */
+       protected static $searchQueryKeys = array(
+               'q',            // google, msn, altavista
+               'p',            // yahoo
+               'query',        // lycos, fireball
+               'eingabe',      // metager
+               'begriff',      // acoon.de
+               'keyword',      // fixx.de
+               'search',       // excite.co.jp
+               // 'highlight', // burning board and other bulletin board systems ;)
+               
+               // ???:
+               'ask',
+               'searchfor',
+               'key',
+               'keywords',
+               'qry',
+               'searchitem',
+               'kwd',
+               'recherche',
+               'search_text',
+               'search_term',
+               'term',
+               'terms',
+               'qq',
+               'qry_str',
+               'qu',
+               //'s',
+               //'k',
+               //'t',
+               'va'
+       );
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               // take keywords from request
+               if (isset($_GET['highlight'])) {
+                       $this->parseKeywords($_GET['highlight']);
+               }
+               // take keywords from referer
+               else if (!empty($_SERVER['HTTP_REFERER'])) {
+                       $url = parse_url($_SERVER['HTTP_REFERER']);
+                       if (!empty($url['query'])) {
+                               $query = explode('&', $url['query']);
+                               foreach ($query as $element) {
+                                       if (strpos($element, '=') === false) continue;
+                                       list($varname, $value) = explode('=', $element, 2);
+                                       
+                                       if (in_array($varname, static::$searchQueryKeys)) {
+                                               $this->parseKeywords(urldecode($value));
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               
+               if (!empty($this->keywords)) {
+                       $this->keywords = array_unique($this->keywords);
+                       $this->keywords = array_map('preg_quote', $this->keywords);
+               }
+       }
+       
+       /**
+        * Parses search keywords.
+        * 
+        * @param       string          $keywordString
+        */
+       protected function parseKeywords($keywordString) {
+               // convert encoding if necessary
+               if (!StringUtil::isASCII($keywordString) && !StringUtil::isUTF8($keywordString)) {
+                       $keywordString = StringUtil::convertEncoding('ISO-8859-1', 'UTF-8', $keywordString);
+               }
+               
+               // remove bad wildcards
+               $keywordString = preg_replace('/(?<!\w)\*/', '', $keywordString);
+               
+               // remove search operators
+               $keywordString = preg_replace('/[\+\-><()~]+/', '', $keywordString);
+               
+               if (StringUtil::substring($keywordString, 0, 1) == '"' && StringUtil::substring($keywordString, -1) == '"') {
+                       // phrases search
+                       $keywordString = StringUtil::trim(StringUtil::substring($keywordString, 1, -1));
+                       
+                       if (!empty($keywordString)) {
+                               $this->keywords = array_merge($this->keywords, array(StringUtil::encodeHTML($keywordString)));
+                       }
+               }
+               else {
+                       // replace word delimiters by space
+                       $keywordString = str_replace(array('.', ','), ' ', $keywordString);
+                       
+                       $keywords = ArrayUtil::encodeHTML(ArrayUtil::trim(explode(' ', $keywordString)));
+                       if (!empty($keywords)) {
+                               $this->keywords = array_merge($this->keywords, $keywords);
+                       }
+               }
+       }
+       
+       /**
+        * Highlights search keywords.
+        * 
+        * @param       string          $text
+        * @return      string          highlighted text
+        */
+       public function doHighlight($text) {
+               if (empty($this->keywords)) return $text;
+               
+               $keywordPattern = '('.implode('|', $this->keywords).')';
+               $keywordPattern = StringUtil::replace('\*', '\w*', $keywordPattern);
+               return preg_replace('+(?<!&|&\w{1}|&\w{2}|&\w{3}|&\w{4}|&\w{5}|&\w{6})'.$keywordPattern.'(?![^<]*>)+i', '<span class="highlight">\\1</span>', $text);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/ListBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/ListBBCode.class.php
new file mode 100644 (file)
index 0000000..a7c2135
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\util\StringUtil;
+
+/**
+ * Parses the [list] bbcode tag.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class ListBBCode extends AbstractBBCode {
+       /**
+        * @see wcf\system\bbcode\IBBCode::getParsedTag()
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               if (StringUtil::indexOf($content, '[*]') !== false) {
+                       // get list elements
+                       $listElements = preg_split('/\[\*\]/', StringUtil::trim($content), -1, PREG_SPLIT_NO_EMPTY);
+                       
+                       // remove empty elements
+                       foreach ($listElements as $key => $val) {
+                               $listElements[$key] = StringUtil::trim($val);
+                               if (empty($listElements[$key]) || $listElements[$key] == '<br />') {
+                                       unset($listElements[$key]);
+                               }
+                       }
+                       
+                       if (!empty($listElements)) {
+                               // get list style type
+                               $listType = 'disc';
+                               if (isset($openingTag['attributes'][0])) $listType = $openingTag['attributes'][0];
+                               $listType = strtolower($listType);
+                               
+                               // replace old types
+                               if ($listType == '1') $listType = 'decimal';
+                               if ($listType == 'a') $listType = 'lower-latin';
+                               
+                               if ($parser->getOutputType() == 'text/html') {
+                                       // build list html
+                                       $listHTML = 'ol';
+                                       if ($listType == 'none' || $listType == 'circle' || $listType == 'square' || $listType == 'disc') {
+                                               $listHTML = 'ul';
+                                       }
+                                       
+                                       return '<'.$listHTML.' style="list-style-type: '.$listType.'" class="nativeList"><li>'.implode('</li><li>', $listElements).'</li></'.$listHTML.'>';
+                               }
+                               else if ($parser->getOutputType() == 'text/simplified-html') {
+                                       $result = '';
+                                       
+                                       $i = 1;
+                                       foreach ($listElements as $listElement) {
+                                               switch ($listType) {
+                                                       case 'decimal':
+                                                               $result .= $i.'. ';
+                                                               break;
+                                                       default:
+                                                               $result .= '- ';
+                                               }
+                                               
+                                               $result .= $listElement."\n";
+                                               $i++;
+                                       }
+                                       
+                                       return $result;
+                               }
+                       }
+               }
+               
+               // no valid list
+               // return bbcode as text
+               return $openingTag['source'].$content.$closingTag['source'];
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/MediaBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/MediaBBCode.class.php
new file mode 100644 (file)
index 0000000..8a9d5cb
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\bbcode\media\provider\BBCodeMediaProvider;
+use wcf\util\StringUtil;
+
+/**
+ * Parses the [media] bbcode tag.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class MediaBBCode extends AbstractBBCode {
+       /**
+        * @see wcf\system\bbcode\IBBCode::getParsedTag()
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               $content = StringUtil::trim($content);
+               
+               if ($parser->getOutputType() == 'text/html') {
+                       foreach (BBCodeMediaProvider::getCache() as $provider) {
+                               if ($provider->matches($content)) {
+                                       return $provider->getOutput($content);
+                               }
+                       }
+               }
+               if ($parser->getOutputType() == 'text/simplified-html') {
+                       foreach (BBCodeMediaProvider::getCache() as $provider) {
+                               if ($provider->matches($content)) {
+                                       return StringUtil::getAnchorTag(StringUtil::decodeHTML($content));
+                               }
+                       }
+               }
+               
+               return $content;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/MessageParser.class.php b/wcfsetup/install/files/lib/system/bbcode/MessageParser.class.php
new file mode 100644 (file)
index 0000000..0a04658
--- /dev/null
@@ -0,0 +1,227 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\bbcode\attribute\BBCodeAttribute;
+use wcf\data\smiley\SmileyCache;
+use wcf\system\event\EventHandler;
+use wcf\util\StringUtil;
+
+/**
+ * Parses bbcode tags, smilies etc. in messages.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class MessageParser extends BBCodeParser {
+       /**
+        * list of smilies
+        * @var array<wcf\data\smiley\Smiley>
+        */
+       protected $smilies = array();
+       
+       /**
+        * cached bbcodes
+        * @var array
+        */
+       protected $cachedCodes = array();
+       
+       /**
+        * regular expression for source code tags
+        * @var string
+        */
+       protected $sourceCodeRegEx = '';
+       
+       /**
+        * currently parsed message
+        * @var string
+        */
+       public $message = '';
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               parent::init();
+               
+               // handle source codes
+               $sourceCodeTags = array();
+               foreach ($this->bbcodes as $bbcode) {
+                       if ($bbcode->isSourceCode) $sourceCodeTags[] = $bbcode->bbcodeTag;
+               }
+               if (!empty($sourceCodeTags)) $this->sourceCodeRegEx = implode('|', $sourceCodeTags);
+               
+               if (MODULE_SMILEY == 1) {
+                       // get smilies
+                       $smilies = SmileyCache::getInstance()->getSmilies();
+                       $categories = SmileyCache::getInstance()->getCategories();
+                       foreach ($smilies as $categoryID => $categorySmilies) {
+                               if ($categories[$categoryID ?: null]->isDisabled) continue;
+                               
+                               foreach ($categorySmilies as $smiley) {
+                                       foreach ($smiley->smileyCodes as $smileyCode) {
+                                               $this->smilies[$smileyCode] = '<img src="'.$smiley->getURL().'" alt="'.StringUtil::encodeHTML($smiley->smileyCode).'" />';
+                                       }
+                               }
+                       }
+                       krsort($this->smilies);
+               }
+       }
+       
+       /**
+        * Parses a message.
+        * 
+        * @param       string          $message
+        * @param       boolean         $enableSmilies
+        * @param       boolean         $enableHtml
+        * @param       boolean         $enableBBCodes
+        * @param       boolean         $doKeywordHighlighting
+        * @return      string          parsed message
+        */
+       public function parse($message, $enableSmilies = true, $enableHtml = false, $enableBBCodes = true, $doKeywordHighlighting = true) {
+               $this->cachedCodes = array();
+               $this->message = $message;
+               
+               // call event
+               EventHandler::getInstance()->fireAction($this, 'beforeParsing');
+               
+               if ($enableBBCodes) {
+                       // cache codes
+                       $this->message = $this->cacheCodes($this->message);
+               }
+               
+               if (!$enableHtml) {
+                       // encode html
+                       $this->message = StringUtil::encodeHTML($this->message);
+                       
+                       // converts newlines to <br />'s
+                       if ($this->getOutputType() == 'text/html') {
+                               $this->message = nl2br($this->message);
+                       }
+               }
+               
+               // parse bbcodes
+               if ($enableBBCodes) {
+                       $this->message = parent::parse($this->message);
+               }
+               
+               // parse smilies
+               if ($enableSmilies) {
+                       $this->message = $this->parseSmilies($this->message, $enableHtml);
+               }
+               
+               if ($enableBBCodes && !empty($this->cachedCodes)) {
+                       // insert cached codes
+                       $this->message = $this->insertCachedCodes($this->message);
+               }
+               
+               // highlight search query
+               if ($doKeywordHighlighting) {
+                       $this->message = KeywordHighlighter::getInstance()->doHighlight($this->message);
+               }
+               
+               // replace bad html tags (script etc.)
+               $badSearch = array('/(javascript):/i', '/(about):/i', '/(vbscript):/i');
+               $badReplace = array('$1<b></b>:', '$1<b></b>:', '$1<b></b>:');
+               $this->message = preg_replace($badSearch, $badReplace, $this->message);
+               
+               // call event
+               EventHandler::getInstance()->fireAction($this, 'afterParsing');
+               
+               return $this->message;
+       }
+       
+       /**
+        * Parses smiley codes.
+        * 
+        * @param       string          $text
+        * @return      string          text
+        */
+       protected function parseSmilies($text, $enableHtml = false) {
+               foreach ($this->smilies as $code => $html) {
+                       //$text = preg_replace('~(?<!&\w{2}|&\w{3}|&\w{4}|&\w{5}|&\w{6}|&#\d{2}|&#\d{3}|&#\d{4}|&#\d{5})'.preg_quote((!$enableHtml ? StringUtil::encodeHTML($code) : $code), '~').'(?![^<]*>)~', $html, $text);
+                       $text = preg_replace('~(?<=^|\s)'.preg_quote((!$enableHtml ? StringUtil::encodeHTML($code) : $code), '~').'(?=$|\s|</li>'.(!$enableHtml ? '|<br />' : '').')~', $html, $text);
+               }
+               
+               return $text;
+       }
+       
+       /**
+        * Caches code bbcodes to avoid parsing of smileys and other bbcodes inside them.
+        * 
+        * @param       string          $text
+        * @return      string
+        */
+       protected function cacheCodes($text) {
+               if (!empty($this->sourceCodeRegEx)) {
+                       $text = preg_replace_callback("~(\[(".$this->sourceCodeRegEx.")
+                               (?:=
+                                       (?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*)
+                                       (?:,(?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*))*
+                               )?\])
+                               (.*?)
+                               (?:\[/\\2\])~six", array($this, 'cacheCodesCallback'), $text);
+               }
+               return $text;
+       }
+       
+       /**
+        * Returns the hash for an matched code bbcode in the message.
+        * 
+        * @param       array           $matches
+        * @return      string
+        */
+       protected function cacheCodesCallback($matches) {
+               // create hash
+               $hash = '@@'.StringUtil::getHash(uniqid(microtime()).$matches[3]).'@@';
+               
+               // build tag
+               $tag = $this->buildTag($matches[1]);
+               $tag['content'] = $matches[3];
+               
+               // save tag
+               $this->cachedCodes[$hash] = $tag;
+               
+               return $hash;
+       }
+       
+       /**
+        * Reinserts cached code bbcodes.
+        * 
+        * @param       string          $text
+        * @return      string
+        */
+       protected function insertCachedCodes($text) {
+               foreach ($this->cachedCodes as $hash => $tag) {
+                       // build code and insert
+                       if ($this->bbcodes[$tag['name']]->className) {
+                               $replacement = $this->bbcodes[$tag['name']]->getProcessor()->getParsedTag($tag, $tag['content'], $tag, $this);
+                       }
+                       else {
+                               $replacement = $this->buildOpeningTag($tag) . StringUtil::encodeHTML($tag['content']) . $this->buildClosingTag($tag);
+                       }
+                       
+                       $text = str_replace($hash, $replacement, $text);
+               }
+               
+               return $text;
+       }
+       
+       /**
+        * @see wcf\system\bbcode\BBCodeParser::isValidTagAttribute()
+        */
+       protected function isValidTagAttribute(array $tagAttributes, BBCodeAttribute $definedTagAttribute) {
+               if (!parent::isValidTagAttribute($tagAttributes, $definedTagAttribute)) {
+                       return false;
+               }
+               
+               // check for cached codes
+               if (isset($tagAttributes[$definedTagAttribute->attributeNo]) && preg_match('/@@[a-f0-9]{40}@@/', $tagAttributes[$definedTagAttribute->attributeNo])) {
+                       return false;
+               }
+               
+               return true;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/PreParser.class.php b/wcfsetup/install/files/lib/system/bbcode/PreParser.class.php
new file mode 100644 (file)
index 0000000..0b4b90f
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\bbcode\media\provider\BBCodeMediaProvider;
+use wcf\data\bbcode\BBCode;
+use wcf\data\bbcode\BBCodeCache;
+use wcf\system\event\EventHandler;
+use wcf\system\Callback;
+use wcf\system\Regex;
+use wcf\system\SingletonFactory;
+use wcf\util\StringStack;
+use wcf\util\StringUtil;
+
+/**
+ * Parses message before inserting them into the database.
+ * 
+ * @author     Tim Duesterhus, Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class PreParser extends SingletonFactory {
+       /**
+        * forbidden characters
+        * @var string
+        */
+       protected static $illegalChars = '[^\x0-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+';
+       
+       /**
+        * list of allowed bbcode tags
+        * @var array<string>
+        */
+       public $allowedBBCodes = null;
+       
+       /**
+        * regular expression for source codes
+        * @var string
+        */
+       protected $sourceCodeRegEx = '';
+       
+       /**
+        * text
+        * @var string
+        */
+       public $text = '';
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               $sourceCodeTags = array();
+               foreach (BBCodeCache::getInstance()->getBBCodes() as $bbcode) {
+                       if ($bbcode->isSourceCode) $sourceCodeTags[] = $bbcode->bbcodeTag;
+               }
+               if (!empty($sourceCodeTags)) $this->sourceCodeRegEx = implode('|', $sourceCodeTags);
+       }
+       
+       /**
+        * Preparses the given text.
+        * 
+        * @param       string                  $text
+        * @param       array<string>           $allowedBBCodes
+        * @return      string
+        */
+       public function parse($text, array $allowedBBCodes = null) {
+               $this->text = $text;
+               $this->allowedBBCodes = $allowedBBCodes;
+               
+               // cache codes
+               $this->cacheCodes();
+               
+               // call event
+               EventHandler::getInstance()->fireAction($this, 'beforeParsing');
+               
+               // parse urls
+               if ($this->allowedBBCodes === null || BBCode::isAllowedBBCode('media', $this->allowedBBCodes) || BBCode::isAllowedBBCode('url', $this->allowedBBCodes)) {
+                       $this->parseURLs();
+               }
+               
+               // parse email addresses
+               if ($this->allowedBBCodes === null || BBCode::isAllowedBBCode('email', $this->allowedBBCodes)) {
+                       $this->parseEmails();
+               }
+               
+               // call event
+               EventHandler::getInstance()->fireAction($this, 'afterParsing');
+               
+               // insert cached codes
+               $this->insertCachedCodes();
+               
+               return $this->text;
+       }
+       
+       /**
+        * Handles pre-parsing of email addresses.
+        */
+       protected function parseEmails() {
+               if (StringUtil::indexOf($this->text, '@') === false) return;
+               
+               static $emailPattern = null;
+               if ($emailPattern === null) {
+                       $emailPattern = new Regex('
+                       (?<!\B|"|\'|=|/|\]|,|:)
+                       (?:)
+                       \w+(?:[\.\-]\w+)*
+                       @
+                       (?:'.self::$illegalChars.'\.)+          # hostname
+                       (?:[a-z]{2,4}(?=\b))
+                       (?!"|\'|\[|\-|\.[a-z])', Regex::IGNORE_WHITESPACE | Regex::CASE_INSENSITIVE);
+               }
+               
+               $this->text = $emailPattern->replace($this->text, '[email]\\0[/email]');
+       }
+       
+       /**
+        * Handles pre-parsing of URLs.
+        */
+       protected function parseURLs() {
+               static $urlPattern = null;
+               static $callback = null;
+               if ($urlPattern === null) {
+                       $urlPattern = new Regex('
+                       (?<!\B|"|\'|=|/|\]|,|\?)
+                       (?:                                             # hostname
+                               (?:ftp|https?)://'.static::$illegalChars.'(?:\.'.static::$illegalChars.')*
+                               |
+                               www\.(?:'.static::$illegalChars.'\.)+
+                               (?:[a-z]{2,4}(?=\b))
+                       )
+                       
+                       (?::\d+)?                                       # port
+                       
+                       (?:
+                               /
+                               [^!.,?;"\'<>()\[\]{}\s]*
+                               (?:
+                                       [!.,?;(){}]+ [^!.,?;"\'<>()\[\]{}\s]+
+                               )*
+                       )?', Regex::IGNORE_WHITESPACE | Regex::CASE_INSENSITIVE);
+               }
+               if ($callback === null) {
+                       $callback = new Callback(function ($matches) {
+                               if ((PreParser::getInstance()->allowedBBCodes === null || BBCode::isAllowedBBCode('media', PreParser::getInstance()->allowedBBCodes)) && BBCodeMediaProvider::isMediaURL($matches[0])) {
+                                       return '[media]'.$matches[0].'[/media]';
+                               }
+                               
+                               if (PreParser::getInstance()->allowedBBCodes === null || BBCode::isAllowedBBCode('url', PreParser::getInstance()->allowedBBCodes)) {
+                                       return '[url]'.$matches[0].'[/url]';
+                               }
+                               
+                               return $matches[0];
+                       });
+               }
+               
+               $this->text = $urlPattern->replace($this->text, $callback);
+       }
+       
+       /**
+        * Caches code bbcodes to avoid parsing inside them.
+        */
+       protected function cacheCodes() {
+               if (!empty($this->sourceCodeRegEx)) {
+                       static $bbcodeRegex = null;
+                       static $callback = null;
+                       
+                       if ($bbcodeRegex === null) {
+                               $bbcodeRegex = new Regex("
+                               (\[(".$this->sourceCodeRegEx.")
+                               (?:=
+                                       (?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*)
+                                       (?:,(?:\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'|[^,\]]*))*
+                               )?\])
+                               (.*?)
+                               (?:\[/\\2\])", Regex::DOT_ALL | Regex::IGNORE_WHITESPACE | Regex::CASE_INSENSITIVE);
+                               
+                               $callback = new Callback(function ($matches) {
+                                       return StringStack::pushToStringStack($matches[0], 'preParserCode');
+                               });
+                       }
+                       
+                       $this->cachedCodes = array();
+                       $this->text = $bbcodeRegex->replace($this->text, $callback);
+               }
+       }
+       
+       /**
+        * Reinserts cached code bbcodes.
+        */
+       protected function insertCachedCodes() {
+               $this->text = StringStack::reinsertStrings($this->text, 'preParserCode');
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/QuoteBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/QuoteBBCode.class.php
new file mode 100644 (file)
index 0000000..0cbb717
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\system\WCF;
+
+/**
+ * Parses the [quote] bbcode tag.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class QuoteBBCode extends AbstractBBCode {
+       /**
+        * @see wcf\system\bbcode\IBBCode::getParsedTag()
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               if ($parser->getOutputType() == 'text/html') {
+                       WCF::getTPL()->assign(array(
+                               'content' => $content,
+                               'quoteLink' => (!empty($openingTag['attributes'][1]) ? $openingTag['attributes'][1] : ''),
+                               'quoteAuthor' => (!empty($openingTag['attributes'][0]) ? $openingTag['attributes'][0] : '')
+                       ));
+                       return WCF::getTPL()->fetch('quoteBBCodeTag');
+               }
+               else if ($parser->getOutputType() == 'text/simplified-html') {
+                       return WCF::getLanguage()->getDynamicVariable('wcf.bbcode.quote.text', array('content' => $content, 'cite' => (!empty($openingTag['attributes'][0]) ? $openingTag['attributes'][0] : '')));
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/SimpleMessageParser.class.php b/wcfsetup/install/files/lib/system/bbcode/SimpleMessageParser.class.php
new file mode 100644 (file)
index 0000000..1b8fed0
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\smiley\SmileyCache;
+use wcf\system\event\EventHandler;
+use wcf\system\SingletonFactory;
+use wcf\util\StringUtil;
+
+/**
+ * Parses urls and smilies in simple messages.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class SimpleMessageParser extends SingletonFactory {
+       /**
+        * forbidden characters
+        * @var string
+        */
+       protected static $illegalChars = '[^\x0-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+';
+       
+       /**
+        * list of smilies
+        * @var array<wcf\data\smiley\Smiley>
+        */
+       protected $smilies = array();
+       
+       /**
+        * currently parsed message
+        * @var string
+        */
+       public $message = '';
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               parent::init();
+               
+               if (MODULE_SMILEY == 1) {
+                       // get smilies
+                       $smilies = SmileyCache::getInstance()->getSmilies();
+                       $categories = SmileyCache::getInstance()->getCategories();
+                       foreach ($smilies as $categoryID => $categorySmilies) {
+                               if ($categories[$categoryID ?: null]->isDisabled) continue;
+                               
+                               foreach ($categorySmilies as $smiley) {
+                                       foreach ($smiley->smileyCodes as $smileyCode) {
+                                               $this->smilies[$smileyCode] = '<img src="'.$smiley->getURL().'" alt="'.StringUtil::encodeHTML($smiley->smileyCode).'" />';
+                                       }
+                               }
+                       }
+                       krsort($this->smilies);
+               }
+       }
+       
+       /**
+        * Parses the given message and returns the parsed message.
+        * 
+        * @param       string          $message
+        * @param       boolean         $parseURLs
+        * @param       boolean         $parseSmilies
+        * @return      string
+        */
+       public function parse($message, $parseURLs = true, $parseSmilies = true) {
+               $this->message = $message;
+               
+               // call event
+               EventHandler::getInstance()->fireAction($this, 'beforeParsing');
+               
+               // encode html
+               $this->message = StringUtil::encodeHTML($this->message);
+               
+               // converts newlines to <br />'s
+               $this->message = nl2br($this->message);
+               
+               // parse urls
+               if ($parseURLs) {
+                       $this->message = $this->parseURLs($this->message);
+               }
+               
+               // parse smilies
+               if ($parseSmilies) {
+                       $this->message = $this->parseSmilies($this->message);
+               }
+               
+               // replace bad html tags (script etc.)
+               $badSearch = array('/(javascript):/i', '/(about):/i', '/(vbscript):/i');
+               $badReplace = array('$1<b></b>:', '$1<b></b>:', '$1<b></b>:');
+               $this->message = preg_replace($badSearch, $badReplace, $this->message);
+               
+               // call event
+               EventHandler::getInstance()->fireAction($this, 'afterParsing');
+               
+               return $this->message;
+       }
+       
+       /**
+        * Parses urls.
+        * 
+        * @param       string          $text
+        * @return      string          text
+        */
+       public function parseURLs($text) {
+               // define pattern
+               $urlPattern = '~(?<!\B|"|\'|=|/|\]|,|\?)
+                       (?:                                             # hostname
+                               (?:ftp|https?)://'.static::$illegalChars.'(?:\.'.static::$illegalChars.')*
+                               |
+                               www\.(?:'.static::$illegalChars.'\.)+
+                               (?:[a-z]{2,4}(?=\b))
+                       )
+                       
+                       (?::\d+)?                                       # port
+                       
+                       (?:
+                               /
+                               [^!.,?;"\'<>()\[\]{}\s]*
+                               (?:
+                                       [!.,?;(){}]+ [^!.,?;"\'<>()\[\]{}\s]+
+                               )*
+                       )?
+                       ~ix';
+               $emailPattern = '~(?<!\B|"|\'|=|/|\]|,|:)
+                       (?:)
+                       \w+(?:[\.\-]\w+)*
+                       @
+                       (?:'.static::$illegalChars.'\.)+                # hostname
+                       (?:[a-z]{2,4}(?=\b))
+                       (?!"|\'|\[|\-)
+                       ~ix';
+               
+               // parse urls
+               $text = preg_replace_callback($urlPattern, array($this, 'parseURLsCallback'), $text);
+               
+               // parse emails
+               if (StringUtil::indexOf($text, '@') !== false) {
+                       $text = preg_replace($emailPattern, '<a href="mailto:\\0">\\0</a>', $text);
+               }
+               
+               return $text;
+       }
+       
+       /**
+        * Callback for preg_replace.
+        * 
+        * @see wcf\system\bbcode\SimpleMessageParser::parseURLs()
+        */
+       protected function parseURLsCallback($matches) {
+               return StringUtil::getAnchorTag(StringUtil::decodeHTML($matches[0]));
+       }
+       
+       /**
+        * Parses smiley codes.
+        * 
+        * @param       string          $text
+        * @return      string          text
+        */
+       public function parseSmilies($text) {
+               foreach ($this->smilies as $code => $html) {
+                       //$text = preg_replace('~(?<!&\w{2}|&\w{3}|&\w{4}|&\w{5}|&\w{6}|&#\d{2}|&#\d{3}|&#\d{4}|&#\d{5})'.preg_quote(StringUtil::encodeHTML($code), '~').'(?![^<]*>)~', $html, $text);
+                       $text = preg_replace('~(?<=^|\s)'.preg_quote(StringUtil::encodeHTML($code), '~').'(?=$|\s|<br />)~', $html, $text);
+               }
+               
+               return $text;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/SpoilerBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/SpoilerBBCode.class.php
new file mode 100644 (file)
index 0000000..d50018a
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\system\WCF;
+
+/**
+ * Parses the [spoiler] bbcode tag.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class SpoilerBBCode extends AbstractBBCode {
+       /**
+        * @see wcf\system\bbcode\IBBCode::getParsedTag()
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               if ($parser->getOutputType() == 'text/html') {
+                       WCF::getTPL()->assign(array(
+                               'content' => $content,
+                               'buttonTitle' => (!empty($openingTag['attributes'][0]) ? $openingTag['attributes'][0] : '')
+                       ));
+                       return WCF::getTPL()->fetch('spoilerBBCodeTag');
+               }
+               if ($parser->getOutputType() == 'text/simplified-html') {
+                       return WCF::getLanguage()->get('wcf.bbcode.spoiler.text');
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/TableBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/TableBBCode.class.php
new file mode 100644 (file)
index 0000000..956ac6a
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\system\Regex;
+use wcf\util\StringUtil;
+
+/**
+ * Parses the [table] bbcode tag.
+ * 
+ * @author     Tim Duesterhus, Marcel Werk
+ * @copyright  2001-2012 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class TableBBCode extends AbstractBBCode {
+       /**
+        * @see wcf\system\bbcode\IBBCode::getParsedTag()
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               if ($parser->getOutputType() == 'text/html') {
+                       $parsedContent = Regex::compile('(?:\s|<br />)*(\[tr\].*\[/tr\])(?:\s|<br />)*', Regex::CASE_INSENSITIVE | Regex::DOT_ALL)->replace($content, '\\1');
+                       
+                       // check syntax
+                       $regex = new Regex('\[/?t[rd]\]', Regex::CASE_INSENSITIVE);
+                       if ($regex->match($parsedContent, true)) {
+                               $matches = $regex->getMatches();
+                               
+                               $openTags = array();
+                               $openTDs = 0;
+                               $firstRowTDs = 0;
+                               
+                               // parse tags
+                               foreach ($matches[0] as $match) {
+                                       switch ($match) {
+                                               case '[td]':
+                                                       if (end($openTags) !== '[tr]') return;
+                                                       $openTags[] = $match;
+                                                       $openTDs++;
+                                               break;
+                                               case '[/td]':
+                                                       if (end($openTags) !== '[td]') return;
+                                                       array_pop($openTags);
+                                               break;
+                                               case '[tr]':
+                                                       if (!empty($openTags)) return;
+                                                       $openTags[] = $match;
+                                               break;
+                                               case '[/tr]':
+                                                       if (end($openTags) !== '[tr]') return;
+                                                       
+                                                       array_pop($openTags);
+                                                       
+                                                       // check that every row has got the same number of tds
+                                                       if ($firstRowTDs === 0) $firstRowTDs = $openTDs;
+                                                       if ($openTDs !== $firstRowTDs) return;
+                                                       
+                                                       $openTDs = 0;
+                                               break;
+                                       }
+                               }
+                               
+                               if (!empty($openTags)) return;
+                       }
+                       else {
+                               return '';
+                       }
+                       
+                       // tr
+                       $parsedContent = Regex::compile('\[tr\](?:\s|<br />)*', Regex::CASE_INSENSITIVE)->replace($parsedContent, '<tr>');
+                       // td
+                       $parsedContent = StringUtil::replaceIgnoreCase('[td]', '<td>', $parsedContent);
+                       // /td
+                       $parsedContent = Regex::compile('\[/td\](?:\s|<br />)*', Regex::CASE_INSENSITIVE)->replace($parsedContent, '</td>');
+                       // /tr
+                       $parsedContent = Regex::compile('\[/tr\](?:\s|<br />)*', Regex::CASE_INSENSITIVE)->replace($parsedContent, '</tr>');
+                       
+                       return '<div class="container bbcodeTable"><table class="table"><tbody>'.$parsedContent.'</tbody></table></div>';
+               }
+               else if ($parser->getOutputType() == 'text/simplified-html') {
+                       // remove table tags
+                       $content = StringUtil::replaceIgnoreCase('[td]', '* ', $content);
+                       $content = StringUtil::replaceIgnoreCase('[/td]', ' ', $content);
+                       $content = StringUtil::replaceIgnoreCase('[tr]', '', $content);
+                       $content = StringUtil::replaceIgnoreCase('[/tr]', '', $content);
+                       
+                       return $content;
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/URLBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/URLBBCode.class.php
new file mode 100644 (file)
index 0000000..30dd613
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+namespace wcf\system\bbcode;
+use wcf\util\StringUtil;
+
+/**
+ * Parses the [url] bbcode tag.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode
+ * @category   Community Framework
+ */
+class URLBBCode extends AbstractBBCode {
+       /**
+        * @see wcf\system\bbcode\IBBCode::getParsedTag()
+        */
+       public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+               $url = '';
+               if (isset($openingTag['attributes'][0])) {
+                       $url = $openingTag['attributes'][0];
+               }
+               
+               $noTitle = ($content == $url);
+               $url = StringUtil::decodeHTML($url);
+               
+               // add protocol if necessary
+               if (!preg_match("/[a-z]:\/\//si", $url)) $url = 'http://'.$url;
+               
+               return StringUtil::getAnchorTag($url, (!$noTitle ? $content : ''), false);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/BashHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/BashHighlighter.class.php
new file mode 100644 (file)
index 0000000..32a888e
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+
+/**
+ * Highlights syntax of bash scripts.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011-2012 Tim Duesterhus
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class BashHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array(';', '=');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$quotes
+        */
+       protected $quotes = array('"', "'", '`');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$singleLineComment
+        */
+       protected $singleLineComment = array('#');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$commentStart
+        */
+       protected $commentStart = array();
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$commentEnd
+        */
+       protected $commentEnd = array();
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$operators
+        */
+       protected $operators = array('||', '&&', '&', '|', '<<=', '>>=', '<<', '+=', '-=', '*=', '/=', '%=',
+                                       '-gt', '-lt', '-n', '-a', '-o',
+                                       '+', '-', '*', '/', '%', '<', '?', ':', '==', '!=', '=', '!', '>', '2>', '>>');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords1
+        */
+       protected $keywords1 = array(
+               'true',
+               'false'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords2
+        */
+       protected $keywords2 = array(
+               'if',
+               'then',
+               'else',
+               'fi',
+               'for',
+               'until',
+               'while',
+               'do',
+               'done',
+               'case',
+               'in',
+               'esac'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords3
+        */
+       protected $keywords3 = array(
+               'echo',
+               'exit',
+               'unset',
+               'read',
+               '[', ']', 'test',
+               'let',
+               'sed',
+               'grep',
+               'awk'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords4
+        */
+       protected $keywords4 = array(
+               '$?'
+       );
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/BrainfuckHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/BrainfuckHighlighter.class.php
new file mode 100644 (file)
index 0000000..d98e5cf
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+
+/**
+ * Highlights syntax of brainfuck.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class BrainfuckHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlight()
+        */
+       public function highlight($string) {
+               $string = preg_replace('/[^-\\+\\.,\\[\\]\\>\\<]+/', '||span class="hlComments"||\\0||/span||', $string);
+               $string = preg_replace('/[\\<\\>]+/', '<span class="hlKeywords4">\\0</span>', $string);
+               $string = preg_replace('/[-\\+]+/', '<span class="hlKeywords1">\\0</span>', $string);
+               $string = preg_replace('/[\\.,]+/', '<span class="hlKeywords2">\\0</span>', $string);
+               $string = preg_replace('/[\\[\\]]+/', '<span class="hlKeywords3">\\0</span>', $string);
+               
+               $string = str_replace(array('||span class="hlComments"||', '||/span||'), array('<span class="hlComments">', '</span>'), $string);
+               return $string;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/CHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/CHighlighter.class.php
new file mode 100644 (file)
index 0000000..1e9f8db
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+
+/**
+ * Highlights syntax of c / c++ source code.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class CHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array('(', ')', '{', '}', '[', ']', ';', '.', ',');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$operators
+        */
+       protected $operators = array('=', '>', '<', '!', '~', '?', ':', '==', '<=', '>=', '!=',
+               '&&', '||', '++', '--', '+', '-', '*', '/', '&', '|', '^', '%', '<<', '>>', '>>>', '+=', '-=', '*=',
+               '/=', '&=', '|=', '^=', '%=', '<<=', '>>=', '>>>=');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords1
+        */
+       protected $keywords1 = array(
+               'and',
+               'and_eq',
+               'asm',
+               'bitand',
+               'bitor',
+               'break',
+               'case',
+               'catch',
+               'compl',
+               'const_cast',
+               'continue',
+               'default',
+               'delete',
+               'do',
+               'dynamic_cast',
+               'else',
+               'for',
+               'fortran',
+               'friend',
+               'goto',
+               'if',
+               'new',
+               'not',
+               'not_eq',
+               'operator',
+               'or',
+               'or_eq',
+               'private',
+               'protected',
+               'public',
+               'reinterpret_cast',
+               'return',
+               'sizeof',
+               'static_cast',
+               'switch',
+               'this',
+               'throw',
+               'try',
+               'typeid',
+               'using',
+               'while',
+               'xor',
+               'xor_eq'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords2
+        */
+       protected $keywords2 = array(
+               'auto',
+               'bool',
+               'char',
+               'class',
+               'const',
+               'double',
+               'enum',
+               'explicit',
+               'export',
+               'extern',
+               'float',
+               'inline',
+               'int',
+               'long',
+               'mutable',
+               'namespace',
+               'register',
+               'short',
+               'signed',
+               'static',
+               'struct',
+               'template',
+               'typedef',
+               'typename',
+               'union',
+               'unsigned',
+               'virtual',
+               'void',
+               'volatile',
+               'wchar_t'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords3
+        */
+       protected $keywords3 = array(
+               '#include',
+               '#define',
+               '#if',
+               '#else',
+               '#ifdef',
+               '#endif'
+       );
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/CssHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/CssHighlighter.class.php
new file mode 100644 (file)
index 0000000..277d02d
--- /dev/null
@@ -0,0 +1,667 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+
+/**
+ * Highlights syntax of cascading style sheets.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class CssHighlighter extends Highlighter {
+       /**
+        * temporary string replacement map for properties that can also be tags
+        * @var array<string>
+        */
+       public static $duplicates = array(
+               'table:' => 't@@able:',
+               'caption:' => 'c@@aption:',
+               'menu:' => 'm@@enu:',
+               'code:' => 'c@@ode:',
+               'sub:' => 's@@ub:',
+               'pre:' => 'p@@re:',
+               'small:' => 's@@mall:'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlightNumbers()
+        */
+       protected function highlightNumbers($string) {
+               $string = preg_replace('!(?<='.$this->separatorsRegEx.')(-?\d*\.?\d+(?:px|pt|em|%|ex|in|cm|mm|pc)?)(?='.$this->separatorsRegEx.')!i', '<span class="hlNumbers">\\0</span>', $string);
+               
+               // highlight colors (hexadecimal numbers)
+               $string = preg_replace('!(?<='.$this->separatorsRegEx.')(#([0-9a-f]{3}|[0-9a-f]{6}))(?='.$this->separatorsRegEx.')!i', '<span class="hlColors">\\0</span>', $string);
+               
+               return $string;
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlightKeywords()
+        */
+       protected function highlightKeywords($string) {
+               $string = parent::highlightKeywords($string);
+               $string = preg_replace('!(?<='.$this->separatorsRegEx.')(@[a-z0-9-]+)(?='.$this->separatorsRegEx.')!i', '<span class="hlKeywords5">\\0</span>', $string);
+               
+               return $string;
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlight()
+        */
+       public function highlight($string) {
+               $string = str_replace('span', '053a0024219422ca9215c0a3ed0578ee76cff477', $string); // fix to not highlight the spans of the highlighter
+               $string = str_replace(':link', ':li@@nk', $string); // fix to highlight pseudo-class different than tag
+               $string = str_replace(array('right:', 'left:'), array('r@@ight:', 'l@@eft:'), $string); // fix to highlight properties different than values
+               $string = strtr($string, self::$duplicates); // fix to highlight properties different than tags
+               
+               $string = parent::highlight($string);
+               
+               $string = strtr($string, array_flip(self::$duplicates)); // fix to highlight properties different than tags
+               $string = str_replace(array('r@@ight', 'l@@eft'), array('right', 'left'), $string); // fix to highlight properties different than values
+               $string = str_replace('li@@nk', 'link', $string); // fix to highlight pseudo-class different than tag
+               return str_replace('053a0024219422ca9215c0a3ed0578ee76cff477', 'span', $string); // fix to not highlight the spans of the highlighter
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$singleLineComment
+        */
+       protected $singleLineComment = array('//');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array('(', ')', '{', '}', ';', '[', ']', ':', ',', '.');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords1
+        */
+       protected $keywords1 = array(
+               'azimuth',
+               'background',
+               'background-attachment',
+               'background-clip',
+               'background-color',
+               'background-image',
+               'background-origin',
+               'background-position',
+               'background-repeat',
+               'background-size',
+               'border',
+               'border-bottom',
+               'border-bottom-color',
+               'border-bottom-radius',
+               'border-bottom-left-radius',
+               'border-bottom-right-radius',
+               'border-bottom-style',
+               'border-bottom-width',
+               'border-collapse',
+               'border-color',
+               'border-l@@eft',
+               'border-left-color',
+               'border-left-radius',
+               'border-left-style',
+               'border-left-width',
+               'border-radius',
+               'border-r@@ight',
+               'border-right-color',
+               'border-right-radius',
+               'border-right-style',
+               'border-right-width',
+               'border-spacing',
+               'border-style',
+               'border-top',
+               'border-top-color',
+               'border-top-radius',
+               'border-top-left-radius',
+               'border-top-right-radius',
+               'border-top-style',
+               'border-top-width',
+               'border-width',
+               'bottom',
+               'box-shadow',
+               'caption-side',
+               'clear',
+               'clip',
+               'color',
+               'content',
+               'counter-increment',
+               'counter-reset',
+               'cue',
+               'cue-after',
+               'cue-before',
+               'cursor',
+               'direction',
+               'display',
+               'elevation',
+               'empty-cells',
+               'float',
+               'font',
+               'font-family',
+               'font-size',
+               'font-style',
+               'font-variant',
+               'font-weight',
+               'height',
+               'l@@eft',
+               'letter-spacing',
+               'line-height',
+               'list-style',
+               'list-style-image',
+               'list-style-position',
+               'list-style-type',
+               'margin',
+               'margin-bottom',
+               'margin-l@@eft',
+               'margin-r@@ight',
+               'margin-top',
+               'max-height',
+               'max-width',
+               'min-height',
+               'min-width',
+               'opacity',
+               'orphans',
+               'outline',
+               'outline-color',
+               'outline-style',
+               'outline-width',
+               'overflow',
+               'overflow-x',
+               'overflow-y',
+               'padding',
+               'padding-bottom',
+               'padding-l@@eft',
+               'padding-r@@ight',
+               'padding-top',
+               'page-break-after',
+               'page-break-before',
+               'page-break-inside',
+               'pause',
+               'pause-after',
+               'pause-before',
+               'pitch',
+               'pitch-range',
+               'play-during',
+               'position',
+               'quotes',
+               'richness',
+               'r@@ight',
+               'scrollbar-3dlight-color',
+               'scrollbar-arrow-color',
+               'scrollbar-base-color',
+               'scrollbar-darkshadow-color',
+               'scrollbar-face-color',
+               'scrollbar-highlight-color',
+               'scrollbar-shadow-color',
+               'scrollbar-track-color',
+               'speak',
+               'speak-header',
+               'speak-numeral',
+               'speak-punctuation',
+               'speech-rate',
+               'stress',
+               'table-layout',
+               'text-align',
+               'text-decoration',
+               'text-indent',
+               'text-overflow',
+               'text-shadow',
+               'text-transform',
+               'top',
+               'unicode-bidi',
+               'vertical-align',
+               'visibility',
+               'voice-family',
+               'volume',
+               'white-space',
+               'widows',
+               'width',
+               'word-spacing',
+               'word-wrap',
+               'z-index',
+               '!important',
+               '@import',
+               '@media'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords2
+        */
+       protected $keywords2 = array(
+               'left-side',
+               'far-left',
+               'left',
+               'center-left',
+               'center-right',
+               'center',
+               'far-right',
+               'right-side',
+               'right',
+               'behind',
+               'leftwards',
+               'rightwards',
+               'inherit',
+               'scroll',
+               'fixed',
+               'transparent',
+               'none',
+               'repeat-x',
+               'repeat-y',
+               'repeat',
+               'no-repeat',
+               'collapse',
+               'separate',
+               'auto',
+               'top',
+               'bottom',
+               'both',
+               'open-quote',
+               'close-quote',
+               'no-open-quote',
+               'no-close-quote',
+               'crosshair',
+               'default',
+               'pointer',
+               'move',
+               'e-resize',
+               'ne-resize',
+               'nw-resize',
+               'n-resize',
+               'se-resize',
+               'sw-resize',
+               's-resize',
+               'text',
+               'wait',
+               'help',
+               'ltr',
+               'rtl',
+               'inline',
+               'block',
+               'list-item',
+               'run-in',
+               'compact',
+               'marker',
+               't@@able', // table
+               'inline-table',
+               'table-row-group',
+               'table-header-group',
+               'table-footer-group',
+               'table-row',
+               'table-column-group',
+               'table-column',
+               'table-cell',
+               'table-caption',
+               'below',
+               'level',
+               'above',
+               'higher',
+               'lower',
+               'show',
+               'hide',
+               'c@@aption', // caption
+               'icon',
+               'm@@enu', // menu
+               'message-box',
+               'small-caption',
+               'status-bar',
+               'normal',
+               'wider',
+               'narrower',
+               'ultra-condensed',
+               'extra-condensed',
+               'condensed',
+               'semi-condensed',
+               'semi-expanded',
+               'expanded',
+               'extra-expanded',
+               'ultra-expanded',
+               'italic',
+               'oblique',
+               'small-caps',
+               'bold',
+               'bolder',
+               'lighter',
+               'inside',
+               'outside',
+               'disc',
+               'circle',
+               'square',
+               'decimal',
+               'decimal-leading-zero',
+               'lower-roman',
+               'upper-roman',
+               'lower-greek',
+               'lower-alpha',
+               'lower-latin',
+               'upper-alpha',
+               'upper-latin',
+               'hebrew',
+               'armenian',
+               'georgian',
+               'cjk-ideographic',
+               'hiragana',
+               'katakana',
+               'hiragana-iroha',
+               'katakana-iroha',
+               'crop',
+               'cross',
+               'invert',
+               'visible',
+               'hidden',
+               'always',
+               'avoid',
+               'x-low',
+               'low',
+               'medium',
+               'high',
+               'x-high',
+               'mix?',
+               'repeat?',
+               'static',
+               'relative',
+               'absolute',
+               'portrait',
+               'landscape',
+               'spell-out',
+               'once',
+               'digits',
+               'continuous',
+               'c@@ode', // code
+               'x-slow',
+               'slow',
+               'fast',
+               'x-fast',
+               'faster',
+               'slower',
+               'justify',
+               'underline',
+               'overline',
+               'line-through',
+               'blink',
+               'capitalize',
+               'uppercase',
+               'lowercase',
+               'e@@mbed', // embed
+               'bidi-override',
+               'baseline',
+               's@@ub', // sub
+               'super',
+               'text-top',
+               'middle',
+               'text-bottom',
+               'silent',
+               'x-soft',
+               'soft',
+               'loud',
+               'x-loud',
+               'p@@re', // pre
+               'nowrap',
+               'serif',
+               'sans-serif',
+               'cursive',
+               'fantasy',
+               'monospace',
+               'empty',
+               'string',
+               'strict',
+               'loose',
+               'char',
+               'true',
+               'false',
+               'dotted',
+               'dashed',
+               'solid',
+               'double',
+               'groove',
+               'ridge',
+               'inset',
+               'outset',
+               'larger',
+               'smaller',
+               'xx-small',
+               'x-small',
+               's@@mall', // small
+               'large',
+               'x-large',
+               'xx-large',
+               'all',
+               'newspaper',
+               'distribute',
+               'distribute-all-lines',
+               'distribute-center-last',
+               'inter-word',
+               'inter-ideograph',
+               'inter-cluster',
+               'kashida',
+               'ideograph-alpha',
+               'ideograph-numeric',
+               'ideograph-parenthesis',
+               'ideograph-space',
+               'keep-all',
+               'break-all',
+               'break-word',
+               'lr-tb',
+               'tb-rl',
+               'thin',
+               'thick',
+               'inline-block',
+               'w-resize',
+               'hand',
+               'distribute-letter',
+               'distribute-space',
+               'whitespace',
+               'male',
+               'female',
+               'child',
+               'print',
+               'screen',
+               'tty',
+               'aural',
+               'all',
+               'braille',
+               'embossed',
+               'handheld',
+               'projection',
+               'tv',
+               'hsl',
+               'hsla',
+               'rgb',
+               'rgba'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords3
+        */
+       protected $keywords3 = array(
+               'active',
+               'after',
+               'before',
+               'checked',
+               'disabled',
+               'empty',
+               'enabled',
+               'first-child',
+               'first-letter',
+               'first-line',
+               'first-of-type',
+               'focus',
+               'lang',
+               'last-child',
+               'last-of-type',
+               'li@@nk', // link
+               'hover',
+               'not',
+               'nth-child',
+               'nth-last-child',
+               'nth-of-type',
+               'nth-last-of-type',
+               'only-child',
+               'only-of-type',
+               'root',
+               'target',
+               'visited'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords4
+        */
+       protected $keywords4 = array(
+               'abbr',
+               'acronym',
+               'address',
+               'area',
+               'article',
+               'aside',
+               'audio',
+               'a',
+               'base',
+               'bdi',
+               'bdo',
+               'big',
+               'blockquote',
+               'body',
+               'br',
+               'button',
+               'b',
+               'canvas',
+               'caption',
+               'cite',
+               'code',
+               'col',
+               'colgroup',
+               'command',
+               'datalist',
+               'dd',
+               'del',
+               'details',
+               'dfn',
+               'div',
+               'dl',
+               'dt',
+               'embed',
+               'em',
+               'fieldset',
+               'figcaption',
+               'figure',
+               'footer',
+               'form',
+               'h1',
+               'h2',
+               'h3',
+               'h4',
+               'h5',
+               'h6',
+               'head',
+               'header',
+               'hgroup',
+               'hr',
+               'html',
+               'iframe',
+               'img',
+               'input',
+               'ins',
+               'i',
+               'kbd',
+               'keygen',
+               'label',
+               'legend',
+               'li',
+               'link',
+               'map',
+               'mark',
+               'menu',
+               'meta',
+               'meter',
+               'nav',
+               'noscript',
+               'object',
+               'ol',
+               'optgroup',
+               'option',
+               'output',
+               'param',
+               'pre',
+               'progress',
+               'p',
+               'q',
+               'rbc',
+               'rb',
+               'rp',
+               'rtc',
+               'rt',
+               'ruby',
+               'samp',
+               'script',
+               'section',
+               'select',
+               'small',
+               'source',
+               '053a0024219422ca9215c0a3ed0578ee76cff477', // span
+               'strong',
+               'style',
+               'sub',
+               'summary',
+               'sup',
+               'table',
+               'tbody',
+               'td',
+               'textarea',
+               'tfoot',
+               'thead',
+               'th',
+               'time',
+               'title',
+               'track',
+               'tr',
+               'tt',
+               'ul',
+               'u',
+               'var',
+               'video',
+               'wbr'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords5
+        */
+       public $keywords5 = array(
+               // modifying
+               'darken',
+               'lighten',
+               'saturate',
+               'desaturate',
+               'fadein',
+               'fadeout',
+               'fade',
+               'spin',
+               'mix',
+               // reading
+               'lightness',
+               'hue',
+               'saturation',
+               'alpha',
+               'percentage',
+               // typechecking
+               'isnumber',
+               'iscolor',
+               'isstring',
+               'iskeyword',
+               'isurl',
+               'ispixel',
+               'ispercentage',
+               'isem',
+               // math
+               'round',
+               'ceil',
+               'floor',
+               // if
+               'when',
+               'not',
+               'and',
+               'true',
+               // others
+               '&'
+       );
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/DiffHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/DiffHighlighter.class.php
new file mode 100644 (file)
index 0000000..7a9d41b
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\util\StringUtil;
+
+/**
+ * Highlights difference files.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011 Tim Duesterhus
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class DiffHighlighter extends Highlighter {
+       /**
+        * keywords for an added line, the + is used in unified diffs, the > in
+        * normal diffs
+        * @var array<string>
+        */
+       protected $add = array("+", ">");
+       
+       /**
+        * keywords for an deleted line, the - is used in unified diff, the < in
+        * normal diffs
+        * @var array<string>
+        */
+       protected $delete = array("-", "<");
+       
+       /**
+        * splitter in changes for normal diff
+        * @var array<string>
+        */
+       protected $splitter = array("---");
+       
+       /**
+        * keywords for the line info, the @ is used in unified diffs, the numbers
+        * in normal diffs
+        * @var array<string>
+        */
+       protected $info = array("@", '0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlight()
+        */
+       public function highlight($data) {
+               $lines = explode("\n", $data);
+               foreach ($lines as $key => $val) {
+                       if (in_array(StringUtil::substring($val, 0, 1), $this->info) || in_array($val, $this->splitter)) {
+                               $lines[$key] = '<span class="hlComments">'.StringUtil::encodeHTML($val).'</span>';
+                       }
+                       else if (in_array(StringUtil::substring($val, 0, 1), $this->add)) {
+                               $lines[$key] = '<span class="hlAdded">'.StringUtil::encodeHTML($val).'</span>';
+                       }
+                       else if (in_array(StringUtil::substring($val, 0, 1), $this->delete)) {
+                               $lines[$key] = '<span class="hlRemoved">'.StringUtil::encodeHTML($val).'</span>';
+                       }
+                       else {
+                               $lines[$key] = StringUtil::encodeHTML($val);
+                       }
+               }
+               
+               $data = implode("\n", $lines);
+               return $data;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/Highlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/Highlighter.class.php
new file mode 100644 (file)
index 0000000..f012672
--- /dev/null
@@ -0,0 +1,319 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\system\Callback;
+use wcf\system\Regex;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\StringStack;
+use wcf\util\StringUtil;
+
+/**
+ * Highlights syntax of source code.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+abstract class Highlighter extends SingletonFactory {
+       /**
+        * allow multiline quotes
+        * @var boolean
+        */
+       protected $allowsNewslinesInQuotes = false;
+       
+       /**
+        * comment end delimiter
+        * @var array<string>
+        */
+       protected $commentEnd = array("*/");
+       
+       /**
+        * comment start delimiter
+        * @var array<string>
+        */
+       protected $commentStart = array("/*");
+       
+       /**
+        * escape sequence
+        * @var array<string>
+        */
+       protected $escapeSequence = array("\\");
+       
+       /**
+        * categorized keywords
+        * @var array<string>
+        */
+       protected $keywords1 = array();
+       
+       /**
+        * categorized keywords
+        * @var array<string>
+        */
+       protected $keywords2 = array();
+       
+       /**
+        * categorized keywords
+        * @var array<string>
+        */
+       protected $keywords3 = array();
+       
+       /**
+        * categorized keywords
+        * @var array<string>
+        */
+       protected $keywords4 = array();
+       
+       /**
+        * categorized keywords
+        * @var array<string>
+        */
+       protected $keywords5 = array();
+       
+       /**
+        * list of arithmetic operators
+        * @var array<string>
+        */
+       protected $operators = array();
+       
+       /**
+        * list of quote marks
+        * @var array<string>
+        */
+       protected $quotes = array("'", '"');
+       
+       /**
+        * list of separator sequences
+        * @var array<string>
+        */
+       protected $separators = array();
+       
+       /**
+        * inline comment sequence
+        * @var array<string>
+        */
+       protected $singleLineComment = array("//");
+       
+       /**
+        * regular expression to extract comments
+        * @var wcf\system\Regex
+        */
+       public $cacheCommentsRegEx = null;
+       
+       /**
+        * regular expression to find quote marks
+        * @var wcf\system\Regex
+        */
+       public $quotesRegEx = null;
+       
+       /**
+        * regular expression to find string separators
+        * @var wcf\system\Regex
+        */
+       public $separatorsRegEx = '';
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               $this->buildRegularExpressions();
+       }
+       
+       /**
+        * Returns the title of this highlighter.
+        * 
+        * @return      string
+        */
+       public function getTitle() {
+               // regex to extract the Highlighter out of the namespaced classname
+               $reType = new Regex('\\\\?wcf\\\\system\\\\bbcode\\\\highlighter\\\\(.*)Highlighter', Regex::CASE_INSENSITIVE);
+               
+               return WCF::getLanguage()->get('wcf.bbcode.code.'.$reType->replace(strtolower(get_class($this)), '\1').'.title');
+       }
+       
+       /**
+        * Highlights syntax of source code.
+        * 
+        * @param       string          $string
+        * @return      string
+        */
+       public function highlight($string) {
+               // cache comments
+               $string = $this->cacheComments($string);
+               
+               // cache quotes
+               $string = $this->cacheQuotes($string);
+               
+               // encode html
+               $string = StringUtil::encodeHTML($string);
+               
+               // do highlight
+               $string = $this->highlightOperators($string);
+               $string = $this->highlightKeywords($string);
+               $string = $this->highlightNumbers($string);
+               
+               // insert and highlight quotes
+               $string = $this->highlightQuotes($string);
+               
+               // insert and highlight comments
+               $string = $this->highlightComments($string);
+               
+               return $string;
+       }
+       
+       /**
+        * Builds regular expressions.
+        */
+       protected function buildRegularExpressions() {
+               // quotes regex
+               $quotedEscapeSequence = preg_quote(implode('', $this->escapeSequence));
+               $quotesRegEx = '';
+               foreach ($this->quotes as $quote) {
+                       if ($quotesRegEx !== '') $quotesRegEx .= '|';
+                       if (!is_array($quote)) $quote = array($quote, $quote);
+                       list($opening, $closing) = $quote;
+                       
+                       $opening = preg_quote($opening);
+                       $closing = preg_quote($closing);
+                       $quotesRegEx .= $opening.'(?:[^'.$closing.$quotedEscapeSequence.']|'.$quotedEscapeSequence.'.)*'.$closing;
+               }
+               
+               if ($quotesRegEx !== '') {
+                       $quotesRegEx = '(?:'.$quotesRegEx.')';
+                       $this->quotesRegEx = new Regex($quotesRegEx, ($this->allowsNewslinesInQuotes) ? Regex::DOT_ALL : Regex::MODIFIER_NONE);
+               }
+               
+               // cache comment regex
+               if (!empty($this->singleLineComment) || !empty($this->commentStart)) {
+                       $cacheCommentsRegEx = '';
+                       
+                       if ($quotesRegEx !== '') {
+                               $cacheCommentsRegEx .= "(".$quotesRegEx.")|";
+                       }
+                       else {
+                               $cacheCommentsRegEx .= "()";
+                       }
+                       
+                       $cacheCommentsRegEx .= "(";
+                       if (!empty($this->singleLineComment)) {
+                               $cacheCommentsRegEx .= "(?:".implode('|', array_map('preg_quote', $this->singleLineComment)).")[^\n]*";
+                               if (!empty($this->commentStart)) {
+                                       $cacheCommentsRegEx .= '|';
+                               }
+                       }
+                       
+                       if (!empty($this->commentStart)) {
+                               $cacheCommentsRegEx .= '(?:'.implode('|', array_map('preg_quote', $this->commentStart)).').*?(?:'.implode('|', array_map('preg_quote', $this->commentEnd)).')';
+                       }
+                       $cacheCommentsRegEx .= ")";
+                       
+                       $this->cacheCommentsRegEx = new Regex($cacheCommentsRegEx, Regex::DOT_ALL);
+               }
+               
+               $this->separatorsRegEx = StringUtil::encodeHTML(implode('|', array_map('preg_quote', $this->separators))).'|\s|&nbsp;|^|$|>|<';
+       }
+       
+       /**
+        * Caches comments.
+        */
+       protected function cacheComments($string) {
+               if ($this->cacheCommentsRegEx !== null) {
+                       $string = $this->cacheCommentsRegEx->replace($string, new Callback(function (array $matches) {
+                               $string = $matches[1];
+                               if (isset($matches[2])) $comment = $matches[2];
+                               else $comment = '';
+                               
+                               // strip slashes
+                               $string = str_replace("\\\"", "\"", $string);
+                               $hash = '';
+                               if (!empty($comment)) {
+                                       $comment = str_replace("\\\"", "\"", $comment);
+                                               
+                                       // create hash
+                                       $hash = StringStack::pushToStringStack('<span class="hlComments">'.StringUtil::encodeHTML($comment).'</span>', 'highlighterComments');
+                               }
+                               
+                               return $string.$hash;
+                       }));
+               }
+               
+               return $string;
+       }
+       
+       /**
+        * Caches quotes.
+        */
+       protected function cacheQuotes($string) {
+               if ($this->quotesRegEx !== null) {
+                       $string = $this->quotesRegEx->replace($string, new Callback(function (array $matches) {
+                               return StringStack::pushToStringStack('<span class="hlQuotes">'.StringUtil::encodeHTML($matches[0]).'</span>', 'highlighterQuotes');
+                       }));
+               }
+               
+               return $string;
+       }
+       
+       /**
+        * Highlights operators.
+        */
+       protected function highlightOperators($string) {
+               if (!empty($this->operators)) {
+                       $string = preg_replace('!('.StringUtil::encodeHTML(implode('|', array_map('preg_quote', $this->operators))).')!i', '<span class="hlOperators">\\0</span>', $string);
+               }
+               
+               return $string;
+       }
+       
+       /**
+        * Highlights keywords.
+        */
+       protected function highlightKeywords($string) {
+               $_this = $this;
+               $buildKeywordRegex = function (array $keywords) use ($_this) {
+                       return '!(?<='.$_this->separatorsRegEx.')('.StringUtil::encodeHTML(implode('|', array_map('preg_quote', $keywords))).')(?='.$_this->separatorsRegEx.')!i';
+               };
+               
+               if (!empty($this->keywords1)) {
+                       $string = preg_replace($buildKeywordRegex($this->keywords1), '<span class="hlKeywords1">\\0</span>', $string);
+               }
+               if (!empty($this->keywords2)) {
+                       $string = preg_replace($buildKeywordRegex($this->keywords2), '<span class="hlKeywords2">\\0</span>', $string);
+               }
+               if (!empty($this->keywords3)) {
+                       $string = preg_replace($buildKeywordRegex($this->keywords3), '<span class="hlKeywords3">\\0</span>', $string);
+               }
+               if (!empty($this->keywords4)) {
+                       $string = preg_replace($buildKeywordRegex($this->keywords4), '<span class="hlKeywords4">\\0</span>', $string);
+               }
+               if (!empty($this->keywords5)) {
+                       $string = preg_replace($buildKeywordRegex($this->keywords5), '<span class="hlKeywords5">\\0</span>', $string);
+               }
+               
+               return $string;
+       }
+       
+       /**
+        * Highlights numbers.
+        */
+       protected function highlightNumbers($string) {
+               $string = preg_replace('!(?<='.$this->separatorsRegEx.')(-?\d+)(?='.$this->separatorsRegEx.')!i', '<span class="hlNumbers">\\0</span>', $string);
+               
+               return $string;
+       }
+       
+       /**
+        * Highlights quotes.
+        */
+       protected function highlightQuotes($string) {
+               return StringStack::reinsertStrings($string, 'highlighterQuotes');
+       }
+       
+       /**
+        * Highlights comments.
+        */
+       protected function highlightComments($string) {
+               return StringStack::reinsertStrings($string, 'highlighterComments');
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/HtmlHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/HtmlHighlighter.class.php
new file mode 100644 (file)
index 0000000..a890f36
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\system\Callback;
+use wcf\system\Regex;
+use wcf\util\StringStack;
+use wcf\util\StringUtil;
+
+/**
+ * Highlights syntax of (x)html documents including style and script blocks.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class HtmlHighlighter extends XmlHighlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::cacheComments()
+        */
+       protected function cacheComments($string) {
+               // cache inline scripts and inline css
+               $string = $this->cacheScriptsAndStyles($string);
+               
+               return parent::cacheComments($string);
+       }
+       
+       /**
+        * Caches scripts and styles in the given string.
+        * 
+        * @param       string          $string
+        * @return      string
+        */
+       protected function cacheScriptsAndStyles($string) {
+               $regex = new Regex('(<(style|script)[^>]*>)(.*?)(</\\2>)', Regex::CASE_INSENSITIVE | Regex::DOT_ALL);
+               
+               return $regex->replace($string, new Callback(function ($matches) {
+                       $type = ($matches[2] === 'script') ? 'js' : 'css';
+                       
+                       // strip slashes
+                       $content = str_replace('\\"', '"', $matches[3]);
+                       $openingTag = str_replace('\\"', '"', $matches[1]);
+                       $closingTag = str_replace('\\"', '"', $matches[4]);
+                       
+                       if (StringUtil::trim($content) == '') return $matches[0];
+                       
+                       $class = '\wcf\system\bbcode\highlighter\\'.ucfirst($type).'Highlighter';
+                       
+                       return $openingTag.StringStack::pushToStringStack('<span class="'.$type.'Highlighter">'.$class::getInstance()->highlight($content).'</span>', 'htmlHighlighter'.ucfirst($type)).$closingTag;
+               }));
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlightComments()
+        */
+       protected function highlightComments($string) {
+               $string = parent::highlightComments($string);
+               
+               // highlight script and style blocks
+               $string = StringStack::reinsertStrings($string, 'htmlHighlighterJs');
+               $string = StringStack::reinsertStrings($string, 'htmlHighlighterCss');
+               
+               return $string;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/JavaHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/JavaHighlighter.class.php
new file mode 100644 (file)
index 0000000..bb09fb2
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+
+/**
+ * Highlights syntax of java source code.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class JavaHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array("(", ")", "{", "}", "[", "]", ";", ".", ",", "<", ">");
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords2
+        */
+       protected $keywords2 = array(
+               'package',
+               'abstract',
+               'break',
+               'case',
+               'catch',
+               'class',
+               'continue',
+               'default',
+               'do',
+               'else',
+               'extends',
+               'false',
+               'finally',
+               'for',
+               'goto',
+               'if',
+               'implements',
+               'instanceof',
+               'interface',
+               'native',
+               'new',
+               'null',
+               'private',
+               'protected',
+               'public',
+               'return',
+               'super',
+               'strictfp',
+               'switch',
+               'synchronized',
+               'this',
+               'throws',
+               'throw',
+               'transient',
+               'true',
+               'try',
+               'volatile',
+               'while',
+               'boolean',
+               'byte',
+               'char',
+               'const',
+               'double',
+               'final',
+               'float',
+               'int',
+               'long',
+               'short',
+               'static',
+               'void',
+               'import'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords3
+        */
+       protected $keywords3 = array(
+               'Boolean',
+               'Float',
+               'Character',
+               'Double',
+               'Enum',
+               'Long',
+               'Short',
+               'Integer',
+               'Math',
+               'Object',
+               'String',
+               'StringBuffer',
+               'System',
+               'Thread',
+               'Exception',
+               'Throwable',
+               'Runnable',
+               'Appendable',
+               'Cloneable',
+               'HashMap',
+               'List',
+               'ArrayList'
+       );
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/JsHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/JsHighlighter.class.php
new file mode 100644 (file)
index 0000000..ab759eb
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+
+/**
+ * Highlights syntax of JavaScript code.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class JsHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array("(", ")", "{", "}", "[", "]", ";", ".", ",");
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$operators
+        */
+       protected $operators = array("=", ">", "<", "!", "~", "?", ":", "==", "<=", ">=", "!=",
+               "&&", "||", "++", "--", "+", "-", "*", "/", "&", "|", "^", "%", "<<", ">>", ">>>", "+=", "-=", "*=",
+               "/=", "&=", "|=", "^=", "%=", "<<=", ">>=", ">>>=");
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords1
+        */
+       protected $keywords1 = array(
+               "String",
+               "Array",
+               "RegExp",
+               "Function",
+               "Math",
+               "Number",
+               "Date",
+               "Image",
+               "window",
+               "document",
+               "navigator",
+               "onAbort",
+               "onBlur",
+               "onChange",
+               "onClick",
+               "onDblClick",
+               "onDragDrop",
+               "onError",
+               "onFocus",
+               "onKeyDown",
+               "onKeyPress",
+               "onKeyUp",
+               "onLoad",
+               "onMouseDown",
+               "onMouseOver",
+               "onMouseOut",
+               "onMouseMove",
+               "onMouseUp",
+               "onMove",
+               "onReset",
+               "onResize",
+               "onSelect",
+               "onSubmit",
+               "onUnload"
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords2
+        */
+       protected $keywords2 = array(
+               "break",
+               "continue",
+               "do",
+               "while",
+               "export",
+               "for",
+               "in",
+               "if",
+               "else",
+               "import",
+               "return",
+               "label",
+               "switch",
+               "case",
+               "var",
+               "with",
+               "delete",
+               "new",
+               "this",
+               "typeof",
+               "void",
+               "abstract",
+               "boolean",
+               "byte",
+               "catch",
+               "char",
+               "class",
+               "const",
+               "debugger",
+               "default",
+               "double",
+               "enum",
+               "extends",
+               "false",
+               "final",
+               "finally",
+               "float",
+               "function",
+               "implements",
+               "goto",
+               "instanceof",
+               "int",
+               "interface",
+               "long",
+               "native",
+               "null",
+               "package",
+               "private",
+               "protected",
+               "public",
+               "short",
+               "static",
+               "super",
+               "synchronized",
+               "throw",
+               "throws",
+               "transient",
+               "true",
+               "try",
+               "volatile"
+       );
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/PerlHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/PerlHighlighter.class.php
new file mode 100644 (file)
index 0000000..a1393e5
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+
+/**
+ * Highlights syntax of Perl sourcecode.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011 Tim Duesterhus
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class PerlHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array('(', ')', '{', '}', '[', ']', ';', '.', ',');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$singleLineComment
+        */
+       protected $singleLineComment = array('#');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$commentStart
+        */
+       protected $commentStart = array();
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$commentEnd
+        */
+       protected $commentEnd = array();
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$operators
+        */
+       protected $operators = array('.=', '=', '>', '<', '!', '~', '?', ':', '==', '<=', '>=', '!=',
+               '&&', '||', '++', '--', '+', '-', '*', '/', '&', '|', '^', '%', '<<', '>>', '>>>', '+=', '-=', '*=',
+               '/=', '&=', '|=', '^=', '%=', '<<=', '>>=', '>>>=', '->', '::');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords1
+        */
+       protected $keywords1 = array(
+               'print',
+               'sprintf',
+               'length',
+               'substr',
+               'eval',
+               'die',
+               'opendir',
+               'closedir',
+               'open',
+               'close',
+               'chmod',
+               'unlink',
+               'flock',
+               'read',
+               'seek',
+               'stat',
+               'truncate',
+               'chomp',
+               'localtime',
+               'defined',
+               'undef',
+               'uc',
+               'lc',
+               'trim',
+               'split',
+               'sort',
+               'keys',
+               'push',
+               'pop',
+               'join',
+               'local',
+               'select',
+               'index',
+               'sqrt',
+               'system',
+               'crypt',
+               'pack',
+               'unpack'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords2
+        */
+       protected $keywords2 = array(
+               'case',
+               'do',
+               'while',
+               'for',
+               'if',
+               'foreach',
+               'my',
+               'else',
+               'elsif',
+               'eq',
+               'ne',
+               'or',
+               'xor',
+               'and',
+               'lt',
+               'gt',
+               'ge',
+               'le',
+               'return',
+               'last',
+               'goto',
+               'unless',
+               'given',
+               'when',
+               'default',
+               'until',
+               'break',
+               'exit'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords3
+        */
+       protected $keywords3 = array(
+               'use',
+               'import',
+               'require',
+               'sub'
+       );
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/PhpHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/PhpHighlighter.class.php
new file mode 100644 (file)
index 0000000..1d0059e
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\system\Regex;
+use wcf\util\StringUtil;
+
+/**
+ * Highlights syntax of PHP sourcecode.
+ * 
+ * @author     Tim Düsterhus, Marcel Werk
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class PhpHighlighter extends Highlighter {
+       public static $colorToClass = array();
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               parent::init();
+               
+               $types = array('default' => 'hlKeywords1', 'keyword' => 'hlKeywords2', 'comment' => 'hlComments', 'string' => 'hlQuotes');
+               
+               self::$colorToClass['<span style="color: '.ini_get('highlight.html').'">'] = '<span>';
+               foreach ($types as $type => $class) {
+                       self::$colorToClass['<span style="color: '.ini_get('highlight.'.$type).'">'] = '<span class="'.$class.'">';
+               }
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlight()
+        */
+       public function highlight($code) {
+               // add starting php tag
+               $phpTagsAdded = false;
+               if (StringUtil::indexOf($code, '<?') === false) {
+                       $phpTagsAdded = true;
+                       $code = '<?php '.$code.' ?>';
+               }
+               
+               // do highlight
+               $highlightedCode = highlight_string($code, true);
+               
+               // clear code
+               $highlightedCode = str_replace('<code>', '', $highlightedCode);
+               $highlightedCode = str_replace('</code>', '', $highlightedCode);
+               
+               // remove added php tags
+               if ($phpTagsAdded) {
+                       $regex = new Regex('([^\\2]*)(&lt;\?php&nbsp;)(.*)(&nbsp;.*\?&gt;)([^\\4]*)', Regex::CASE_INSENSITIVE | Regex::DOT_ALL);
+                       $highlightedCode = $regex->replace($highlightedCode, '\\1\\3\\5');
+               }
+               
+               // remove breaks
+               $highlightedCode = str_replace("\n", "", $highlightedCode);
+               $highlightedCode = str_replace('<br />', "\n", $highlightedCode);
+               // get tabs back
+               $highlightedCode = str_replace('&nbsp;&nbsp;&nbsp;&nbsp;', "\t", $highlightedCode);
+               // convert colors to classes
+               $highlightedCode = strtr($highlightedCode, self::$colorToClass);
+               
+               // replace double quotes by entity 
+               return Regex::compile('(?<!\<span class=)"(?!\>)')->replace($highlightedCode, '&quot;');
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/PlainHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/PlainHighlighter.class.php
new file mode 100644 (file)
index 0000000..9316fc3
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\util\StringUtil;
+
+/**
+ * Does no highlighting.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011 Tim Duesterhus
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class PlainHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlight()
+        */
+       public function highlight($code) {
+               return StringUtil::encodeHTML($code);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/PythonHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/PythonHighlighter.class.php
new file mode 100644 (file)
index 0000000..983422d
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+
+/**
+ * Highlights syntax of Python sourcecode.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011-2012 Tim Duesterhus
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class PythonHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array('(', ')',/* from __future__ import braces '{', '}', */'[', ']', ';', '.', ',', ':');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$singleLineComment
+        */
+       protected $singleLineComment = array('#');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$commentStart
+        */
+       protected $commentStart = array();
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$commentEnd
+        */
+       protected $commentEnd = array();
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$operators
+        */
+       protected $operators = array('+=', '-=', '**=', '*=', '//=', '/=', '%=', '~=', '+', '-', '**', '*', '//', '/', '%', 
+                                       '&=', '<<=', '>>=', '^=', '~', '&', '^', '|', '<<', '>>', '=', '!=', '<', '>', '<=', '>=');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$quotes
+        */
+       protected $quotes = array(array("r'", "'"), array("u'", "'"), array('r"', '"'), array('u"', '"'), "'", '"');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords1
+        */
+       protected $keywords1 = array(
+               'print',
+               'del',
+               'str',
+               'int',
+               'len',
+               'repr',
+               'range',
+               'raise',
+               'pass',
+               'continue',
+               'break',
+               'return'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords2
+        */
+       protected $keywords2 = array(
+               'if',
+               'elif',
+               'else',
+               'try',
+               'except',
+               'finally',
+               'for',
+               'in',
+               'while'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords3
+        */
+       protected $keywords3 = array(
+               'from',
+               'import',
+               'as',
+               'class',
+               'def'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords4
+        */
+       protected $keywords4 = array(
+               '__name__',
+               '__init__',
+               '__str__',
+               '__del__',
+               'self',
+               'True',
+               'False',
+               'None',
+               'and',
+               'or',
+               'not',
+               'is'
+       );
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/SqlHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/SqlHighlighter.class.php
new file mode 100644 (file)
index 0000000..009d75f
--- /dev/null
@@ -0,0 +1,434 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\system\Callback;
+use wcf\util\StringStack;
+use wcf\util\StringUtil;
+
+/**
+ * Highlights syntax of sql queries.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class SqlHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$allowsNewslinesInQuotes
+        */
+       protected $allowsNewslinesInQuotes = true;
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$quotes
+        */
+       protected $quotes = array("'", '"');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$singleLineComment
+        */
+       protected $singleLineComment = array('#', '--');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array('(', ')', ',', ';');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$operators
+        */
+       protected $operators = array('<>', '~=', '!=', '^=', '=', '<', '<=', '>', '>=', '*', '/', '+', '-', '||', '@', '%', '&', '?', '\$');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::cacheComments()
+        */
+       protected function cacheComments($string) {
+               if ($this->cacheCommentsRegEx !== null) {
+                       $string = $this->cacheCommentsRegEx->replace($string, new Callback(function (array $matches) {
+                               $string = $matches[1];
+                               if (isset($matches[2])) $comment = $matches[2];
+                               else $comment = '';
+                               
+                               // strip slashes
+                               $string = str_replace("\\\"", "\"", $string);
+                               $hash = '';
+                               if (!empty($comment)) {
+                                       $comment = str_replace("\\\"", "\"", $comment);
+                                               
+                                       // create hash
+                                       $hash = StringStack::pushToStringStack('<span class="hlComments">'.StringUtil::encodeHTML($comment).'</span>', 'highlighterComments', '!!!');
+                               }
+                               
+                               return $string.$hash;
+                       }));
+               }
+               
+               return $string;
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::cacheQuotes()
+        */
+       protected function cacheQuotes($string) {
+               if ($this->quotesRegEx !== null) {
+                       $string = $this->quotesRegEx->replace($string, new Callback(function (array $matches) {
+                               return StringStack::pushToStringStack('<span class="hlQuotes">'.StringUtil::encodeHTML($matches[0]).'</span>', 'highlighterQuotes', '!!!');
+                       }));
+               }
+               
+               return $string;
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords1
+        */
+       protected $keywords1 = array(
+               'action',
+               'add',
+               'aggregate',
+               'all',
+               'alter',
+               'after',
+               'and',
+               'as',
+               'asc',
+               'avg',
+               'avg_row_length',
+               'auto_increment',
+               'between',
+               'bigint',
+               'bit',
+               'binary',
+               'blob',
+               'bool',
+               'both',
+               'by',
+               'cascade',
+               'case',
+               'char',
+               'character',
+               'change',
+               'check',
+               'checksum',
+               'column',
+               'columns',
+               'comment',
+               'constraint',
+               'create',
+               'cross',
+               'current_date',
+               'current_time',
+               'current_timestamp',
+               'data',
+               'database',
+               'databases',
+               'date',
+               'datetime',
+               'day',
+               'day_hour',
+               'day_minute',
+               'day_second',
+               'dayofmonth',
+               'dayofweek',
+               'dayofyear',
+               'dec',
+               'decimal',
+               'default',
+               'delayed',
+               'delay_key_write',
+               'delete',
+               'desc',
+               'describe',
+               'distinct',
+               'distinctrow',
+               'double',
+               'drop',
+               'end',
+               'else',
+               'escape',
+               'escaped',
+               'enclosed',
+               'enum',
+               'explain',
+               'exists',
+               'fields',
+               'file',
+               'first',
+               'float',
+               'float4',
+               'float8',
+               'flush',
+               'foreign',
+               'from',
+               'for',
+               'full',
+               'function',
+               'global',
+               'grant',
+               'grants',
+               'group',
+               'having',
+               'heap',
+               'high_priority',
+               'hour',
+               'hour_minute',
+               'hour_second',
+               'hosts',
+               'identified',
+               'ignore',
+               'in',
+               'index',
+               'infile',
+               'inner',
+               'insert',
+               'insert_id',
+               'int',
+               'integer',
+               'interval',
+               'int1',
+               'int2',
+               'int3',
+               'int4',
+               'int8',
+               'into',
+               'if',
+               'is',
+               'isam',
+               'join',
+               'key',
+               'keys',
+               'kill',
+               'last_insert_id',
+               'leading',
+               'left',
+               'length',
+               'like',
+               'lines',
+               'limit',
+               'load',
+               'local',
+               'lock',
+               'logs',
+               'long',
+               'longblob',
+               'longtext',
+               'low_priority',
+               'max',
+               'max_rows',
+               'match',
+               'mediumblob',
+               'mediumtext',
+               'mediumint',
+               'middleint',
+               'min_rows',
+               'minute',
+               'minute_second',
+               'modify',
+               'month',
+               'monthname',
+               'myisam',
+               'natural',
+               'numeric',
+               'no',
+               'not',
+               'null',
+               'on',
+               'optimize',
+               'option',
+               'optionally',
+               'or',
+               'order',
+               'outer',
+               'outfile',
+               'pack_keys',
+               'partial',
+               'password',
+               'precision',
+               'primary',
+               'procedure',
+               'process',
+               'processlist',
+               'privileges',
+               'read',
+               'real',
+               'references',
+               'reload',
+               'regexp',
+               'rename',
+               'replace',
+               'restrict',
+               'returns',
+               'revoke',
+               'rlike',
+               'row',
+               'rows',
+               'second',
+               'select',
+               'set',
+               'show',
+               'shutdown',
+               'smallint',
+               'soname',
+               'sql_big_tables',
+               'sql_big_selects',
+               'sql_low_priority_updates',
+               'sql_log_off',
+               'sql_log_update',
+               'sql_select_limit',
+               'sql_small_result',
+               'sql_big_result',
+               'sql_warnings',
+               'straight_join',
+               'starting',
+               'status',
+               'string',
+               'table',
+               'tables',
+               'temporary',
+               'terminated',
+               'text',
+               'then',
+               'time',
+               'timestamp',
+               'tinyblob',
+               'tinytext',
+               'tinyint',
+               'trailing',
+               'to',
+               'type',
+               'use',
+               'using',
+               'unique',
+               'unlock',
+               'unsigned',
+               'update',
+               'usage',
+               'values',
+               'varchar',
+               'variables',
+               'varying',
+               'varbinary',
+               'with',
+               'write',
+               'when',
+               'where',
+               'year',
+               'year_month',
+               'zerofill'
+       );
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$keywords2
+        */
+       protected $keywords2 = array(
+               'ABS',
+               'ACOS',
+               'ADDDATE',
+               'ASCII',
+               'ASIN',
+               'ATAN',
+               'ATAN2',
+               'AVG',
+               'BENCHMARK',
+               'BIN',
+               'CEILING',
+               'CHAR',
+               'COALESCE',
+               'CONCAT',
+               'CONV',
+               'COS',
+               'COT',
+               'COUNT',
+               'CURDATE',
+               'CURTIME',
+               'DATABASE',
+               'DAYNAME',
+               'DAYOFMONTH',
+               'DAYOFWEEK',
+               'DAYOFYEAR',
+               'DECODE',
+               'DEGREES',
+               'ELT',
+               'ENCODE',
+               'ENCRYPT',
+               'EXP',
+               'EXTRACT',
+               'FIELD',
+               'FLOOR',
+               'FORMAT',
+               'GREATEST',
+               'HEX',
+               'HOUR',
+               'IF',
+               'IFNULL',
+               'INSERT',
+               'INSTR',
+               'INTERVAL',
+               'ISNULL',
+               'LCASE',
+               'LEAST',
+               'LEFT',
+               'LENGTH',
+               'LOCATE',
+               'LOCATE',
+               'LOG',
+               'LOG10',
+               'LOWER',
+               'LPAD',
+               'LTRIM',
+               'MAX',
+               'MD5',
+               'MID',
+               'MIN',
+               'MINUTE',
+               'MOD',
+               'MONTH',
+               'MONTHNAME',
+               'NOW',
+               'NULLIF',
+               'OCT',
+               'ORD',
+               'PASSWORD',
+               'PI',
+               'POSITION',
+               'POW',
+               'POWER',
+               'prepare',
+               'QUARTER',
+               'RADIANS',
+               'RAND',
+               'REPEAT',
+               'REPLACE',
+               'REVERSE',
+               'RIGHT',
+               'ROUND',
+               'ROUND',
+               'RPAD',
+               'RTRIM',
+               'SECOND',
+               'SIGN',
+               'SIN',
+               'SOUNDEX',
+               'SPACE',
+               'SQRT',
+               'STD',
+               'STDDEV',
+               'STRCMP',
+               'SUBDATE',
+               'SUBSTRING',
+               'SUBSTRING',
+               'SUM',
+               'SYSDATE',
+               'TAN',
+               'TRIM',
+               'TRUNCATE',
+               'UCASE',
+               'UPPER',
+               'USER',
+               'VERSION',
+               'WEEK',
+               'WEEKDAY',
+               'YEAR'
+       );
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/TexHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/TexHighlighter.class.php
new file mode 100644 (file)
index 0000000..1e477e2
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\system\Regex;
+
+/**
+ * Highlights syntax of TeX source code.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class TexHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$quotes
+        */
+       protected $quotes = array();
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$singleLineComment
+        */
+       protected $singleLineComment = array('%');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlightKeywords()
+        */
+       protected function highlightKeywords($string) {
+               $string = Regex::compile('\\$([^\\$]*)\\$', Regex::DOT_ALL)->replace($string, '<span class="hlKeywords2">\\0</span>');
+               $string = Regex::compile('(\\\\(?:[a-z]+))(\\[[^\\]\\\\]+\\])?(\\{[^\\}]*\\})?', Regex::CASE_INSENSITIVE)->replace($string, '<span class="hlKeywords3">\\1</span><span class="hlKeywords4">\\2</span><span class="hlKeywords1">\\3</span>');
+               $string = str_replace('\\\\', '<span class="hlKeywords3">\\\\</span>', $string);
+               
+               return $string;
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlightNumbers()
+        */
+       protected function highlightNumbers($string) {
+               // do not highlight numbers
+               return $string;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/TplHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/TplHighlighter.class.php
new file mode 100644 (file)
index 0000000..00ae85a
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\system\Regex;
+
+/**
+ * Highlights syntax of template documents with smarty-syntax.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class TplHighlighter extends HtmlHighlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlightComments()
+        */
+       protected function highlightComments($string) {
+               $string = parent::highlightComments($string);
+               
+               // highlight template tags
+               $string = Regex::compile('\{(?=\S).+?(?<=\S)\}', Regex::DOT_ALL)->replace($string, '<span class="hlKeywords3">\\0</span>');
+               
+               return $string;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/bbcode/highlighter/XmlHighlighter.class.php b/wcfsetup/install/files/lib/system/bbcode/highlighter/XmlHighlighter.class.php
new file mode 100644 (file)
index 0000000..86985af
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+namespace wcf\system\bbcode\highlighter;
+use wcf\system\Callback;
+use wcf\system\Regex;
+use wcf\util\StringStack;
+use wcf\util\StringUtil;
+
+/**
+ * Highlights syntax of xml sourcecode.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.bbcode.highlighter
+ * @category   Community Framework
+ */
+class XmlHighlighter extends Highlighter {
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$allowsNewslinesInQuotes
+        */
+       protected $allowsNewslinesInQuotes = true;
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$quotes
+        */
+       protected $quotes = array('"');
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$singleLineComment
+        */
+       protected $singleLineComment = array();
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$commentStart
+        */
+       protected $commentStart = array("<!--");
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$commentEnd
+        */
+       protected $commentEnd = array("-->");
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$separators
+        */
+       protected $separators = array("<", ">");
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::$operators
+        */
+       protected $operators = array();
+       
+       const XML_ATTRIBUTE_NAME = '[a-z0-9](?:(?:(?<!-)-)?[a-z0-9])*';
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlightKeywords()
+        */
+       protected function highlightKeywords($string) {
+               $string = parent::highlightKeywords($string);
+               // find tags
+               $regex = new Regex('&lt;(?:/|\!|\?)?[a-z0-9]+(?:\s+'.self::XML_ATTRIBUTE_NAME.'(?:=[^\s/\?&]+)?)*(?:\s+/|\?)?&gt;', Regex::CASE_INSENSITIVE);
+               $string = $regex->replace($string, new Callback(function ($matches) {
+                       // highlight attributes
+                       $tag = Regex::compile(XmlHighlighter::XML_ATTRIBUTE_NAME.'(?:=[^\s/\?&]+)?(?=\s|&)', Regex::CASE_INSENSITIVE)->replace($matches[0], '<span class="hlKeywords2">\\0</span>');
+                       
+                       // highlight tag
+                       return '<span class="hlKeywords1">'.$tag.'</span>';
+               }));
+               
+               return $string;
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::cacheQuotes()
+        */
+       protected function cacheQuotes($string) {
+               // highlight CDATA-Tags as quotes
+               $string = Regex::compile('<!\[CDATA\[.*?\]\]>', Regex::DOT_ALL)->replace($string, new Callback(function (array $matches) {
+                       return StringStack::pushToStringStack('<span class="hlQuotes">'.StringUtil::encodeHTML($matches[0]).'</span>', 'highlighterQuotes');
+               }));
+               
+               $string = parent::cacheQuotes($string);
+               
+               return $string;
+       }
+       
+       /**
+        * @see wcf\system\bbcode\highlighter\Highlighter::highlightNumbers()
+        */
+       protected function highlightNumbers($string) {
+               // do not highlight numbers
+               return $string;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/cache/builder/BBCodeCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/BBCodeCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..b9053ac
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\bbcode\attribute\BBCodeAttribute;
+use wcf\data\bbcode\BBCode;
+use wcf\system\WCF;
+
+/**
+ * Caches the bbcodes.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.cache.builder
+ * @category   Community Framework
+ */
+class BBCodeCacheBuilder extends AbstractCacheBuilder {
+       /**
+        * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+        */
+       protected function rebuild(array $parameters) {
+               $data = $attributes = array();
+               
+               // get attributes
+               $sql = "SELECT          attribute.*, bbcode.bbcodeTag
+                       FROM            wcf".WCF_N."_bbcode_attribute attribute
+                       LEFT JOIN       wcf".WCF_N."_bbcode bbcode
+                       ON              (bbcode.bbcodeID = attribute.bbcodeID)
+                       WHERE           bbcode.isDisabled = 0
+                       ORDER BY        attribute.attributeNo";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+               while ($row = $statement->fetchArray()) {
+                       if (!isset($attributes[$row['bbcodeTag']])) $attributes[$row['bbcodeTag']] = array();
+                       
+                       $attributes[$row['bbcodeTag']][$row['attributeNo']] = new BBCodeAttribute(null, $row);
+               }
+               
+               // get bbcodes
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_bbcode
+                       WHERE   isDisabled = 0";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+               while ($row = $statement->fetchArray()) {
+                       $row['attributes'] = (isset($attributes[$row['bbcodeTag']]) ? $attributes[$row['bbcodeTag']] : array());
+                       $data[$row['bbcodeTag']] = new BBCode(null, $row);
+               }
+               
+               return $data;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/cache/builder/BBCodeMediaProviderCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/BBCodeMediaProviderCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..e56d693
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\bbcode\media\provider\BBCodeMediaProviderList;
+
+/**
+ * Caches the BBCode media providers.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2011-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.cache.builder
+ * @category   Community Framework
+ */
+class BBCodeMediaProviderCacheBuilder extends AbstractCacheBuilder {
+       /**
+        * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+        */
+       protected function rebuild(array $parameters) {
+               $providerList = new BBCodeMediaProviderList();
+               $providerList->readObjects();
+               
+               return $providerList->getObjects();
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/cache/builder/SmileyCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/SmileyCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..64e351e
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+namespace wcf\system\cache\builder;
+use wcf\system\WCF;
+
+/**
+ * Caches the smilies.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.cache.builder
+ * @category   Community Framework
+ */
+class SmileyCacheBuilder extends AbstractCacheBuilder {
+       /**
+        * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+        */
+       protected function rebuild(array $parameters) {
+               $data = array('smilies' => array());
+               
+               // get smilies
+               $sql = "SELECT          *
+                       FROM            wcf".WCF_N."_smiley
+                       ORDER BY        showOrder";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+               while ($object = $statement->fetchObject('wcf\data\smiley\Smiley')) {
+                       $object->smileyCodes = $object->getAliases();
+                       $object->smileyCodes[] = $object->smileyCode;
+                       
+                       $data['smilies'][$object->categoryID][$object->smileyID] = $object;
+               }
+               
+               return $data;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/category/SmileyCategoryType.class.php b/wcfsetup/install/files/lib/system/category/SmileyCategoryType.class.php
new file mode 100644 (file)
index 0000000..252ebc8
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+namespace wcf\system\category;
+use wcf\system\category\AbstractCategoryType;
+use wcf\system\WCF;
+
+/**
+ * Category implementation for smilies.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.category
+ * @category   Community Framework
+ */
+class SmileyCategoryType extends AbstractCategoryType {
+       /**
+        * @see wcf\system\category\AbstractCategoryType::$langVarPrefix
+        */
+       protected $langVarPrefix = 'wcf.smiley.category';
+       
+       /**
+        * @see wcf\system\category\AbstractCategoryType::$forceDescription
+        */
+       protected $hasDescription = false;
+       
+       /**
+        * @see wcf\system\category\AbstractCategoryType::$maximumNestingLevel
+        */
+       protected $maximumNestingLevel = 0;
+       
+       /**
+        * @see wcf\system\category\ICategoryType::getApplication()
+        */
+       public function getApplication() {
+               return 'wcf';
+       }
+       
+       /**
+        * @see wcf\system\category\ICategoryType::canAddCategory()
+        */
+       public function canAddCategory() {
+               return $this->canEditCategory();
+       }
+       
+       /**
+        * @see wcf\system\category\ICategoryType::canDeleteCategory()
+        */
+       public function canDeleteCategory() {
+               return $this->canEditCategory();
+       }
+       
+       /**
+        * @see wcf\system\category\ICategoryType::canEditCategory()
+        */
+       public function canEditCategory() {
+               return WCF::getSession()->getPermission('admin.content.smiley.canManageSmiley');
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/option/user/group/BBCodeSelectUserGroupOptionType.class.php b/wcfsetup/install/files/lib/system/option/user/group/BBCodeSelectUserGroupOptionType.class.php
new file mode 100644 (file)
index 0000000..6573f19
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+namespace wcf\system\option\user\group;
+use wcf\data\bbcode\BBCodeCache;
+use wcf\data\option\Option;
+use wcf\system\exception\UserInputException;
+use wcf\system\option\AbstractOptionType;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * User group option type implementation for BBCode select lists.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage system.option
+ * @category   Community Framework
+ */
+class BBCodeSelectUserGroupOptionType extends AbstractOptionType implements IUserGroupOptionType {
+       /**
+        * available BBCodes
+        * @var array<string>
+        */
+       protected $bbCodes = null;
+       
+       /**
+        * @see wcf\system\option\IOptionType::getData()
+        */
+       public function getData(Option $option, $newValue) {
+               if (!is_array($newValue)) {
+                       $newValue = array();
+               }
+               
+               return implode(',', $newValue);
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionType::getFormElement()
+        */
+       public function getFormElement(Option $option, $value) {
+               if ($this->bbCodes === null) {
+                       $this->loadBBCodeSelection();
+               }
+               
+               $selectedBBCodes = array();
+               if ($value == 'all') {
+                       $selectedBBCodes = $this->bbCodes;
+               }
+               else {
+                       $selectedBBCodes = explode(',', $value);
+               }
+               
+               WCF::getTPL()->assign(array(
+                       'bbCodes' => $this->bbCodes,
+                       'option' => $option,
+                       'selectedBBCodes' => $selectedBBCodes
+               ));
+               
+               return WCF::getTPL()->fetch('bbCodeSelectOptionType');
+       }
+       
+       /**
+        * Loads the list of BBCodes for the HTML select element.
+        * 
+        * @return      array<string>
+        */
+       protected function loadBBCodeSelection() {
+               $this->bbCodes = array_keys(BBCodeCache::getInstance()->getBBCodes());
+               asort($this->bbCodes);
+       }
+       
+       /**
+        * @see wcf\system\option\user\group\IUserGroupOptionType::merge()
+        */
+       public function diff($defaultValue, $groupValue) {
+               if ($this->bbCodes === null) {
+                       $this->loadBBCodeSelection();
+               }
+               
+               if ($defaultValue == 'all') {
+                       $defaultValue = $this->bbCodes;
+               }
+               else {
+                       $defaultValue = explode(',', StringUtil::unifyNewlines($defaultValue));
+               }
+               if ($groupValue == 'all') {
+                       $groupValue = $this->bbCodes;
+               }
+               else {
+                       $groupValue = explode(',', StringUtil::unifyNewlines($groupValue));
+               }
+               
+               $result = array_diff($groupValue, $defaultValue);
+               if (empty($result)) {
+                       return null;
+               }
+               
+               return implode(',', $result);
+       }
+       
+       /**
+        * @see wcf\system\option\user\group\IUserGroupOptionType::merge()
+        */
+       public function merge($defaultValue, $groupValue) {
+               if ($this->bbCodes === null) {
+                       $this->loadBBCodeSelection();
+               }
+               
+               if ($defaultValue == 'all') {
+                       $defaultValue = $this->bbCodes;
+               }
+               else if (empty($defaultValue) || $defaultValue == 'none') {
+                       $defaultValue = array();
+               }
+               else {
+                       $defaultValue = explode(',', StringUtil::unifyNewlines($defaultValue));
+               }
+               if ($groupValue == 'all') {
+                       $groupValue = $this->bbCodes;
+               }
+               else if (empty($groupValue) || $groupValue == 'none') {
+                       $groupValue = array();
+               }
+               else {
+                       $groupValue = explode(',', StringUtil::unifyNewlines($groupValue));
+               }
+               
+               $newValue = array_unique(array_merge($defaultValue, $groupValue));
+               sort($newValue);
+               
+               return implode(',', $newValue);
+       }
+       
+       /**
+        * @see wcf\system\option\IOptionType::validate()
+        */
+       public function validate(Option $option, $newValue) {
+               if (!is_array($newValue)) {
+                       $newValue = array();
+               }
+               
+               if ($this->bbCodes === null) {
+                       $this->loadBBCodeSelection();
+               }
+               
+               foreach ($newValue as $tag) {
+                       if (!in_array($tag, $this->bbCodes)) {
+                               throw new UserInputException($option->optionName, 'validationFailed');
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/BBCodePackageInstallationPlugin.class.php
new file mode 100644 (file)
index 0000000..d3b73db
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+namespace wcf\system\package\plugin;
+use wcf\data\bbcode\attribute\BBCodeAttributeEditor;
+use wcf\system\exception\SystemException;
+use wcf\system\WCF;
+
+/**
+ * Installs, updates and deletes bbcodes.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.package.plugin
+ * @category   Community Framework
+ */
+class BBCodePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$className
+        */
+       public $className = 'wcf\data\bbcode\BBCodeEditor';
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName
+        */
+       public $tableName = 'bbcode';
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$tagName
+        */
+       public $tagName = 'bbcode';
+       
+       /**
+        * list of attributes per bbcode id
+        * @var array<array>
+        */
+       protected $attributes = array();
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::handleDelete()
+        */
+       protected function handleDelete(array $items) {
+               $sql = "DELETE FROM     wcf".WCF_N."_".$this->tableName."
+                       WHERE           bbcodeTag = ?
+                                       AND packageID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               foreach ($items as $item) {
+                       $statement->execute(array(
+                               $item['attributes']['name'],
+                               $this->installation->getPackageID()
+                       ));
+               }
+       }
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::getElement()
+        */
+       protected function getElement(\DOMXPath $xpath, array &$elements, \DOMElement $element) {
+               $nodeValue = $element->nodeValue;
+               
+               // read pages
+               if ($element->tagName == 'attributes') {
+                       $nodeValue = array();
+                       
+                       $attributes = $xpath->query('child::*', $element);
+                       foreach ($attributes as $attribute) {
+                               $attributeNo = $attribute->getAttribute('name');
+                               $nodeValue[$attributeNo] = array();
+                               
+                               $attributeValues = $xpath->query('child::*', $attribute);
+                               foreach ($attributeValues as $attributeValue) {
+                                       $nodeValue[$attributeNo][$attributeValue->tagName] = $attributeValue->nodeValue;
+                               }
+                       }
+               }
+               
+               $elements[$element->tagName] = $nodeValue;
+       }
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::prepareImport()
+        */
+       protected function prepareImport(array $data) {
+               return array(
+                       'bbcodeTag' => $data['attributes']['name'],
+                       'htmlOpen' => (!empty($data['elements']['htmlopen']) ? $data['elements']['htmlopen'] : ''),
+                       'htmlClose' => (!empty($data['elements']['htmlclose']) ? $data['elements']['htmlclose'] : ''),
+                       'allowedChildren' => (!empty($data['elements']['allowedchildren']) ? $data['elements']['allowedchildren'] : 'all'),
+                       'wysiwygIcon' => (!empty($data['elements']['wysiwygicon']) ? $data['elements']['wysiwygicon'] : ''),
+                       'attributes' => (isset($data['elements']['attributes']) ? $data['elements']['attributes'] : array()),
+                       'className' => (!empty($data['elements']['classname']) ? $data['elements']['classname'] : ''),
+                       'isSourceCode' => (!empty($data['elements']['sourcecode']) ? 1 : 0),
+               );
+       }
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::validateImport()
+        */
+       protected function validateImport(array $data) {
+               if ($data['bbcodeTag'] == 'all' || $data['bbcodeTag'] == 'none') {
+                       throw new SystemException("BBCodes can't be called 'all' or 'none'");
+               }
+       }
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::findExistingItem()
+        */
+       protected function findExistingItem(array $data) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_".$this->tableName."
+                       WHERE   bbcodeTag = ?
+                               AND packageID = ?";
+               $parameters = array(
+                       $data['bbcodeTag'],
+                       $this->installation->getPackageID()
+               );
+               
+               return array(
+                       'sql' => $sql,
+                       'parameters' => $parameters
+               );
+       }
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::import()
+        */
+       protected function import(array $row, array $data) {
+               // extract attributes
+               $attributes = $data['attributes'];
+               unset($data['attributes']);
+               
+               // import or update action
+               $object = parent::import($row, $data);
+               
+               // store attributes for later import
+               $this->attributes[$object->bbcodeID] = $attributes;
+       }
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::postImport()
+        */
+       protected function postImport() {
+               // clear attributes
+               $sql = "DELETE FROM     wcf".WCF_N."_bbcode_attribute
+                       WHERE           bbcodeID IN (
+                                               SELECT  bbcodeID
+                                               FROM    wcf".WCF_N."_bbcode
+                                               WHERE   packageID = ?
+                                       )";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($this->installation->getPackageID()));
+               
+               if (!empty($this->attributes)) {
+                       foreach ($this->attributes as $bbcodeID => $bbcodeAttributes) {
+                               foreach ($bbcodeAttributes as $attributeNo => $attribute) {
+                                       BBCodeAttributeEditor::create(array(
+                                               'bbcodeID' => $bbcodeID,
+                                               'attributeNo' => $attributeNo,
+                                               'attributeHtml' => (!empty($attribute['html']) ? $attribute['html'] : ''),
+                                               'validationPattern' => (!empty($attribute['validationpattern']) ? $attribute['validationpattern'] : ''),
+                                               'required' => (!empty($attribute['required']) ? $attribute['required'] : 0),
+                                               'useText' => (!empty($attribute['usetext']) ? $attribute['usetext'] : 0),
+                                       ));
+                               }
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/package/plugin/SmileyPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/SmileyPackageInstallationPlugin.class.php
new file mode 100644 (file)
index 0000000..792171c
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+namespace wcf\system\package\plugin;
+use wcf\system\WCF;
+
+/**
+ * Installs, updates and deletes smilies.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.bbcode
+ * @subpackage acp.package.plugin
+ * @category   Community Framework
+ */
+class SmileyPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$className
+        */
+       public $className = 'wcf\data\smiley\SmileyEditor';
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName
+        */
+       public $tableName = 'smiley';
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$tagName
+        */
+       public $tagName = 'smiley';
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::handleDelete()
+        */
+       protected function handleDelete(array $items) {
+               $sql = "DELETE FROM     wcf".WCF_N."_".$this->tableName."
+                       WHERE           smileyCode = ?
+                                       AND packageID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               foreach ($items as $item) {
+                       $statement->execute(array(
+                               $item['attributes']['name'],
+                               $this->installation->getPackageID()
+                       ));
+               }
+       }
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::prepareImport()
+        */
+       protected function prepareImport(array $data) {
+               return array(
+                       'smileyCode' => $data['attributes']['name'],
+                       'smileyTitle' => $data['elements']['title'],
+                       'smileyPath' => $data['elements']['path'],
+                       'aliases' => (isset($data['elements']['aliases']) ? $data['elements']['aliases'] : '')
+               );
+       }
+       
+       /**
+        * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::findExistingItem()
+        */
+       protected function findExistingItem(array $data) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_".$this->tableName."
+                       WHERE   smileyCode = ?
+                               AND packageID = ?";
+               $parameters = array(
+                       $data['smileyCode'],
+                       $this->installation->getPackageID()
+               );
+               
+               return array(
+                       'sql' => $sql,
+                       'parameters' => $parameters
+               );
+       }
+}
diff --git a/wcfsetup/install/files/style/bbcode.less b/wcfsetup/install/files/style/bbcode.less
new file mode 100644 (file)
index 0000000..632bb74
--- /dev/null
@@ -0,0 +1,284 @@
+/* Code Box */
+.codeBox {
+       background-color: @wcfContentBackgroundColor;
+       
+       > div {
+               padding: @wcfGapMedium @wcfGapLarge @wcfGapMedium @wcfGapMedium;
+               
+               .boxShadowNative(~"inset 4em 0 0 " @wcfContainerBackgroundColor ~", inset 4.1em 0 0" lighten(@wcfContainerBorderColor, 10%));
+               
+               > div {
+                       margin: 0 0 @wcfGapSmall 3.4em;
+                       
+                       > h3 {
+                               font-size: 110%;
+                               font-weight: bold;
+                               padding-left: @wcfGapSmall;
+                               position: relative;
+                               
+                               > span.icon {
+                                       position: absolute;
+                                       right: 0;
+                                       top: 3px;
+                               }
+                       }
+               }
+               
+               > ol {
+                       list-style-type: decimal;
+                       margin-left: 3.4em;
+                       
+                       > li {
+                               font-family: Consolas, 'Courier New', monospace;
+                               padding-left: @wcfGapSmall;
+                               position: relative;
+                               white-space: pre-wrap;
+                               word-break: break-all;
+                               word-wrap: break-word;
+                               
+                               > .lineAnchor {
+                                       display: block;
+                                       height: 1.4em;
+                                       left: -3.6em;
+                                       position: absolute;
+                                       top: 0;
+                                       width: 3.0em;
+                               }
+                               
+                               &:target {
+                                       background-color: @wcfHighlightBackgroundColor; 
+                               }
+                       }
+               }
+       }
+}
+
+@media only screen and (max-width: 800px) {
+       .codeBox {
+               > div {
+                       padding: @wcfGapSmall;
+                       
+                       .boxShadow(0, 0, transparent);
+                       
+                       > div {
+                               margin-left: 0;
+                               
+                               > h3 {
+                                       padding-left: 0;
+                               }
+                       }
+                       
+                       > ol {
+                               list-style-type: none;
+                               margin-left: 0;
+                               
+                               > li {
+                                       padding-left: 0;
+                               }
+                       }
+               }
+       }
+}
+
+/* ############## Code Styles ############## */
+
+/* -- -- -- Code Box -- -- -- */
+
+.codeBox .hlQuotes {
+       color: red;
+}
+
+.codeBox .hlComments,
+.codeBox .hlOperators {
+       color: green;
+}
+
+.codeBox .hlKeywords1 {
+       color: blue;
+}
+
+.codeBox .hlKeywords2 {
+       color: darkred;
+}
+
+.codeBox .hlKeywords3 {
+       color: darkviolet;
+}
+
+.codeBox .hlKeywords4 {
+       color: darkgoldenrod;
+}
+
+.codeBox .hlKeywords5 {
+       color: crimson;
+}
+
+.codeBox .hlNumbers {
+       color: darkorange;
+}
+
+/* -- -- -- Code Highlighters -- -- -- */
+
+/* DIFF */
+
+.diffHighlighter .hlComments {
+       color: darkviolet;
+}
+
+.diffHighlighter .hlRemoved {
+       color: red;
+}
+
+.diffHighlighter .hlAdded {
+       color: green;
+}
+
+/* PHP */
+
+.phpHighlighter .hlKeywords2 {
+       color: green;
+}
+
+.phpHighlighter .hlComments {
+       color: darkgoldenrod;
+}
+
+/* CSS */
+
+.cssHighlighter .hlComments {
+       color: #236e26;
+}
+
+.cssHighlighter .hlColors {
+       color: #751116;
+}
+
+.cssHighlighter .hlNumbers,
+.sqlHighlighter .hlNumbers {
+       color: #1906fd;
+}
+
+.cssHighlighter .hlKeywords1 {
+       color: #87154f;
+}
+
+.cssHighlighter .hlKeywords2 {
+       color: #994509;
+}
+
+.cssHighlighter .hlKeywords3,
+.cssHighlighter .hlKeywords4 {
+       color: inherit;
+}
+
+/* SQL */
+
+.sqlHighlighter .hlKeywords1 {
+       color: #663821;
+}
+
+.sqlHighlighter .hlKeywords2 {
+       color: #871550;
+}
+
+/* Quote Box */
+.quoteBox {
+       background-color: @wcfContentBackgroundColor;
+       min-height: 28px;
+       margin-bottom: @wcfGapTiny;
+       position: relative;
+       
+       &.containerPadding {
+               padding-left: 54px;
+       }
+       
+       &:before {
+               content: "\f10d";
+               color: @wcfDimmedColor;
+               font-family: FontAwesome;
+               font-size: 28px;
+               position: absolute;
+               left: @wcfGapMedium;
+               top: @wcfGapSmall;
+       }
+       
+       > header {
+               padding-bottom: @wcfGapTiny;
+               border-bottom: 1px dotted @wcfContainerBorderColor;
+               margin-bottom: @wcfGapSmall;
+               
+               > h3 {
+                       font-weight: bold;
+               }
+       }
+       
+       /* nested quotes */
+       .quoteBox {
+               background-image: none;
+               padding-left: @wcfGapLarge;
+               min-height: 0;
+               
+               &:before {
+                       display: none;
+               }
+       }
+}
+
+@media only screen and (max-width: 800px) {
+       .quoteBox {
+               &:before {
+                       font-size: 14px;
+                       left: @wcfGapSmall;
+               }
+               
+               &.containerPadding {
+                       padding-left: 28px;
+               }
+               
+               .quoteBox {
+                       padding-left: @wcfGapSmall;
+               }
+       }
+}
+
+/* spoiler box */
+.spoilerBox {
+       background-color: @wcfContentBackgroundColor;
+       padding-bottom: @wcfGapSmall;
+       
+       > header {
+               margin-bottom: @wcfGapSmall;
+       }
+       
+       > div {
+               padding-bottom: @wcfGapSmall;
+       }
+}
+
+/* inline code tag */
+.inlineCode {
+       background-color: @wcfContentBackgroundColor;
+       border: 1px solid @wcfContainerBorderColor;
+       display: inline-block;
+       font-family: Consolas, 'Courier New', monospace;
+       margin: 0 2px;
+       padding: 0 5px;
+       
+       .borderRadius(3px);
+}
+
+/* smiley list */
+ul.smileyList > li {
+       display: inline;
+}
+
+/* bbcode table */
+.bbcodeTable {
+       display: inline-block;
+}
+
+/* media provider */
+/* thanks for this generic class name usage, github */
+.gist .highlight {
+       background-color: inherit;
+}
index becf44e3c34ed22ee5f70c136b7e604602d43e58..9e0bc0b60f0aa31976a59319aa1d48379f480412 100644 (file)
                <item name="wcf.acp.attachment.noItems"><![CDATA[Es wurden keine Dateianhänge gefunden.]]></item>
        </category>
        
+       <category name="wcf.acp.bbcode">
+               <item name="wcf.acp.bbcode.add"><![CDATA[BBCode hinzufügen]]></item>
+               <item name="wcf.acp.bbcode.add.userGroupOptionInfo"><![CDATA[Beachten Sie, dass der neu erstellte BBCode standardmäßig überall verwenden werden kann. Soll das nicht der Fall sein, bearbeiten Sie nach der Erstellung des BBCodes die entsprechenden Benutzergruppenrechte.]]></item>
+               <item name="wcf.acp.bbcode.allowedChildren"><![CDATA[Erlaubte Kindelemente]]></item>
+               <item name="wcf.acp.bbcode.allowedChildren.error.notValid"><![CDATA[Die Form für die erlaubten Kindelemente ist ungültig.]]></item>
+               <item name="wcf.acp.bbcode.attribute"><![CDATA[Attribut]]></item>
+               <item name="wcf.acp.bbcode.attribute.attributeHtml"><![CDATA[HTML-Code des Attributs]]></item>
+               <item name="wcf.acp.bbcode.attribute.validationPattern.error.notValid"><![CDATA[Dies ist kein gültiger regulärer Ausdruck.]]></item>
+               <item name="wcf.acp.bbcode.attribute.validationPattern"><![CDATA[Regulärer Ausdruck zur Validierung]]></item>
+               <item name="wcf.acp.bbcode.attribute.required"><![CDATA[Attribut muss ausgefüllt sein]]></item>
+               <item name="wcf.acp.bbcode.attribute.useText"><![CDATA[Inhalt übernehmen]]></item>
+               <item name="wcf.acp.bbcode.attribute.useText.description"><![CDATA[Soll der Inhalt des BBCodes als Wert für dieses Attribut übernommen werden, wenn es nicht gesetzt ist?]]></item>
+               <item name="wcf.acp.bbcode.attributes"><![CDATA[Attribute]]></item>
+               <item name="wcf.acp.bbcode.bbcodeTag"><![CDATA[BBCode]]></item>
+               <item name="wcf.acp.bbcode.bbcodeTag.error.inUse"><![CDATA[Dieser BBCode ist bereits vorhanden.]]></item>
+               <item name="wcf.acp.bbcode.bbcodeTag.error.notValid"><![CDATA[Dieser BBCode ist ungültig.]]></item>
+               <item name="wcf.acp.bbcode.className"><![CDATA[Klassen-Name]]></item>
+               <item name="wcf.acp.bbcode.className.error.notFound"><![CDATA[Diese Klasse wurde nicht gefunden.]]></item>
+               <item name="wcf.acp.bbcode.delete.sure"><![CDATA[Wollen Sie den BBCode „[{$bbcode->bbcodeTag}]“ wirklich löschen?]]></item>
+               <item name="wcf.acp.bbcode.edit"><![CDATA[BBCode bearbeiten]]></item>
+               <item name="wcf.acp.bbcode.htmlClose"><![CDATA[Schließendes HTML-Tag]]></item>
+               <item name="wcf.acp.bbcode.htmlOpen"><![CDATA[Öffnendes HTML-Tag]]></item>
+               <item name="wcf.acp.bbcode.list"><![CDATA[BBCodes]]></item>
+               
+               <item name="wcf.acp.bbcode.mediaProvider.add"><![CDATA[Medienanbieter hinzufügen]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.delete.sure"><![CDATA[Wollen Sie den Medienanbieter „{$mediaProvider->title}“ wirklich löschen?]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.edit"><![CDATA[Medienanbieter bearbeiten]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.html"><![CDATA[HTML-Code]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.html.description"><![CDATA[{literal}Geben Sie hier den HTML-Code für das Medium an. Variablen im Format <code>{$variable}</code> werden durch die entsprechend benannte Untergruppe des regulären Ausdrucks ersetzt.<br />
+               Beispiel:<br />
+               <code>{$ID}</code> wird durch die beispielhaften Medium-IDs der Erklärung oberhalb ersetzt.{/literal}]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.list"><![CDATA[Medienanbieter]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.regex"><![CDATA[Regulärer Ausdruck]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.regex.description"><![CDATA[Geben Sie hier das Linkformat als <a href="{@$__wcf->getPath('wcf')}acp/dereferrer.php?url=http%3A%2F%2Fde.wikipedia.org%2Fwiki%2FRegul%25C3%25A4rer_Ausdruck">regulären Ausdruck</a> an.<br />
+               Mehrere Ausdrücke können zeilenweise angegeben werden.<br />
+               Beispiele für die Medium-ID Erkennungen:
+               <ul>
+                       <li><code>(?&lt;ID&gt;[0-9]+)</code> erkennt eine Medium-ID aus Zahlen.</li>
+                       <li><code>(?&lt;ID&gt;[0-9a-zA-Z]+)</code> erkennt eine alphanumerische Medium-ID.</li>
+               </ul>]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.regex.error.notValid"><![CDATA[Dies ist kein gültiger regulärer Ausdruck.]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.title"><![CDATA[Medienanbieter]]></item>
+               <item name="wcf.acp.bbcode.isSourceCode"><![CDATA[Inhalt als Quelltext behandeln]]></item>
+               <item name="wcf.acp.bbcode.isSourceCode.description"><![CDATA[Der Inhalt dieses BBCodes wird als Quelltext interpretiert, BBCodes und Smileys werden nicht umgewandelt.]]></item>
+               <item name="wcf.acp.bbcode.showButton"><![CDATA[Button im WYSIWYG-Editor anzeigen]]></item>
+               <item name="wcf.acp.bbcode.buttonLabel"><![CDATA[Button-Beschriftung]]></item>
+               <item name="wcf.acp.bbcode.wysiwygIcon"><![CDATA[Button-Grafik]]></item>
+       </category>
+       
        <category name="wcf.acp.cache">
                <item name="wcf.acp.cache.button.clear"><![CDATA[Cache leeren]]></item>
                <item name="wcf.acp.cache.clear.sure"><![CDATA[Wollen Sie den Cache wirklich komplett leeren?]]></item>
                <item name="wcf.acp.group.option.user.attachment.quota.description"><![CDATA[Dem Nutzer zur Verfügung stehenden Speicherplatz für alle Dateianhänge insgesamt.]]></item>
                <item name="wcf.acp.group.option.category.admin.attachment"><![CDATA[Dateianhänge]]></item>
                <item name="wcf.acp.group.option.admin.attachment.canManageAttachment"><![CDATA[Kann Dateianhänge verwalten]]></item>
+               <item name="wcf.acp.group.option.category.admin.content.bbcode"><![CDATA[BBCodes]]></item>
+               <item name="wcf.acp.group.option.admin.content.bbcode.canManageBBCode"><![CDATA[Kann BBCodes verwalten]]></item>
+               <item name="wcf.acp.group.option.category.admin.content.smiley"><![CDATA[Smileys]]></item>
+               <item name="wcf.acp.group.option.admin.content.smiley.canManageSmiley"><![CDATA[Kann Smileys verwalten]]></item>
        </category>
        
        <category name="wcf.acp.index">
                <item name="wcf.acp.menu.link.template.group.add"><![CDATA[Templategruppe hinzufügen]]></item>
                <item name="wcf.acp.menu.link.attachment"><![CDATA[Dateianhänge]]></item>
                <item name="wcf.acp.menu.link.attachment.list"><![CDATA[Dateianhänge verwalten]]></item>
+               <item name="wcf.acp.menu.link.bbcode"><![CDATA[BBCodes]]></item>
+               <item name="wcf.acp.menu.link.bbcode.add"><![CDATA[BBCode hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.bbcode.list"><![CDATA[BBCodes auflisten]]></item>
+               <item name="wcf.acp.menu.link.bbcode.mediaProvider.add"><![CDATA[Medienanbieter hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.bbcode.mediaProvider.list"><![CDATA[Medienanbieter auflisten]]></item>
+               <item name="wcf.acp.menu.link.smiley"><![CDATA[Smileys]]></item>
+               <item name="wcf.acp.menu.link.smiley.add"><![CDATA[Smiley hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.smiley.list"><![CDATA[Smileys auflisten]]></item>
+               <item name="wcf.acp.menu.link.smiley.category.add"><![CDATA[Smiley-Kategorie hinzufügen]]></item>
+               <item name="wcf.acp.menu.link.smiley.category.list"><![CDATA[Smiley-Kategorien auflisten]]></item>
        </category>
        
        <category name="wcf.acp.option">
                <item name="wcf.acp.option.attachment_storage"><![CDATA[Speicherort der Dateianhänge]]></item>
                <item name="wcf.acp.option.attachment_storage.description"><![CDATA[Sie können optional einen vom Standard abweichenden Speicherort für die Dateianhänge definieren. Bereits existierende Dateianhänge müssen bei einer Änderung des Speicherortes manuell in den neuen Ort übertragen werden.]]></item>
                <item name="wcf.acp.option.module_attachment"><![CDATA[Dateianhänge]]></item>
+               <item name="wcf.acp.option.module_smiley"><![CDATA[Smileys]]></item>
        </category>
        
        <category name="wcf.acp.package">
                <item name="wcf.acp.sessionLog.time"><![CDATA[Datum]]></item>
        </category>
        
+       <category name="wcf.acp.smiley">
+               <item name="wcf.acp.smiley.list"><![CDATA[Smileys]]></item>
+               <item name="wcf.acp.smiley.noItems"><![CDATA[Es sind keine Smileys vorhanden.]]></item>
+               <item name="wcf.acp.smiley.delete.sure"><![CDATA[Wollen Sie das Smiley „{$smiley->smileyTitle|language}“ wirklich löschen?]]></item>
+               <item name="wcf.acp.smiley.add"><![CDATA[Smiley hinzufügen]]></item>
+               <item name="wcf.acp.smiley.edit"><![CDATA[Smiley bearbeiten]]></item>
+               <item name="wcf.acp.smiley.title"><![CDATA[Titel]]></item>
+               <item name="wcf.acp.smiley.category.noItems"><![CDATA[Diese Kategorie enthält keine Smileys.]]></item>
+               <item name="wcf.acp.smiley.categoryID"><![CDATA[Kategorie]]></item>
+               <item name="wcf.acp.smiley.categoryID.default"><![CDATA[Standard-Kategorie]]></item>
+               <item name="wcf.acp.smiley.categoryID.error.notValid"><![CDATA[Die gewählte Kategorie ist nicht gültig.]]></item>
+               <item name="wcf.acp.smiley.smileyCode"><![CDATA[Primärer Smiley-Code]]></item>
+               <item name="wcf.acp.smiley.aliases"><![CDATA[Alternative Smiley-Codes]]></item>
+               <item name="wcf.acp.smiley.showOrder"><![CDATA[Position]]></item>
+       </category>
+       
        <category name="wcf.acp.style">
                <item name="wcf.acp.style.add"><![CDATA[Stil hinzufügen]]></item>
                <item name="wcf.acp.style.advanced"><![CDATA[Erweiterte Einstellungen]]></item>
@@ -886,6 +966,30 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions()
                <item name="wcf.attachment.fileType"><![CDATA[Dateityp]]></item>
        </category>
        
+       <category name="wcf.bbcode">
+               <item name="wcf.bbcode.code.text"><![CDATA[{@$highlighterTitle} ({#$lines} Zeile{if $lines != 1}n{/if})]]></item>
+               <item name="wcf.bbcode.code.bash.title"><![CDATA[Shell-Script]]></item>
+               <item name="wcf.bbcode.code.brainfuck.title"><![CDATA[Brainfuck-Quellcode]]></item>
+               <item name="wcf.bbcode.code.c.title"><![CDATA[C-Quellcode]]></item>
+               <item name="wcf.bbcode.code.css.title"><![CDATA[CSS-Quellcode]]></item>
+               <item name="wcf.bbcode.code.diff.title"><![CDATA[Unterschiede-Datei]]></item>
+               <item name="wcf.bbcode.code.html.title"><![CDATA[HTML-Quellcode]]></item>
+               <item name="wcf.bbcode.code.java.title"><![CDATA[Java-Quellcode]]></item>
+               <item name="wcf.bbcode.code.js.title"><![CDATA[JavaScript-Quellcode]]></item>
+               <item name="wcf.bbcode.code.perl.title"><![CDATA[Perl-Quellcode]]></item>
+               <item name="wcf.bbcode.code.php.title"><![CDATA[PHP-Quellcode]]></item>
+               <item name="wcf.bbcode.code.plain.title"><![CDATA[Quellcode]]></item>
+               <item name="wcf.bbcode.code.sql.title"><![CDATA[SQL-Abfrage]]></item>
+               <item name="wcf.bbcode.code.tex.title"><![CDATA[TeX-Quellcode]]></item>
+               <item name="wcf.bbcode.code.tpl.title"><![CDATA[Smarty-Template]]></item>
+               <item name="wcf.bbcode.code.xml.title"><![CDATA[XML-Quellcode]]></item>
+               <item name="wcf.bbcode.quote.title"><![CDATA[{@$quoteAuthor} schrieb:]]></item>
+               <item name="wcf.bbcode.quote.text"><![CDATA[Zitat{if $cite} von {@$cite}{/if}: „{@$content}“]]></item>
+               <item name="wcf.bbcode.spoiler.show"><![CDATA[Spoiler anzeigen]]></item>
+               <item name="wcf.bbcode.spoiler.text"><![CDATA[(Versteckter Text)]]></item>
+       </category>
+       
+       
        <category name="wcf.category">
                <item name="wcf.category.add"><![CDATA[Kategorie hinzufügen]]></item>
                <item name="wcf.category.button.list"><![CDATA[Kategorien auflisten]]></item>
@@ -1227,6 +1331,7 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getAllowedExtensions()
                <item name="wcf.user.error.isBanned"><![CDATA[Ihr Benutzeraccount wurde gesperrt{if $__wcf->user->banReason}: {@$__wcf->user->banReason|htmlspecialchars|nl2br}{else}.{/if}]]></item>
        </category>
        
-       <!-- i18n category -->
+       <!-- i18n categories -->
        <category name="wcf.user.option" />
+       <category name="wcf.smiley" />
 </language>
index 68f5119803c29ad0c3d01c95d2c7d3087bb9ccf0..d3fd5ca184d3b4a66478ef18d7a9454b20ebf5b7 100644 (file)
                <item name="wcf.acp.attachment.noItems"><![CDATA[There are no attachments.]]></item>
        </category>
        
+       <category name="wcf.acp.bbcode">
+               <item name="wcf.acp.bbcode.add"><![CDATA[Add BBCode]]></item>
+               <item name="wcf.acp.bbcode.add.userGroupOptionInfo"><![CDATA[Newly created BBCodes will be accessible for everyone. You can restrict usage by editing the specific user group permissions.]]></item>
+               <item name="wcf.acp.bbcode.allowedChildren"><![CDATA[Allowed Children]]></item>
+               <item name="wcf.acp.bbcode.allowedChildren.error.notValid"><![CDATA[List of allowed children is invalid.]]></item>
+               <item name="wcf.acp.bbcode.attribute"><![CDATA[Attribute]]></item>
+               <item name="wcf.acp.bbcode.attribute.attributeHtml"><![CDATA[Attribute HTML code]]></item>
+               <item name="wcf.acp.bbcode.attribute.validationPattern.error.notValid"><![CDATA[Regular expression failed to validate.]]></item>
+               <item name="wcf.acp.bbcode.attribute.validationPattern"><![CDATA[Regular Expression for Validation]]></item>
+               <item name="wcf.acp.bbcode.attribute.required"><![CDATA[Required]]></item>
+               <item name="wcf.acp.bbcode.attribute.useText"><![CDATA[Use content]]></item>
+               <item name="wcf.acp.bbcode.attribute.useText.description"><![CDATA[Content between BBCode tags will be used as attribute value unless it was explicitly specified]]></item>
+               <item name="wcf.acp.bbcode.attributes"><![CDATA[Attribute]]></item>
+               <item name="wcf.acp.bbcode.bbcodeTag"><![CDATA[BBCode]]></item>
+               <item name="wcf.acp.bbcode.bbcodeTag.error.inUse"><![CDATA[BBCode already exists.]]></item>
+               <item name="wcf.acp.bbcode.bbcodeTag.error.notValid"><![CDATA[BBCode is invalid.]]></item>
+               <item name="wcf.acp.bbcode.className"><![CDATA[PHP Class Name]]></item>
+               <item name="wcf.acp.bbcode.className.error.notFound"><![CDATA[Unable to find specified class.]]></item>
+               <item name="wcf.acp.bbcode.delete.sure"><![CDATA[Do you really want to delete the BBCode “[{$bbcode->bbcodeTag}]”?]]></item>
+               <item name="wcf.acp.bbcode.edit"><![CDATA[Edit BBCode]]></item>
+               <item name="wcf.acp.bbcode.htmlClose"><![CDATA[Closing HTML tag]]></item>
+               <item name="wcf.acp.bbcode.htmlOpen"><![CDATA[Opening HTML tag]]></item>
+               <item name="wcf.acp.bbcode.list"><![CDATA[BBCodes]]></item>
+               
+               <item name="wcf.acp.bbcode.mediaProvider.add"><![CDATA[Add Media Provider]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.delete.sure"><![CDATA[Do you really want to delete the media provider “{$mediaProvider->title}”?]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.edit"><![CDATA[Edit Media Provider]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.html"><![CDATA[HTML Code]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.html.description"><![CDATA[{literal}Enter the media’s HTML code. Variables are recognized as {$variable} and will be replaced with the regular expression’s matching named group.<br />
+Example:<br />
+<code>{$ID}</code> will be replaced with the medium ID as shown above.{/literal}]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.list"><![CDATA[Media Providers]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.regex"><![CDATA[Regular Expression]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.regex.description"><![CDATA[Provide the link format as a <a href="{@$__wcf->getPath()}acp/dereferrer.php?url=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FRegular_expression">regular expression</a>. One expression per line.<br />
+Examples for medium ID detection:
+<ul>
+       <li><code>(?&lt;ID&gt;[0-9]+)</code> detects a numeric ID.</li>
+       <li><code>(?&lt;ID&gt;[0-9a-zA-Z]+)</code> detects an alphanumeric ID</li>
+</ul>]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.regex.error.notValid"><![CDATA[Regular expression failed to validate.]]></item>
+               <item name="wcf.acp.bbcode.mediaProvider.title"><![CDATA[Media Providers]]></item>
+               <item name="wcf.acp.bbcode.isSourceCode"><![CDATA[Content represents source code]]></item>
+               <item name="wcf.acp.bbcode.isSourceCode.description"><![CDATA[Disables conversion of bbcodes and smilies.]]></item>
+               <item name="wcf.acp.bbcode.showButton"><![CDATA[Show button in WYSIWYG editor]]></item>
+               <item name="wcf.acp.bbcode.buttonLabel"><![CDATA[Button Label]]></item>
+               <item name="wcf.acp.bbcode.wysiwygIcon"><![CDATA[Button Image]]></item>
+       </category>
+       
        <category name="wcf.acp.cache">
                <item name="wcf.acp.cache.button.clear"><![CDATA[Clear Cache]]></item>
                <item name="wcf.acp.cache.clear.sure"><![CDATA[Do you really want to clear the cache?]]></item>
                <item name="wcf.acp.group.option.user.attachment.quota.description"><![CDATA[Total quota for all attachments uploaded by a user]]></item>
                <item name="wcf.acp.group.option.category.admin.attachment"><![CDATA[Attachments]]></item>
                <item name="wcf.acp.group.option.admin.attachment.canManageAttachment"><![CDATA[Can manage attachments]]></item>
+               <item name="wcf.acp.group.option.category.admin.content.bbcode"><![CDATA[BBCodes]]></item>
+               <item name="wcf.acp.group.option.admin.content.bbcode.canManageBBCode"><![CDATA[Can manage BBCodes]]></item>
+               <item name="wcf.acp.group.option.category.admin.content.smiley"><![CDATA[Smilies]]></item>
+               <item name="wcf.acp.group.option.admin.content.smiley.canManageSmiley"><![CDATA[Can manage smilies]]></item>
        </category>
        
        <category name="wcf.acp.index">
                <item name="wcf.acp.menu.link.template.group.add"><![CDATA[Add Template Group]]></item>
                <item name="wcf.acp.menu.link.attachment"><![CDATA[Attachments]]></item>
                <item name="wcf.acp.menu.link.attachment.list"><![CDATA[Browse Attachments]]></item>
+               <item name="wcf.acp.menu.link.bbcode"><![CDATA[BBCodes]]></item>
+               <item name="wcf.acp.menu.link.bbcode.add"><![CDATA[Add BBCode]]></item>
+               <item name="wcf.acp.menu.link.bbcode.list"><![CDATA[List BBCodes]]></item>
+               <item name="wcf.acp.menu.link.bbcode.mediaProvider.add"><![CDATA[Add Media Provider]]></item>
+               <item name="wcf.acp.menu.link.bbcode.mediaProvider.list"><![CDATA[List Media Provider]]></item>
+               <item name="wcf.acp.menu.link.smiley"><![CDATA[Smilies]]></item>
+               <item name="wcf.acp.menu.link.smiley.add"><![CDATA[Add Smiley]]></item>
+               <item name="wcf.acp.menu.link.smiley.list"><![CDATA[List Smilies]]></item>
+               <item name="wcf.acp.menu.link.smiley.category.add"><![CDATA[Add Smiley Category]]></item>
+               <item name="wcf.acp.menu.link.smiley.category.list"><![CDATA[List Smiley Categories]]></item>
        </category>
        
        <category name="wcf.acp.option">
                <item name="wcf.acp.option.attachment_storage"><![CDATA[Storage Location]]></item>
                <item name="wcf.acp.option.attachment_storage.description"><![CDATA[Changes storage location for attachments. Heads up! You are responsible to move already existing attachments to the new location.]]></item>
                <item name="wcf.acp.option.module_attachment"><![CDATA[Attachments]]></item>
+               <item name="wcf.acp.option.module_smiley"><![CDATA[Smilies]]></item>
        </category>
        
        <category name="wcf.acp.package">
                <item name="wcf.acp.sessionLog.time"><![CDATA[Time]]></item>
        </category>
        
+       <category name="wcf.acp.smiley">
+               <item name="wcf.acp.smiley.list"><![CDATA[Smilies]]></item>
+               <item name="wcf.acp.smiley.noItems"><![CDATA[There are no smilies.]]></item>
+               <item name="wcf.acp.smiley.delete.sure"><![CDATA[Do you really want to delete the smiley “{$smiley->smileyTitle|language}”?]]></item>
+               <item name="wcf.acp.smiley.add"><![CDATA[Add Smiley]]></item>
+               <item name="wcf.acp.smiley.edit"><![CDATA[Edit Smiley]]></item>
+               <item name="wcf.acp.smiley.title"><![CDATA[Title]]></item>
+               <item name="wcf.acp.smiley.category.noItems"><![CDATA[This category contains no smilies.]]></item>
+               <item name="wcf.acp.smiley.categoryID"><![CDATA[Category]]></item>
+               <item name="wcf.acp.smiley.categoryID.default"><![CDATA[Default Category]]></item>
+               <item name="wcf.acp.smiley.categoryID.error.notValid"><![CDATA[Chosen category is invalid.]]></item>
+               <item name="wcf.acp.smiley.smileyCode"><![CDATA[Primary Smiley Code]]></item>
+               <item name="wcf.acp.smiley.aliases"><![CDATA[Alternative Smiley Codes]]></item>
+               <item name="wcf.acp.smiley.showOrder"><![CDATA[Show Order]]></item>
+       </category>
+       
        <category name="wcf.acp.style">
                <item name="wcf.acp.style.add"><![CDATA[Add Style]]></item>
                <item name="wcf.acp.style.advanced"><![CDATA[Advanced Settings]]></item>
@@ -885,6 +964,29 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]>
                <item name="wcf.attachment.fileType"><![CDATA[Type]]></item>
        </category>
        
+       <category name="wcf.bbcode">
+               <item name="wcf.bbcode.code.text"><![CDATA[{@$highlighterTitle} ({#$lines} line{if $lines != 1}s{/if})]]></item>
+               <item name="wcf.bbcode.code.bash.title"><![CDATA[Shell-Script]]></item>
+               <item name="wcf.bbcode.code.brainfuck.title"><![CDATA[Brainfuck Source Code]]></item>
+               <item name="wcf.bbcode.code.c.title"><![CDATA[C Source Code]]></item>
+               <item name="wcf.bbcode.code.css.title"><![CDATA[CSS Source Code]]></item>
+               <item name="wcf.bbcode.code.diff.title"><![CDATA[Difference-File]]></item>
+               <item name="wcf.bbcode.code.html.title"><![CDATA[HTML Source Code]]></item>
+               <item name="wcf.bbcode.code.java.title"><![CDATA[Java Source Code]]></item>
+               <item name="wcf.bbcode.code.js.title"><![CDATA[JavaScript Source Code]]></item>
+               <item name="wcf.bbcode.code.perl.title"><![CDATA[Perl Source Code]]></item>
+               <item name="wcf.bbcode.code.php.title"><![CDATA[PHP Source Code]]></item>
+               <item name="wcf.bbcode.code.plain.title"><![CDATA[Source Code]]></item>
+               <item name="wcf.bbcode.code.sql.title"><![CDATA[SQL-Query]]></item>
+               <item name="wcf.bbcode.code.tex.title"><![CDATA[TeX Source Code]]></item>
+               <item name="wcf.bbcode.code.tpl.title"><![CDATA[Smarty-Template]]></item>
+               <item name="wcf.bbcode.code.xml.title"><![CDATA[XML Source Code]]></item>
+               <item name="wcf.bbcode.quote.title"><![CDATA[{@$quoteAuthor} wrote:]]></item>
+               <item name="wcf.bbcode.quote.text"><![CDATA[Quote{if $cite} from {@$cite}{/if}: “{@$content}”]]></item>
+               <item name="wcf.bbcode.spoiler.show"><![CDATA[Show Spoiler]]></item>
+               <item name="wcf.bbcode.spoiler.text"><![CDATA[(Hidden Content)]]></item>
+       </category>
+       
        <category name="wcf.category">
                <item name="wcf.category.add"><![CDATA[Add Category]]></item>
                <item name="wcf.category.button.list"><![CDATA[List Categories]]></item>
@@ -1226,6 +1328,7 @@ Allowed extensions: {', '|implode:$attachmentHandler->getAllowedExtensions()}]]>
                <item name="wcf.user.error.isBanned"><![CDATA[Your user account has been banned{if $__wcf->user->banReason}: {@$__wcf->user->banReason|htmlspecialchars|nl2br}{else}.{/if}]]></item>
        </category>
        
-       <!-- i18n category -->
+       <!-- i18n categories -->
        <category name="wcf.user.option" />
+       <category name="wcf.smiley" />
 </language>
index 6f6fa7dd9e99d55b99d2cb7b22d6b40d5c65da76..61c8ea7c081eda6865e96a6a662f4282cd0b6539 100644 (file)
@@ -156,6 +156,43 @@ CREATE TABLE wcf1_attachment (
        KEY (objectID, uploadTime)
 );
 
+DROP TABLE IF EXISTS wcf1_bbcode;
+CREATE TABLE wcf1_bbcode (
+       bbcodeID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       bbcodeTag VARCHAR(255) NOT NULL,
+       packageID INT(10) NOT NULL,
+       htmlOpen VARCHAR(255) NOT NULL DEFAULT '',
+       htmlClose VARCHAR(255) NOT NULL DEFAULT '',
+       allowedChildren VARCHAR(255) NOT NULL DEFAULT 'all',
+       className VARCHAR(255) NOT NULL DEFAULT '',
+       wysiwygIcon varchar(255) NOT NULL DEFAULT '',
+       buttonLabel VARCHAR(255) NOT NULL DEFAULT '',
+       isSourceCode TINYINT(1) NOT NULL DEFAULT 0,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       showButton TINYINT(1) NOT NULL DEFAULT 0,
+       UNIQUE KEY bbcodeTag (bbcodeTag)
+);
+
+DROP TABLE IF EXISTS wcf1_bbcode_attribute;
+CREATE TABLE wcf1_bbcode_attribute (
+       attributeID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       bbcodeID INT(10) NOT NULL,
+       attributeNo TINYINT(3) NOT NULL DEFAULT 0,
+       attributeHtml VARCHAR(255) NOT NULL DEFAULT '',
+       validationPattern VARCHAR(255) NOT NULL DEFAULT '',
+       required TINYINT(1) NOT NULL DEFAULT 0,
+       useText TINYINT(1) NOT NULL DEFAULT 0,
+       UNIQUE KEY attributeNo (bbcodeID, attributeNo)
+);
+
+DROP TABLE IF EXISTS wcf1_bbcode_media_provider;
+CREATE TABLE wcf1_bbcode_media_provider (
+       providerID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       title VARCHAR(255) NOT NULL,
+       regex TEXT NOT NULL,
+       html TEXT NOT NULL
+);
+
 DROP TABLE IF EXISTS wcf1_category;
 CREATE TABLE wcf1_category (
        categoryID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -572,6 +609,19 @@ CREATE TABLE wcf1_sitemap (
        UNIQUE KEY sitemapName (packageID, sitemapName)
 );
 
+DROP TABLE IF EXISTS wcf1_smiley;
+CREATE TABLE wcf1_smiley (
+       smileyID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       packageID INT(10) NOT NULL,
+       categoryID INT(10),
+       smileyPath VARCHAR(255) NOT NULL DEFAULT '',
+       smileyTitle VARCHAR(255) NOT NULL DEFAULT '',
+       smileyCode VARCHAR(255) NOT NULL DEFAULT '',
+       aliases TEXT NOT NULL,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       UNIQUE KEY smileyCode (smileyCode)
+);
+
 DROP TABLE IF EXISTS wcf1_spider;
 CREATE TABLE wcf1_spider (
        spiderID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -814,6 +864,10 @@ ALTER TABLE wcf1_application ADD FOREIGN KEY (packageID) REFERENCES wcf1_package
 ALTER TABLE wcf1_attachment ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_attachment ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
 
+ALTER TABLE wcf1_bbcode ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_bbcode_attribute ADD FOREIGN KEY (bbcodeID) REFERENCES wcf1_bbcode (bbcodeID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_category ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_clipboard_action ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
@@ -886,6 +940,9 @@ ALTER TABLE wcf1_session ADD FOREIGN KEY (spiderID) REFERENCES wcf1_spider (spid
 
 ALTER TABLE wcf1_sitemap ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_smiley ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_smiley ADD FOREIGN KEY (categoryID) REFERENCES wcf1_category (categoryID) ON DELETE SET NULL;
+
 ALTER TABLE wcf1_user_storage ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_style ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
@@ -1040,3 +1097,26 @@ INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('useFluidLa
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('pageLogo', '');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('individualLess', '');
 INSERT INTO wcf1_style_variable (variableName, defaultValue) VALUES ('overrideLess', '');
+
+-- media providers
+-- Videos
+       -- Youtube
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('YouTube', 'https?://(?:.+?\\.)?youtu(?:\\.be/|be\\.com/watch\\?(?:.*?&)?v=)(?<ID>[a-zA-Z0-9_-]+)(?<start>#t=(?:\\d+|(?:\\d+h(?:\\d+m)?(?:\\d+s)?)|(?:\\d+m(?:\\d+s)?)|(?:\\d+s))$)?', '<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/{$ID}?wmode=transparent{$start}" frameborder="0" allowfullscreen></iframe>');
+       -- Vimeo
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Vimeo', 'http://vimeo\\.com/(?<ID>\\d+)', '<iframe src="http://player.vimeo.com/video/{$ID}" width="400" height="225" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>');
+       -- MyVideo
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('MyVideo', 'http://(?:www\\.)?myvideo\\.de/watch/(?<ID>\\d+)', '<object style="width:611px;height:383px;" width="611" height="383"><embed src="http://www.myvideo.de/movie/{$ID}" width="611" height="383" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true"></embed><param name="movie" value="http://www.myvideo.de/movie/{$ID}"></param><param name="AllowFullscreen" value="true"></param><param name="AllowScriptAccess" value="always"></param></object>');
+       -- Clipfish
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Clipfish', 'http://(?:www\\.)?clipfish\\.de/video/(?<ID>\\d+)/', '<div style="width:464px; height:404px;"><div style="width:464px; height:384px;"><iframe src="http://www.clipfish.de/embed_video/?vid={$ID}&amp;as=0&amp;col=990000" name="Clipfish Embedded Video" width="464" height="384" align="left" marginheight="0" marginwidth="0" frameborder="0" scrolling="no"></iframe></div></div>');
+       -- Veoh
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Veoh', 'http://(?:www\\.)?veoh\\.com/watch/v(?<ID>\\d+[a-zA-Z0-9]+)', '<object width="410" height="341" id="veohFlashPlayer" name="veohFlashPlayer"><param name="movie" value="http://www.veoh.com/swf/webplayer/WebPlayer.swf?version=AFrontend.5.7.0.1308&amp;permalinkId=v{$ID}&amp;player=videodetailsembedded&amp;videoAutoPlay=0&amp;id=anonymous"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.veoh.com/swf/webplayer/WebPlayer.swf?version=AFrontend.5.7.0.1308&amp;permalinkId=v{$ID}&amp;player=videodetailsembedded&amp;videoAutoPlay=0&amp;id=anonymous" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="410" height="341" id="veohFlashPlayerEmbed" name="veohFlashPlayerEmbed"></embed></object>');
+       -- DailyMotion
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('DailyMotion', 'https?://(?:www\\.)?dailymotion\\.com/video/(?<ID>[a-zA-Z0-9]+)', '<iframe frameborder="0" width="480" height="208" src="http://www.dailymotion.com/embed/video/{$ID}"></iframe>');
+       -- YouKu
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('YouKu', 'https?://(?:.+?\\.)?youku\\.com/v_show/id_(?<ID>[a-zA-Z0-9_-]+)(?:\\.html)?', '<iframe height=498 width=510 src="http://player.youku.com/embed/{$ID}" frameborder="0" allowfullscreen></iframe>');
+-- Misc
+       -- github gist
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('github gist', 'https://gist.github.com/(?<ID>[^/]+/[0-9a-zA-Z]+)', '<script src="https://gist.github.com/{$ID}.js"> </script>');
+       -- soundcloud
+       INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Soundcloud', 'https?://soundcloud.com/(?<artist>[a-zA-Z0-9_-]+)/(?<song>[a-zA-Z0-9_-]+)', '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=http%3A%2F%2Fsoundcloud.com%2F{$artist}%2F{$song}"></iframe>');
+