Use new titled code box macro
authorMatthias Schmidt <gravatronics@live.com>
Fri, 23 Apr 2021 09:06:23 +0000 (11:06 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Fri, 23 Apr 2021 09:06:23 +0000 (11:06 +0200)
98 files changed:
docs/javascript/typescript.md
docs/package/package-xml.md
docs/package/pip/acl-option.md
docs/package/pip/acp-menu.md
docs/package/pip/acp-search-provider.md
docs/package/pip/bbcode.md
docs/package/pip/box.md
docs/package/pip/clipboard-action.md
docs/package/pip/core-object.md
docs/package/pip/cronjob.md
docs/package/pip/event-listener.md
docs/package/pip/language.md
docs/package/pip/media-provider.md
docs/package/pip/menu-item.md
docs/package/pip/menu.md
docs/package/pip/object-type-definition.md
docs/package/pip/object-type.md
docs/package/pip/option.md
docs/package/pip/page.md
docs/package/pip/pip.md
docs/package/pip/smiley.md
docs/package/pip/sql.md
docs/package/pip/template-listener.md
docs/package/pip/user-menu.md
docs/package/pip/user-notification-event.md
docs/package/pip/user-profile-menu.md
docs/php/api/caches_persistent-caches.md
docs/php/api/caches_runtime-caches.md
docs/php/api/cronjobs.md
docs/php/api/events.md
docs/php/api/sitemaps.md
docs/php/api/user_notifications.md
docs/php/code-style.md
docs/php/database-objects.md
docs/php/gdpr.md
docs/stylesheets/extra.css
docs/tutorial/series/part_1.md
docs/tutorial/series/part_2.md
docs/tutorial/series/part_3.md
docs/tutorial/series/part_4.md
docs/tutorial/series/part_5.md
snippets/package/package.xml [new file with mode: 0644]
snippets/package/pip/aclOption.xml [new file with mode: 0644]
snippets/package/pip/acpMenu.xml [new file with mode: 0644]
snippets/package/pip/acpSearchProvider.xml [new file with mode: 0644]
snippets/package/pip/bbcode.xml [new file with mode: 0644]
snippets/package/pip/box.xml [new file with mode: 0644]
snippets/package/pip/clipboardAction.xml [new file with mode: 0644]
snippets/package/pip/coreObject.xml [new file with mode: 0644]
snippets/package/pip/cronjob.xml [new file with mode: 0644]
snippets/package/pip/en.xml [new file with mode: 0644]
snippets/package/pip/eventListener.xml [new file with mode: 0644]
snippets/package/pip/install.sql [new file with mode: 0644]
snippets/package/pip/mediaProvider.xml [new file with mode: 0644]
snippets/package/pip/menu.xml [new file with mode: 0644]
snippets/package/pip/menuItem.xml [new file with mode: 0644]
snippets/package/pip/objectType.xml [new file with mode: 0644]
snippets/package/pip/objectTypeDefinition.xml [new file with mode: 0644]
snippets/package/pip/option.xml [new file with mode: 0644]
snippets/package/pip/packageInstallationPlugin.xml [new file with mode: 0644]
snippets/package/pip/page.xml [new file with mode: 0644]
snippets/package/pip/smiley.xml [new file with mode: 0644]
snippets/package/pip/templateListener.xml [new file with mode: 0644]
snippets/package/pip/userMenu.xml [new file with mode: 0644]
snippets/package/pip/userNotificationEvent.xml [new file with mode: 0644]
snippets/package/pip/userProfileMenu.xml [new file with mode: 0644]
snippets/php/api/caches/ExampleCacheBuilder.class.php [new file with mode: 0644]
snippets/php/api/caches/UserRuntimeCache.class.php [new file with mode: 0644]
snippets/php/api/cronjobs/LastActivityCronjob.class.php [new file with mode: 0644]
snippets/php/api/events/ExampleAddFormListener.class.php [new file with mode: 0644]
snippets/php/api/events/ExampleComponent.class.php [new file with mode: 0644]
snippets/php/api/events/ExampleEventListener.class.php [new file with mode: 0644]
snippets/php/api/events/ExampleParser1.class.php [new file with mode: 0644]
snippets/php/api/events/ExampleParser2.class.php [new file with mode: 0644]
snippets/php/api/events/ExampleParserEventListener.class.php [new file with mode: 0644]
snippets/php/api/events/eventListener.xml [new file with mode: 0644]
snippets/php/api/sitemaps/UserSitemapObject.class.php [new file with mode: 0644]
snippets/php/api/user_notifications/FooUserNotificationEvent.class.php [new file with mode: 0644]
snippets/php/api/user_notifications/FooUserNotificationObject.class.php [new file with mode: 0644]
snippets/php/api/user_notifications/FooUserNotificationObjectType.class.php [new file with mode: 0644]
snippets/php/api/user_notifications/objectType.xml [new file with mode: 0644]
snippets/php/api/user_notifications/userNotificationEvent.xml [new file with mode: 0644]
snippets/php/code-style/Box.class.php [new file with mode: 0644]
snippets/php/database-objects/Example.class.php [new file with mode: 0644]
snippets/php/database-objects/ExampleAction.class.php [new file with mode: 0644]
snippets/php/database-objects/ExampleEditor.class.php [new file with mode: 0644]
snippets/php/database-objects/ExampleList.class.php [new file with mode: 0644]
snippets/php/database-objects/ViewableExample.class.php [new file with mode: 0644]
snippets/php/database-objects/ViewableExampleList.class.php [new file with mode: 0644]
snippets/php/gdpr/MyUserExportGdprActionListener.class.php [new file with mode: 0644]
snippets/typescript/.eslintignore [new file with mode: 0644]
snippets/typescript/.eslintrc.js [new file with mode: 0644]
snippets/typescript/.gitattributes [new file with mode: 0644]
snippets/typescript/.prettierrc [new file with mode: 0644]
snippets/typescript/Example.js [new file with mode: 0644]
snippets/typescript/Example.ts [new file with mode: 0644]
snippets/typescript/package.json [new file with mode: 0644]
snippets/typescript/tsconfig.json [new file with mode: 0644]

index b39bd885417af93ca68bce279d5b3642fd71f8ff..64327df67aea0c5717d6cd207363e24ec2f9b24e 100644 (file)
@@ -6,57 +6,21 @@ To consume the types of WoltLab Suite, you will need to install the `@woltlab/wc
 
 A full `package.json` that includes WoltLab Suite, TypeScript, eslint and Prettier could look like the following.
 
-```json
-{
-  "devDependencies": {
-    "@typescript-eslint/eslint-plugin": "^4.6.1",
-    "@typescript-eslint/parser": "^4.6.1",
-    "eslint": "^7.12.1",
-    "eslint-config-prettier": "^6.15.0",
-    "prettier": "^2.1.2",
-    "tslib": "^2.0.3",
-    "typescript": "^4.1.3"
-  },
-  "dependencies": {
-    "@woltlab/wcf": "https://github.com/WoltLab/WCF.git#master"
-  }
-}
-```
+{jinja{ codebox(
+    "json",
+    "typescript/package.json",
+    "package.json"
+) }}
 
 After installing the types using npm, you will also need to configure `tsconfig.json` to take the types into account.
 To do so, you will need to add them to the `compilerOptions.paths` option.
 A complete `tsconfig.json` file that matches the configuration of WoltLab Suite could look like the following.
 
-```json
-{
-  "include": [
-    "node_modules/@woltlab/wcf/global.d.ts",
-    "ts/**/*"
-  ],
-  "compilerOptions": {
-    "target": "es2017",
-    "module": "amd",
-    "rootDir": "ts/",
-    "outDir": "files/js/",
-    "lib": [
-      "dom",
-      "es2017"
-    ],
-    "strictNullChecks": true,
-    "moduleResolution": "node",
-    "esModuleInterop": true,
-    "noImplicitThis": true,
-    "strictBindCallApply": true,
-    "baseUrl": ".",
-    "paths": {
-      "*": [
-        "node_modules/@woltlab/wcf/ts/*"
-      ]
-    },
-    "importHelpers": true
-  }
-}
-```
+{jinja{ codebox(
+    "json",
+    "typescript/tsconfig.json",
+    "tsconfig.json"
+) }}
 
 After this initial set-up, you would place your TypeScript source files into the `ts/` folder of your project.
 The generated JavaScript target files will be placed into `files/js/` and thus will be installed by the [file PIP](../package/pip/file.md).
@@ -67,69 +31,32 @@ WoltLab Suite uses additional tools to ensure the high quality and a consistent
 The current configuration of these tools is as follows.
 It is recommended to re-use this configuration as is.
 
-### .prettierrc
-
-```yml
-trailingComma: all
-printWidth: 120
-```
-
-### .eslintrc.js
-
-```javascript
-module.exports = {
-  root: true,
-  parser: "@typescript-eslint/parser",
-  parserOptions: {
-    tsconfigRootDir: __dirname,
-    project: ["./tsconfig.json"]
-  },
-  plugins: ["@typescript-eslint"],
-  extends: [
-    "eslint:recommended",
-    "plugin:@typescript-eslint/recommended",
-    "plugin:@typescript-eslint/recommended-requiring-type-checking",
-    "prettier",
-    "prettier/@typescript-eslint"
-  ],
-  rules: {
-    "@typescript-eslint/ban-types": [
-      "error", {
-        types: {
-          "object": false
-        },
-        extendDefaults: true
-      }
-    ],
-    "@typescript-eslint/no-explicit-any": 0,
-    "@typescript-eslint/no-non-null-assertion": 0,
-    "@typescript-eslint/no-unsafe-assignment": 0,
-    "@typescript-eslint/no-unsafe-call": 0,
-    "@typescript-eslint/no-unsafe-member-access": 0,
-    "@typescript-eslint/no-unsafe-return": 0,
-    "@typescript-eslint/no-unused-vars": [
-      "error", {
-        "argsIgnorePattern": "^_"
-      }
-    ]
-  }
-};
-```
-
-### .eslintignore
+{jinja{ codebox(
+    "yml",
+    "typescript/.prettierrc",
+    ".prettierrc"
+) }}
 
-```gitignore
-**/*.js
-```
+{jinja{ codebox(
+    "javascript",
+    "typescript/.eslintrc.js",
+    ".eslintrc.js"
+) }}
 
-### .gitattributes
+{jinja{ codebox(
+    "gitignore",
+    "typescript/.eslintignore",
+    ".eslintignore"
+) }}
 
 This `.gitattributes` configuration will automatically collapse the generated JavaScript target files in GitHub’s Diff view.
 You will not need it if you do not use git or GitHub.
 
-```gitattributes
-files/js/**/*.js linguist-generated
-```
+{jinja{ codebox(
+    "gitattributes",
+    "typescript/.gitattributes",
+    ".gitattributes"
+) }}
 
 ## Writing a simple module
 
@@ -139,28 +66,19 @@ The TypeScript compiler can be launched in Watch Mode by running `npx tsc -w`.
 WoltLab Suite’s modules can be imported using the standard ECMAScript module import syntax by specifying the full module name.
 The public API of the module can also be exported using the standard ECMAScript module export syntax.
 
-```typescript
-import * as Language from "WoltLabSuite/Core/Language";
-
-export function run() {
-  alert(Language.get("wcf.foo.bar"));
-}
-```
+{jinja{ codebox(
+    "typescript",
+    "typescript/Example.ts",
+    "ts/Example.ts"
+) }}
 
 This simple example module will compile to plain JavaScript that is compatible with the AMD loader that is used by WoltLab Suite.
 
-```javascript
-define(["require", "exports", "tslib", "WoltLabSuite/Core/Language"], function (require, exports, tslib_1, Language) {
-    "use strict";
-    Object.defineProperty(exports, "__esModule", { value: true });
-    exports.run = void 0;
-    Language = tslib_1.__importStar(Language);
-    function run() {
-        alert(Language.get("wcf.foo.bar"));
-    }
-    exports.run = run;
-});
-```
+{jinja{ codebox(
+    "javascript",
+    "typescript/Example.js",
+    "files/js/Example.js"
+) }}
 
 Within templates it can be consumed as follows.
 
index ff34404a5e13ad8f73fb46e617570918f249ebb1..06a1528538203d7a4ca6a6ff8dd33bad06287afa 100644 (file)
@@ -5,35 +5,11 @@ It provides the meta data (e.g. package name, description, author) and the instr
 
 ## Example
 
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<package name="com.example.package" 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/2019/package.xsd">
-       <packageinformation>
-               <packagename>Simple Package</packagename>
-               <packagedescription>A simple package to demonstrate the package system of WoltLab Suite Core</packagedescription>
-               <version>1.0.0</version>
-               <date>2016-12-18</date>
-       </packageinformation>
-
-       <authorinformation>
-               <author>YOUR NAME</author>
-               <authorurl>http://www.example.com</authorurl>
-       </authorinformation>
-
-       <requiredpackages>
-               <requiredpackage minversion="3.0.0">com.woltlab.wcf</requiredpackage>
-       </requiredpackages>
-       
-       <excludedpackages>
-               <excludedpackage version="6.0.0 Alpha 1">com.woltlab.wcf</excludedpackage>
-       </excludedpackages>
-
-       <instructions type="install">
-               <instruction type="file" />
-               <instruction type="template">templates.tar</instruction>
-       </instructions>
-</package>
-```
+{jinja{ codebox(
+    "xml",
+    "package/package.xml",
+    "package.xml"
+) }}
 
 
 ## Elements
index cd3d489d02f6cdbea8ac58a14d499ee0342d7a69..c54fa9c199adf65eecea0260435af01a18df8544 100644 (file)
@@ -28,38 +28,8 @@ The name of the acl object type (of the object type definition `com.woltlab.wcf.
 
 ## Example
 
-```xml
-<?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/2019/aclOption.xsd">
-    <import>
-        <categories>
-            <category name="user.example">
-                <objecttype>com.example.wcf.example</objecttype>
-            </category>
-            <category name="mod.example">
-                <objecttype>com.example.wcf.example</objecttype>
-            </category>
-        </categories>
-        
-        <options>
-            <option name="canAddExample">
-                <categoryname>user.example</categoryname>
-                <objecttype>com.example.wcf.example</objecttype>
-            </option>
-            <option name="canDeleteExample">
-                <categoryname>mod.example</categoryname>
-                <objecttype>com.example.wcf.example</objecttype>
-            </option>
-        </options>
-    </import>
-
-    <delete>
-        <optioncategory name="old.example">
-           <objecttype>com.example.wcf.example</objecttype>
-        </optioncategory>
-        <option name="canDoSomethingWithExample">
-           <objecttype>com.example.wcf.example</objecttype>
-        </option>
-    </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/aclOption.xml",
+    "aclOption.xml"
+) }}
index 152c1d915e96f1adb7edbd27bfca070cf2689c38..6f185d4152eabdc540970a63b144ae5cb7d3fbb9 100644 (file)
@@ -48,27 +48,8 @@ The permissions element can contain a comma-separated list of permissions of whi
 
 ## Example
 
-```xml
-<?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/2019/acpMenu.xsd">
-       <import>
-               <acpmenuitem name="foo.acp.menu.link.example">
-                       <parent>wcf.acp.menu.link.application</parent>
-               </acpmenuitem>
-
-               <acpmenuitem name="foo.acp.menu.link.example.list">
-                       <controller>foo\acp\page\ExampleListPage</controller>
-                       <parent>foo.acp.menu.link.example</parent>
-                       <permissions>admin.foo.canManageExample</permissions>
-                       <showorder>1</showorder>
-               </acpmenuitem>
-
-               <acpmenuitem name="foo.acp.menu.link.example.add">
-                       <controller>foo\acp\form\ExampleAddForm</controller>
-                       <parent>foo.acp.menu.link.example.list</parent>
-                       <permissions>admin.foo.canManageExample</permissions>
-                       <icon>fa-plus</icon>
-               </acpmenuitem>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/acpMenu.xml",
+    "acpMenu.xml"
+) }}
index 3c482f2853e0f469c9de9cdd30bf7379d6aab1f6..45543b0be08317b2ae5a402e38ac43fcc20cb0db 100644 (file)
@@ -19,14 +19,8 @@ Determines at which position of the search result list the provided results are
 
 ## Example
 
-```xml
-<?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/2019/acpSearchProvider.xsd">
-       <import>
-               <acpsearchprovider name="com.woltlab.wcf.example">
-                       <classname>wcf\system\search\acp\ExampleACPSearchResultProvider</classname>
-                       <showorder>1</showorder>
-               </acpsearchprovider>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/acpSearchProvider.xml",
+    "acpSearchProvider.xml"
+) }}
index 8af589f3f4915fdc1cf3e83b0a6d5786d30c4561..f4fed2b1061c8a709ab4930527a1bc73e8e2487c 100644 (file)
@@ -85,27 +85,8 @@ Specifies whether the text content of the BBCode should become this attribute's
 
 ## Example
 
-```
-<?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/2019/bbcode.xsd">
-       <import>
-               <bbcode name="foo">
-                       <classname>wcf\system\bbcode\FooBBCode</classname>
-                       <attributes>
-                               <attribute name="0">
-                                       <validationpattern>^\d+$</validationpattern>
-                                       <required>1</required>
-                               </attribute>
-                       </attributes>
-               </bbcode>
-               
-               <bbcode name="example">
-                       <htmlopen>div</htmlopen>
-                       <htmlclose>div</htmlclose>
-                       <isBlockElement>1</isBlockElement>
-                       <wysiwygicon>fa-bath</wysiwygicon>
-                       <buttonlabel>wcf.editor.button.example</buttonlabel>
-               </bbcode>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/bbcode.xml",
+    "bbcode.xml"
+) }}
index fcf4d7209cb52d91cdd27c0d535a9eb9ea619e9e..d90a0cb3e8a520c65579e83c579cb763ecfcb481 100644 (file)
@@ -75,34 +75,8 @@ The content that should be used to populate the box, only used and required if t
 
 ## Example
 
-```xml
-<?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/2019/box.xsd">
-    <import>
-        <box identifier="com.woltlab.wcf.RecentActivity">
-            <name language="de">Letzte Aktivitäten</name>
-            <name language="en">Recent Activities</name>
-            <boxType>system</boxType>
-            <objectType>com.woltlab.wcf.recentActivityList</objectType>
-            <position>contentBottom</position>
-            <showHeader>0</showHeader>
-            <visibleEverywhere>0</visibleEverywhere>
-            <visibilityExceptions>
-                <page>com.woltlab.wcf.Dashboard</page>
-            </visibilityExceptions>
-            <limit>10</limit>
-
-            <content language="de">
-                <title>Letzte Aktivitäten</title>
-            </content>
-            <content language="en">
-                <title>Recent Activities</title>
-            </content>
-        </box>
-    </import>
-
-    <delete>
-        <box identifier="com.woltlab.wcf.RecentActivity" />
-    </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/box.xml",
+    "box.xml"
+) }}
index d436ffecdfee476dfa7c3a1a3478e372132b545c..e2dba6b68970bd08dbea254b7b54243208f56c0c 100644 (file)
@@ -24,31 +24,8 @@ Determines at which position of the clipboard action list the action is shown.
 
 ## Example
 
-```xml
-<?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/2019/clipboardAction.xsd">
-       <import>
-               <action name="delete">
-                       <actionclassname>wcf\system\clipboard\action\ExampleClipboardAction</actionclassname>
-                       <showorder>1</showorder>
-                       <pages>
-                               <page>wcf\acp\page\ExampleListPage</page>
-                       </pages>
-               </action>
-               <action name="foo">
-                       <actionclassname>wcf\system\clipboard\action\ExampleClipboardAction</actionclassname>
-                       <showorder>2</showorder>
-                       <pages>
-                               <page>wcf\acp\page\ExampleListPage</page>
-                       </pages>
-               </action>
-               <action name="bar">
-                       <actionclassname>wcf\system\clipboard\action\ExampleClipboardAction</actionclassname>
-                       <showorder>3</showorder>
-                       <pages>
-                               <page>wcf\acp\page\ExampleListPage</page>
-                       </pages>
-               </action>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/clipboardAction.xml",
+    "clipboardAction.xml"
+) }}
index 008a7ca08e10a66522dda415642d569272060745..26f41f792a7a3326f55f398320f501f70173ebb1 100644 (file)
@@ -12,15 +12,10 @@ The fully qualified class name of the class.
 
 ## Example
 
-```xml
-<?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/2019/coreObject.xsd">
-       <import>
-               <coreobject>
-                       <objectname>wcf\system\example\ExampleHandler</objectname>
-               </coreobject>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/coreObject.xml",
+    "coreObject.xml"
+) }}
 
 This object can be accessed in templates via `$__wcf->getExampleHandler()` (in general: the method name begins with `get` and ends with the unqualified class name).
index da722dc25c693ce89dbd2a8d90d3f9dbc345c389..ff626f7104468e473d05c88b9d6ba10fef1511ba 100644 (file)
@@ -39,23 +39,8 @@ The options element can contain a comma-separated list of options of which at le
 
 ## Example
 
-```xml
-<?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/2019/cronjob.xsd">
-       <import>
-               <cronjob name="com.example.package.example">
-                       <classname>wcf\system\cronjob\ExampleCronjob</classname>
-                       <description>Serves as an example</description>
-                       <description language="de">Stellt ein Beispiel dar</description>
-                       <startminute>0</startminute>
-                       <starthour>2</starthour>
-                       <startdom>*/2</startdom>
-                       <startmonth>*</startmonth>
-                       <startdow>*</startdow>
-                       <canbeedited>1</canbeedited>
-                       <canbedisabled>1</canbedisabled>
-               </cronjob>
-       </import>
-</data>
-```
-
+{jinja{ codebox(
+    "xml",
+    "package/pip/cronjob.xml",
+    "cronjob.xml"
+) }}
index ceaa61a00b61653ff104f26ee9b74b043d665e0a..9078509a93696ef1025556d6a6f3f3bf5677cd1e 100644 (file)
@@ -55,28 +55,8 @@ The permissions element can contain a comma-separated list of permissions of whi
 
 ## Example
 
-```xml
-<?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/2019/eventListener.xsd">
-       <import>
-               <eventlistener name="inheritedAdminExample">
-                       <eventclassname>wcf\acp\form\UserAddForm</eventclassname>
-                       <eventname>assignVariables,readFormParameters,save,validate</eventname>
-                       <listenerclassname>wcf\system\event\listener\InheritedAdminExampleListener</listenerclassname>
-                       <inherit>1</inherit>
-                       <environment>admin</environment>
-               </eventlistener>
-               
-               <eventlistener name="nonInheritedUserExample">
-                       <eventclassname>wcf\form\SettingsForm</eventclassname>
-                       <eventname>assignVariables</eventname>
-                       <listenerclassname>wcf\system\event\listener\NonInheritedUserExampleListener</listenerclassname>
-               </eventlistener>
-       </import>
-       
-       <delete>
-               <eventlistener name="oldEventListenerName" />
-       </delete>
-</data>
-
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/eventListener.xml",
+    "eventListener.xml"
+) }}
index 182088877b34bc52bb21b242c49f16ca717b7557..5cc816b717d4b87a60129e974a594fac5741e03b 100644 (file)
@@ -23,11 +23,8 @@ The text content of the `<item>` node is the value of the language item. Languag
 
 ## Example
 
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<language 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/2019/language.xsd" languagecode="de">
-       <category name="wcf.example">
-               <item name="wcf.example.foo"><![CDATA[<strong>Look!</strong>]]></item>
-       </category>
-</language>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/en.xml",
+    "language/en.xml"
+) }}
index 98706d4a47114802a6fd8a84b9d3f5fed190ab16..ef163ba5284164dc4a3e4fbfa3a74b957eb722cf 100644 (file)
@@ -32,27 +32,8 @@ Replacement HTML that gets populated using the captured matches in `<regex>`, va
 
 ## Example
 
-```xml
-<?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/2019/mediaProvider.xsd">
-  <import>
-    <provider name="youtube">
-      <title>YouTube</title>
-      <regex><![CDATA[https?://(?:.+?\.)?youtu(?:\.be/|be\.com/(?:#/)?watch\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\?|&)t=(?P<start>[0-9hms]+)$)?]]></regex>
-      <!-- advanced PHP callback -->
-      <className><![CDATA[wcf\system\bbcode\media\provider\YouTubeBBCodeMediaProvider]]></className>
-    </provider>
-
-    <provider name="youtube-playlist">
-      <title>YouTube Playlist</title>
-      <regex><![CDATA[https?://(?:.+?\.)?youtu(?:\.be/|be\.com/)playlist\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]></regex>
-      <!-- uses a simple HTML replacement -->
-      <html><![CDATA[<div class="videoContainer"><iframe src="https://www.youtube.com/embed/videoseries?list={$ID}" allowfullscreen></iframe></div>]]></html>
-    </provider>
-  </import>
-
-  <delete>
-    <provider identifier="example" />
-  </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/mediaProvider.xml",
+    "mediaProvider.xml"
+) }}
index e50add08f60b64246557b95e5f71a3bb6f3858c0..13fe829fe8d4ef5bd4b04aa2b6f9bffad5c49b1a 100644 (file)
@@ -22,20 +22,8 @@ The page that the link should point to, requires the internal identifier set by
 
 ## Example
 
-```xml
-<?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/2019/menuItem.xsd">
-    <import>
-        <item identifier="com.woltlab.wcf.Dashboard">
-            <menu>com.woltlab.wcf.MainMenu</menu>
-            <title language="de">Dashboard</title>
-            <title language="en">Dashboard</title>
-            <page>com.woltlab.wcf.Dashboard</page>
-        </item>
-    </import>
-
-    <delete>
-        <item identifier="com.woltlab.wcf.FooterLinks" />
-    </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/menuItem.xml",
+    "menuItem.xml"
+) }}
index d11e9bb9f5a2d6f40149a9ccd44a34828cc4cd9a..3cd311cfc618b2c5c13b2487e93f2820ed0ea4ee 100644 (file)
@@ -24,25 +24,8 @@ The following elements of the [box PIP](box.md) are supported, please refer to t
 
 ## Example
 
-```xml
-<?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/2019/menu.xsd">
-    <import>
-        <menu identifier="com.woltlab.wcf.FooterLinks">
-            <title language="de">Footer-Links</title>
-            <title language="en">Footer Links</title>
-
-            <box>
-                <position>footer</position>
-                <cssClassName>boxMenuLinkGroup</cssClassName>
-                <showHeader>0</showHeader>
-                <visibleEverywhere>1</visibleEverywhere>
-            </box>
-        </menu>
-    </import>
-
-    <delete>
-        <menu identifier="com.woltlab.wcf.FooterLinks" />
-    </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/menu.xml",
+    "menu.xml"
+) }}
index d84d5e2a024227740306c910a806ab0d072050d7..3ca05db5b38fd9f83c5500f7196921987c5dcbf9 100644 (file)
@@ -22,14 +22,8 @@ The name of the PHP interface [objectTypes](object-type.md) have to implement.
 
 ## Example
 
-```xml
-<?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/2019/objectTypeDefinition.xsd">
-       <import>
-               <definition>
-                       <name>com.woltlab.wcf.example</name>
-                       <interfacename>wcf\system\example\IExampleObjectType</interfacename>
-               </definition>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/objectTypeDefinition.xml",
+    "objectTypeDefinition.xml"
+) }}
index 1f3a6d9069e81b511ed8b86940f9aa6e6318db59..3fe1bbb0e76fc4ba4b4ae931a32559d1edc982c7 100644 (file)
@@ -25,16 +25,8 @@ Refer to the documentation of these for further explanation.
 
 ## Example
 
-```xml
-<?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/2019/objectType.xsd">
-       <import>
-               <type>
-                       <name>com.woltlab.wcf.example</name>
-                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
-                       <classname>wcf\system\worker\ExampleRebuildWorker</classname>
-                       <nicevalue>130</nicevalue>
-               </type>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/objectType.xml",
+    "objectType.xml"
+) }}
index 906805072afe0bc08034a47a1e70156bcd5178c7..07587ec05f402e5908c6b1fc06d5729f74a0438e 100644 (file)
@@ -127,45 +127,8 @@ If you want to provide an optional description of the option, you have to provid
 
 ## Example
 
-```xml
-<?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/2019/option.xsd">
-       <import>
-               <categories>
-                       <category name="example" />
-                       <category name="example.sub">
-                               <parent>example</parent>
-                               <options>module_example</options>
-                       </category>
-               </categories>
-               
-               <options>
-                       <option name="module_example">
-                               <categoryname>module.community</categoryname>
-                               <optiontype>boolean</optiontype>
-                               <defaultvalue>1</defaultvalue>
-                       </option>
-                       
-                       <option name="example_integer">
-                               <categoryname>example.sub</categoryname>
-                               <optiontype>integer</optiontype>
-                               <defaultvalue>10</defaultvalue>
-                               <minvalue>5</minvalue>
-                               <maxvalue>40</maxvalue>
-                       </option>
-                       
-                       <option name="example_select">
-                               <categoryname>example.sub</categoryname>
-                               <optiontype>select</optiontype>
-                               <defaultvalue>DESC</defaultvalue>
-                               <selectoptions>ASC:wcf.global.sortOrder.ascending
-DESC:wcf.global.sortOrder.descending</selectoptions>
-                       </option>
-               </options>
-       </import>
-       
-       <delete>
-               <option name="outdated_example" />
-       </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/option.xml",
+    "option.xml"
+) }}
index fb562b21e45277368508526bf87b4a849ecc2a2d..61585cac11bacee6ba23836fd53995d3beded5b4 100644 (file)
@@ -74,29 +74,8 @@ The content that should be used to populate the page, only used and required if
 
 ## Example
 
-```xml
-<?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/2019/page.xsd">
-    <import>
-        <page identifier="com.woltlab.wcf.MembersList">
-            <pageType>system</pageType>
-            <controller>wcf\page\MembersListPage</controller>
-            <name language="de">Mitglieder</name>
-            <name language="en">Members</name>
-            <permissions>user.profile.canViewMembersList</permissions>
-            <options>module_members_list</options>
-
-            <content language="en">
-                <title>Members</title>
-            </content>
-            <content language="de">
-                <title>Mitglieder</title>
-            </content>
-        </page>
-    </import>
-
-    <delete>
-        <page identifier="com.woltlab.wcf.MembersList" />
-    </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/page.xml",
+    "page.xml"
+) }}
\ No newline at end of file
index 0472a99bacc228aaaf18451ce6ba1acb2f408e8f..b76cb3db7fe93499c680ad05995c107d71f7bd02 100644 (file)
@@ -10,14 +10,8 @@ Each package installation plugin is described as an `<pip>` element with a `name
 
 ## Example
 
-```xml
-<?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/2019/packageInstallationPlugin.xsd">
-       <import>
-               <pip name="custom">wcf\system\package\plugin\CustomPackageInstallationPlugin</pip>
-       </import>
-       <delete>
-               <pip name="outdated" />
-       </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/packageInstallationPlugin.xml",
+    "packageInstallationPlugin.xml"
+) }}
index a6c14e51148abc37c9d380d055b8c3bc38114136..dffb4ed64e68cca985255d5e7b68a5e4b8e84eb4 100644 (file)
@@ -32,17 +32,8 @@ Determines at which position of the smiley list the smiley is shown.
 
 ## Example
 
-```xml
-<?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/2019/smiley.xsd">
-       <import>
-               <smiley name=":example:">
-                       <title>example</title>
-                       <path>images/smilies/example.png</path>
-                       <path2x>images/smilies/example@2x.png</path2x>
-                       <aliases><![CDATA[:alias:
-:more_aliases:]]></aliases>
-               </smiley>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/smiley.xml",
+    "smiley.xml"
+) }}
index 3bcc607cf414796107a7f68a6f540f5664ed8108..469df6a57a714449b09a42f5e5f32e9e6647cb08 100644 (file)
@@ -58,15 +58,8 @@ If you really need triggers, you should consider adding them by custom SQL queri
 
 Example content:
 
-```sql
-CREATE TABLE wcf1_foo_bar (
-       fooID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
-       packageID INT(10) NOT NULL,
-       bar VARCHAR(255) NOT NULL DEFAULT '',
-       foobar VARCHAR(50) NOT NULL DEFAULT '',
-       
-       UNIQUE KEY baz (bar, foobar)
-);
-
-ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
-```
+{jinja{ codebox(
+    "sql",
+    "package/pip/install.sql",
+    "install.sql"
+) }}
\ No newline at end of file
index 57b2d6d2c7ecd78db7733b46194b07712d63b0c5..7f22a5d51bc176af7dcf2224111a09a56291e13e 100644 (file)
@@ -56,20 +56,8 @@ The permissions element can contain a comma-separated list of permissions of whi
 
 ## Example
 
-```xml
-<?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/2019/templatelistener.xsd">
-       <import>
-               <templatelistener name="example">
-                       <environment>user</environment>
-                       <templatename>headIncludeJavaScript</templatename>
-                       <eventname>javascriptInclude</eventname>
-                       <templatecode><![CDATA[{include file='__myCustomJavaScript'}]]></templatecode>
-               </templatelistener>
-       </import>
-       
-       <delete>
-               <templatelistener name="oldTemplateListenerName" />
-       </delete>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/templateListener.xml",
+    "templateListener.xml"
+) }}
index 1c0bf41d125ce74da1791bbfb843ec8077b88431..82b3e720cb883f67f1e5348e9440a266f0168e7a 100644 (file)
@@ -55,27 +55,8 @@ the class has to implement the `wcf\system\menu\user\IUserMenuItemProvider` inte
 
 ## Example
 
-```xml
-<?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/2019/userMenu.xsd">
-       <import>
-               <usermenuitem name="wcf.user.menu.foo">
-                       <iconclassname>fa-home</iconclassname>
-               </usermenuitem>
-               
-               <usermenuitem name="wcf.user.menu.foo.bar">
-                       <controller>wcf\page\FooBarListPage</controller>
-                       <parent>wcf.user.menu.foo</parent>
-                       <permissions>user.foo.canBar</permissions>
-                       <classname>wcf\system\menu\user\FooBarMenuItemProvider</classname>
-               </usermenuitem>
-               
-               <usermenuitem name="wcf.user.menu.foo.baz">
-                       <controller>wcf\page\FooBazListPage</controller>
-                       <parent>wcf.user.menu.foo</parent>
-                       <permissions>user.foo.canBaz</permissions>
-                       <options>module_foo_bar</options>
-               </usermenuitem>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/userMenu.xml",
+    "userMenu.xml"
+) }}
index cd11a05678c7707af1d3c5fc262f3f5602c234d7..eef427a8cd6bc775a447678df1f1d1f7c024553a 100644 (file)
@@ -42,17 +42,8 @@ The permissions element can contain a comma-separated list of permissions of whi
 
 ## Example
 
-```xml
-<?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/2019/userNotificationEvent.xsd">
-       <import>
-               <event>
-                       <name>like</name>
-                       <objecttype>com.woltlab.example.comment.like.notification</objecttype>
-                       <classname>wcf\system\user\notification\event\ExampleCommentLikeUserNotificationEvent</classname>
-                       <preset>1</preset>
-                       <options>module_like</options>
-               </event>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/userNotificationEvent.xml",
+    "userNotificationEvent.xml"
+) }}
index 4052d6698b8d553538765634258fe24b9f1c7037..f39fc3da032881a6c93690cde9bcdb79b99d4c65 100644 (file)
@@ -31,15 +31,8 @@ The permissions element can contain a comma-separated list of permissions of whi
 
 ## Example
 
-```xml
-<?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/2019/userProfileMenu.xsd">
-       <import>
-               <userprofilemenuitem name="example">
-                       <classname>wcf\system\menu\user\profile\content\ExampleProfileMenuContent</classname>
-                       <showorder>3</showorder>
-                       <options>module_example</options>
-               </userprofilemenuitem>
-       </import>
-</data>
-```
+{jinja{ codebox(
+    "xml",
+    "package/pip/userProfileMenu.xml",
+    "userProfileMenu.xml"
+) }}
index 1e775cb066a91f3e5eed30b6bd0c26b41ee3b92b..213073d2e348baf0a8ee79f7ce39337bdf03020c 100644 (file)
@@ -25,23 +25,12 @@ of background on caches and examples that should help you in your decision.
 Every cache builder should derive from the base class [AbstractCacheBuilder](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/cache/builder/AbstractCacheBuilder.class.php)
 that already implements the mandatory interface [ICacheBuilder](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/cache/builder/ICacheBuilder.class.php).
 
-```php
-<?php
-namespace wcf\system\cache\builder;
-
-class ExampleCacheBuilder extends AbstractCacheBuilder {
-  // 3600 = 1hr
-  protected $maxLifetime = 3600;
 
-  public function rebuild(array $parameters) {
-    $data = [];
-
-    // fetch and process your data and assign it to `$data`
-
-    return $data;
-  }
-}
-```
+{jinja{ codebox(
+"php",
+"php/api/caches/ExampleCacheBuilder.class.php",
+"files/lib/system/cache/builder/ExampleCacheBuilder.class.php"
+) }}
 
 Reading data from your cache builder is quite simple and follows a consistent
 pattern. The callee only needs to know the name of the cache builder, which
index 9fd5483c44fd2121722e3c4376fbb2bb2947ec50..77d97cea6698b614fdc1d9c79c96b6d5c73cd1c7 100644 (file)
@@ -49,29 +49,8 @@ $users = UserRuntimeCache::getInstance()->getObjects([3, 4]);
 
 ## Example
 
-```php
-<?php
-namespace wcf\system\cache\runtime;
-use wcf\data\user\User;
-use wcf\data\user\UserList;
-
-/**
- * Runtime cache implementation for users.
- *
- * @author     Matthias Schmidt
- * @copyright  2001-2016 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Cache\Runtime
- * @since      3.0
- * 
- * @method     User[]          getCachedObjects()
- * @method     User            getObject($objectID)
- * @method     User[]          getObjects(array $objectIDs)
- */
-class UserRuntimeCache extends AbstractRuntimeCache {
-       /**
-        * @inheritDoc
-        */
-       protected $listClassName = UserList::class;
-}
-```
+{jinja{ codebox(
+   "php",
+   "php/api/caches/UserRuntimeCache.class.php",
+   "files/lib/system/cache/runtime/UserRuntimeCache.class.php"
+) }}
index e66bd24f6d1a338dde6998e369c75ff91ec00c53..18fd6ffd65b36c26c38255dfc09b48beebc2d28e 100644 (file)
@@ -9,38 +9,11 @@ This page focuses on the technical aspects of cronjobs, [the cronjob package ins
 
 ## Example
 
-```php
-<?php
-namespace wcf\system\cronjob;
-use wcf\data\cronjob\Cronjob;
-use wcf\system\WCF;
-
-/**
- * Updates the last activity timestamp in the user table.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2016 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\System\Cronjob
- */
-class LastActivityCronjob extends AbstractCronjob {
-       /**
-        * @inheritDoc
-        */
-       public function execute(Cronjob $cronjob) {
-               parent::execute($cronjob);
-               
-               $sql = "UPDATE  wcf".WCF_N."_user user_table,
-                               wcf".WCF_N."_session session
-                       SET     user_table.lastActivityTime = session.lastActivityTime
-                       WHERE   user_table.userID = session.userID
-                               AND session.userID <> 0";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute();
-       }
-}
-
-```
+{jinja{ codebox(
+    "php",
+    "php/api/cronjobs/LastActivityCronjob.class.php",
+    "files/lib/system/cronjob/LastActivityCronjob.class.php"
+) }}
 
 
 ## `ICronjob` Interface
index 4188b9332886dcce782cfd35af891646c59cc759..736c2ba3b06cdceb4866ecc641fee13fd79f489f 100644 (file)
@@ -12,21 +12,11 @@ A comprehensive list of all available events is provided [here](event_list.md).
 Let's start with a simple example to illustrate how the event system works.
 Consider this pre-existing class:
 
-```php
-<?php
-namespace wcf\system\example;
-use wcf\system\event\EventHandler;
-
-class ExampleComponent {
-       public $var = 1;
-       
-       public function getVar() {
-               EventHandler::getInstance()->fireAction($this, 'getVar');
-               
-               return $this->var;
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/events/ExampleComponent.class.php",
+  "files/lib/system/example/ExampleComponent.class.php"
+) }}
 
 where an event with event name `getVar` is fired in the `getVar()` method.
 
@@ -51,17 +41,11 @@ else {
 
 Now, consider that we have registered the following event listener to this event:
 
-```php
-<?php
-namespace wcf\system\event\listener;
-
-class ExampleEventListener implements IParameterizedEventListener {
-       public function execute($eventObj, $className, $eventName, array &$parameters) {
-               $eventObj->var = 2;
-       }
-}
-
-```
+{jinja{ codebox(
+  "php",
+  "php/api/events/ExampleEventListener.class.php",
+  "files/lib/system/event/listener/ExampleEventListener.class.php"
+) }}
 
 Whenever the event in the `getVar()` method is called, this method (of the same event listener object) is called.
 In this case, the value of the method's first parameter is the `ExampleComponent` object passed as the first argument of the `EventHandler::fireAction()` call in `ExampleComponent::getVar()`.
@@ -99,66 +83,31 @@ The only thing to do is to call the `wcf\system\event\EventHandler::fireAction($
 
 Consider the following method which gets some text that the methods parses.
 
-```php
-<?php
-namespace wcf\system\example;
-use wcf\system\event\EventHandler;
-
-class ExampleParser {
-       public function parse($text) {
-               // [some parsing done by default]
-               
-               $parameters = ['text' => $text];
-               EventHandler::getInstance()->fireAction($this, 'parse', $parameters);
-               
-               return $parameters['text'];
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/events/ExampleParser1.class.php",
+  "files/lib/system/example/ExampleParser.class.php"
+) }}
 
 After the default parsing by the method itself, the author wants to enable plugins to do additional parsing and thus fires an event and passes the parsed text as an additional parameter.
 Then, a plugin can deliver the following event listener
 
-```php
-<?php
-namespace wcf\system\event\listener;
-
-class ExampleParserEventListener implements IParameterizedEventListener {
-       public function execute($eventObj, $className, $eventName, array &$parameters) {
-               $text = $parameters['text'];
-               
-               // [some additional parsing which changes $text]
-               
-               $parameters['text'] = $text;
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/events/ExampleParserEventListener.class.php",
+  "files/lib/system/event/listener/ExampleParserEventListener.class.php"
+) }}
 
 which can access the text via `$parameters['text']`.
 
 This example can also be perfectly used to illustrate how to name multiple events in the same method.
 Let's assume that the author wants to enable plugins to change the text before and after the method does its own parsing and thus fires two events:
 
-```php
-<?php
-namespace wcf\system\example;
-use wcf\system\event\EventHandler;
-
-class ExampleParser {
-       public function parse($text) {
-               $parameters = ['text' => $text];
-               EventHandler::getInstance()->fireAction($this, 'beforeParsing', $parameters);
-               $text = $parameters['text'];
-               
-               // [some parsing done by default]
-               
-               $parameters = ['text' => $text];
-               EventHandler::getInstance()->fireAction($this, 'afterParsing', $parameters);
-               
-               return $parameters['text'];
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/events/ExampleParser2.class.php",
+  "files/lib/system/example/ExampleParser.class.php"
+) }}
 
 
 ## Advanced Example: Additional Form Field
@@ -184,50 +133,11 @@ The points in the program flow of [AbstractForm](../pages.md#abstractform) that
 
 All of these cases can be covered the by following code in which we assume that `wcf\form\ExampleAddForm` is the form to create example objects and that `wcf\form\ExampleEditForm` extends `wcf\form\ExampleAddForm` and is used for editing existing example objects.
 
-```php
-<?php
-namespace wcf\system\event\listener;
-use wcf\form\ExampleAddForm;
-use wcf\form\ExampleEditForm;
-use wcf\system\exception\UserInputException;
-use wcf\system\WCF;
-
-class ExampleAddFormListener implements IParameterizedEventListener {
-       protected $var = 0;
-       
-       public function execute($eventObj, $className, $eventName, array &$parameters) {
-               $this->$eventName($eventObj);
-       }
-       
-       protected function assignVariables() {
-               WCF::getTPL()->assign('var', $this->var);
-       }
-       
-       protected function readData(ExampleEditForm $eventObj) {
-               if (empty($_POST)) {
-                       $this->var = $eventObj->example->var;
-               }
-       }
-       
-       protected function readFormParameters() {
-               if (isset($_POST['var'])) $this->var = intval($_POST['var']);
-       }
-       
-       protected function save(ExampleAddForm $eventObj) {
-               $eventObj->additionalFields = array_merge($eventObj->additionalFields, ['var' => $this->var]);
-       }
-       
-       protected function saved() {
-               $this->var = 0;
-       }
-       
-       protected function validate() {
-               if ($this->var < 0) {
-                       throw new UserInputException('var', 'isNegative');
-               }
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/events/ExampleAddFormListener.class.php",
+  "files/lib/system/event/listener/ExampleAddFormListener.class.php"
+) }}
 
 The `execute` method in this example just delegates the call to a method with the same name as the event so that this class mimics the structure of a form class itself.
 The form object is passed to the methods but is only given in the method signatures as a parameter here whenever the form object is actually used.
@@ -243,28 +153,8 @@ Furthermore, the type-hinting of the parameter illustrates in which contexts the
 
 Lastly, the following XML file has to be used to register the event listeners (you can find more information about how to register event listeners on [the eventListener package installation plugin page](../../package/pip/event-listener.md)):
 
-```xml
-<?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/2019/eventListener.xsd">
-       <import>
-               <eventlistener name="exampleAddInherited">
-                       <eventclassname>wcf\form\ExampleAddForm</eventclassname>
-                       <eventname>assignVariables,readFormParameters,save,validate</eventname>
-                       <listenerclassname>wcf\system\event\listener\ExampleAddFormListener</listenerclassname>
-                       <inherit>1</inherit>
-               </eventlistener>
-               
-               <eventlistener name="exampleAdd">
-                       <eventclassname>wcf\form\ExampleAddForm</eventclassname>
-                       <eventname>saved</eventname>
-                       <listenerclassname>wcf\system\event\listener\ExampleAddFormListener</listenerclassname>
-               </eventlistener>
-               
-               <eventlistener name="exampleEdit">
-                       <eventclassname>wcf\form\ExampleEditForm</eventclassname>
-                       <eventname>readData</eventname>
-                       <listenerclassname>wcf\system\event\listener\ExampleAddFormListener</listenerclassname>
-               </eventlistener>
-       </import>
-</data>
-```
\ No newline at end of file
+{jinja{ codebox(
+  "xml",
+  "php/api/events/eventListener.xml",
+  "eventListener.xml"
+) }}
index 50fbd71441e9a84e70903fb85c3308db512290d9..9ad079e566be940de330170ad9ddd58404035e44 100644 (file)
@@ -26,45 +26,11 @@ Other optional methods are:
 
 As an example, the implementation for users looks like this:
 
-```php
-<?php
-namespace wcf\system\sitemap\object;
-use wcf\data\user\User;
-use wcf\data\DatabaseObject;
-use wcf\system\WCF;
-
-/**
- * User sitemap implementation.
- *
- * @author     Joshua Ruesweg
- * @copyright  2001-2017 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Sitemap\Object
- * @since      3.1
- */
-class UserSitemapObject extends AbstractSitemapObjectObjectType {
-       /**
-        * @inheritDoc
-        */
-       public function getObjectClass() {
-               return User::class;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function getLastModifiedColumn() {
-               return 'lastActivityTime';
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function canView(DatabaseObject $object) {
-               return WCF::getSession()->getPermission('user.profile.canViewUserProfile');
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/sitemaps/UserSitemapObject.class.php",
+  "files/lib/system/sitemap/object/UserSitemapObject.class.php"
+) }}
 
 Next, the sitemap object must be registered as an object type:
 
index 68a0724379cf1f107fceb58c4091bd90be81a215..523302022966f05f315407fcd01a45b265dff546 100644 (file)
@@ -7,106 +7,28 @@ WoltLab Suite includes a powerful user notification system that supports notific
 
 For any type of object related to events, you have to define an object type for the object type definition `com.woltlab.wcf.notification.objectType`:
 
-```xml
-<?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/2019/objectType.xsd">
-       <import>
-               <type>
-                       <name>com.woltlab.example.foo</name>
-                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
-                       <classname>example\system\user\notification\object\type\FooUserNotificationObjectType</classname>
-                       <category>com.woltlab.example</category>
-               </type>
-       </import>
-</data>
-```
+{jinja{ codebox(
+  "xml",
+  "php/api/user_notifications/objectType.xml",
+  "objectType.xml"
+) }}
 
 The referenced class `FooUserNotificationObjectType` has to implement the [IUserNotificationObjectType](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/user/notification/object/type/IUserNotificationObjectType.class.php) interface, which should be done by extending [AbstractUserNotificationObjectType](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/user/notification/object/type/AbstractUserNotificationObjectType.class.php).
 
-
-```php
-<?php
-namespace example\system\user\notification\object\type;
-use example\data\foo\Foo;
-use example\data\foo\FooList;
-use example\system\user\notification\object\FooUserNotificationObject;
-use wcf\system\user\notification\object\type\AbstractUserNotificationObjectType;
-
-/**
- * Represents a foo as a notification object type.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
- * @license    WoltLab License <http://www.woltlab.com/license-agreement.html>
- * @package    WoltLabSuite\Example\System\User\Notification\Object\Type
- */
-class FooUserNotificationObjectType extends AbstractUserNotificationObjectType {
-       /**
-        * @inheritDoc
-        */
-       protected static $decoratorClassName = FooUserNotificationObject::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $objectClassName = Foo::class;
-       
-       /**
-        * @inheritDoc
-        */
-       protected static $objectListClassName = FooList::class;
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/user_notifications/FooUserNotificationObjectType.class.php",
+  "files/lib/system/user/notification/object/type/FooUserNotificationObjectType.class.php"
+) }}
 
 You have to set the class names of the database object (`$objectClassName`) and the related list (`$objectListClassName`).
-Additionally, you have to create a class that implements the [IUserNotificationObject](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/user/notification/object/IUserNotificationObject.class.php) whose name you have to set as the value of the `$decoratorClassName` property. 
-
-```php
-<?php
-namespace example\system\user\notification\object;
-use example\data\foo\Foo;
-use wcf\data\DatabaseObjectDecorator;
-use wcf\system\user\notification\object\IUserNotificationObject;
+Additionally, you have to create a class that implements the [IUserNotificationObject](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/user/notification/object/IUserNotificationObject.class.php) whose name you have to set as the value of the `$decoratorClassName` property.
 
-/**
- * Represents a foo as a notification object.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
- * @license    WoltLab License <http://www.woltlab.com/license-agreement.html>
- * @package    WoltLabSuite\Example\System\User\Notification\Object
- * 
- * @method     Foo     getDecoratedObject()
- * @mixin      Foo
- */
-class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
-       /**
-        * @inheritDoc
-        */
-       protected static $baseClass = Foo::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               return $this->getDecoratedObject()->getTitle();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getURL() {
-               return $this->getDecoratedObject()->getLink();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getAuthorID() {
-               return $this->getDecoratedObject()->userID;
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/user_notifications/FooUserNotificationObject.class.php",
+  "files/lib/system/user/notification/object/FooUserNotificationObject.class.php"
+) }}
 
 - The `getTitle()` method returns the title of the object.
   In this case, we assume that the `Foo` class has implemented the [ITitledObject](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/data/ITitledObject.class.php) interface so that the decorated `Foo` can handle this method call itself.
@@ -121,165 +43,21 @@ class FooUserNotificationObject extends DatabaseObjectDecorator implements IUser
 Each event that you fire in your package needs to be registered using the [user notification event package installation plugin](../../package/pip/user-notification-event.md).
 An example file might look like this:
 
-```xml
-<?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/2019/userNotificationEvent.xsd">
-       <import>
-               <event>
-                       <name>bar</name>
-                       <objecttype>com.woltlab.example.foo</objecttype>
-                       <classname>example\system\user\notification\event\FooUserNotificationEvent</classname>
-                       <preset>1</preset>
-               </event>
-       </import>
-</data>
-```
+{jinja{ codebox(
+  "xml",
+  "php/api/user_notifications/userNotificationEvent.xml",
+  "userNotificationEvent.xml"
+) }}
 
 Here, you reference the user notification object type created via `objectType.xml`.
 The referenced class in the `<classname>` element has to implement the [IUserNotificationEvent](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/user/notification/event/IUserNotificationEvent.class.php) interface by extending the [AbstractUserNotificationEvent](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/user/notification/event/AbstractUserNotificationEvent.class.php) class or the [AbstractSharedUserNotificationEvent](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/user/notification/event/AbstractSharedUserNotificationEvent.class.php) class if you want to pre-load additional data before processing notifications.
 In `AbstractSharedUserNotificationEvent::prepare()`, you can, for example, tell runtime caches to prepare to load certain objects which then are loaded all at once when the objects are needed.
 
-```php
-<?php
-namespace example\system\user\notification\event;
-use example\system\cache\runtime\BazRuntimeCache;
-use example\system\user\notification\object\FooUserNotificationObject;
-use wcf\system\email\Email;
-use wcf\system\request\LinkHandler;
-use wcf\system\user\notification\event\AbstractSharedUserNotificationEvent;
-
-/**
- * Notification event for foos.
- * 
- * @author     Matthias Schmidt
- * @copyright  2001-2017 WoltLab GmbH
- * @license    WoltLab License <http://www.woltlab.com/license-agreement.html>
- * @package    WoltLabSuite\Example\System\User\Notification\Event
- * 
- * @method     FooUserNotificationObject       getUserNotificationObject()
- */
-class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent {
-       /**
-        * @inheritDoc
-        */
-       protected $stackable = true;
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function checkAccess() {
-               $this->getUserNotificationObject()->setBaz(BazRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->bazID));
-               
-               if (!$this->getUserNotificationObject()->isAccessible()) {
-                       // do some cleanup, if necessary
-                       
-                       return false;
-               }
-               
-               return true;
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getEmailMessage($notificationType = 'instant') {
-               $this->getUserNotificationObject()->setBaz(BazRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->bazID));
-               
-               $messageID = '<com.woltlab.example.baz/'.$this->getUserNotificationObject()->bazID.'@'.Email::getHost().'>';
-               
-               return [
-                       'application' => 'example',
-                       'in-reply-to' => [$messageID],
-                       'message-id' => 'com.woltlab.example.foo/'.$this->getUserNotificationObject()->fooID,
-                       'references' => [$messageID],
-                       'template' => 'email_notification_foo'
-               ];
-       }
-       
-       /**
-        * @inheritDoc
-        * @since       5.0
-        */
-       public function getEmailTitle() {
-               $this->getUserNotificationObject()->setBaz(BazRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->bazID));
-               
-               return $this->getLanguage()->getDynamicVariable('example.foo.notification.mail.title', [
-                       'userNotificationObject' => $this->getUserNotificationObject()
-               ]);
-       }
-       
-       /** @noinspection PhpMissingParentCallCommonInspection */
-       /**
-        * @inheritDoc
-        */
-       public function getEventHash() {
-               return sha1($this->eventID . '-' . $this->getUserNotificationObject()->bazID);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getLink() {
-               return LinkHandler::getInstance()->getLink('Foo', [
-                       'application' => 'example',
-                       'object' => $this->getUserNotificationObject()->getDecoratedObject()
-               ]);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getMessage() {
-               $authors = $this->getAuthors();
-               $count = count($authors);
-               
-               if ($count > 1) {
-                       if (isset($authors[0])) {
-                               unset($authors[0]);
-                       }
-                       $count = count($authors);
-                       
-                       return $this->getLanguage()->getDynamicVariable('example.foo.notification.message.stacked', [
-                               'author' => $this->author,
-                               'authors' => array_values($authors),
-                               'count' => $count,
-                               'guestTimesTriggered' => $this->notification->guestTimesTriggered,
-                               'message' => $this->getUserNotificationObject(),
-                               'others' => $count - 1
-                       ]);
-               }
-               
-               return $this->getLanguage()->getDynamicVariable('example.foo.notification.message', [
-                       'author' => $this->author,
-                       'userNotificationObject' => $this->getUserNotificationObject()
-               ]);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function getTitle() {
-               $count = count($this->getAuthors());
-               if ($count > 1) {
-                       return $this->getLanguage()->getDynamicVariable('example.foo.notification.title.stacked', [
-                               'count' => $count,
-                               'timesTriggered' => $this->notification->timesTriggered
-                       ]);
-               }
-               
-               return $this->getLanguage()->get('example.foo.notification.title');
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       protected function prepare() {
-               BazRuntimeCache::getInstance()->cacheObjectID($this->getUserNotificationObject()->bazID);
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/api/user_notifications/FooUserNotificationEvent.class.php",
+  "files/lib/system/user/notification/event/FooUserNotificationEvent.class.php"
+) }}
 
 - The `$stackable` property is `false` by default and has to be explicitly set to `true` if stacking of notifications should be enabled.
   Stacking of notification does not create new notifications for the same event for a certain object if the related action as been triggered by different users.
index 0c86c94b420e02026da253da24552e15aae6547f..cdbe9ea1175e3f2228c2f9640eb25703d10a9662 100644 (file)
@@ -145,30 +145,11 @@ $className = Example::class;
 
 Some database objects provide static getters, either if they are decorators or for a unique combination of database table columns, like `wcf\data\box\Box::getBoxByIdentifier()`:
 
-```php
-<?php
-namespace wcf\data\box;
-use wcf\data\DatabaseObject;
-use wcf\system\WCF;
-
-class Box extends DatabaseObject { 
-       /**
-        * Returns the box with the given identifier.
-        *
-        * @param       string          $identifier
-        * @return      Box|null
-        */
-       public static function getBoxByIdentifier($identifier) {
-               $sql = "SELECT  *
-                       FROM    wcf".WCF_N."_box
-                       WHERE   identifier = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute([$identifier]);
-               
-               return $statement->fetchObject(self::class);
-       }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/code-style/Box.class.php",
+  "files/lib/data/box/Box.class.php"
+) }}
 
 Such methods should always either return the desired object or `null` if the object does not exist.
 `wcf\system\database\statement\PreparedStatement::fetchObject()` already takes care of this distinction so that its return value can simply be returned by such methods.
index d07de71e23ef191572b90947cda3575b7f8e5790..de6e178ca3a9efc68f1d0e4a0ec9cb89aff3ff1b 100644 (file)
@@ -9,13 +9,11 @@ Developers are required to provide the proper DatabaseObject implementations the
 
 The basic model derives from `wcf\data\DatabaseObject` and provides a convenient constructor to fetch a single row or construct an instance using pre-loaded rows.
 
-```php
-<?php
-namespace wcf\data\example;
-use wcf\data\DatabaseObject;
-
-class Example extends DatabaseObject {}
-```
+{jinja{ codebox(
+  "php",
+  "php/database-objects/Example.class.php",
+  "files/lib/data/example/Example.class.php"
+) }}
 
 The class is intended to be empty by default and there only needs to be code if you want to add additional logic to your model. Both the class name and primary key are determined by `DatabaseObject` using the namespace and class name of the derived class. The example above uses the namespace `wcf\…` which is used as table prefix and the class name `Example` is converted into `exampleID`, resulting in the database table name `wcfN_example` with the primary key `exampleID`.
 
@@ -26,23 +24,11 @@ You can prevent this automatic guessing by setting the class properties `$databa
 
 If you already have a `DatabaseObject` class and would like to extend it with additional data or methods, for example by providing a class `ViewableExample` which features view-related changes without polluting the original object, you can use `DatabaseObjectDecorator` which a default implementation of a decorator for database objects.
 
-```php
-<?php
-namespace wcf\data\example;
-use wcf\data\DatabaseObjectDecorator;
-
-class ViewableExample extends DatabaseObjectDecorator {
-    protected static $baseClass = Example::class;
-    
-    public function getOutput() {
-        $output = '';
-        
-        // [determine output]
-        
-        return $output;
-    }
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/database-objects/ViewableExample.class.php",
+  "files/lib/data/example/ViewableExample.class.php"
+) }}
 
 It is mandatory to set the static `$baseClass` property to the name of the decorated class.
 
@@ -56,15 +42,11 @@ You can access the decorated objects directly via `DatabaseObjectDecorator::getD
 
 Adding, editing and deleting models is done using the `DatabaseObjectEditor` class that decorates a `DatabaseObject` and uses its data to perform the actions.
 
-```php
-<?php
-namespace wcf\data\example;
-use wcf\data\DatabaseObjectEditor;
-
-class ExampleEditor extends DatabaseObjectEditor {
-    protected static $baseClass = Example::class;
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/database-objects/ExampleEditor.class.php",
+  "files/lib/data/example/ExampleEditor.class.php"
+) }}
 
 The editor class requires you to provide the fully qualified name of the model, that is the class name including the complete namespace. Database table name and index key will be pulled directly from the model.
 
@@ -120,15 +102,11 @@ $exampleEditor->delete();
 
 Every row is represented as a single instance of the model, but the instance creation deals with single rows only. Retrieving larger sets of rows would be quite inefficient due to the large amount of queries that will be dispatched. This is solved with the `DatabaseObjectList` object that exposes an interface to query the database table using arbitrary conditions for data selection. All rows will be fetched using a single query and the resulting rows are automatically loaded into separate models.
 
-```php
-<?php
-namespace wcf\data\example;
-use wcf\data\DatabaseObjectList;
-
-class ExampleList extends DatabaseObjectList {
-    public $className = Example::class;
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/database-objects/ExampleList.class.php",
+  "files/lib/data/example/ExampleList.class.php"
+) }}
 
 The following code listing illustrates loading a large set of examples and iterating over the list to retrieve the objects.
 
@@ -184,14 +162,11 @@ $exampleList->decoratorClassName = \wcf\data\example\ViewableExample::class;
 
 Of course, you do not have to set the property after creating the list object, you can also set it by creating a dedicated class:
 
-```php
-<?php
-namespace wcf\data\example;
-
-class ViewableExampleList extends ExampleList {
-    public $decoratorClassName = ViewableExample::class;
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/database-objects/ViewableExampleList.class.php",
+  "files/lib/data/example/ViewableExampleList.class.php"
+) }}
 
 
 ## AbstractDatabaseObjectAction
@@ -203,15 +178,11 @@ Row creation and manipulation can be performed using the aforementioned `Databas
 
 The `AbstractDatabaseObjectAction` solves both problems by wrapping around the editor class and thus provide an additional layer between the action that should be taken and the actual process. The first problem is solved by a fixed set of events being fired, the second issue is addressed by having a single entry point for all data editing.
 
-```php
-<?php
-namespace wcf\data\example;
-use wcf\data\AbstractDatabaseObjectAction;
-
-class ExampleAction extends AbstractDatabaseObjectAction {
-    public $className = ExampleEditor::class;
-}
-```
+{jinja{ codebox(
+  "php",
+  "php/database-objects/ExampleAction.class.php",
+  "files/lib/data/example/ExampleAction.class.php"
+) }}
 
 ### Executing an Action
 
index e3212dd55cdb150c50a93e7fda94a2e0992c3643..243c71dbed277e23d0d49362607c70bd7be8c1ba 100644 (file)
@@ -21,31 +21,11 @@ has been dumped to the `$data` property.
 
 ### Example code
 
-```php
-<?php
-namespace wcf\system\event\listener;
-use wcf\acp\action\UserExportGdprAction;
-use wcf\data\user\UserProfile;
-
-class MyUserExportGdprActionListener implements IParameterizedEventListener {
-  public function execute(/** @var UserExportGdprAction $eventObj */$eventObj, $className, $eventName, array &$parameters) {
-    /** @var UserProfile $user */
-    $user = $eventObj->user;
-
-    $eventObj->data['my.fancy.plugin'] = [
-      'superPersonalData' => "This text is super personal and should be included in the output",
-      'weirdIpAddresses' => $eventObj->exportIpAddresses('app'.WCF_N.'_non_standard_column_names_for_ip_addresses', 'ipAddressColumnName', 'timeColumnName', 'userIDColumnName')
-    ];
-    $eventObj->exportUserProperties[] = 'shouldAlwaysExportThisField';
-    $eventObj->exportUserPropertiesIfNotEmpty[] = 'myFancyField';
-    $eventObj->exportUserOptionSettings[] = 'thisSettingIsAlwaysExported';
-    $eventObj->exportUserOptionSettingsIfNotEmpty[] = 'someSettingContainingPersonalData';
-    $eventObj->ipAddresses['my.fancy.plugin'] = ['wcf'.WCF_N.'_my_fancy_table', 'wcf'.WCF_N.'_i_also_store_ipaddresses_here'];
-    $eventObj->skipUserOptions[] = 'thisLooksLikePersonalDataButItIsNot';
-    $eventObj->skipUserOptions[] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt';
-  }
-}
-```
+{jinja{ codebox(
+    "php",
+    "php/gdpr/MyUserExportGdprActionListener.class.php",
+    "files/lib/system/event/listener/MyUserExportGdprActionListener.class.php"
+) }}
 
 ### `$data`
 
index e4965d9d3e14f142dc2239d9915f6a3c94414857..d31c1fcaec2199ff06f31f91ca6736f2a3572857 100644 (file)
@@ -103,3 +103,18 @@ code, kbd, pre {
     border-left: .2rem solid #448aff;
     background-color: rgba(0,184,212,.1);
 }
+
+.titledCodeBox .codeBoxTitle {
+    background-color: rgb(225, 225, 225);
+    font-size: .85em;
+    font-weight: bold;
+    padding: 0.5em 1em;
+}
+
+.titledCodeBox .codeBoxTitle code {
+    background: none;
+}
+
+.titledCodeBox .codeBoxTitle + .highlighttable {
+    margin-top: 0;
+}
index 66c3eee8af3ded14fe3324d82ffdd8887a4308c3..0f2491e12cb6cdd15650467134082da1b381e1a4 100644 (file)
@@ -84,9 +84,11 @@ Thus, the database table we will store the people in only contains three columns
 
 The first file for our package is the `install_com.woltlab.wcf.people.php` file used to create such a database table during package installation:
 
-```sql
---8<-- "tutorial/tutorial-series/part-1/files/acp/database/install_com.woltlab.wcf.people.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/acp/database/install_com.woltlab.wcf.people.php",
+    "files/acp/database/install_com.woltlab.wcf.people.php"
+) }}
 
 ### Database Object
 
@@ -94,9 +96,11 @@ The first file for our package is the `install_com.woltlab.wcf.people.php` file
 
 In our PHP code, each person will be represented by an object of the following class:
 
-```php
---8<-- "tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php",
+    "files/lib/data/person/Person.class.php"
+) }}
 
 The important thing here is that `Person` extends `DatabaseObject`.
 Additionally, we implement the `IRouteController` interface, which allows us to use `Person` objects to create links, and we implement PHP's magic [__toString()](https://secure.php.net/manual/en/language.oop5.magic.php#object.tostring) method for convenience.
@@ -106,9 +110,11 @@ an action class, an editor class and a list class.
 
 #### `PersonAction`
 
-```php
---8<-- "tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php",
+    "files/lib/data/person/PersonAction.class.php"
+) }}
 
 This implementation of `AbstractDatabaseObjectAction` is very basic and only sets the `$permissionsDelete` and `$requireACP` properties.
 This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX.
@@ -118,18 +124,22 @@ We will later use the [userGroupOption package installation plugin](../../packag
 
 #### `PersonEditor`
 
-```php
---8<-- "tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php",
+    "files/lib/data/person/PersonEditor.class.php"
+) }}
 
 This implementation of `DatabaseObjectEditor` fulfills the minimum requirement for a database object editor:
 setting the static `$baseClass` property to the database object class name.
 
 #### `PersonList`
 
-```php
---8<-- "tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php",
+    "files/lib/data/person/PersonList.class.php"
+) }}
 
 Due to the default implementation of `DatabaseObjectList`, our `PersonList` class just needs to extend it and everything else is either automatically set by the code of `DatabaseObjectList` or, in the case of properties and methods, provided by that class.
 
@@ -153,9 +163,11 @@ We need to create three menu items:
 1. a third level menu item for the people list page, and
 1. a fourth level menu item for the form to add new people.
 
-```xml
---8<-- "tutorial/tutorial-series/part-1/acpMenu.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-1/acpMenu.xml",
+    "acpMenu.xml"
+) }}
 
 We choose `wcf.acp.menu.link.content` as the parent menu item for the first menu item `wcf.acp.menu.link.person` because the people we are managing is just one form of content.
 The fourth level menu item `wcf.acp.menu.link.person.add` will only be shown as an icon and thus needs an additional element `icon` which takes a FontAwesome icon class as value.
@@ -166,9 +178,11 @@ To list the people in the ACP, we need a `PersonListPage` class and a `personLis
 
 #### `PersonListPage`
 
-```php
---8<-- "tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php",
+    "files/lib/data/person/PersonListPage.class.php"
+) }}
 
 As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal:
 
@@ -180,9 +194,11 @@ As WoltLab Suite Core already provides a powerful default implementation of a so
 
 #### `personList.tpl`
 
-```smarty
---8<-- "tutorial/tutorial-series/part-1/acptemplates/personList.tpl"
-```
+{jinja{ codebox(
+    "smarty",
+    "tutorial/tutorial-series/part-1/acptemplates/personList.tpl",
+    "acptemplates/personList.tpl"
+) }}
 
 We will go piece by piece through the template code:
 
@@ -213,9 +229,11 @@ Like the person list, the form to add new people requires a controller class and
 
 #### `PersonAddForm`
 
-```php
---8<-- "tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php",
+    "files/lib/acp/form/PersonAddForm.class.php"
+) }}
 
 The properties here consist of three types:
 the “housekeeping” properties `$activeMenuItem` and `$neededPermissions`, which fulfill the same roles as for `PersonListPage`, and the [`$objectEditLinkController` property](../../migration/wsc52/php.md#addform), which is used to generate a link to edit the newly created person after submitting the form, and finally `$formAction` and `$objectActionClass` required by the [PHP form builder API](../../php/api/form_builder/overview.md) used to generate the form.
@@ -230,9 +248,11 @@ Because of using form builder, we only have to set up the two form fields for en
 
 #### `personAdd.tpl`
 
-```smarty
---8<-- "tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl"
-```
+{jinja{ codebox(
+    "smarty",
+    "tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl",
+    "acptemplates/personAdd.tpl"
+) }}
 
 We will now only concentrate on the new parts compared to `personList.tpl`:
 
@@ -245,9 +265,11 @@ As mentioned before, for the form to edit existing people, we only need a new co
 
 #### `PersonEditForm`
 
-```php
---8<-- "tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php",
+    "files/lib/acp/form/PersonEditForm.class.php"
+) }}
 
 In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited.
 
@@ -265,9 +287,11 @@ This page should also be directly linked in the main menu.
 
 First, let us register the page with the system because every front end page or form needs to be explicitly registered using the [page package installation plugin](../../package/pip/page.md):
 
-```xml
---8<-- "tutorial/tutorial-series/part-1/page.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-1/page.xml",
+    "page.xml"
+) }}
 
 For more information about what each of the elements means, please refer to the [page package installation plugin page](../../package/pip/page.md).
 
@@ -275,9 +299,11 @@ For more information about what each of the elements means, please refer to the
 
 Next, we register the menu item using the [menuItem package installation plugin](../../package/pip/menu-item.md):
 
-```xml
---8<-- "tutorial/tutorial-series/part-1/menuItem.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-1/menuItem.xml",
+    "menuItem.xml"
+) }}
 
 Here, the import parts are that we register the menu item for the main menu `com.woltlab.wcf.MainMenu` and link the menu item with the page `com.woltlab.wcf.people.PersonList`, which we just registered.
 
@@ -289,9 +315,11 @@ This is no problem because the qualified names of the classes differ and the fil
 
 #### `PersonListPage`
 
-```php
---8<-- "tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php",
+    "files/lib/page/PersonListPage.class.php"
+) }}
 
 This class is almost identical to the ACP version.
 In the front end, we do not need to set the active menu item manually because the system determines the active menu item automatically based on the requested page.
@@ -300,9 +328,11 @@ In the front end, we explicitly set the `$defaultSortField` so that the people l
 
 #### `personList.tpl`
 
-```smarty
---8<-- "tutorial/tutorial-series/part-1/templates/personList.tpl"
-```
+{jinja{ codebox(
+    "smarty",
+    "tutorial/tutorial-series/part-1/templates/personList.tpl",
+    "templates/personList.tpl"
+) }}
 
 If you compare this template to the one used in the ACP, you will recognize similar elements like the `.paginationTop` element, the `p.info` element if no people exist, and the `.contentFooter` element.
 Furthermore, we include a template called `header` before actually showing any of the page contents and terminate the template by including the `footer` template.
@@ -326,9 +356,11 @@ Now, let us take a closer look at the differences:
 
 We have already used the `admin.content.canManagePeople` permissions several times, now we need to install it using the [userGroupOption package installation plugin](../../package/pip/user-group-option.md):
 
-```xml
---8<-- "tutorial/tutorial-series/part-1/userGroupOption.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-1/userGroupOption.xml",
+    "userGroupOption.xml"
+) }}
 
 We use the existing `admin.content` user group option category for the permission as the people are “content” (similar the the ACP menu item).
 As the permission is for administrators only, we set `defaultvalue` to `0` and `admindefaultvalue` to `1`.
@@ -341,9 +373,11 @@ This is achieved by setting `usersonly` to `1`.
 Lastly, we need to create the `package.xml` file.
 For more information about this kind of file, please refer to [the `package.xml` page](../../package/package-xml.md).
 
-```xml
---8<-- "tutorial/tutorial-series/part-1/package.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-1/package.xml",
+    "package.xml"
+) }}
 
 As this is a package for WoltLab Suite Core 3, we need to require it using `<requiredpackage>`.
 We require the latest version (when writing this tutorial) `5.4.0 Alpha 1`.
index 5cc3bef007ef5f0aedea2a510ee981bc6a40dc4e..1d3c1f1b54d548260ce511f511030cf1f135bacd 100644 (file)
@@ -65,9 +65,11 @@ The package will have the following file structure:
 The existing model of a person only contains the person’s first name and their last name (in additional to the id used to identify created people).
 To add the birthday to the model, we need to create an additional database table column using the [`database` package installation plugin](../../package/pip/database.md):
 
-```sql
---8<-- "tutorial/tutorial-series/part-2/files/acp/database/install_com.woltlab.wcf.people.birthday.php"
-```
+{jinja{ codebox(
+    "sql",
+    "tutorial/tutorial-series/part-2/files/acp/database/install_com.woltlab.wcf.people.birthday.php",
+    "files/acp/database/install_com.woltlab.wcf.people.birthday.php"
+) }}
 
 If we have a [`Person` object](part_1.md#person), this new property can be accessed the same way as the `personID` property, the `firstName` property, or the `lastName` property from the base package: `$person->birthday`.
 
@@ -76,9 +78,11 @@ If we have a [`Person` object](part_1.md#person), this new property can be acces
 
 To set the birthday of a person, we only have to add another form field with an event listener:
 
-```php
---8<-- "tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php",
+    "files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php"
+) }}
 
 registered via
 
@@ -99,13 +103,17 @@ It is important to set `<inherit>1</inherit>` so that the event listener is also
 
 The language item `wcf.person.birthday` used in the label is the only new one for this package:
 
-```sql
---8<-- "tutorial/tutorial-series/part-2/language/de.xml"
-```
+{jinja{ codebox(
+    "sql",
+    "tutorial/tutorial-series/part-2/language/de.xml",
+    "language/de.xml"
+) }}
 
-```sql
---8<-- "tutorial/tutorial-series/part-2/language/en.xml"
-```
+{jinja{ codebox(
+    "sql",
+    "tutorial/tutorial-series/part-2/language/en.xml",
+    "language/en.xml"
+) }}
 
 
 ## Adding Birthday Table Column in ACP
@@ -118,9 +126,11 @@ To add a birthday column to the person list page in the ACP, we need three parts
 
 The first part is a very simple class:
 
-```php
---8<-- "tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php",
+    "files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php"
+) }}
 
 !!! info "We use `SortablePage` as a type hint instead of `wcf\acp\page\PersonListPage` because we will be using the same event listener class in the front end to also allow sorting that list by birthday."
 
@@ -145,9 +155,11 @@ In the front end, we also want to make the list sortable by birthday and show th
 To add the birthday as a valid sort field, we use `BirthdaySortFieldPersonListPageListener` just as in the ACP.
 In the front end, we will now use a template (`__personListBirthdaySortField.tpl`) instead of a directly putting the template code in the `templateListener.xml` file:
 
-```smarty
---8<-- "tutorial/tutorial-series/part-2/templates/__personListBirthdaySortField.tpl"
-```
+{jinja{ codebox(
+    "smarty",
+    "tutorial/tutorial-series/part-2/templates/__personListBirthdaySortField.tpl",
+    "templates/__personListBirthdaySortField.tpl"
+) }}
 
 !!! info "You might have noticed the two underscores at the beginning of the template file. For templates that are included via template listeners, this is the naming convention we use."
 
@@ -155,18 +167,22 @@ Putting the template code into a file has the advantage that in the administrato
 
 To show the birthday, we use the following template code for the `personStatistics` template event, which again makes sure that the birthday is only shown if it is actually set:
 
-```smarty
---8<-- "tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl"
-```
+{jinja{ codebox(
+    "smarty",
+    "tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl",
+    "templates/__personListBirthday.tpl"
+) }}
 
 
 ## `templateListener.xml`
 
 The following code shows the `templateListener.xml` file used to install all mentioned template listeners:
 
-```xml
---8<-- "tutorial/tutorial-series/part-2/templateListener.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-2/templateListener.xml",
+    "templateListener.xml"
+) }}
 
 In cases where a template is used, we simply use the `include` syntax to load the template.
 
@@ -175,18 +191,22 @@ In cases where a template is used, we simply use the `include` syntax to load th
 
 There are two event listeners that make `birthday` a valid sort field in the ACP and the front end, respectively, and the third event listener takes care of setting the birthday.
 
-```xml
---8<-- "tutorial/tutorial-series/part-2/eventListener.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-2/eventListener.xml",
+    "eventListener.xml"
+) }}
 
 
 ## `package.xml`
 
 The only relevant difference between the `package.xml` file of the base page from part 1 and the `package.xml` file of this package is that this package requires the base package `com.woltlab.wcf.people` (see `<requiredpackages>`):
 
-```xml
---8<-- "tutorial/tutorial-series/part-2/package.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-2/package.xml",
+    "package.xml"
+) }}
 
 ---
 
index be81774896c08f6e0b098cdc1c317a570a964d1e..481702eb206a62d12ebc8a90326b5a8bbf4e156e 100644 (file)
@@ -78,9 +78,11 @@ The complete package will have the following file structure (including the files
 
 To reduce the number of database queries when different APIs require person objects, we implement a [runtime cache](../../php/api/caches_runtime-caches.md) for people:
 
-```php
---8<-- "tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php",
+    "files/lib/system/cache/runtime/PersonRuntimeCache.class.php"
+) }}
 
 
 ## Comments
@@ -88,15 +90,19 @@ To reduce the number of database queries when different APIs require person obje
 To allow users to comment on people, we need to tell the system that people support comments.
 This is done by registering a `com.woltlab.wcf.comment.commentableContent` object type whose processor implements [ICommentManager](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/comment/manager/ICommentManager.class.php):
 
-```xml
---8<-- "tutorial/tutorial-series/part-3/objectType.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-3/objectType.xml",
+    "objectType.xml"
+) }}
 
 The `PersonCommentManager` class extended `ICommentManager`’s default implementation [AbstractCommentManager](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/comment/manager/AbstractCommentManager.class.php):
 
-```php
---8<-- "tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php",
+    "files/lib/system/comment/manager/PersonCommentManager.class.php"
+) }}
 
 - First, the system is told the names of the permissions via the `$permission*` properties.
   More information about comment permissions can be found [here](../../php/api/comments.md#user-group-options).
@@ -118,9 +124,11 @@ With this option, comments on individual people can be disabled.
 
 ### `PersonPage`
 
-```php
---8<-- "tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php",
+    "files/lib/page/PersonPage.class.php"
+) }}
 
 The `PersonPage` class is similar to the `PersonEditForm` in the ACP in that it reads the id of the requested person from the request data and validates the id in `readParameters()`.
 The rest of the code only handles fetching the list of comments on the requested person.
@@ -129,9 +137,11 @@ The `assignVariables()` method assigns some additional template variables like `
 
 ### `person.tpl`
 
-```tpl
---8<-- "tutorial/tutorial-series/part-3/templates/person.tpl"
-```
+{jinja{ codebox(
+    "tpl",
+    "tutorial/tutorial-series/part-3/templates/person.tpl",
+    "templates/person.tpl"
+) }}
 
 For now, the `person` template is still very empty and only shows the comments in the content area.
 The template code shown for comments is very generic and used in this form in many locations as it only sets the header of the comment list and the container `ul#personCommentList` element for the comments shown by `commentList` template.
@@ -141,9 +151,11 @@ The attribute `wysiwygSelector` should be the id of the comment list `personComm
 
 ### `page.xml`
 
-```xml
---8<-- "tutorial/tutorial-series/part-3/page.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-3/page.xml",
+    "page.xml"
+) }}
 
 The `page.xml` file has been extended for the new person page with identifier `com.woltlab.wcf.people.Person`.
 Compared to the pre-existing `com.woltlab.wcf.people.PersonList` page, there are four differences:
@@ -157,9 +169,11 @@ Compared to the pre-existing `com.woltlab.wcf.people.PersonList` page, there are
 
 ### `PersonPageHandler`
 
-```php
---8<-- "tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php",
+    "files/lib/system/page/handler/PersonPageHandler.class.php"
+) }}
 
 Like any page handler, the `PersonPageHandler` class has to implement the [IMenuPageHandler](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/page/handler/IMenuPageHandler.class.php) interface, which should be done by extending the [AbstractMenuPageHandler](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/page/handler/AbstractMenuPageHandler.class.php) class.
 As we want  administrators to link to specific people in menus, for example, we have to also implement the [ILookupPageHandler](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/page/handler/ILookupPageHandler.class.php) interface by extending the [AbstractLookupPageHandler](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/page/handler/AbstractLookupPageHandler.class.php) class.
index 4b8de6c29b91b7df95d47bb447bf312fbf472c8f..232742d2c3e5c7845697f04090b1ea6caa39e6c7 100644 (file)
@@ -58,9 +58,11 @@ To do so, we first have to register a new object type for this person list box c
 
 The `com.woltlab.wcf.boxController` object type definition requires the provided class to implement `wcf\system\box\IBoxController`:
 
-```php
---8<-- "tutorial/tutorial-series/part-4/files/lib/system/box/PersonListBoxController.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-4/files/lib/system/box/PersonListBoxController.class.php",
+    "files/lib/system/box/PersonListBoxController.class.php"
+) }}
 
 By extending `AbstractDatabaseObjectListBoxController`, we only have to provide minimal data ourself and rely mostly on the default implementation provided by `AbstractDatabaseObjectListBoxController`:
 
@@ -97,9 +99,11 @@ We will support filtering the people by their first and last name so that, for e
 
 The first step for condition support is to register a object type definition for the relevant conditions requiring the `IObjectListCondition` interface:
 
-```xml
---8<-- "tutorial/tutorial-series/part-4/objectTypeDefinition.xml"
-```
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-4/objectTypeDefinition.xml",
+    "objectTypeDefinition.xml"
+) }}
 
 Next, we register the specific conditions for filtering by the first and last name using this object type condition:
 
@@ -118,9 +122,11 @@ Next, we register the specific conditions for filtering by the first and last na
 
 `PersonFirstNameTextPropertyCondition` and `PersonLastNameTextPropertyCondition` only differ minimally so that we only focus on `PersonFirstNameTextPropertyCondition` here, which relies on the default implementation `AbstractObjectTextPropertyCondition` and only requires specifying different object properties:
 
-```php
---8<-- "tutorial/tutorial-series/part-4/files/lib/system/condition/person/PersonFirstNameTextPropertyCondition.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-4/files/lib/system/condition/person/PersonFirstNameTextPropertyCondition.class.php",
+    "files/lib/system/condition/person/PersonFirstNameTextPropertyCondition.class.php"
+) }}
 
 1. `$className` contains the class name of the relevant database object from which the class name of the database object list is derived and `$propertyName` is the name of the database object's property that contains the value used for filtering.
 1. By setting `$supportsMultipleValues` to `true`, multiple comma-separated values can be specified so that, for example, a box can also only list people with either of two specific first names.
index 62e1be42dd1ed3d2d6ce9f8fc3e74438de37e67d..67554cf90fb80dac11e87197ea0b6d2b150bbb51 100644 (file)
@@ -76,18 +76,22 @@ Before we focus on the main aspects of this part, we mention some minor aspects
 
 The PHP file with the database layout has been updated as follows:
 
-```php
---8<-- "tutorial/tutorial-series/part-5/files/acp/database/install_com.woltlab.wcf.people.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-5/files/acp/database/install_com.woltlab.wcf.people.php",
+    "files/acp/database/install_com.woltlab.wcf.people.php"
+) }}
 
 - The number of pieces of information per person is tracked via the new `informationCount` column.
 - The `wcf1_person_information` table has been added for the `PersonInformation` model.
   The meaning of the different columns is explained in the property documentation part of `PersonInformation`'s documentation (see below).
   The two foreign keys ensure that if a person is deleted, all of their information is also deleted, and that if a user is deleted, the `userID` column is set to `NULL`.
 
-```php
---8<-- "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformation.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformation.class.php",
+    "files/lib/data/person/information/PersonInformation.class.php"
+) }}
 
 `PersonInformation` provides two methods, `canDelete()` and `canEdit()`, to check whether the active user can delete or edit a specific piece of information.
 In both cases, it is checked if the current user has created the relevant piece of information to check the user-specific permissions or to fall back to the moderator-specific permissions.
@@ -100,18 +104,22 @@ To generate such an output, `HtmlOutputProcessor::process()` is used and here is
 
 While `PersonInformationEditor` is simply the default implementation and thus not explicitly shown here, `PersonInformationList::readObjects()` caches the relevant ids of the associated people and users who created the pieces of information using runtime caches:
 
-```php
---8<-- "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformationList.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformationList.class.php",
+    "files/lib/data/person/information/PersonInformationList.class.php"
+) }}
 
 
 ## Listing and Deleting Person Information
 
 The `person.tpl` template has been updated to include a block for listing the information at the beginning:
 
-```smarty
---8<-- "tutorial/tutorial-series/part-5/templates/person.tpl"
-```
+{jinja{ codebox(
+    "smarty",
+    "tutorial/tutorial-series/part-5/templates/person.tpl",
+    "templates/person.tpl"
+) }}
 
 To keep things simple here, we reuse the structure and CSS classes used for comments.
 Additionally, we always list all pieces of information.
@@ -133,9 +141,11 @@ To create new pieces of information or editing existing ones, we do not add new
 
 When clicking on the add button or on any of the edit buttons, a dialog opens with the relevant form:
 
-```TypeScript
---8<-- "tutorial/tutorial-series/part-5/ts/WoltLabSuite/Core/Controller/Person.ts"
-```
+{jinja{ codebox(
+    "typescript",
+    "tutorial/tutorial-series/part-5/ts/WoltLabSuite/Core/Controller/Person.ts",
+    "ts/WoltLabSuite/Core/Controller/Person.ts"
+) }}
 
 We use the [`WoltLabSuite/Core/Form/Builder/Dialog` module](https://github.com/WoltLab/WCF/blob/master/ts/WoltLabSuite/Core/Form/Builder/Dialog.ts), which takes care of the internal handling with regard to these dialogs.
 We only have to provide some data during for initializing these objects and call the `open()` function after a button has been clicked.
@@ -157,9 +167,11 @@ Explanation of the initialization arguments for `WoltLabSuite/Core/Form/Builder/
 
 Next, we focus on `PersonInformationAction`, which actually provides the contents of these dialogs and creates and edits the information:
 
-```php
---8<-- "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformationAction.class.php"
-```
+{jinja{ codebox(
+    "php",
+    "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformationAction.class.php",
+    "files/lib/data/person/information/PersonInformationAction.class.php"
+) }}
 
 When setting up the `WoltLabSuite/Core/Form/Builder/Dialog` object for adding new pieces of information, we specified `getAddDialog` and `submitAddDialog` as the names of the dialog getter and submit handler.
 In addition to these two methods, the matching validation methods `validateGetAddDialog()` and `validateGetAddDialog()` are also added.
@@ -215,6 +227,8 @@ As we store the name of the user who create a new piece of information and store
 
 Lastly, we present the updated `eventListener.xml` file with new entries for all of these event listeners:
 
-```xml
---8<-- "tutorial/tutorial-series/part-5/eventListener.xml"
-```
\ No newline at end of file
+{jinja{ codebox(
+    "xml",
+    "tutorial/tutorial-series/part-5/eventListener.xml",
+    "eventListener.xml"
+) }}
\ No newline at end of file
diff --git a/snippets/package/package.xml b/snippets/package/package.xml
new file mode 100644 (file)
index 0000000..992231d
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package name="com.example.package" 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/2019/package.xsd">
+       <packageinformation>
+               <packagename>Simple Package</packagename>
+               <packagedescription>A simple package to demonstrate the package system of WoltLab Suite Core</packagedescription>
+               <version>1.0.0</version>
+               <date>2016-12-18</date>
+       </packageinformation>
+
+       <authorinformation>
+               <author>YOUR NAME</author>
+               <authorurl>http://www.example.com</authorurl>
+       </authorinformation>
+
+       <requiredpackages>
+               <requiredpackage minversion="3.0.0">com.woltlab.wcf</requiredpackage>
+       </requiredpackages>
+
+       <excludedpackages>
+               <excludedpackage version="6.0.0 Alpha 1">com.woltlab.wcf</excludedpackage>
+       </excludedpackages>
+
+       <instructions type="install">
+               <instruction type="file" />
+               <instruction type="template">templates.tar</instruction>
+       </instructions>
+</package>
\ No newline at end of file
diff --git a/snippets/package/pip/aclOption.xml b/snippets/package/pip/aclOption.xml
new file mode 100644 (file)
index 0000000..bc2af5e
--- /dev/null
@@ -0,0 +1,33 @@
+<?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/2019/aclOption.xsd">
+       <import>
+               <categories>
+                       <category name="user.example">
+                               <objecttype>com.example.wcf.example</objecttype>
+                       </category>
+                       <category name="mod.example">
+                               <objecttype>com.example.wcf.example</objecttype>
+                       </category>
+               </categories>
+
+               <options>
+                       <option name="canAddExample">
+                               <categoryname>user.example</categoryname>
+                               <objecttype>com.example.wcf.example</objecttype>
+                       </option>
+                       <option name="canDeleteExample">
+                               <categoryname>mod.example</categoryname>
+                               <objecttype>com.example.wcf.example</objecttype>
+                       </option>
+               </options>
+       </import>
+
+       <delete>
+               <optioncategory name="old.example">
+                       <objecttype>com.example.wcf.example</objecttype>
+               </optioncategory>
+               <option name="canDoSomethingWithExample">
+                       <objecttype>com.example.wcf.example</objecttype>
+               </option>
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/acpMenu.xml b/snippets/package/pip/acpMenu.xml
new file mode 100644 (file)
index 0000000..d1e4461
--- /dev/null
@@ -0,0 +1,22 @@
+<?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/2019/acpMenu.xsd">
+       <import>
+               <acpmenuitem name="foo.acp.menu.link.example">
+                       <parent>wcf.acp.menu.link.application</parent>
+               </acpmenuitem>
+
+               <acpmenuitem name="foo.acp.menu.link.example.list">
+                       <controller>foo\acp\page\ExampleListPage</controller>
+                       <parent>foo.acp.menu.link.example</parent>
+                       <permissions>admin.foo.canManageExample</permissions>
+                       <showorder>1</showorder>
+               </acpmenuitem>
+
+               <acpmenuitem name="foo.acp.menu.link.example.add">
+                       <controller>foo\acp\form\ExampleAddForm</controller>
+                       <parent>foo.acp.menu.link.example.list</parent>
+                       <permissions>admin.foo.canManageExample</permissions>
+                       <icon>fa-plus</icon>
+               </acpmenuitem>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/acpSearchProvider.xml b/snippets/package/pip/acpSearchProvider.xml
new file mode 100644 (file)
index 0000000..4d24839
--- /dev/null
@@ -0,0 +1,9 @@
+<?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/2019/acpSearchProvider.xsd">
+       <import>
+               <acpsearchprovider name="com.woltlab.wcf.example">
+                       <classname>wcf\system\search\acp\ExampleACPSearchResultProvider</classname>
+                       <showorder>1</showorder>
+               </acpsearchprovider>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/bbcode.xml b/snippets/package/pip/bbcode.xml
new file mode 100644 (file)
index 0000000..6ba64ba
--- /dev/null
@@ -0,0 +1,22 @@
+<?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/2019/bbcode.xsd">
+       <import>
+               <bbcode name="foo">
+                       <classname>wcf\system\bbcode\FooBBCode</classname>
+                       <attributes>
+                               <attribute name="0">
+                                       <validationpattern>^\d+$</validationpattern>
+                                       <required>1</required>
+                               </attribute>
+                       </attributes>
+               </bbcode>
+
+               <bbcode name="example">
+                       <htmlopen>div</htmlopen>
+                       <htmlclose>div</htmlclose>
+                       <isBlockElement>1</isBlockElement>
+                       <wysiwygicon>fa-bath</wysiwygicon>
+                       <buttonlabel>wcf.editor.button.example</buttonlabel>
+               </bbcode>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/box.xml b/snippets/package/pip/box.xml
new file mode 100644 (file)
index 0000000..e21b1ab
--- /dev/null
@@ -0,0 +1,29 @@
+<?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/2019/box.xsd">
+       <import>
+               <box identifier="com.woltlab.wcf.RecentActivity">
+                       <name language="de">Letzte Aktivitäten</name>
+                       <name language="en">Recent Activities</name>
+                       <boxType>system</boxType>
+                       <objectType>com.woltlab.wcf.recentActivityList</objectType>
+                       <position>contentBottom</position>
+                       <showHeader>0</showHeader>
+                       <visibleEverywhere>0</visibleEverywhere>
+                       <visibilityExceptions>
+                               <page>com.woltlab.wcf.Dashboard</page>
+                       </visibilityExceptions>
+                       <limit>10</limit>
+
+                       <content language="de">
+                               <title>Letzte Aktivitäten</title>
+                       </content>
+                       <content language="en">
+                               <title>Recent Activities</title>
+                       </content>
+               </box>
+       </import>
+
+       <delete>
+               <box identifier="com.woltlab.wcf.RecentActivity" />
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/clipboardAction.xml b/snippets/package/pip/clipboardAction.xml
new file mode 100644 (file)
index 0000000..29b7253
--- /dev/null
@@ -0,0 +1,26 @@
+<?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/2019/clipboardAction.xsd">
+       <import>
+               <action name="delete">
+                       <actionclassname>wcf\system\clipboard\action\ExampleClipboardAction</actionclassname>
+                       <showorder>1</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ExampleListPage</page>
+                       </pages>
+               </action>
+               <action name="foo">
+                       <actionclassname>wcf\system\clipboard\action\ExampleClipboardAction</actionclassname>
+                       <showorder>2</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ExampleListPage</page>
+                       </pages>
+               </action>
+               <action name="bar">
+                       <actionclassname>wcf\system\clipboard\action\ExampleClipboardAction</actionclassname>
+                       <showorder>3</showorder>
+                       <pages>
+                               <page>wcf\acp\page\ExampleListPage</page>
+                       </pages>
+               </action>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/coreObject.xml b/snippets/package/pip/coreObject.xml
new file mode 100644 (file)
index 0000000..a77cffd
--- /dev/null
@@ -0,0 +1,8 @@
+<?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/2019/coreObject.xsd">
+       <import>
+               <coreobject>
+                       <objectname>wcf\system\example\ExampleHandler</objectname>
+               </coreobject>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/cronjob.xml b/snippets/package/pip/cronjob.xml
new file mode 100644 (file)
index 0000000..583b8a7
--- /dev/null
@@ -0,0 +1,17 @@
+<?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/2019/cronjob.xsd">
+       <import>
+               <cronjob name="com.example.package.example">
+                       <classname>wcf\system\cronjob\ExampleCronjob</classname>
+                       <description>Serves as an example</description>
+                       <description language="de">Stellt ein Beispiel dar</description>
+                       <startminute>0</startminute>
+                       <starthour>2</starthour>
+                       <startdom>*/2</startdom>
+                       <startmonth>*</startmonth>
+                       <startdow>*</startdow>
+                       <canbeedited>1</canbeedited>
+                       <canbedisabled>1</canbedisabled>
+               </cronjob>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/en.xml b/snippets/package/pip/en.xml
new file mode 100644 (file)
index 0000000..bb3c016
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<language 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/2019/language.xsd" languagecode="en">
+       <category name="wcf.example">
+               <item name="wcf.example.foo"><![CDATA[<strong>Look!</strong>]]></item>
+       </category>
+</language>
\ No newline at end of file
diff --git a/snippets/package/pip/eventListener.xml b/snippets/package/pip/eventListener.xml
new file mode 100644 (file)
index 0000000..50e1af5
--- /dev/null
@@ -0,0 +1,22 @@
+<?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/2019/eventListener.xsd">
+       <import>
+               <eventlistener name="inheritedAdminExample">
+                       <eventclassname>wcf\acp\form\UserAddForm</eventclassname>
+                       <eventname>assignVariables,readFormParameters,save,validate</eventname>
+                       <listenerclassname>wcf\system\event\listener\InheritedAdminExampleListener</listenerclassname>
+                       <inherit>1</inherit>
+                       <environment>admin</environment>
+               </eventlistener>
+
+               <eventlistener name="nonInheritedUserExample">
+                       <eventclassname>wcf\form\SettingsForm</eventclassname>
+                       <eventname>assignVariables</eventname>
+                       <listenerclassname>wcf\system\event\listener\NonInheritedUserExampleListener</listenerclassname>
+               </eventlistener>
+       </import>
+
+       <delete>
+               <eventlistener name="oldEventListenerName" />
+       </delete>
+</data>
diff --git a/snippets/package/pip/install.sql b/snippets/package/pip/install.sql
new file mode 100644 (file)
index 0000000..d74dae8
--- /dev/null
@@ -0,0 +1,10 @@
+CREATE TABLE wcf1_foo_bar (
+    fooID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+    packageID INT(10) NOT NULL,
+    bar VARCHAR(255) NOT NULL DEFAULT '',
+    foobar VARCHAR(50) NOT NULL DEFAULT '',
+
+    UNIQUE KEY baz (bar, foobar)
+);
+
+ALTER TABLE wcf1_foo_bar ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
\ No newline at end of file
diff --git a/snippets/package/pip/mediaProvider.xml b/snippets/package/pip/mediaProvider.xml
new file mode 100644 (file)
index 0000000..7c5c51c
--- /dev/null
@@ -0,0 +1,22 @@
+<?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/2019/mediaProvider.xsd">
+       <import>
+               <provider name="youtube">
+                       <title>YouTube</title>
+                       <regex><![CDATA[https?://(?:.+?\.)?youtu(?:\.be/|be\.com/(?:#/)?watch\?(?:.*?&)?v=)(?P<ID>[a-zA-Z0-9_-]+)(?:(?:\?|&)t=(?P<start>[0-9hms]+)$)?]]></regex>
+                       <!-- advanced PHP callback -->
+                       <className><![CDATA[wcf\system\bbcode\media\provider\YouTubeBBCodeMediaProvider]]></className>
+               </provider>
+
+               <provider name="youtube-playlist">
+                       <title>YouTube Playlist</title>
+                       <regex><![CDATA[https?://(?:.+?\.)?youtu(?:\.be/|be\.com/)playlist\?(?:.*?&)?list=(?P<ID>[a-zA-Z0-9_-]+)]]></regex>
+                       <!-- uses a simple HTML replacement -->
+                       <html><![CDATA[<div class="videoContainer"><iframe src="https://www.youtube.com/embed/videoseries?list={$ID}" allowfullscreen></iframe></div>]]></html>
+               </provider>
+       </import>
+
+       <delete>
+               <provider name="example" />
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/menu.xml b/snippets/package/pip/menu.xml
new file mode 100644 (file)
index 0000000..cdac279
--- /dev/null
@@ -0,0 +1,20 @@
+<?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/2019/menu.xsd">
+       <import>
+               <menu identifier="com.woltlab.wcf.FooterLinks">
+                       <title language="de">Footer-Links</title>
+                       <title language="en">Footer Links</title>
+
+                       <box>
+                               <position>footer</position>
+                               <cssClassName>boxMenuLinkGroup</cssClassName>
+                               <showHeader>0</showHeader>
+                               <visibleEverywhere>1</visibleEverywhere>
+                       </box>
+               </menu>
+       </import>
+
+       <delete>
+               <menu identifier="com.woltlab.wcf.FooterLinks" />
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/menuItem.xml b/snippets/package/pip/menuItem.xml
new file mode 100644 (file)
index 0000000..d6f838f
--- /dev/null
@@ -0,0 +1,15 @@
+<?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/2019/menuItem.xsd">
+       <import>
+               <item identifier="com.woltlab.wcf.Dashboard">
+                       <menu>com.woltlab.wcf.MainMenu</menu>
+                       <title language="de">Dashboard</title>
+                       <title language="en">Dashboard</title>
+                       <page>com.woltlab.wcf.Dashboard</page>
+               </item>
+       </import>
+
+       <delete>
+               <item identifier="com.woltlab.wcf.FooterLinks" />
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/objectType.xml b/snippets/package/pip/objectType.xml
new file mode 100644 (file)
index 0000000..6df16ab
--- /dev/null
@@ -0,0 +1,11 @@
+<?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/2019/objectType.xsd">
+       <import>
+               <type>
+                       <name>com.woltlab.wcf.example</name>
+                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
+                       <classname>wcf\system\worker\ExampleRebuildWorker</classname>
+                       <nicevalue>130</nicevalue>
+               </type>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/objectTypeDefinition.xml b/snippets/package/pip/objectTypeDefinition.xml
new file mode 100644 (file)
index 0000000..a1190f7
--- /dev/null
@@ -0,0 +1,9 @@
+<?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/2019/objectTypeDefinition.xsd">
+       <import>
+               <definition>
+                       <name>com.woltlab.wcf.example</name>
+                       <interfacename>wcf\system\example\IExampleObjectType</interfacename>
+               </definition>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/option.xml b/snippets/package/pip/option.xml
new file mode 100644 (file)
index 0000000..75d9ad6
--- /dev/null
@@ -0,0 +1,40 @@
+<?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/2019/option.xsd">
+       <import>
+               <categories>
+                       <category name="example" />
+                       <category name="example.sub">
+                               <parent>example</parent>
+                               <options>module_example</options>
+                       </category>
+               </categories>
+
+               <options>
+                       <option name="module_example">
+                               <categoryname>module.community</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
+
+                       <option name="example_integer">
+                               <categoryname>example.sub</categoryname>
+                               <optiontype>integer</optiontype>
+                               <defaultvalue>10</defaultvalue>
+                               <minvalue>5</minvalue>
+                               <maxvalue>40</maxvalue>
+                       </option>
+
+                       <option name="example_select">
+                               <categoryname>example.sub</categoryname>
+                               <optiontype>select</optiontype>
+                               <defaultvalue>DESC</defaultvalue>
+                               <selectoptions>ASC:wcf.global.sortOrder.ascending
+                                       DESC:wcf.global.sortOrder.descending</selectoptions>
+                       </option>
+               </options>
+       </import>
+
+       <delete>
+               <option name="outdated_example" />
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/packageInstallationPlugin.xml b/snippets/package/pip/packageInstallationPlugin.xml
new file mode 100644 (file)
index 0000000..68dd192
--- /dev/null
@@ -0,0 +1,9 @@
+<?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/2019/packageInstallationPlugin.xsd">
+       <import>
+               <pip name="custom">wcf\system\package\plugin\CustomPackageInstallationPlugin</pip>
+       </import>
+       <delete>
+               <pip name="outdated" />
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/page.xml b/snippets/package/pip/page.xml
new file mode 100644 (file)
index 0000000..dda3799
--- /dev/null
@@ -0,0 +1,24 @@
+<?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/2019/page.xsd">
+       <import>
+               <page identifier="com.woltlab.wcf.MembersList">
+                       <pageType>system</pageType>
+                       <controller>wcf\page\MembersListPage</controller>
+                       <name language="de">Mitglieder</name>
+                       <name language="en">Members</name>
+                       <permissions>user.profile.canViewMembersList</permissions>
+                       <options>module_members_list</options>
+
+                       <content language="en">
+                               <title>Members</title>
+                       </content>
+                       <content language="de">
+                               <title>Mitglieder</title>
+                       </content>
+               </page>
+       </import>
+
+       <delete>
+               <page identifier="com.woltlab.wcf.MembersList" />
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/smiley.xml b/snippets/package/pip/smiley.xml
new file mode 100644 (file)
index 0000000..c49c6ae
--- /dev/null
@@ -0,0 +1,12 @@
+<?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/2019/smiley.xsd">
+       <import>
+               <smiley name=":example:">
+                       <title>example</title>
+                       <path>images/smilies/example.png</path>
+                       <path2x>images/smilies/example@2x.png</path2x>
+                       <aliases><![CDATA[:alias:
+:more_aliases:]]></aliases>
+               </smiley>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/templateListener.xml b/snippets/package/pip/templateListener.xml
new file mode 100644 (file)
index 0000000..e964e52
--- /dev/null
@@ -0,0 +1,15 @@
+<?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/2019/templatelistener.xsd">
+       <import>
+               <templatelistener name="example">
+                       <environment>user</environment>
+                       <templatename>headIncludeJavaScript</templatename>
+                       <eventname>javascriptInclude</eventname>
+                       <templatecode><![CDATA[{include file='__myCustomJavaScript'}]]></templatecode>
+               </templatelistener>
+       </import>
+
+       <delete>
+               <templatelistener name="oldTemplateListenerName" />
+       </delete>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/userMenu.xml b/snippets/package/pip/userMenu.xml
new file mode 100644 (file)
index 0000000..41bd6d5
--- /dev/null
@@ -0,0 +1,22 @@
+<?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/2019/userMenu.xsd">
+       <import>
+               <usermenuitem name="wcf.user.menu.foo">
+                       <iconclassname>fa-home</iconclassname>
+               </usermenuitem>
+
+               <usermenuitem name="wcf.user.menu.foo.bar">
+                       <controller>wcf\page\FooBarListPage</controller>
+                       <parent>wcf.user.menu.foo</parent>
+                       <permissions>user.foo.canBar</permissions>
+                       <classname>wcf\system\menu\user\FooBarMenuItemProvider</classname>
+               </usermenuitem>
+
+               <usermenuitem name="wcf.user.menu.foo.baz">
+                       <controller>wcf\page\FooBazListPage</controller>
+                       <parent>wcf.user.menu.foo</parent>
+                       <permissions>user.foo.canBaz</permissions>
+                       <options>module_foo_bar</options>
+               </usermenuitem>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/userNotificationEvent.xml b/snippets/package/pip/userNotificationEvent.xml
new file mode 100644 (file)
index 0000000..bbd68a2
--- /dev/null
@@ -0,0 +1,12 @@
+<?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/2019/userNotificationEvent.xsd">
+       <import>
+               <event>
+                       <name>like</name>
+                       <objecttype>com.woltlab.example.comment.like.notification</objecttype>
+                       <classname>wcf\system\user\notification\event\ExampleCommentLikeUserNotificationEvent</classname>
+                       <preset>1</preset>
+                       <options>module_like</options>
+               </event>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/package/pip/userProfileMenu.xml b/snippets/package/pip/userProfileMenu.xml
new file mode 100644 (file)
index 0000000..4d220e3
--- /dev/null
@@ -0,0 +1,10 @@
+<?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/2019/userProfileMenu.xsd">
+       <import>
+               <userprofilemenuitem name="example">
+                       <classname>wcf\system\menu\user\profile\content\ExampleProfileMenuContent</classname>
+                       <showorder>3</showorder>
+                       <options>module_example</options>
+               </userprofilemenuitem>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/php/api/caches/ExampleCacheBuilder.class.php b/snippets/php/api/caches/ExampleCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..835f88d
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+namespace wcf\system\cache\builder;
+
+class ExampleCacheBuilder extends AbstractCacheBuilder {
+    // 3600 = 1hr
+    protected $maxLifetime = 3600;
+
+    public function rebuild(array $parameters) {
+        $data = [];
+
+        // fetch and process your data and assign it to `$data`
+
+        return $data;
+    }
+}
\ No newline at end of file
diff --git a/snippets/php/api/caches/UserRuntimeCache.class.php b/snippets/php/api/caches/UserRuntimeCache.class.php
new file mode 100644 (file)
index 0000000..951de56
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\system\cache\runtime;
+use wcf\data\user\User;
+use wcf\data\user\UserList;
+
+/**
+ * Runtime cache implementation for users.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cache\Runtime
+ * @since      3.0
+ *
+ * @method     User[]          getCachedObjects()
+ * @method     User            getObject($objectID)
+ * @method     User[]          getObjects(array $objectIDs)
+ */
+class UserRuntimeCache extends AbstractRuntimeCache {
+    /**
+     * @inheritDoc
+     */
+    protected $listClassName = UserList::class;
+}
\ No newline at end of file
diff --git a/snippets/php/api/cronjobs/LastActivityCronjob.class.php b/snippets/php/api/cronjobs/LastActivityCronjob.class.php
new file mode 100644 (file)
index 0000000..0032dec
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\system\WCF;
+
+/**
+ * Updates the last activity timestamp in the user table.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Cronjob
+ */
+class LastActivityCronjob extends AbstractCronjob {
+    /**
+     * @inheritDoc
+     */
+    public function execute(Cronjob $cronjob) {
+        parent::execute($cronjob);
+
+        $sql = "UPDATE  wcf".WCF_N."_user user_table,
+                        wcf".WCF_N."_session session
+                SET     user_table.lastActivityTime = session.lastActivityTime
+                WHERE   user_table.userID = session.userID
+                    AND session.userID <> 0";
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute();
+    }
+}
diff --git a/snippets/php/api/events/ExampleAddFormListener.class.php b/snippets/php/api/events/ExampleAddFormListener.class.php
new file mode 100644 (file)
index 0000000..ce4f73c
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+namespace wcf\system\event\listener;
+use wcf\form\ExampleAddForm;
+use wcf\form\ExampleEditForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+class ExampleAddFormListener implements IParameterizedEventListener {
+    protected $var = 0;
+
+    public function execute($eventObj, $className, $eventName, array &$parameters) {
+        $this->$eventName($eventObj);
+    }
+
+    protected function assignVariables() {
+        WCF::getTPL()->assign('var', $this->var);
+    }
+
+    protected function readData(ExampleEditForm $eventObj) {
+        if (empty($_POST)) {
+            $this->var = $eventObj->example->var;
+        }
+    }
+
+    protected function readFormParameters() {
+        if (isset($_POST['var'])) $this->var = intval($_POST['var']);
+    }
+
+    protected function save(ExampleAddForm $eventObj) {
+        $eventObj->additionalFields = array_merge($eventObj->additionalFields, ['var' => $this->var]);
+    }
+
+    protected function saved() {
+        $this->var = 0;
+    }
+
+    protected function validate() {
+        if ($this->var < 0) {
+            throw new UserInputException('var', 'isNegative');
+        }
+    }
+}
diff --git a/snippets/php/api/events/ExampleComponent.class.php b/snippets/php/api/events/ExampleComponent.class.php
new file mode 100644 (file)
index 0000000..8ceb61c
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace wcf\system\example;
+use wcf\system\event\EventHandler;
+
+class ExampleComponent {
+    public $var = 1;
+
+    public function getVar() {
+        EventHandler::getInstance()->fireAction($this, 'getVar');
+
+        return $this->var;
+    }
+}
\ No newline at end of file
diff --git a/snippets/php/api/events/ExampleEventListener.class.php b/snippets/php/api/events/ExampleEventListener.class.php
new file mode 100644 (file)
index 0000000..0b4d549
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+namespace wcf\system\event\listener;
+
+class ExampleEventListener implements IParameterizedEventListener {
+    public function execute($eventObj, $className, $eventName, array &$parameters) {
+        $eventObj->var = 2;
+    }
+}
diff --git a/snippets/php/api/events/ExampleParser1.class.php b/snippets/php/api/events/ExampleParser1.class.php
new file mode 100644 (file)
index 0000000..cc50e84
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+namespace wcf\system\example;
+use wcf\system\event\EventHandler;
+
+class ExampleParser {
+    public function parse($text) {
+        // [some parsing done by default]
+
+        $parameters = ['text' => $text];
+        EventHandler::getInstance()->fireAction($this, 'parse', $parameters);
+
+        return $parameters['text'];
+    }
+}
\ No newline at end of file
diff --git a/snippets/php/api/events/ExampleParser2.class.php b/snippets/php/api/events/ExampleParser2.class.php
new file mode 100644 (file)
index 0000000..ece0b69
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+namespace wcf\system\example;
+use wcf\system\event\EventHandler;
+
+class ExampleParser {
+    public function parse($text) {
+        $parameters = ['text' => $text];
+        EventHandler::getInstance()->fireAction($this, 'beforeParsing', $parameters);
+        $text = $parameters['text'];
+
+        // [some parsing done by default]
+
+        $parameters = ['text' => $text];
+        EventHandler::getInstance()->fireAction($this, 'afterParsing', $parameters);
+
+        return $parameters['text'];
+    }
+}
diff --git a/snippets/php/api/events/ExampleParserEventListener.class.php b/snippets/php/api/events/ExampleParserEventListener.class.php
new file mode 100644 (file)
index 0000000..98e945d
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+namespace wcf\system\event\listener;
+
+class ExampleParserEventListener implements IParameterizedEventListener {
+    public function execute($eventObj, $className, $eventName, array &$parameters) {
+        $text = $parameters['text'];
+
+        // [some additional parsing which changes $text]
+
+        $parameters['text'] = $text;
+    }
+}
\ No newline at end of file
diff --git a/snippets/php/api/events/eventListener.xml b/snippets/php/api/events/eventListener.xml
new file mode 100644 (file)
index 0000000..c05e12b
--- /dev/null
@@ -0,0 +1,23 @@
+<?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/2019/eventListener.xsd">
+       <import>
+               <eventlistener name="exampleAddInherited">
+                       <eventclassname>wcf\form\ExampleAddForm</eventclassname>
+                       <eventname>assignVariables,readFormParameters,save,validate</eventname>
+                       <listenerclassname>wcf\system\event\listener\ExampleAddFormListener</listenerclassname>
+                       <inherit>1</inherit>
+               </eventlistener>
+
+               <eventlistener name="exampleAdd">
+                       <eventclassname>wcf\form\ExampleAddForm</eventclassname>
+                       <eventname>saved</eventname>
+                       <listenerclassname>wcf\system\event\listener\ExampleAddFormListener</listenerclassname>
+               </eventlistener>
+
+               <eventlistener name="exampleEdit">
+                       <eventclassname>wcf\form\ExampleEditForm</eventclassname>
+                       <eventname>readData</eventname>
+                       <listenerclassname>wcf\system\event\listener\ExampleAddFormListener</listenerclassname>
+               </eventlistener>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/php/api/sitemaps/UserSitemapObject.class.php b/snippets/php/api/sitemaps/UserSitemapObject.class.php
new file mode 100644 (file)
index 0000000..226974e
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+namespace wcf\system\sitemap\object;
+use wcf\data\user\User;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * User sitemap implementation.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Sitemap\Object
+ * @since      3.1
+ */
+class UserSitemapObject extends AbstractSitemapObjectObjectType {
+    /**
+     * @inheritDoc
+     */
+    public function getObjectClass() {
+        return User::class;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getLastModifiedColumn() {
+        return 'lastActivityTime';
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function canView(DatabaseObject $object) {
+        return WCF::getSession()->getPermission('user.profile.canViewUserProfile');
+    }
+}
diff --git a/snippets/php/api/user_notifications/FooUserNotificationEvent.class.php b/snippets/php/api/user_notifications/FooUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..c8df908
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+namespace example\system\user\notification\event;
+use example\system\cache\runtime\BazRuntimeCache;
+use example\system\user\notification\object\FooUserNotificationObject;
+use wcf\system\email\Email;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\notification\event\AbstractSharedUserNotificationEvent;
+
+/**
+ * Notification event for foos.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    WoltLab License <http://www.woltlab.com/license-agreement.html>
+ * @package    WoltLabSuite\Example\System\User\Notification\Event
+ *
+ * @method     FooUserNotificationObject       getUserNotificationObject()
+ */
+class FooUserNotificationEvent extends AbstractSharedUserNotificationEvent {
+    /**
+     * @inheritDoc
+     */
+    protected $stackable = true;
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+    /**
+     * @inheritDoc
+     */
+    public function checkAccess() {
+        $this->getUserNotificationObject()->setBaz(BazRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->bazID));
+
+        if (!$this->getUserNotificationObject()->isAccessible()) {
+            // do some cleanup, if necessary
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+    /**
+     * @inheritDoc
+     */
+    public function getEmailMessage($notificationType = 'instant') {
+        $this->getUserNotificationObject()->setBaz(BazRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->bazID));
+
+        $messageID = '<com.woltlab.example.baz/'.$this->getUserNotificationObject()->bazID.'@'.Email::getHost().'>';
+
+        return [
+            'application' => 'example',
+            'in-reply-to' => [$messageID],
+            'message-id' => 'com.woltlab.example.foo/'.$this->getUserNotificationObject()->fooID,
+            'references' => [$messageID],
+            'template' => 'email_notification_foo'
+        ];
+    }
+
+    /**
+     * @inheritDoc
+     * @since  5.0
+     */
+    public function getEmailTitle() {
+        $this->getUserNotificationObject()->setBaz(BazRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->bazID));
+
+        return $this->getLanguage()->getDynamicVariable('example.foo.notification.mail.title', [
+            'userNotificationObject' => $this->getUserNotificationObject()
+        ]);
+    }
+
+    /** @noinspection PhpMissingParentCallCommonInspection */
+    /**
+     * @inheritDoc
+     */
+    public function getEventHash() {
+        return sha1($this->eventID . '-' . $this->getUserNotificationObject()->bazID);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getLink() {
+        return LinkHandler::getInstance()->getLink('Foo', [
+            'application' => 'example',
+            'object' => $this->getUserNotificationObject()->getDecoratedObject()
+        ]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getMessage() {
+        $authors = $this->getAuthors();
+        $count = count($authors);
+
+        if ($count > 1) {
+            if (isset($authors[0])) {
+                unset($authors[0]);
+            }
+            $count = count($authors);
+
+            return $this->getLanguage()->getDynamicVariable('example.foo.notification.message.stacked', [
+                'author' => $this->author,
+                'authors' => array_values($authors),
+                'count' => $count,
+                'guestTimesTriggered' => $this->notification->guestTimesTriggered,
+                'message' => $this->getUserNotificationObject(),
+                'others' => $count - 1
+            ]);
+        }
+
+        return $this->getLanguage()->getDynamicVariable('example.foo.notification.message', [
+            'author' => $this->author,
+            'userNotificationObject' => $this->getUserNotificationObject()
+        ]);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle() {
+        $count = count($this->getAuthors());
+        if ($count > 1) {
+            return $this->getLanguage()->getDynamicVariable('example.foo.notification.title.stacked', [
+                'count' => $count,
+                'timesTriggered' => $this->notification->timesTriggered
+            ]);
+        }
+
+        return $this->getLanguage()->get('example.foo.notification.title');
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected function prepare() {
+        BazRuntimeCache::getInstance()->cacheObjectID($this->getUserNotificationObject()->bazID);
+    }
+}
\ No newline at end of file
diff --git a/snippets/php/api/user_notifications/FooUserNotificationObject.class.php b/snippets/php/api/user_notifications/FooUserNotificationObject.class.php
new file mode 100644 (file)
index 0000000..80bcbea
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+namespace example\system\user\notification\object;
+use example\data\foo\Foo;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * Represents a foo as a notification object.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    WoltLab License <http://www.woltlab.com/license-agreement.html>
+ * @package    WoltLabSuite\Example\System\User\Notification\Object
+ *
+ * @method     Foo     getDecoratedObject()
+ * @mixin      Foo
+ */
+class FooUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
+    /**
+     * @inheritDoc
+     */
+    protected static $baseClass = Foo::class;
+
+    /**
+     * @inheritDoc
+     */
+    public function getTitle() {
+        return $this->getDecoratedObject()->getTitle();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getURL() {
+        return $this->getDecoratedObject()->getLink();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getAuthorID() {
+        return $this->getDecoratedObject()->userID;
+    }
+}
\ No newline at end of file
diff --git a/snippets/php/api/user_notifications/FooUserNotificationObjectType.class.php b/snippets/php/api/user_notifications/FooUserNotificationObjectType.class.php
new file mode 100644 (file)
index 0000000..bfb4fb1
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace example\system\user\notification\object\type;
+use example\data\foo\Foo;
+use example\data\foo\FooList;
+use example\system\user\notification\object\FooUserNotificationObject;
+use wcf\system\user\notification\object\type\AbstractUserNotificationObjectType;
+
+/**
+ * Represents a foo as a notification object type.
+ *
+ * @author     Matthias Schmidt
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    WoltLab License <http://www.woltlab.com/license-agreement.html>
+ * @package    WoltLabSuite\Example\System\User\Notification\Object\Type
+ */
+class FooUserNotificationObjectType extends AbstractUserNotificationObjectType {
+    /**
+     * @inheritDoc
+     */
+    protected static $decoratorClassName = FooUserNotificationObject::class;
+
+    /**
+     * @inheritDoc
+     */
+    protected static $objectClassName = Foo::class;
+
+    /**
+     * @inheritDoc
+     */
+    protected static $objectListClassName = FooList::class;
+}
\ No newline at end of file
diff --git a/snippets/php/api/user_notifications/objectType.xml b/snippets/php/api/user_notifications/objectType.xml
new file mode 100644 (file)
index 0000000..95c159b
--- /dev/null
@@ -0,0 +1,11 @@
+<?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/2019/objectType.xsd">
+       <import>
+               <type>
+                       <name>com.woltlab.example.foo</name>
+                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+                       <classname>example\system\user\notification\object\type\FooUserNotificationObjectType</classname>
+                       <category>com.woltlab.example</category>
+               </type>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/php/api/user_notifications/userNotificationEvent.xml b/snippets/php/api/user_notifications/userNotificationEvent.xml
new file mode 100644 (file)
index 0000000..94df577
--- /dev/null
@@ -0,0 +1,11 @@
+<?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/2019/userNotificationEvent.xsd">
+       <import>
+               <event>
+                       <name>bar</name>
+                       <objecttype>com.woltlab.example.foo</objecttype>
+                       <classname>example\system\user\notification\event\FooUserNotificationEvent</classname>
+                       <preset>1</preset>
+               </event>
+       </import>
+</data>
\ No newline at end of file
diff --git a/snippets/php/code-style/Box.class.php b/snippets/php/code-style/Box.class.php
new file mode 100644 (file)
index 0000000..500c821
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+namespace wcf\data\box;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+class Box extends DatabaseObject {
+    /**
+     * Returns the box with the given identifier.
+     *
+     * @param  string          $identifier
+     * @return Box|null
+     */
+    public static function getBoxByIdentifier($identifier) {
+        $sql = "SELECT *
+                       FROM    wcf".WCF_N."_box
+                       WHERE   identifier = ?";
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute([$identifier]);
+
+        return $statement->fetchObject(self::class);
+    }
+}
\ No newline at end of file
diff --git a/snippets/php/database-objects/Example.class.php b/snippets/php/database-objects/Example.class.php
new file mode 100644 (file)
index 0000000..ab2adb9
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+namespace wcf\data\example;
+use wcf\data\DatabaseObject;
+
+class Example extends DatabaseObject {}
diff --git a/snippets/php/database-objects/ExampleAction.class.php b/snippets/php/database-objects/ExampleAction.class.php
new file mode 100644 (file)
index 0000000..32c8583
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+namespace wcf\data\example;
+use wcf\data\AbstractDatabaseObjectAction;
+
+class ExampleAction extends AbstractDatabaseObjectAction {
+    public $className = ExampleEditor::class;
+}
\ No newline at end of file
diff --git a/snippets/php/database-objects/ExampleEditor.class.php b/snippets/php/database-objects/ExampleEditor.class.php
new file mode 100644 (file)
index 0000000..40ac78f
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+namespace wcf\data\example;
+use wcf\data\DatabaseObjectEditor;
+
+class ExampleEditor extends DatabaseObjectEditor {
+    protected static $baseClass = Example::class;
+}
\ No newline at end of file
diff --git a/snippets/php/database-objects/ExampleList.class.php b/snippets/php/database-objects/ExampleList.class.php
new file mode 100644 (file)
index 0000000..1da0b37
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+namespace wcf\data\example;
+use wcf\data\DatabaseObjectList;
+
+class ExampleList extends DatabaseObjectList {
+    public $className = Example::class;
+}
\ No newline at end of file
diff --git a/snippets/php/database-objects/ViewableExample.class.php b/snippets/php/database-objects/ViewableExample.class.php
new file mode 100644 (file)
index 0000000..ff653d5
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+namespace wcf\data\example;
+use wcf\data\DatabaseObjectDecorator;
+
+class ViewableExample extends DatabaseObjectDecorator {
+    protected static $baseClass = Example::class;
+
+    public function getOutput() {
+        $output = '';
+
+        // [determine output]
+
+        return $output;
+    }
+}
diff --git a/snippets/php/database-objects/ViewableExampleList.class.php b/snippets/php/database-objects/ViewableExampleList.class.php
new file mode 100644 (file)
index 0000000..447319b
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+namespace wcf\data\example;
+
+class ViewableExampleList extends ExampleList {
+    public $decoratorClassName = ViewableExample::class;
+}
\ No newline at end of file
diff --git a/snippets/php/gdpr/MyUserExportGdprActionListener.class.php b/snippets/php/gdpr/MyUserExportGdprActionListener.class.php
new file mode 100644 (file)
index 0000000..c18ed64
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\system\event\listener;
+use wcf\acp\action\UserExportGdprAction;
+use wcf\data\user\UserProfile;
+
+class MyUserExportGdprActionListener implements IParameterizedEventListener {
+    public function execute(/** @var UserExportGdprAction $eventObj */$eventObj, $className, $eventName, array &$parameters) {
+        /** @var UserProfile $user */
+        $user = $eventObj->user;
+
+        $eventObj->data['my.fancy.plugin'] = [
+            'superPersonalData' => "This text is super personal and should be included in the output",
+            'weirdIpAddresses' => $eventObj->exportIpAddresses('app'.WCF_N.'_non_standard_column_names_for_ip_addresses', 'ipAddressColumnName', 'timeColumnName', 'userIDColumnName')
+        ];
+        $eventObj->exportUserProperties[] = 'shouldAlwaysExportThisField';
+        $eventObj->exportUserPropertiesIfNotEmpty[] = 'myFancyField';
+        $eventObj->exportUserOptionSettings[] = 'thisSettingIsAlwaysExported';
+        $eventObj->exportUserOptionSettingsIfNotEmpty[] = 'someSettingContainingPersonalData';
+        $eventObj->ipAddresses['my.fancy.plugin'] = ['wcf'.WCF_N.'_my_fancy_table', 'wcf'.WCF_N.'_i_also_store_ipaddresses_here'];
+        $eventObj->skipUserOptions[] = 'thisLooksLikePersonalDataButItIsNot';
+        $eventObj->skipUserOptions[] = 'thisIsAlsoNotPersonalDataPleaseIgnoreIt';
+    }
+}
\ No newline at end of file
diff --git a/snippets/typescript/.eslintignore b/snippets/typescript/.eslintignore
new file mode 100644 (file)
index 0000000..cf44e14
--- /dev/null
@@ -0,0 +1 @@
+**/*.js
\ No newline at end of file
diff --git a/snippets/typescript/.eslintrc.js b/snippets/typescript/.eslintrc.js
new file mode 100644 (file)
index 0000000..e778643
--- /dev/null
@@ -0,0 +1,37 @@
+module.exports = {
+    root: true,
+    parser: "@typescript-eslint/parser",
+    parserOptions: {
+        tsconfigRootDir: __dirname,
+        project: ["./tsconfig.json"]
+    },
+    plugins: ["@typescript-eslint"],
+    extends: [
+        "eslint:recommended",
+        "plugin:@typescript-eslint/recommended",
+        "plugin:@typescript-eslint/recommended-requiring-type-checking",
+        "prettier",
+        "prettier/@typescript-eslint"
+    ],
+    rules: {
+        "@typescript-eslint/ban-types": [
+            "error", {
+                types: {
+                    "object": false
+                },
+                extendDefaults: true
+            }
+        ],
+        "@typescript-eslint/no-explicit-any": 0,
+        "@typescript-eslint/no-non-null-assertion": 0,
+        "@typescript-eslint/no-unsafe-assignment": 0,
+        "@typescript-eslint/no-unsafe-call": 0,
+        "@typescript-eslint/no-unsafe-member-access": 0,
+        "@typescript-eslint/no-unsafe-return": 0,
+        "@typescript-eslint/no-unused-vars": [
+            "error", {
+                "argsIgnorePattern": "^_"
+            }
+        ]
+    }
+};
\ No newline at end of file
diff --git a/snippets/typescript/.gitattributes b/snippets/typescript/.gitattributes
new file mode 100644 (file)
index 0000000..40f5573
--- /dev/null
@@ -0,0 +1 @@
+files/js/**/*.js linguist-generated
\ No newline at end of file
diff --git a/snippets/typescript/.prettierrc b/snippets/typescript/.prettierrc
new file mode 100644 (file)
index 0000000..83785c0
--- /dev/null
@@ -0,0 +1,2 @@
+trailingComma: all
+printWidth: 120
\ No newline at end of file
diff --git a/snippets/typescript/Example.js b/snippets/typescript/Example.js
new file mode 100644 (file)
index 0000000..f204679
--- /dev/null
@@ -0,0 +1,10 @@
+define(["require", "exports", "tslib", "WoltLabSuite/Core/Language"], function (require, exports, tslib_1, Language) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.run = void 0;
+    Language = tslib_1.__importStar(Language);
+    function run() {
+        alert(Language.get("wcf.foo.bar"));
+    }
+    exports.run = run;
+});
\ No newline at end of file
diff --git a/snippets/typescript/Example.ts b/snippets/typescript/Example.ts
new file mode 100644 (file)
index 0000000..4fbfaba
--- /dev/null
@@ -0,0 +1,5 @@
+import * as Language from "WoltLabSuite/Core/Language";
+
+export function run() {
+  alert(Language.get("wcf.foo.bar"));
+}
\ No newline at end of file
diff --git a/snippets/typescript/package.json b/snippets/typescript/package.json
new file mode 100644 (file)
index 0000000..3b6210e
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "devDependencies": {
+    "@typescript-eslint/eslint-plugin": "^4.6.1",
+    "@typescript-eslint/parser": "^4.6.1",
+    "eslint": "^7.12.1",
+    "eslint-config-prettier": "^6.15.0",
+    "prettier": "^2.1.2",
+    "tslib": "^2.0.3",
+    "typescript": "^4.1.3"
+  },
+  "dependencies": {
+    "@woltlab/wcf": "https://github.com/WoltLab/WCF.git#master"
+  }
+}
\ No newline at end of file
diff --git a/snippets/typescript/tsconfig.json b/snippets/typescript/tsconfig.json
new file mode 100644 (file)
index 0000000..3d55b75
--- /dev/null
@@ -0,0 +1,28 @@
+{
+  "include": [
+    "node_modules/@woltlab/wcf/global.d.ts",
+    "ts/**/*"
+  ],
+  "compilerOptions": {
+    "target": "es2017",
+    "module": "amd",
+    "rootDir": "ts/",
+    "outDir": "files/js/",
+    "lib": [
+      "dom",
+      "es2017"
+    ],
+    "strictNullChecks": true,
+    "moduleResolution": "node",
+    "esModuleInterop": true,
+    "noImplicitThis": true,
+    "strictBindCallApply": true,
+    "baseUrl": ".",
+    "paths": {
+      "*": [
+        "node_modules/@woltlab/wcf/ts/*"
+      ]
+    },
+    "importHelpers": true
+  }
+}
\ No newline at end of file