Update `codebox` macro
[GitHub/WoltLab/woltlab.github.io.git] / docs / tutorial / series / part_1.md
CommitLineData
8659eb86 1# Tutorial Series Part 1: Base Structure
eebb318c
MS
2
3In the first part of this tutorial series, we will lay out what the basic version of package should be able to do and how to implement these functions.
4
5
6## Package Functionality
7
8The package should provide the following possibilities/functions:
9
10- Sortable list of all people in the ACP
11- Ability to add, edit and delete people in the ACP
12- Restrict the ability to add, edit and delete people (in short: manage people) in the ACP
13- Sortable list of all people in the front end
14
15
16## Used Components
17
18We will use the following package installation plugins:
19
21609ed2
MS
20- [acpTemplate package installation plugin](../../package/pip/acp-template.md),
21- [acpMenu package installation plugin](../../package/pip/acp-menu.md),
5f7eed00 22- [database package installation plugin](../../package/pip/database.md),
21609ed2
MS
23- [file package installation plugin](../../package/pip/file.md),
24- [language package installation plugin](../../package/pip/language.md),
25- [menuItem package installation plugin](../../package/pip/menu-item.md),
26- [page package installation plugin](../../package/pip/page.md),
21609ed2
MS
27- [template package installation plugin](../../package/pip/template.md),
28- [userGroupOption package installation plugin](../../package/pip/user-group-option.md),
eebb318c 29
21609ed2 30use [database objects](../../php/database-objects.md), create [pages](../../php/pages.md) and use [templates](../../view/templates.md).
eebb318c
MS
31
32
33## Package Structure
34
35The package will have the following file structure:
36
37```
38├── acpMenu.xml
39├── acptemplates
60ed94fa
MS
40│ ├── personAdd.tpl
41│ └── personList.tpl
eebb318c 42├── files
5f7eed00
MS
43│ ├── acp
44│ │ └── database
45│ │ └── install_com.woltlab.wcf.people.php
60ed94fa
MS
46│ └── lib
47│ ├── acp
48│ │ ├── form
49│ │ │ ├── PersonAddForm.class.php
50│ │ │ └── PersonEditForm.class.php
51│ │ └── page
52│ │ └── PersonListPage.class.php
53│ ├── data
54│ │ └── person
60ed94fa 55│ │ ├── Person.class.php
5f7eed00 56│ │ ├── PersonAction.class.php
60ed94fa
MS
57│ │ ├── PersonEditor.class.php
58│ │ └── PersonList.class.php
59│ └── page
60│ └── PersonListPage.class.php
eebb318c 61├── language
60ed94fa
MS
62│ ├── de.xml
63│ └── en.xml
eebb318c
MS
64├── menuItem.xml
65├── package.xml
66├── page.xml
67├── templates
60ed94fa 68│ └── personList.tpl
eebb318c
MS
69└── userGroupOption.xml
70```
71
72
73## Person Modeling
74
75### Database Table
76
77As the first step, we have to model the people we want to manage with this package.
78As this is only an introductory tutorial, we will keep things simple and only consider the first and last name of a person.
79Thus, the database table we will store the people in only contains three columns:
80
811. `personID` is the unique numeric identifier of each person created,
821. `firstName` contains the first name of the person,
831. `lastName` contains the last name of the person.
84
5f7eed00 85The first file for our package is the `install_com.woltlab.wcf.people.php` file used to create such a database table during package installation:
eebb318c 86
9a3f5fa3 87{jinja{ codebox(
f778fce2
MS
88 title="files/acp/database/install_com.woltlab.wcf.people.php",
89 language="php",
90 filepath="tutorial/tutorial-series/part-1/files/acp/database/install_com.woltlab.wcf.people.php"
9a3f5fa3 91) }}
eebb318c
MS
92
93### Database Object
94
95#### `Person`
96
97In our PHP code, each person will be represented by an object of the following class:
98
9a3f5fa3 99{jinja{ codebox(
f778fce2
MS
100 title="files/lib/data/person/Person.class.php",
101 language="php",
102 filepath="tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php"
9a3f5fa3 103) }}
eebb318c
MS
104
105The important thing here is that `Person` extends `DatabaseObject`.
332f1908 106Additionally, 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.
eebb318c
MS
107
108For every database object, you need to implement three additional classes:
109an action class, an editor class and a list class.
110
111#### `PersonAction`
112
9a3f5fa3 113{jinja{ codebox(
f778fce2
MS
114 title="files/lib/data/person/PersonAction.class.php",
115 language="php",
116 filepath="tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php"
9a3f5fa3 117) }}
eebb318c
MS
118
119This implementation of `AbstractDatabaseObjectAction` is very basic and only sets the `$permissionsDelete` and `$requireACP` properties.
120This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX.
121`$permissionsDelete` has to be set to the permission needed in order to delete a person.
21609ed2 122We will later use the [userGroupOption package installation plugin](../../package/pip/user-group-option.md) to create the `admin.content.canManagePeople` permission.
eebb318c
MS
123`$requireACP` restricts deletion of people to the ACP.
124
125#### `PersonEditor`
126
9a3f5fa3 127{jinja{ codebox(
f778fce2
MS
128 title="files/lib/data/person/PersonEditor.class.php",
129 language="php",
130 filepath="tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php"
9a3f5fa3 131) }}
eebb318c
MS
132
133This implementation of `DatabaseObjectEditor` fulfills the minimum requirement for a database object editor:
134setting the static `$baseClass` property to the database object class name.
135
136#### `PersonList`
137
9a3f5fa3 138{jinja{ codebox(
f778fce2
MS
139 title="files/lib/data/person/PersonList.class.php",
140 language="php",
141 filepath="tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php"
9a3f5fa3 142) }}
eebb318c
MS
143
144Due 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.
145
146
147## ACP
148
149Next, we will take care of the controllers and views for the ACP.
150In total, we need three each:
151
1521. page to list people,
1531. form to add people, and
1541. form to edit people.
155
156Before we create the controllers and views, let us first create the menu items for the pages in the ACP menu.
157
158### ACP Menu
159
160We need to create three menu items:
161
1621. a “parent” menu item on the second level of the ACP menu item tree,
1631. a third level menu item for the people list page, and
1641. a fourth level menu item for the form to add new people.
165
9a3f5fa3 166{jinja{ codebox(
f778fce2
MS
167 title="acpMenu.xml",
168 language="xml",
169 filepath="tutorial/tutorial-series/part-1/acpMenu.xml"
9a3f5fa3 170) }}
eebb318c
MS
171
172We 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.
173The 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.
174
175### People List
176
177To list the people in the ACP, we need a `PersonListPage` class and a `personList` template.
178
179#### `PersonListPage`
180
9a3f5fa3 181{jinja{ codebox(
f778fce2
MS
182 title="files/lib/data/person/PersonListPage.class.php",
183 language="php",
184 filepath="tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php"
9a3f5fa3 185) }}
eebb318c
MS
186
187As WoltLab Suite Core already provides a powerful default implementation of a sortable page, our work here is minimal:
188
1891. We need to set the active ACP menu item via the `$activeMenuItem`.
1901. `$neededPermissions` contains a list of permissions of which the user needs to have at least one in order to see the person list.
191 We use the same permission for both the menu item and the page.
1921. The database object list class whose name is provided via `$objectListClassName` and that handles fetching the people from database is the `PersonList` class, which we have already created.
1931. To validate the sort field passed with the request, we set `$validSortFields` to the available database table columns.
194
195#### `personList.tpl`
196
9a3f5fa3 197{jinja{ codebox(
f778fce2
MS
198 title="acptemplates/personList.tpl",
199 language="smarty",
200 filepath="tutorial/tutorial-series/part-1/acptemplates/personList.tpl"
9a3f5fa3 201) }}
eebb318c
MS
202
203We will go piece by piece through the template code:
204
2051. We include the `header` template and set the page title `wcf.acp.person.list`.
206 You have to include this template for every page!
2071. We set the content header and additional provide a button to create a new person in the content header navigation.
2081. As not all people are listed on the same page if many people have been created, we need a pagination for which we use the `pages` template plugin.
209 The `{hascontent}{content}{/content}{/hascontent}` construct ensures the `.paginationTop` element is only shown if the `pages` template plugin has a return value, thus if a pagination is necessary.
2101. Now comes the main part of the page, the list of the people, which will only be displayed if any people exist.
211 Otherwise, an info box is displayed using the generic `wcf.global.noItems` language item.
8fdd7126 212 The `$objects` template variable is automatically assigned by `wcf\page\MultipleLinkPage` and contains the `PersonList` object used to read the people from database.
eebb318c
MS
213 The table itself consists of a `thead` and a `tbody` element and is extendable with more columns using the template events `columnHeads` and `columns`.
214 In general, every table should provide these events.
215 The default structure of a table is used here so that the first column of the content rows contains icons to edit and to delete the row (and provides another standard event `rowButtons`) and that the second column contains the ID of the person.
216 The table can be sorted by clicking on the head of each column.
217 The used variables `$sortField` and `$sortOrder` are automatically assigned to the template by `SortablePage`.
2181. The `.contentFooter` element is only shown if people exist as it basically repeats the `.contentHeaderNavigation` and `.paginationTop` element.
5f7eed00
MS
2191. The delete button for each person shown in the `.columnIcon` element relies on the global [`WoltLabSuite/Core/Ui/Object/Action`](../../migration/wsc53/javascript.md#wcfactiondelete-and-wcfactiontoggle) module which only requires the `jsObjectActionContainer` CSS class in combination with the `data-object-action-class-name` attribute for the `table` element, the `jsObjectActionObject` CSS class for each person's `tr` element in combination with the `data-object-id` attribute, and lastly the delete button itself, which is created with the [`objectAction` template plugin](../../view/template-plugins.md#view/template-plugins/#54-objectaction).
2201. The [`.jsReloadPageWhenEmpty` CSS class](../../migration/wsc53/javascript.md#wcftableemptytablehandler) on the `tbody` element ensures that once all persons on the page have been deleted, the page is reloaded.
eebb318c
MS
2211. Lastly, the `footer` template is included that terminates the page.
222 You also have to include this template for every page!
223
224Now, we have finished the page to manage the people so that we can move on to the forms with which we actually create and edit the people.
225
226### Person Add Form
227
228Like the person list, the form to add new people requires a controller class and a template.
229
230#### `PersonAddForm`
231
9a3f5fa3 232{jinja{ codebox(
f778fce2
MS
233 title="files/lib/acp/form/PersonAddForm.class.php",
234 language="php",
235 filepath="tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php"
9a3f5fa3 236) }}
eebb318c 237
5f7eed00
MS
238The properties here consist of three types:
239the “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.
eebb318c 240
5f7eed00 241Because of using form builder, we only have to set up the two form fields for entering the first and last name, respectively:
eebb318c 242
5f7eed00
MS
2431. Each field is a simple single-line text field, thus we use [`TextFormField`](../../php/api/form_builder/form_fields.md#textformfield).
2441. The parameter of the `create()` method expects the id of the field/name of the database object property, which is `firstName` and `lastName`, respectively, here.
2451. The language item of the label shown in the ouput above the input field is set via the `label()` method.
2461. As both fields have to be filled out, `required()` is called, and the maximum length is set via `maximumLength()`.
2471. Lastly, to make it easier to fill out the form more quickly, the first field is auto-focused by calling `autoFocus()`.
eebb318c
MS
248
249#### `personAdd.tpl`
250
9a3f5fa3 251{jinja{ codebox(
f778fce2
MS
252 title="acptemplates/personAdd.tpl",
253 language="smarty",
254 filepath="tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl"
9a3f5fa3 255) }}
eebb318c
MS
256
257We will now only concentrate on the new parts compared to `personList.tpl`:
258
2591. We use the `$action` variable to distinguish between the languages items used for adding a person and for creating a person.
5f7eed00 2601. Because of form builder, we only have to call `{@$form->getHtml()}` to generate all relevant output for the form.
eebb318c
MS
261
262### Person Edit Form
263
264As mentioned before, for the form to edit existing people, we only need a new controller as the template has already been implemented in a way that it handles both, adding and editing.
265
266#### `PersonEditForm`
267
9a3f5fa3 268{jinja{ codebox(
f778fce2
MS
269 title="files/lib/acp/form/PersonEditForm.class.php",
270 language="php",
271 filepath="tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php"
9a3f5fa3 272) }}
eebb318c
MS
273
274In general, edit forms extend the associated add form so that the code to read and to validate the input data is simply inherited.
275
5f7eed00 276After setting a different active menu item, we have to change the value of `$formAction` because this form, in contrast to `PersonAddForm`, does not create but update existing persons.
eebb318c 277
5f7eed00 278As we rely on form builder, the only thing necessary in this controller is to read and validate the edit object, i.e. the edited person, which is done in `readParameters()`.
eebb318c
MS
279
280
281## Frontend
282
283For the front end, that means the part with which the visitors of a website interact, we want to implement a simple sortable page that lists the people.
284This page should also be directly linked in the main menu.
285
286### `page.xml`
287
21609ed2 288First, 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):
eebb318c 289
9a3f5fa3 290{jinja{ codebox(
f778fce2
MS
291 title="page.xml",
292 language="xml",
293 filepath="tutorial/tutorial-series/part-1/page.xml"
9a3f5fa3 294) }}
eebb318c 295
21609ed2 296For more information about what each of the elements means, please refer to the [page package installation plugin page](../../package/pip/page.md).
eebb318c
MS
297
298### `menuItem.xml`
299
21609ed2 300Next, we register the menu item using the [menuItem package installation plugin](../../package/pip/menu-item.md):
eebb318c 301
9a3f5fa3 302{jinja{ codebox(
f778fce2
MS
303 title="menuItem.xml",
304 language="xml",
305 filepath="tutorial/tutorial-series/part-1/menuItem.xml"
9a3f5fa3 306) }}
eebb318c
MS
307
308Here, 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.
309
310### People List
311
312As in the ACP, we need a controller and a template.
1e4d8ded 313You might notice that both the controller’s (unqualified) class name and the template name are the same for the ACP and the front end.
eebb318c
MS
314This is no problem because the qualified names of the classes differ and the files are stored in different directories and because the templates are installed by different package installation plugins and are also stored in different directories.
315
316#### `PersonListPage`
317
9a3f5fa3 318{jinja{ codebox(
f778fce2
MS
319 title="files/lib/page/PersonListPage.class.php",
320 language="php",
321 filepath="tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php"
9a3f5fa3 322) }}
eebb318c
MS
323
324This class is almost identical to the ACP version.
325In 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.
326Furthermore, `$neededPermissions` has not been set because in the front end, users do not need any special permission to access the page.
327In the front end, we explicitly set the `$defaultSortField` so that the people listed on the page are sorted by their last name (in ascending order) by default.
328
329#### `personList.tpl`
330
9a3f5fa3 331{jinja{ codebox(
f778fce2
MS
332 title="templates/personList.tpl",
333 language="smarty",
334 filepath="tutorial/tutorial-series/part-1/templates/personList.tpl"
9a3f5fa3 335) }}
eebb318c
MS
336
337If 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.
338Furthermore, we include a template called `header` before actually showing any of the page contents and terminate the template by including the `footer` template.
339
340Now, let us take a closer look at the differences:
341
342- We do not explicitly create a `.contentHeader` element but simply assign the title to the `contentTitle` variable.
343 The value of the assignment is simply the title of the page and a badge showing the number of listed people.
344 The `header` template that we include later will handle correctly displaying the content header on its own based on the `$contentTitle` variable.
1e4d8ded 345- Next, we create additional element for the HTML document’s `<head>` element.
eebb318c
MS
346 In this case, we define the [canonical link of the page](https://en.wikipedia.org/wiki/Canonical_link_element) and, because we are showing paginated content, add links to the previous and next page (if they exist).
347- We want the page to be sortable but as we will not be using a table for listing the people like in the ACP, we are not able to place links to sort the people into the table head.
348 Instead, usually a box is created in the sidebar on the right-hand side that contains `select` elements to determine sort field and sort order.
349- The main part of the page is the listing of the people.
350 We use a structure similar to the one used for displaying registered users.
1e4d8ded 351 Here, for each person, we simply display a FontAwesome icon representing a person and show the person’s full name relying on `Person::__toString()`.
eebb318c
MS
352 Additionally, like in the user list, we provide the initially empty `ul.inlineList.commaSeparated` and `dl.plain.inlineDataList.small` elements that can be filled by plugins using the templates events.
353
354
355## `userGroupOption.xml`
356
21609ed2 357We 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):
eebb318c 358
9a3f5fa3 359{jinja{ codebox(
f778fce2
MS
360 title="userGroupOption.xml",
361 language="xml",
362 filepath="tutorial/tutorial-series/part-1/userGroupOption.xml"
9a3f5fa3 363) }}
eebb318c
MS
364
365We use the existing `admin.content` user group option category for the permission as the people are “content” (similar the the ACP menu item).
366As the permission is for administrators only, we set `defaultvalue` to `0` and `admindefaultvalue` to `1`.
367This permission is only relevant for registered users so that it should not be visible when editing the guest user group.
368This is achieved by setting `usersonly` to `1`.
369
370
371## `package.xml`
372
373Lastly, we need to create the `package.xml` file.
21609ed2 374For more information about this kind of file, please refer to [the `package.xml` page](../../package/package-xml.md).
eebb318c 375
9a3f5fa3 376{jinja{ codebox(
f778fce2
MS
377 title="package.xml",
378 language="xml",
379 filepath="tutorial/tutorial-series/part-1/package.xml"
9a3f5fa3 380) }}
eebb318c
MS
381
382As this is a package for WoltLab Suite Core 3, we need to require it using `<requiredpackage>`.
5f7eed00
MS
383We require the latest version (when writing this tutorial) `5.4.0 Alpha 1`.
384Additionally, we disallow installation of the package in the next major version `6.0` by excluding the `6.0.0 Alpha 1` version.
eebb318c
MS
385
386The most important part are to installation instructions.
387First, we install the ACP templates, files and templates, create the database table and import the language item.
388Afterwards, the ACP menu items and the permission are added.
389Now comes the part of the instructions where the order of the instructions is crucial:
390In `menuItem.xml`, we refer to the `com.woltlab.wcf.people.PersonList` page that is delivered by `page.xml`.
391As the menu item package installation plugin validates the given page and throws an exception if the page does not exist, we need to install the page before the menu item!
392
393---
394
395This concludes the first part of our tutorial series after which you now have a working simple package with which you can manage people in the ACP and show the visitors of your website a simple list of all created people in the front end.
8921997f 396
086a42fa 397The 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-1).