481702eb206a62d12ebc8a90326b5a8bbf4e156e
[GitHub/WoltLab/woltlab.github.io.git] / docs / tutorial / series / part_3.md
1 # Part 3: Person Page and Comments
2
3 In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details.
4 To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite’s reusable comment functionality.
5
6
7 ## Package Functionality
8
9 In addition to the existing functions from [part 1](part_1.md), the package will provide the following possibilities/functions after this part of the tutorial:
10
11 - Details page for each person linked in the front end person list
12 - Comment on people on their respective page (can be disabled per person)
13 - User online location for person details page with name and link to person details page
14 - Create menu items linking to specific person details pages
15
16
17 ## Used Components
18
19 In addition to the components used in [part 1](part_1.md), we will use the [objectType package installation plugin](../../package/pip/object-type.md), use the [comment API](../../php/api/comments.md), create a [runtime cache](../../php/api/caches_runtime-caches.md), and create a page handler.
20
21
22 ## Package Structure
23
24 The complete package will have the following file structure (including the files from [part 1](part_1.md)):
25
26 ```
27 ├── acpMenu.xml
28 ├── acptemplates
29 │ ├── personAdd.tpl
30 │ └── personList.tpl
31 ├── files
32 │ ├── acp
33 │ │ └── database
34 │ │ └── install_com.woltlab.wcf.people.php
35 │ └── lib
36 │ ├── acp
37 │ │ ├── form
38 │ │ │ ├── PersonAddForm.class.php
39 │ │ │ └── PersonEditForm.class.php
40 │ │ └── page
41 │ │ └── PersonListPage.class.php
42 │ ├── data
43 │ │ └── person
44 │ │ ├── Person.class.php
45 │ │ ├── PersonAction.class.php
46 │ │ ├── PersonEditor.class.php
47 │ │ └── PersonList.class.php
48 │ ├── page
49 │ │ ├── PersonListPage.class.php
50 │ │ └── PersonPage.class.php
51 │ └── system
52 │ ├── cache
53 │ │ └── runtime
54 │ │ └── PersonRuntimeCache.class.php
55 │ ├── comment
56 │ │ └── manager
57 │ │ └── PersonCommentManager.class.php
58 │ └── page
59 │ └── handler
60 │ └── PersonPageHandler.class.php
61 ├── language
62 │ ├── de.xml
63 │ └── en.xml
64 ├── menuItem.xml
65 ├── objectType.xml
66 ├── package.xml
67 ├── page.xml
68 ├── templates
69 │ ├── person.tpl
70 │ └── personList.tpl
71 └── userGroupOption.xml
72 ```
73
74 !!! warning "We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new `Person::getLink()` method and new language items have been added. For all changes, please refer to the [source code on GitHub]({jinja{ config.repo_url }}tree/{jinja{ config.edit_uri.split("/")[1] }}/snippets/tutorial/tutorial-series/part-3)."
75
76
77 ## Runtime Cache
78
79 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:
80
81 {jinja{ codebox(
82 "php",
83 "tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php",
84 "files/lib/system/cache/runtime/PersonRuntimeCache.class.php"
85 ) }}
86
87
88 ## Comments
89
90 To allow users to comment on people, we need to tell the system that people support comments.
91 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):
92
93 {jinja{ codebox(
94 "xml",
95 "tutorial/tutorial-series/part-3/objectType.xml",
96 "objectType.xml"
97 ) }}
98
99 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):
100
101 {jinja{ codebox(
102 "php",
103 "tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php",
104 "files/lib/system/comment/manager/PersonCommentManager.class.php"
105 ) }}
106
107 - First, the system is told the names of the permissions via the `$permission*` properties.
108 More information about comment permissions can be found [here](../../php/api/comments.md#user-group-options).
109 - The `getLink()` method returns the link to the person with the passed comment id.
110 As in `isAccessible()`, `PersonRuntimeCache` is used to potentially save database queries.
111 - The `isAccessible()` method checks if the active user can access the relevant person.
112 As we do not have any special restrictions for accessing people, we only need to check if the person exists.
113 - The `getTitle()` method returns the title used for comments and responses, which is just a generic language item in this case.
114 - The `updateCounter()` updates the comments’ counter of the person.
115 We have added a new `comments` database table column to the `wcf1_person` database table in order to keep track on the number of comments.
116
117 Additionally, we have added a new `enableComments` database table column to the `wcf1_person` database table whose value can be set when creating or editing a person in the ACP.
118 With this option, comments on individual people can be disabled.
119
120 !!! info "Liking comments is already built-in and only requires some extra code in the `PersonPage` class for showing the likes of pre-loaded comments."
121
122
123 ## Person Page
124
125 ### `PersonPage`
126
127 {jinja{ codebox(
128 "php",
129 "tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php",
130 "files/lib/page/PersonPage.class.php"
131 ) }}
132
133 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()`.
134 The rest of the code only handles fetching the list of comments on the requested person.
135 In `readData()`, this list is fetched using `CommentHandler::getCommentList()` if comments are enabled for the person.
136 The `assignVariables()` method assigns some additional template variables like `$commentCanAdd`, which is `1` if the active person can add comments and is `0` otherwise, `$lastCommentTime`, which contains the UNIX timestamp of the last comment, and `$likeData`, which contains data related to the likes for the disabled comments.
137
138 ### `person.tpl`
139
140 {jinja{ codebox(
141 "tpl",
142 "tutorial/tutorial-series/part-3/templates/person.tpl",
143 "templates/person.tpl"
144 ) }}
145
146 For now, the `person` template is still very empty and only shows the comments in the content area.
147 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.
148 The `ul#personCommentList` elements has five additional `data-` attributes required by the JavaScript API for comments for loading more comments or creating new ones.
149 The `commentListAddComment` template adds the WYSIWYG support.
150 The attribute `wysiwygSelector` should be the id of the comment list `personCommentList` with an additional `AddComment` suffix.
151
152 ### `page.xml`
153
154 {jinja{ codebox(
155 "xml",
156 "tutorial/tutorial-series/part-3/page.xml",
157 "page.xml"
158 ) }}
159
160 The `page.xml` file has been extended for the new person page with identifier `com.woltlab.wcf.people.Person`.
161 Compared to the pre-existing `com.woltlab.wcf.people.PersonList` page, there are four differences:
162
163 1. It has a `<handler>` element with a class name as value.
164 This aspect will be discussed in more detail in the next section.
165 1. There are no `<content>` elements because, both, the title and the content of the page are dynamically generated in the template.
166 1. The `<requireObjectID>` tells the system that this page requires an object id to properly work, in this case a valid person id.
167 1. This page has a `<parent>` page, the person list page.
168 In general, the details page for any type of object that is listed on a different page has the list page as its parent.
169
170 ### `PersonPageHandler`
171
172 {jinja{ codebox(
173 "php",
174 "tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php",
175 "files/lib/system/page/handler/PersonPageHandler.class.php"
176 ) }}
177
178 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.
179 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.
180
181 For the `ILookupPageHandler` interface, we need to implement three methods:
182
183 1. `getLink($objectID)` returns the link to the person page with the given id.
184 In this case, we simply delegate this method call to the `Person` object returned by `PersonRuntimeCache::getObject()`.
185 1. `isValid($objectID)` returns `true` if the person with the given id exists, otherwise `false`.
186 Here, we use `PersonRuntimeCache::getObject()` again and check if the return value is `null`, which is the case for non-existing people.
187 1. `lookup($searchString)` is used when setting up an internal link and when searching for the linked person.
188 This method simply searches the first and last name of the people and returns an array with the person data.
189 While the `link`, the `objectID`, and the `title` element are self-explanatory, the `image` element can either contain an HTML `<img>` tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with `fa-`).
190
191 Additionally, the class also implements [IOnlineLocationPageHandler](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/page/handler/IOnlineLocationPageHandler.class.php) which is used to determine the online location of users.
192 To ensure upwards-compatibility if the `IOnlineLocationPageHandler` interface changes, the [TOnlineLocationPageHandler](https://github.com/WoltLab/WCF/blob/master/wcfsetup/install/files/lib/system/page/handler/TOnlineLocationPageHandler.class.php) trait is used.
193 The `IOnlineLocationPageHandler` interface requires two methods to be implemented:
194
195 1. `getOnlineLocation(Page $page, UserOnline $user)` returns the textual description of the online location.
196 The language item for the user online locations should use the pattern `wcf.page.onlineLocation.{page identifier}`.
197 1. `prepareOnlineLocation(Page $page, UserOnline $user)` is called for each user online before the `getOnlineLocation()` calls.
198 In this case, calling `prepareOnlineLocation()` first enables us to add all relevant person ids to the person runtime cache so that for all `getOnlineLocation()` calls combined, only one database query is necessary to fetch all person objects.
199
200 ---
201
202 This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person.
203
204 The complete source code of this part can be found on [GitHub]({jinja{ config.repo_url }}tree/{jinja{ config.edit_uri.split("/")[1] }}/snippets/tutorial/tutorial-series/part-3).
205