… to also support inline contents.
A full `package.json` that includes WoltLab Suite, TypeScript, eslint and Prettier could look like the following.
{jinja{ codebox(
- "json",
- "typescript/package.json",
- "package.json"
+ title="package.json",
+ language="json",
+ filepath="typescript/package.json"
) }}
After installing the types using npm, you will also need to configure `tsconfig.json` to take the types into account.
A complete `tsconfig.json` file that matches the configuration of WoltLab Suite could look like the following.
{jinja{ codebox(
- "json",
- "typescript/tsconfig.json",
- "tsconfig.json"
+ title="tsconfig.json",
+ language="json",
+ filepath="typescript/tsconfig.json"
) }}
After this initial set-up, you would place your TypeScript source files into the `ts/` folder of your project.
It is recommended to re-use this configuration as is.
{jinja{ codebox(
- "yml",
- "typescript/.prettierrc",
- ".prettierrc"
+ title=".prettierrc",
+ language="yml",
+ filepath="typescript/.prettierrc"
) }}
{jinja{ codebox(
- "javascript",
- "typescript/.eslintrc.js",
- ".eslintrc.js"
+ title=".eslintrc.js",
+ language="javascript",
+ filepath="typescript/.eslintrc.js"
) }}
{jinja{ codebox(
- "gitignore",
- "typescript/.eslintignore",
- ".eslintignore"
+ title=".eslintignore",
+ language="gitignore",
+ filepath="typescript/.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.
{jinja{ codebox(
- "gitattributes",
- "typescript/.gitattributes",
- ".gitattributes"
+ title=".gitattributes",
+ language="gitattributes",
+ filepath="typescript/.gitattributes"
) }}
## Writing a simple module
The public API of the module can also be exported using the standard ECMAScript module export syntax.
{jinja{ codebox(
- "typescript",
- "typescript/Example.ts",
- "ts/Example.ts"
+ title="ts/Example.ts",
+ language="typescript",
+ filepath="typescript/Example.ts"
) }}
This simple example module will compile to plain JavaScript that is compatible with the AMD loader that is used by WoltLab Suite.
{jinja{ codebox(
- "javascript",
- "typescript/Example.js",
- "files/js/Example.js"
+ title="files/js/Example.js",
+ language="javascript",
+ filepath="typescript/Example.js"
) }}
Within templates it can be consumed as follows.
## Example
{jinja{ codebox(
- "xml",
- "package/package.xml",
- "package.xml"
+ title="package.xml",
+ language="xml",
+ filepath="package/package.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/aclOption.xml",
- "aclOption.xml"
+ title="aclOption.xml",
+ language="xml",
+ filepath="package/pip/aclOption.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/acpMenu.xml",
- "acpMenu.xml"
+ title="acpMenu.xml",
+ language="xml",
+ filepath="package/pip/acpMenu.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/acpSearchProvider.xml",
- "acpSearchProvider.xml"
+ title="acpSearchProvider.xml",
+ language="xml",
+ filepath="package/pip/acpSearchProvider.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/bbcode.xml",
- "bbcode.xml"
+ title="bbcode.xml",
+ language="xml",
+ filepath="package/pip/bbcode.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/box.xml",
- "box.xml"
+ title="box.xml",
+ language="xml",
+ filepath="package/pip/box.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/clipboardAction.xml",
- "clipboardAction.xml"
+ title="clipboardAction.xml",
+ language="xml",
+ filepath="package/pip/clipboardAction.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/coreObject.xml",
- "coreObject.xml"
+ title="coreObject.xml",
+ language="xml",
+ filepath="package/pip/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).
## Example
{jinja{ codebox(
- "xml",
- "package/pip/cronjob.xml",
- "cronjob.xml"
+ title="cronjob.xml",
+ language="xml",
+ filepath="package/pip/cronjob.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/eventListener.xml",
- "eventListener.xml"
+ title="eventListener.xml",
+ language="xml",
+ filepath="package/pip/eventListener.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/en.xml",
- "language/en.xml"
+ title="language/en.xml",
+ language="xml",
+ filepath="package/pip/en.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/mediaProvider.xml",
- "mediaProvider.xml"
+ title="mediaProvider.xml",
+ language="xml",
+ filepath="package/pip/mediaProvider.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/menuItem.xml",
- "menuItem.xml"
+ title="menuItem.xml",
+ language="xml",
+ filepath="package/pip/menuItem.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/menu.xml",
- "menu.xml"
+ title="menu.xml",
+ language="xml",
+ filepath="package/pip/menu.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/objectTypeDefinition.xml",
- "objectTypeDefinition.xml"
+ title="objectTypeDefinition.xml",
+ language="xml",
+ filepath="package/pip/objectTypeDefinition.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/objectType.xml",
- "objectType.xml"
+ title="objectType.xml",
+ language="xml",
+ filepath="package/pip/objectType.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/option.xml",
- "option.xml"
+ title="option.xml",
+ language="xml",
+ filepath="package/pip/option.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/page.xml",
- "page.xml"
+ title="page.xml",
+ language="xml",
+ filepath="package/pip/page.xml"
) }}
\ No newline at end of file
## Example
{jinja{ codebox(
- "xml",
- "package/pip/packageInstallationPlugin.xml",
- "packageInstallationPlugin.xml"
+ title="packageInstallationPlugin.xml",
+ language="xml",
+ filepath="package/pip/packageInstallationPlugin.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/smiley.xml",
- "smiley.xml"
+ title="smiley.xml",
+ language="xml",
+ filepath="package/pip/smiley.xml"
) }}
Example content:
{jinja{ codebox(
- "sql",
- "package/pip/install.sql",
- "install.sql"
+ title="install.sql",
+ language="sql",
+ filepath="package/pip/install.sql"
) }}
\ No newline at end of file
## Example
{jinja{ codebox(
- "xml",
- "package/pip/templateListener.xml",
- "templateListener.xml"
+ title="templateListener.xml",
+ language="xml",
+ filepath="package/pip/templateListener.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/userMenu.xml",
- "userMenu.xml"
+ title="userMenu.xml",
+ language="xml",
+ filepath="package/pip/userMenu.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/userNotificationEvent.xml",
- "userNotificationEvent.xml"
+ title="userNotificationEvent.xml",
+ language="xml",
+ filepath="package/pip/userNotificationEvent.xml"
) }}
## Example
{jinja{ codebox(
- "xml",
- "package/pip/userProfileMenu.xml",
- "userProfileMenu.xml"
+ title="userProfileMenu.xml",
+ language="xml",
+ filepath="package/pip/userProfileMenu.xml"
) }}
## Example
{jinja{ codebox(
- "php",
- "php/api/cronjobs/LastActivityCronjob.class.php",
- "files/lib/system/cronjob/LastActivityCronjob.class.php"
+ title="files/lib/system/cronjob/LastActivityCronjob.class.php",
+ language="php",
+ filepath="php/api/cronjobs/LastActivityCronjob.class.php"
) }}
### Example code
{jinja{ codebox(
- "php",
- "php/gdpr/MyUserExportGdprActionListener.class.php",
- "files/lib/system/event/listener/MyUserExportGdprActionListener.class.php"
+ title="files/lib/system/event/listener/MyUserExportGdprActionListener.class.php",
+ language="php",
+ filepath="php/gdpr/MyUserExportGdprActionListener.class.php"
) }}
### `$data`
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:
{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"
+ title="files/acp/database/install_com.woltlab.wcf.people.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/files/acp/database/install_com.woltlab.wcf.people.php"
) }}
### Database Object
In our PHP code, each person will be represented by an object of the following class:
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php",
- "files/lib/data/person/Person.class.php"
+ title="files/lib/data/person/Person.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php"
) }}
The important thing here is that `Person` extends `DatabaseObject`.
#### `PersonAction`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php",
- "files/lib/data/person/PersonAction.class.php"
+ title="files/lib/data/person/PersonAction.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php"
) }}
This implementation of `AbstractDatabaseObjectAction` is very basic and only sets the `$permissionsDelete` and `$requireACP` properties.
#### `PersonEditor`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php",
- "files/lib/data/person/PersonEditor.class.php"
+ title="files/lib/data/person/PersonEditor.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php"
) }}
This implementation of `DatabaseObjectEditor` fulfills the minimum requirement for a database object editor:
#### `PersonList`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php",
- "files/lib/data/person/PersonList.class.php"
+ title="files/lib/data/person/PersonList.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/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.
1. a fourth level menu item for the form to add new people.
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-1/acpMenu.xml",
- "acpMenu.xml"
+ title="acpMenu.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-1/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.
#### `PersonListPage`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php",
- "files/lib/data/person/PersonListPage.class.php"
+ title="files/lib/data/person/PersonListPage.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php"
) }}
As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal:
#### `personList.tpl`
{jinja{ codebox(
- "smarty",
- "tutorial/tutorial-series/part-1/acptemplates/personList.tpl",
- "acptemplates/personList.tpl"
+ title="acptemplates/personList.tpl",
+ language="smarty",
+ filepath="tutorial/tutorial-series/part-1/acptemplates/personList.tpl"
) }}
We will go piece by piece through the template code:
#### `PersonAddForm`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php",
- "files/lib/acp/form/PersonAddForm.class.php"
+ title="files/lib/acp/form/PersonAddForm.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php"
) }}
The properties here consist of three types:
#### `personAdd.tpl`
{jinja{ codebox(
- "smarty",
- "tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl",
- "acptemplates/personAdd.tpl"
+ title="acptemplates/personAdd.tpl",
+ language="smarty",
+ filepath="tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl"
) }}
We will now only concentrate on the new parts compared to `personList.tpl`:
#### `PersonEditForm`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php",
- "files/lib/acp/form/PersonEditForm.class.php"
+ title="files/lib/acp/form/PersonEditForm.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/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.
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):
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-1/page.xml",
- "page.xml"
+ title="page.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-1/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).
Next, we register the menu item using the [menuItem package installation plugin](../../package/pip/menu-item.md):
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-1/menuItem.xml",
- "menuItem.xml"
+ title="menuItem.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-1/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.
#### `PersonListPage`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php",
- "files/lib/page/PersonListPage.class.php"
+ title="files/lib/page/PersonListPage.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php"
) }}
This class is almost identical to the ACP version.
#### `personList.tpl`
{jinja{ codebox(
- "smarty",
- "tutorial/tutorial-series/part-1/templates/personList.tpl",
- "templates/personList.tpl"
+ title="templates/personList.tpl",
+ language="smarty",
+ filepath="tutorial/tutorial-series/part-1/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.
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):
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-1/userGroupOption.xml",
- "userGroupOption.xml"
+ title="userGroupOption.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-1/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).
For more information about this kind of file, please refer to [the `package.xml` page](../../package/package-xml.md).
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-1/package.xml",
- "package.xml"
+ title="package.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-1/package.xml"
) }}
As this is a package for WoltLab Suite Core 3, we need to require it using `<requiredpackage>`.
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):
{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"
+ title="files/acp/database/install_com.woltlab.wcf.people.birthday.php",
+ language="sql",
+ filepath="tutorial/tutorial-series/part-2/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`.
To set the birthday of a person, we only have to add another form field with an event listener:
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php",
- "files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php"
+ title="files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php"
) }}
registered via
The language item `wcf.person.birthday` used in the label is the only new one for this package:
{jinja{ codebox(
- "sql",
- "tutorial/tutorial-series/part-2/language/de.xml",
- "language/de.xml"
+ title="language/de.xml",
+ language="sql",
+ filepath="tutorial/tutorial-series/part-2/language/de.xml"
) }}
{jinja{ codebox(
- "sql",
- "tutorial/tutorial-series/part-2/language/en.xml",
- "language/en.xml"
+ title="language/en.xml",
+ language="sql",
+ filepath="tutorial/tutorial-series/part-2/language/en.xml"
) }}
The first part is a very simple class:
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php",
- "files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php"
+ title="files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-2/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."
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:
{jinja{ codebox(
- "smarty",
- "tutorial/tutorial-series/part-2/templates/__personListBirthdaySortField.tpl",
- "templates/__personListBirthdaySortField.tpl"
+ title="templates/__personListBirthdaySortField.tpl",
+ language="smarty",
+ filepath="tutorial/tutorial-series/part-2/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."
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:
{jinja{ codebox(
- "smarty",
- "tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl",
- "templates/__personListBirthday.tpl"
+ title="templates/__personListBirthday.tpl",
+ language="smarty",
+ filepath="tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl"
) }}
The following code shows the `templateListener.xml` file used to install all mentioned template listeners:
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-2/templateListener.xml",
- "templateListener.xml"
+ title="templateListener.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-2/templateListener.xml"
) }}
In cases where a template is used, we simply use the `include` syntax to load the template.
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.
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-2/eventListener.xml",
- "eventListener.xml"
+ title="eventListener.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-2/eventListener.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>`):
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-2/package.xml",
- "package.xml"
+ title="package.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-2/package.xml"
) }}
---
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:
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php",
- "files/lib/system/cache/runtime/PersonRuntimeCache.class.php"
+ title="files/lib/system/cache/runtime/PersonRuntimeCache.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php"
) }}
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):
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-3/objectType.xml",
- "objectType.xml"
+ title="objectType.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-3/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):
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php",
- "files/lib/system/comment/manager/PersonCommentManager.class.php"
+ title="files/lib/system/comment/manager/PersonCommentManager.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php"
) }}
- First, the system is told the names of the permissions via the `$permission*` properties.
### `PersonPage`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php",
- "files/lib/page/PersonPage.class.php"
+ title="files/lib/page/PersonPage.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-3/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()`.
### `person.tpl`
{jinja{ codebox(
- "tpl",
- "tutorial/tutorial-series/part-3/templates/person.tpl",
- "templates/person.tpl"
+ title="templates/person.tpl",
+ language="tpl",
+ filepath="tutorial/tutorial-series/part-3/templates/person.tpl"
) }}
For now, the `person` template is still very empty and only shows the comments in the content area.
### `page.xml`
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-3/page.xml",
- "page.xml"
+ title="page.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-3/page.xml"
) }}
The `page.xml` file has been extended for the new person page with identifier `com.woltlab.wcf.people.Person`.
### `PersonPageHandler`
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php",
- "files/lib/system/page/handler/PersonPageHandler.class.php"
+ title="files/lib/system/page/handler/PersonPageHandler.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-3/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.
The `com.woltlab.wcf.boxController` object type definition requires the provided class to implement `wcf\system\box\IBoxController`:
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-4/files/lib/system/box/PersonListBoxController.class.php",
- "files/lib/system/box/PersonListBoxController.class.php"
+ title="files/lib/system/box/PersonListBoxController.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-4/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`:
The first step for condition support is to register a object type definition for the relevant conditions requiring the `IObjectListCondition` interface:
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-4/objectTypeDefinition.xml",
- "objectTypeDefinition.xml"
+ title="objectTypeDefinition.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-4/objectTypeDefinition.xml"
) }}
Next, we register the specific conditions for filtering by the first and last name using this object type condition:
`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:
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-4/files/lib/system/condition/person/PersonFirstNameTextPropertyCondition.class.php",
- "files/lib/system/condition/person/PersonFirstNameTextPropertyCondition.class.php"
+ title="files/lib/system/condition/person/PersonFirstNameTextPropertyCondition.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-4/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.
The PHP file with the database layout has been updated as follows:
{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"
+ title="files/acp/database/install_com.woltlab.wcf.people.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-5/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 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`.
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformation.class.php",
- "files/lib/data/person/information/PersonInformation.class.php"
+ title="files/lib/data/person/information/PersonInformation.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-5/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.
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:
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformationList.class.php",
- "files/lib/data/person/information/PersonInformationList.class.php"
+ title="files/lib/data/person/information/PersonInformationList.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformationList.class.php"
) }}
The `person.tpl` template has been updated to include a block for listing the information at the beginning:
{jinja{ codebox(
- "smarty",
- "tutorial/tutorial-series/part-5/templates/person.tpl",
- "templates/person.tpl"
+ title="templates/person.tpl",
+ language="smarty",
+ filepath="tutorial/tutorial-series/part-5/templates/person.tpl"
) }}
To keep things simple here, we reuse the structure and CSS classes used for comments.
When clicking on the add button or on any of the edit buttons, a dialog opens with the relevant form:
{jinja{ codebox(
- "typescript",
- "tutorial/tutorial-series/part-5/ts/WoltLabSuite/Core/Controller/Person.ts",
- "ts/WoltLabSuite/Core/Controller/Person.ts"
+ title="ts/WoltLabSuite/Core/Controller/Person.ts",
+ language="typescript",
+ filepath="tutorial/tutorial-series/part-5/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.
Next, we focus on `PersonInformationAction`, which actually provides the contents of these dialogs and creates and edits the information:
{jinja{ codebox(
- "php",
- "tutorial/tutorial-series/part-5/files/lib/data/person/information/PersonInformationAction.class.php",
- "files/lib/data/person/information/PersonInformationAction.class.php"
+ title="files/lib/data/person/information/PersonInformationAction.class.php",
+ language="php",
+ filepath="tutorial/tutorial-series/part-5/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.
Lastly, we present the updated `eventListener.xml` file with new entries for all of these event listeners:
{jinja{ codebox(
- "xml",
- "tutorial/tutorial-series/part-5/eventListener.xml",
- "eventListener.xml"
+ title="eventListener.xml",
+ language="xml",
+ filepath="tutorial/tutorial-series/part-5/eventListener.xml"
) }}
\ No newline at end of file
def define_env(env):
@env.macro
- def codebox(language, filepath, title = ""):
- if title is not "":
- return f"""
+ def codebox(title = None, language = "", filepath = None, contents = ""):
+ if title is not None:
+ if filepath is not None:
+ return f"""
<div class="titledCodeBox">
<div class="codeBoxTitle"><code>{title}</code></div>
```{language}
--8<-- "{filepath}"
```
</div>
+"""
+ else:
+ return f"""
+<div class="titledCodeBox">
+ <div class="codeBoxTitle"><code>{title}</code></div>
+```{language}
+{contents}
+```
+</div>
"""
else:
- return f"""
+ if filepath is not None:
+ return f"""
```{language}
--8<-- "{filepath}"
```
+"""
+ else:
+ return f"""
+```{language}
+{contents}
+```
"""