From 85e6151ddba4078d43a20698a9fec77a01e20968 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Mon, 28 Dec 2020 16:13:42 +0100 Subject: [PATCH] Fix highlighting external files --- docs/migration_wsc-31_form-builder.md | 36 ++--- ...l_tutorial-series_part-1-base-structure.md | 102 +++++++-------- ...-event-listeners-and-template-listeners.md | 66 +++++----- ...-series_part-3-person-page-and-comments.md | 42 +++--- mkdocs.yml | 2 + .../formBuilder/PersonAddForm_new.class.php | 58 +++++++++ .../formBuilder/PersonAddForm_old.class.php | 109 ++++++++++++++++ .../formBuilder/PersonEditForm_new.class.php | 33 +++++ .../formBuilder/PersonEditForm_old.class.php | 91 +++++++++++++ .../wsc-31/formBuilder/personAdd_new.tpl | 19 +++ .../wsc-31/formBuilder/personAdd_old.tpl | 68 ++++++++++ snippets/tutorial/basic-app/files/global.php | 8 ++ snippets/tutorial/basic-app/files/index.php | 3 + .../files/lib/page/ExamplePage.class.php | 5 + .../files/lib/system/APPCore.class.php | 11 ++ snippets/tutorial/basic-app/menuItem.xml | 11 ++ snippets/tutorial/basic-app/package.xml | 31 +++++ snippets/tutorial/basic-app/page.xml | 19 +++ .../tutorial/basic-app/templates/example.tpl | 7 + .../tutorial-series/part-1/acpMenu.xml | 19 +++ .../part-1/acptemplates/personAdd.tpl | 64 +++++++++ .../part-1/acptemplates/personList.tpl | 93 +++++++++++++ .../lib/acp/form/PersonAddForm.class.php | 113 ++++++++++++++++ .../lib/acp/form/PersonEditForm.class.php | 91 +++++++++++++ .../lib/acp/page/PersonListPage.class.php | 34 +++++ .../files/lib/data/person/Person.class.php | 34 +++++ .../lib/data/person/PersonAction.class.php | 27 ++++ .../lib/data/person/PersonEditor.class.php | 22 ++++ .../lib/data/person/PersonList.class.php | 18 +++ .../files/lib/page/PersonListPage.class.php | 28 ++++ .../tutorial-series/part-1/install.sql | 6 + .../tutorial-series/part-1/language/de.xml | 27 ++++ .../tutorial-series/part-1/language/en.xml | 27 ++++ .../tutorial-series/part-1/menuItem.xml | 11 ++ .../tutorial-series/part-1/package.xml | 39 ++++++ .../tutorial/tutorial-series/part-1/page.xml | 18 +++ .../part-1/templates/personList.tpl | 102 +++++++++++++++ .../part-1/userGroupOption.xml | 14 ++ .../acptemplates/__personAddBirthday.tpl | 15 +++ .../tutorial-series/part-2/eventListener.xml | 41 ++++++ .../BirthdayPersonAddFormListener.class.php | 90 +++++++++++++ ...ySortFieldPersonListPageListener.class.php | 22 ++++ .../tutorial-series/part-2/install.sql | 1 + .../tutorial-series/part-2/language/de.xml | 6 + .../tutorial-series/part-2/language/en.xml | 6 + .../tutorial-series/part-2/package.xml | 38 ++++++ .../part-2/templateListener.xml | 40 ++++++ .../part-2/templates/__personListBirthday.tpl | 4 + .../__personListBirthdaySortField.tpl | 1 + .../tutorial-series/part-3/acpMenu.xml | 19 +++ .../part-3/acptemplates/personAdd.tpl | 72 ++++++++++ .../part-3/acptemplates/personList.tpl | 93 +++++++++++++ .../lib/acp/form/PersonAddForm.class.php | 123 ++++++++++++++++++ .../lib/acp/form/PersonEditForm.class.php | 93 +++++++++++++ .../lib/acp/page/PersonListPage.class.php | 34 +++++ .../files/lib/data/person/Person.class.php | 48 +++++++ .../lib/data/person/PersonAction.class.php | 27 ++++ .../lib/data/person/PersonEditor.class.php | 22 ++++ .../lib/data/person/PersonList.class.php | 18 +++ .../files/lib/page/PersonListPage.class.php | 28 ++++ .../files/lib/page/PersonPage.class.php | 89 +++++++++++++ .../runtime/PersonRuntimeCache.class.php | 24 ++++ .../manager/PersonCommentManager.class.php | 83 ++++++++++++ .../page/handler/PersonPageHandler.class.php | 96 ++++++++++++++ .../tutorial-series/part-3/install.sql | 8 ++ .../tutorial-series/part-3/language/de.xml | 47 +++++++ .../tutorial-series/part-3/language/en.xml | 47 +++++++ .../tutorial-series/part-3/menuItem.xml | 11 ++ .../tutorial-series/part-3/objectType.xml | 10 ++ .../tutorial-series/part-3/package.xml | 40 ++++++ .../tutorial/tutorial-series/part-3/page.xml | 27 ++++ .../part-3/templates/person.tpl | 42 ++++++ .../part-3/templates/personList.tpl | 109 ++++++++++++++++ .../part-3/userGroupOption.xml | 79 +++++++++++ 74 files changed, 2938 insertions(+), 123 deletions(-) create mode 100644 snippets/migration/wsc-31/formBuilder/PersonAddForm_new.class.php create mode 100644 snippets/migration/wsc-31/formBuilder/PersonAddForm_old.class.php create mode 100644 snippets/migration/wsc-31/formBuilder/PersonEditForm_new.class.php create mode 100644 snippets/migration/wsc-31/formBuilder/PersonEditForm_old.class.php create mode 100644 snippets/migration/wsc-31/formBuilder/personAdd_new.tpl create mode 100644 snippets/migration/wsc-31/formBuilder/personAdd_old.tpl create mode 100644 snippets/tutorial/basic-app/files/global.php create mode 100644 snippets/tutorial/basic-app/files/index.php create mode 100644 snippets/tutorial/basic-app/files/lib/page/ExamplePage.class.php create mode 100644 snippets/tutorial/basic-app/files/lib/system/APPCore.class.php create mode 100644 snippets/tutorial/basic-app/menuItem.xml create mode 100644 snippets/tutorial/basic-app/package.xml create mode 100644 snippets/tutorial/basic-app/page.xml create mode 100644 snippets/tutorial/basic-app/templates/example.tpl create mode 100644 snippets/tutorial/tutorial-series/part-1/acpMenu.xml create mode 100644 snippets/tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl create mode 100644 snippets/tutorial/tutorial-series/part-1/acptemplates/personList.tpl create mode 100644 snippets/tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php create mode 100644 snippets/tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php create mode 100644 snippets/tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php create mode 100644 snippets/tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php create mode 100644 snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php create mode 100644 snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php create mode 100644 snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php create mode 100644 snippets/tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php create mode 100644 snippets/tutorial/tutorial-series/part-1/install.sql create mode 100644 snippets/tutorial/tutorial-series/part-1/language/de.xml create mode 100644 snippets/tutorial/tutorial-series/part-1/language/en.xml create mode 100644 snippets/tutorial/tutorial-series/part-1/menuItem.xml create mode 100644 snippets/tutorial/tutorial-series/part-1/package.xml create mode 100644 snippets/tutorial/tutorial-series/part-1/page.xml create mode 100644 snippets/tutorial/tutorial-series/part-1/templates/personList.tpl create mode 100644 snippets/tutorial/tutorial-series/part-1/userGroupOption.xml create mode 100644 snippets/tutorial/tutorial-series/part-2/acptemplates/__personAddBirthday.tpl create mode 100644 snippets/tutorial/tutorial-series/part-2/eventListener.xml create mode 100644 snippets/tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php create mode 100644 snippets/tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php create mode 100644 snippets/tutorial/tutorial-series/part-2/install.sql create mode 100644 snippets/tutorial/tutorial-series/part-2/language/de.xml create mode 100644 snippets/tutorial/tutorial-series/part-2/language/en.xml create mode 100644 snippets/tutorial/tutorial-series/part-2/package.xml create mode 100644 snippets/tutorial/tutorial-series/part-2/templateListener.xml create mode 100644 snippets/tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl create mode 100644 snippets/tutorial/tutorial-series/part-2/templates/__personListBirthdaySortField.tpl create mode 100644 snippets/tutorial/tutorial-series/part-3/acpMenu.xml create mode 100644 snippets/tutorial/tutorial-series/part-3/acptemplates/personAdd.tpl create mode 100644 snippets/tutorial/tutorial-series/part-3/acptemplates/personList.tpl create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/acp/form/PersonAddForm.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/acp/form/PersonEditForm.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/acp/page/PersonListPage.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/data/person/Person.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonAction.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonEditor.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonList.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/page/PersonListPage.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php create mode 100644 snippets/tutorial/tutorial-series/part-3/install.sql create mode 100644 snippets/tutorial/tutorial-series/part-3/language/de.xml create mode 100644 snippets/tutorial/tutorial-series/part-3/language/en.xml create mode 100644 snippets/tutorial/tutorial-series/part-3/menuItem.xml create mode 100644 snippets/tutorial/tutorial-series/part-3/objectType.xml create mode 100644 snippets/tutorial/tutorial-series/part-3/package.xml create mode 100644 snippets/tutorial/tutorial-series/part-3/page.xml create mode 100644 snippets/tutorial/tutorial-series/part-3/templates/person.tpl create mode 100644 snippets/tutorial/tutorial-series/part-3/templates/personList.tpl create mode 100644 snippets/tutorial/tutorial-series/part-3/userGroupOption.xml diff --git a/docs/migration_wsc-31_form-builder.md b/docs/migration_wsc-31_form-builder.md index a15dba62..0c00f532 100644 --- a/docs/migration_wsc-31_form-builder.md +++ b/docs/migration_wsc-31_form-builder.md @@ -7,36 +7,36 @@ This form is the perfect first examples as it is very simple with only two text As a reminder, here are the two relevant PHP files and the relevant template file: -{% highlight php %} -{% include migration/wsc-31/formBuilder/PersonAddForm_old.class.php %} -{% endhighlight %} +```php +--8<-- "migration/wsc-31/formBuilder/PersonAddForm_old.class.php" +``` -{% highlight php %} -{% include migration/wsc-31/formBuilder/PersonEditForm_old.class.php %} -{% endhighlight %} +```php +--8<-- "migration/wsc-31/formBuilder/PersonEditForm_old.class.php" +``` -{% highlight php %} -{% include migration/wsc-31/formBuilder/personAdd_old.tpl %} -{% endhighlight %} +```php +--8<-- "migration/wsc-31/formBuilder/personAdd_old.tpl" +``` Updating the template is easy as the complete form is replace by a single line of code: -{% highlight php %} -{% include migration/wsc-31/formBuilder/personAdd_new.tpl %} -{% endhighlight %} +```php +--8<-- "migration/wsc-31/formBuilder/personAdd_new.tpl" +``` `PersonEditForm` also becomes much simpler: only the edited `Person` object must be read: -{% highlight php %} -{% include migration/wsc-31/formBuilder/PersonEditForm_new.class.php %} -{% endhighlight %} +```php +--8<-- "migration/wsc-31/formBuilder/PersonEditForm_new.class.php" +``` Most of the work is done in `PersonAddForm`: -{% highlight php %} -{% include migration/wsc-31/formBuilder/PersonAddForm_new.class.php %} -{% endhighlight %} +```php +--8<-- "migration/wsc-31/formBuilder/PersonAddForm_new.class.php" +``` But, as you can see, the number of lines almost decreased by half. All changes are due to extending `AbstractFormBuilderForm`: diff --git a/docs/tutorial_tutorial-series_part-1-base-structure.md b/docs/tutorial_tutorial-series_part-1-base-structure.md index 7daed09a..6447eb57 100644 --- a/docs/tutorial_tutorial-series_part-1-base-structure.md +++ b/docs/tutorial_tutorial-series_part-1-base-structure.md @@ -82,9 +82,9 @@ Thus, the database table we will store the people in only contains three columns The first file for our package is the `install.sql` file used to create such a database table during package installation: -{% highlight sql %} -{% include tutorial/tutorial-series/part-1/install.sql %} -{% endhighlight %} +```sql +--8<-- "tutorial/tutorial-series/part-1/install.sql" +``` ### Database Object @@ -92,9 +92,9 @@ The first file for our package is the `install.sql` file used to create such a d In our PHP code, each person will be represented by an object of the following class: -{% highlight php %} -{% include tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php %} -{% endhighlight %} +```php +--8<-- "tutorial/tutorial-series/part-1/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. @@ -104,9 +104,9 @@ an action class, an editor class and a list class. #### `PersonAction` -{% highlight php %} -{% include tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php %} -{% endhighlight %} +```php +--8<-- "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. This is done so that later on, when implementing the people list for the ACP, we can delete people simply via AJAX. @@ -116,18 +116,18 @@ We will later use the [userGroupOption package installation plugin](package_pip_ #### `PersonEditor` -{% highlight php %} -{% include tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php %} -{% endhighlight %} +```php +--8<-- "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: setting the static `$baseClass` property to the database object class name. #### `PersonList` -{% highlight php %} -{% include tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php %} -{% endhighlight %} +```php +--8<-- "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. @@ -151,9 +151,9 @@ 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. -{% highlight xml %} -{% include tutorial/tutorial-series/part-1/acpMenu.xml %} -{% endhighlight %} +```xml +--8<-- "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. 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. @@ -164,9 +164,9 @@ To list the people in the ACP, we need a `PersonListPage` class and a `personLis #### `PersonListPage` -{% highlight php %} -{% include tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php %} -{% endhighlight %} +```php +--8<-- "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: @@ -178,9 +178,9 @@ As WoltLab Suite Core already provides a powerful default implementation of a so #### `personList.tpl` -{% highlight smarty %} -{% include tutorial/tutorial-series/part-1/acptemplates/personList.tpl %} -{% endhighlight %} +```smarty +--8<-- "tutorial/tutorial-series/part-1/acptemplates/personList.tpl" +``` We will go piece by piece through the template code: @@ -212,9 +212,9 @@ Like the person list, the form to add new people requires a controller class and #### `PersonAddForm` -{% highlight php %} -{% include tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php %} -{% endhighlight %} +```php +--8<-- "tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php" +``` The properties here consist of two types: the “housekeeping” properties `$activeMenuItem` and `$neededPermissions`, which fulfill the same roles as for `PersonListPage`, and the “data” properties `$firstName` and `$lastName`, which will contain the data entered by the user of the person to be created. @@ -235,9 +235,9 @@ Now, let's go through each method in execution order: #### `personAdd.tpl` -{% highlight smarty %} -{% include tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl %} -{% endhighlight %} +```smarty +--8<-- "tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl" +``` We will now only concentrate on the new parts compared to `personList.tpl`: @@ -266,9 +266,9 @@ As mentioned before, for the form to edit existing people, we only need a new co #### `PersonEditForm` -{% highlight php %} -{% include tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php %} -{% endhighlight %} +```php +--8<-- "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. @@ -301,9 +301,9 @@ 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): -{% highlight xml %} -{% include tutorial/tutorial-series/part-1/page.xml %} -{% endhighlight %} +```xml +--8<-- "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). @@ -311,9 +311,9 @@ 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): -{% highlight xml %} -{% include tutorial/tutorial-series/part-1/menuItem.xml %} -{% endhighlight %} +```xml +--8<-- "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. @@ -325,9 +325,9 @@ This is no problem because the qualified names of the classes differ and the fil #### `PersonListPage` -{% highlight php %} -{% include tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php %} -{% endhighlight %} +```php +--8<-- "tutorial/tutorial-series/part-1/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. @@ -336,9 +336,9 @@ In the front end, we explicitly set the `$defaultSortField` so that the people l #### `personList.tpl` -{% highlight smarty %} -{% include tutorial/tutorial-series/part-1/templates/personList.tpl %} -{% endhighlight %} +```smarty +--8<-- "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. Furthermore, we include a template called `header` before actually showing any of the page contents and terminate the template by including the `footer` template. @@ -362,9 +362,9 @@ 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): -{% highlight xml %} -{% include tutorial/tutorial-series/part-1/userGroupOption.xml %} -{% endhighlight %} +```xml +--8<-- "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). As the permission is for administrators only, we set `defaultvalue` to `0` and `admindefaultvalue` to `1`. @@ -377,9 +377,9 @@ 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). -{% highlight xml %} -{% include tutorial/tutorial-series/part-1/package.xml %} -{% endhighlight %} +```xml +--8<-- "tutorial/tutorial-series/part-1/package.xml" +``` As this is a package for WoltLab Suite Core 3, we need to require it using ``. We require the latest version (when writing this tutorial) `3.0.0 RC 4`. diff --git a/docs/tutorial_tutorial-series_part-2-event-listeners-and-template-listeners.md b/docs/tutorial_tutorial-series_part-2-event-listeners-and-template-listeners.md index 1a5fc498..443f43d5 100644 --- a/docs/tutorial_tutorial-series_part-2-event-listeners-and-template-listeners.md +++ b/docs/tutorial_tutorial-series_part-2-event-listeners-and-template-listeners.md @@ -66,9 +66,9 @@ 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 [sql package installation plugin](package_pip_sql.md): -{% highlight sql %} -{% include tutorial/tutorial-series/part-2/install.sql %} -{% endhighlight %} +```sql +--8<-- "tutorial/tutorial-series/part-2/install.sql" +``` If we have a [Person object](tutorial_tutorial-series_part-1-base-structure.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`. @@ -78,20 +78,20 @@ If we have a [Person object](tutorial_tutorial-series_part-1-base-structure.md#p To set the birthday of a person, we need to extend the `personAdd` template to add an additional birthday field. This can be achieved using the `dataFields` template event at whose position we inject the following template code: -{% highlight sql %} -{% include tutorial/tutorial-series/part-2/acptemplates/__personAddBirthday.tpl %} -{% endhighlight %} +```sql +--8<-- "tutorial/tutorial-series/part-2/acptemplates/__personAddBirthday.tpl" +``` which we store in a `__personAddBirthday.tpl` template file. The used language item `wcf.person.birthday` is actually the only new one for this package: -{% highlight sql %} -{% include tutorial/tutorial-series/part-2/language/de.xml %} -{% endhighlight %} +```sql +--8<-- "tutorial/tutorial-series/part-2/language/de.xml" +``` -{% highlight sql %} -{% include tutorial/tutorial-series/part-2/language/en.xml %} -{% endhighlight %} +```sql +--8<-- "tutorial/tutorial-series/part-2/language/en.xml" +``` The template listener needs to be registered using the [templateListener package installation plugin](package_pip_template-listener.md). The corresponding complete `templateListener.xml` file is included [below](#templatelistenerxml). @@ -109,9 +109,9 @@ Before we take a look at the event listener code, we need to identify exactly wh The following event listeners achieves these requirements: -{% highlight php %} -{% include tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php %} -{% endhighlight %} +```php +--8<-- "tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php" +``` Some notes on the code: @@ -138,9 +138,9 @@ 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: -{% highlight php %} -{% include tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php %} -{% endhighlight %} +```php +--8<-- "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." @@ -165,9 +165,9 @@ 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: -{% highlight smarty %} -{% include tutorial/tutorial-series/part-2/templates/__personListBirthdaySortField.tpl %} -{% endhighlight %} +```smarty +--8<-- "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." @@ -175,18 +175,18 @@ 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: -{% highlight smarty %} -{% include tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl %} -{% endhighlight %} +```smarty +--8<-- "tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl" +``` ## `templateListener.xml` The following code shows the `templateListener.xml` file used to install all mentioned template listeners: -{% highlight xml %} -{% include tutorial/tutorial-series/part-2/templateListener.xml %} -{% endhighlight %} +```xml +--8<-- "tutorial/tutorial-series/part-2/templateListener.xml" +``` In cases where a template is used, we simply use the `include` syntax to load the template. @@ -197,18 +197,18 @@ There are two event listeners, `birthdaySortFieldAdminPersonList` and `birthdayS The event listener `birthdayPersonAddFormInherited` takes care of the events that are relevant for both adding and editing people, thus it listens to the `PersonAddForm` class but has `inherit` set to `1` so that it also listens to the events of the `PersonEditForm` class. In contrast, reading the existing birthday from a person is only relevant for editing so that the event listener `birthdayPersonEditForm` only listens to that class. -{% highlight xml %} -{% include tutorial/tutorial-series/part-2/eventListener.xml %} -{% endhighlight %} +```xml +--8<-- "tutorial/tutorial-series/part-2/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 ``): -{% highlight xml %} -{% include tutorial/tutorial-series/part-2/package.xml %} -{% endhighlight %} +```xml +--8<-- "tutorial/tutorial-series/part-2/package.xml" +``` --- diff --git a/docs/tutorial_tutorial-series_part-3-person-page-and-comments.md b/docs/tutorial_tutorial-series_part-3-person-page-and-comments.md index ddd173e7..fe8c27d0 100644 --- a/docs/tutorial_tutorial-series_part-3-person-page-and-comments.md +++ b/docs/tutorial_tutorial-series_part-3-person-page-and-comments.md @@ -76,9 +76,9 @@ 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: -{% highlight php %} -{% include tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php %} -{% endhighlight %} +```php +--8<-- "tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php" +``` ## Comments @@ -86,15 +86,15 @@ 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): -{% highlight xml %} -{% include tutorial/tutorial-series/part-3/objectType.xml %} -{% endhighlight %} +```xml +--8<-- "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): -{% highlight php %} -{% include tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php %} -{% endhighlight %} +```php +--8<-- "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. More information about comment permissions can be found [here](php_api_comments.md#user-group-options). @@ -116,9 +116,9 @@ With this option, comments on individual people can be disabled. ### `PersonPage` -{% highlight php %} -{% include tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php %} -{% endhighlight %} +```php +--8<-- "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()`. The rest of the code only handles fetching the list of comments on the requested person. @@ -127,9 +127,9 @@ The `assignVariables()` method assigns some additional template variables like ` ### `person.tpl` -{% highlight tpl %} -{% include tutorial/tutorial-series/part-3/templates/person.tpl %} -{% endhighlight %} +```tpl +--8<-- "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. 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. @@ -139,9 +139,9 @@ The attribute `wysiwygSelector` should be the id of the comment list `personComm ### `page.xml` -{% highlight xml %} -{% include tutorial/tutorial-series/part-3/page.xml %} -{% endhighlight %} +```xml +--8<-- "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`. Compared to the pre-existing `com.woltlab.wcf.people.PersonList` page, there are four differences: @@ -155,9 +155,9 @@ Compared to the pre-existing `com.woltlab.wcf.people.PersonList` page, there are ### `PersonPageHandler` -{% highlight php %} -{% include tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php %} -{% endhighlight %} +```php +--8<-- "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. 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. diff --git a/mkdocs.yml b/mkdocs.yml index 25027787..2e40b022 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -90,6 +90,8 @@ markdown_extensions: - abbr - pymdownx.highlight - pymdownx.superfences + - pymdownx.snippets: + base_path: "snippets/" extra_css: - stylesheets/extra.css diff --git a/snippets/migration/wsc-31/formBuilder/PersonAddForm_new.class.php b/snippets/migration/wsc-31/formBuilder/PersonAddForm_new.class.php new file mode 100644 index 00000000..71e0a9e1 --- /dev/null +++ b/snippets/migration/wsc-31/formBuilder/PersonAddForm_new.class.php @@ -0,0 +1,58 @@ + + * @package WoltLabSuite\Core\Acp\Form + */ +class PersonAddForm extends AbstractFormBuilderForm { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person.add'; + + /** + * @inheritDoc + */ + public $formAction = 'create'; + + /** + * @inheritDoc + */ + public $neededPermissions = ['admin.content.canManagePeople']; + + /** + * @inheritDoc + */ + public $objectActionClass = PersonAction::class; + + /** + * @inheritDoc + */ + protected function createForm() { + parent::createForm(); + + $dataContainer = FormContainer::create('data') + ->appendChildren([ + TextFormField::create('firstName') + ->label('wcf.person.firstName') + ->required() + ->maximumLength(255), + + TextFormField::create('lastName') + ->label('wcf.person.lastName') + ->required() + ->maximumLength(255) + ]); + + $this->form->appendChild($dataContainer); + } +} diff --git a/snippets/migration/wsc-31/formBuilder/PersonAddForm_old.class.php b/snippets/migration/wsc-31/formBuilder/PersonAddForm_old.class.php new file mode 100644 index 00000000..4a81edde --- /dev/null +++ b/snippets/migration/wsc-31/formBuilder/PersonAddForm_old.class.php @@ -0,0 +1,109 @@ + + * @package WoltLabSuite\Core\Acp\Form + */ +class PersonAddForm extends AbstractForm { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person.add'; + + /** + * first name of the person + * @var string + */ + public $firstName = ''; + + /** + * last name of the person + * @var string + */ + public $lastName = ''; + + /** + * @inheritDoc + */ + public $neededPermissions = ['admin.content.canManagePeople']; + + /** + * @inheritDoc + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'action' => 'add', + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]); + } + + /** + * @inheritDoc + */ + public function readFormParameters() { + parent::readFormParameters(); + + if (isset($_POST['firstName'])) $this->firstName = StringUtil::trim($_POST['firstName']); + if (isset($_POST['lastName'])) $this->lastName = StringUtil::trim($_POST['lastName']); + } + + /** + * @inheritDoc + */ + public function save() { + parent::save(); + + $this->objectAction = new PersonAction([], 'create', [ + 'data' => array_merge($this->additionalFields, [ + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]) + ]); + $this->objectAction->executeAction(); + + $this->saved(); + + // reset values + $this->firstName = ''; + $this->lastName = ''; + + // show success message + WCF::getTPL()->assign('success', true); + } + + /** + * @inheritDoc + */ + public function validate() { + parent::validate(); + + // validate first name + if (empty($this->firstName)) { + throw new UserInputException('firstName'); + } + if (mb_strlen($this->firstName) > 255) { + throw new UserInputException('firstName', 'tooLong'); + } + + // validate last name + if (empty($this->lastName)) { + throw new UserInputException('lastName'); + } + if (mb_strlen($this->lastName) > 255) { + throw new UserInputException('lastName', 'tooLong'); + } + } +} diff --git a/snippets/migration/wsc-31/formBuilder/PersonEditForm_new.class.php b/snippets/migration/wsc-31/formBuilder/PersonEditForm_new.class.php new file mode 100644 index 00000000..d541a8a4 --- /dev/null +++ b/snippets/migration/wsc-31/formBuilder/PersonEditForm_new.class.php @@ -0,0 +1,33 @@ + + * @package WoltLabSuite\Core\Acp\Form + */ +class PersonEditForm extends PersonAddForm { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person'; + + /** + * @inheritDoc + */ + public function readParameters() { + parent::readParameters(); + + if (isset($_REQUEST['id'])) { + $this->formObject = new Person(intval($_REQUEST['id'])); + if (!$this->formObject->personID) { + throw new IllegalLinkException(); + } + } + } +} diff --git a/snippets/migration/wsc-31/formBuilder/PersonEditForm_old.class.php b/snippets/migration/wsc-31/formBuilder/PersonEditForm_old.class.php new file mode 100644 index 00000000..ad8f86a8 --- /dev/null +++ b/snippets/migration/wsc-31/formBuilder/PersonEditForm_old.class.php @@ -0,0 +1,91 @@ + + * @package WoltLabSuite\Core\Acp\Form + */ +class PersonEditForm extends PersonAddForm { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person'; + + /** + * edited person object + * @var Person + */ + public $person = null; + + /** + * id of the edited person + * @var integer + */ + public $personID = 0; + + /** + * @inheritDoc + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'action' => 'edit', + 'person' => $this->person + ]); + } + + /** + * @inheritDoc + */ + public function readData() { + parent::readData(); + + if (empty($_POST)) { + $this->firstName = $this->person->firstName; + $this->lastName = $this->person->lastName; + } + } + + /** + * @inheritDoc + */ + public function readParameters() { + parent::readParameters(); + + if (isset($_REQUEST['id'])) $this->personID = intval($_REQUEST['id']); + $this->person = new Person($this->personID); + if (!$this->person->personID) { + throw new IllegalLinkException(); + } + } + + /** + * @inheritDoc + */ + public function save() { + AbstractForm::save(); + + $this->objectAction = new PersonAction([$this->person], 'update', [ + 'data' => array_merge($this->additionalFields, [ + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]) + ]); + $this->objectAction->executeAction(); + + $this->saved(); + + // show success message + WCF::getTPL()->assign('success', true); + } +} diff --git a/snippets/migration/wsc-31/formBuilder/personAdd_new.tpl b/snippets/migration/wsc-31/formBuilder/personAdd_new.tpl new file mode 100644 index 00000000..4cf2ec4a --- /dev/null +++ b/snippets/migration/wsc-31/formBuilder/personAdd_new.tpl @@ -0,0 +1,19 @@ +{include file='header' pageTitle='wcf.acp.person.'|concat:$action} + +
+
+

{lang}wcf.acp.person.{$action}{/lang}

+
+ + +
+ +{@$form->getHtml()} + +{include file='footer'} diff --git a/snippets/migration/wsc-31/formBuilder/personAdd_old.tpl b/snippets/migration/wsc-31/formBuilder/personAdd_old.tpl new file mode 100644 index 00000000..d9b357c6 --- /dev/null +++ b/snippets/migration/wsc-31/formBuilder/personAdd_old.tpl @@ -0,0 +1,68 @@ +{include file='header' pageTitle='wcf.acp.person.'|concat:$action} + +
+
+

{lang}wcf.acp.person.{$action}{/lang}

+
+ + +
+ +{include file='formError'} + +{if $success|isset} +

{lang}wcf.global.success.{$action}{/lang}

+{/if} + +
+
+ +
+
+ + {if $errorField == 'firstName'} + + {if $errorType == 'empty'} + {lang}wcf.global.form.error.empty{/lang} + {else} + {lang}wcf.acp.person.firstName.error.{$errorType}{/lang} + {/if} + + {/if} +
+ + + +
+
+ + {if $errorField == 'lastName'} + + {if $errorType == 'empty'} + {lang}wcf.global.form.error.empty{/lang} + {else} + {lang}wcf.acp.person.lastName.error.{$errorType}{/lang} + {/if} + + {/if} +
+ + + {event name='dataFields'} +
+ + {event name='sections'} + +
+ + {@SECURITY_TOKEN_INPUT_TAG} +
+
+ +{include file='footer'} diff --git a/snippets/tutorial/basic-app/files/global.php b/snippets/tutorial/basic-app/files/global.php new file mode 100644 index 00000000..197e6958 --- /dev/null +++ b/snippets/tutorial/basic-app/files/global.php @@ -0,0 +1,8 @@ +handle('app'); diff --git a/snippets/tutorial/basic-app/files/lib/page/ExamplePage.class.php b/snippets/tutorial/basic-app/files/lib/page/ExamplePage.class.php new file mode 100644 index 00000000..3af931c2 --- /dev/null +++ b/snippets/tutorial/basic-app/files/lib/page/ExamplePage.class.php @@ -0,0 +1,5 @@ + + + + + com.woltlab.wcf.MainMenu + Beispiel-Seite + Example Page + com.example.app.Example + + + diff --git a/snippets/tutorial/basic-app/package.xml b/snippets/tutorial/basic-app/package.xml new file mode 100644 index 00000000..b4754e6d --- /dev/null +++ b/snippets/tutorial/basic-app/package.xml @@ -0,0 +1,31 @@ + + + + Example App + A very basic example of an app. + 1 + 3.1.0 + 2018-03-29 + + + + Example Author + https://www.example.com + + + + com.woltlab.wcf + + + + + + + + + + + + + + diff --git a/snippets/tutorial/basic-app/page.xml b/snippets/tutorial/basic-app/page.xml new file mode 100644 index 00000000..2b00a900 --- /dev/null +++ b/snippets/tutorial/basic-app/page.xml @@ -0,0 +1,19 @@ + + + + + system + app\page\ExamplePage + Beispiel-Seite + Example App + 1 + + + Hello World + + + Hello World + + + + diff --git a/snippets/tutorial/basic-app/templates/example.tpl b/snippets/tutorial/basic-app/templates/example.tpl new file mode 100644 index 00000000..f40cce36 --- /dev/null +++ b/snippets/tutorial/basic-app/templates/example.tpl @@ -0,0 +1,7 @@ +{include file='header'} + +
+

Example Text

+
+ +{include file='footer'} diff --git a/snippets/tutorial/tutorial-series/part-1/acpMenu.xml b/snippets/tutorial/tutorial-series/part-1/acpMenu.xml new file mode 100644 index 00000000..8ad2af2c --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/acpMenu.xml @@ -0,0 +1,19 @@ + + + + + wcf.acp.menu.link.content + + + wcf\acp\page\PersonListPage + wcf.acp.menu.link.person + admin.content.canManagePeople + + + wcf\acp\form\PersonAddForm + wcf.acp.menu.link.person.list + admin.content.canManagePeople + fa-plus + + + diff --git a/snippets/tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl b/snippets/tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl new file mode 100644 index 00000000..a19603ab --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/acptemplates/personAdd.tpl @@ -0,0 +1,64 @@ +{include file='header' pageTitle='wcf.acp.person.'|concat:$action} + +
+
+

{lang}wcf.acp.person.{$action}{/lang}

+
+ + +
+ +{include file='formNotice'} + +
+
+ +
+
+ + {if $errorField == 'firstName'} + + {if $errorType == 'empty'} + {lang}wcf.global.form.error.empty{/lang} + {else} + {lang}wcf.acp.person.firstName.error.{$errorType}{/lang} + {/if} + + {/if} +
+ + + +
+
+ + {if $errorField == 'lastName'} + + {if $errorType == 'empty'} + {lang}wcf.global.form.error.empty{/lang} + {else} + {lang}wcf.acp.person.lastName.error.{$errorType}{/lang} + {/if} + + {/if} +
+ + + {event name='dataFields'} +
+ + {event name='sections'} + +
+ + {csrfToken} +
+
+ +{include file='footer'} diff --git a/snippets/tutorial/tutorial-series/part-1/acptemplates/personList.tpl b/snippets/tutorial/tutorial-series/part-1/acptemplates/personList.tpl new file mode 100644 index 00000000..39507ecd --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/acptemplates/personList.tpl @@ -0,0 +1,93 @@ +{include file='header' pageTitle='wcf.acp.person.list'} + +
+
+

{lang}wcf.acp.person.list{/lang}

+
+ + +
+ +{hascontent} +
+ {content}{pages print=true assign=pagesLinks controller="PersonList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content} +
+{/hascontent} + +{if $objects|count} +
+ + + + + + + + {event name='columnHeads'} + + + + + {foreach from=$objects item=person} + + + + + + + {event name='columns'} + + {/foreach} + +
{lang}wcf.global.objectID{/lang}{lang}wcf.person.firstName{/lang}{lang}wcf.person.lastName{/lang}
+ + + + {event name='rowButtons'} + {#$person->personID}{$person->firstName}{$person->lastName}
+
+ + +{else} +

{lang}wcf.global.noItems{/lang}

+{/if} + + + +{include file='footer'} diff --git a/snippets/tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php b/snippets/tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php new file mode 100644 index 00000000..fd3beeb8 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/files/lib/acp/form/PersonAddForm.class.php @@ -0,0 +1,113 @@ + + * @package WoltLabSuite\Core\Acp\Form + */ +class PersonAddForm extends AbstractForm { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person.add'; + + /** + * first name of the person + * @var string + */ + public $firstName = ''; + + /** + * last name of the person + * @var string + */ + public $lastName = ''; + + /** + * @inheritDoc + */ + public $neededPermissions = ['admin.content.canManagePeople']; + + /** + * @inheritDoc + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'action' => 'add', + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]); + } + + /** + * @inheritDoc + */ + public function readFormParameters() { + parent::readFormParameters(); + + if (isset($_POST['firstName'])) $this->firstName = StringUtil::trim($_POST['firstName']); + if (isset($_POST['lastName'])) $this->lastName = StringUtil::trim($_POST['lastName']); + } + + /** + * @inheritDoc + */ + public function save() { + parent::save(); + + $this->objectAction = new PersonAction([], 'create', [ + 'data' => array_merge($this->additionalFields, [ + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]) + ]); + $returnValues = $this->objectAction->executeAction(); + + $this->saved(); + + // reset values + $this->firstName = ''; + $this->lastName = ''; + + // show success message + WCF::getTPL()->assign([ + 'success' => true, + 'objectEditLink' => LinkHandler::getInstance()->getControllerLink(PersonEditForm::class, ['id' => $returnValues['returnValues']->personID]), + ]); + } + + /** + * @inheritDoc + */ + public function validate() { + parent::validate(); + + // validate first name + if (empty($this->firstName)) { + throw new UserInputException('firstName'); + } + if (mb_strlen($this->firstName) > 255) { + throw new UserInputException('firstName', 'tooLong'); + } + + // validate last name + if (empty($this->lastName)) { + throw new UserInputException('lastName'); + } + if (mb_strlen($this->lastName) > 255) { + throw new UserInputException('lastName', 'tooLong'); + } + } +} diff --git a/snippets/tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php b/snippets/tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php new file mode 100644 index 00000000..ad8f86a8 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/files/lib/acp/form/PersonEditForm.class.php @@ -0,0 +1,91 @@ + + * @package WoltLabSuite\Core\Acp\Form + */ +class PersonEditForm extends PersonAddForm { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person'; + + /** + * edited person object + * @var Person + */ + public $person = null; + + /** + * id of the edited person + * @var integer + */ + public $personID = 0; + + /** + * @inheritDoc + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'action' => 'edit', + 'person' => $this->person + ]); + } + + /** + * @inheritDoc + */ + public function readData() { + parent::readData(); + + if (empty($_POST)) { + $this->firstName = $this->person->firstName; + $this->lastName = $this->person->lastName; + } + } + + /** + * @inheritDoc + */ + public function readParameters() { + parent::readParameters(); + + if (isset($_REQUEST['id'])) $this->personID = intval($_REQUEST['id']); + $this->person = new Person($this->personID); + if (!$this->person->personID) { + throw new IllegalLinkException(); + } + } + + /** + * @inheritDoc + */ + public function save() { + AbstractForm::save(); + + $this->objectAction = new PersonAction([$this->person], 'update', [ + 'data' => array_merge($this->additionalFields, [ + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]) + ]); + $this->objectAction->executeAction(); + + $this->saved(); + + // show success message + WCF::getTPL()->assign('success', true); + } +} diff --git a/snippets/tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php b/snippets/tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php new file mode 100644 index 00000000..a16f28dd --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/files/lib/acp/page/PersonListPage.class.php @@ -0,0 +1,34 @@ + + * @package WoltLabSuite\Core\Acp\Page + */ +class PersonListPage extends SortablePage { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person.list'; + + /** + * @inheritDoc + */ + public $neededPermissions = ['admin.content.canManagePeople']; + + /** + * @inheritDoc + */ + public $objectListClassName = PersonList::class; + + /** + * @inheritDoc + */ + public $validSortFields = ['personID', 'firstName', 'lastName']; +} diff --git a/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php b/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php new file mode 100644 index 00000000..fe30af44 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/Person.class.php @@ -0,0 +1,34 @@ + + * @package WoltLabSuite\Core\Data\Person + * + * @property-read integer $personID unique id of the person + * @property-read string $firstName first name of the person + * @property-read string $lastName last name of the person + */ +class Person extends DatabaseObject implements IRouteController { + /** + * Returns the first and last name of the person if a person object is treated as a string. + * + * @return string + */ + public function __toString() { + return $this->getTitle(); + } + + /** + * @inheritDoc + */ + public function getTitle() { + return $this->firstName . ' ' . $this->lastName; + } +} diff --git a/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php b/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php new file mode 100644 index 00000000..d76b9267 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonAction.class.php @@ -0,0 +1,27 @@ + + * @package WoltLabSuite\Core\Data\Person + * + * @method Person create() + * @method PersonEditor[] getObjects() + * @method PersonEditor getSingleObject() + */ +class PersonAction extends AbstractDatabaseObjectAction { + /** + * @inheritDoc + */ + protected $permissionsDelete = ['admin.content.canManagePeople']; + + /** + * @inheritDoc + */ + protected $requireACP = ['delete']; +} diff --git a/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php b/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php new file mode 100644 index 00000000..3890bc10 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonEditor.class.php @@ -0,0 +1,22 @@ + + * @package WoltLabSuite\Core\Data\Person + * + * @method static Person create(array $parameters = []) + * @method Person getDecoratedObject() + * @mixin Person + */ +class PersonEditor extends DatabaseObjectEditor { + /** + * @inheritDoc + */ + protected static $baseClass = Person::class; +} diff --git a/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php b/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php new file mode 100644 index 00000000..bedf543c --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/files/lib/data/person/PersonList.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\Data\Person + * + * @method Person current() + * @method Person[] getObjects() + * @method Person|null search($objectID) + * @property Person[] $objects + */ +class PersonList extends DatabaseObjectList {} diff --git a/snippets/tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php b/snippets/tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php new file mode 100644 index 00000000..839b3e2a --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/files/lib/page/PersonListPage.class.php @@ -0,0 +1,28 @@ + + * @package WoltLabSuite\Core\Page + */ +class PersonListPage extends SortablePage { + /** + * @inheritDoc + */ + public $defaultSortField = 'lastName'; + + /** + * @inheritDoc + */ + public $objectListClassName = PersonList::class; + + /** + * @inheritDoc + */ + public $validSortFields = ['personID', 'firstName', 'lastName']; +} diff --git a/snippets/tutorial/tutorial-series/part-1/install.sql b/snippets/tutorial/tutorial-series/part-1/install.sql new file mode 100644 index 00000000..111a0f8e --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/install.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS wcf1_person; +CREATE TABLE wcf1_person ( + personID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(255) NOT NULL, + lastName VARCHAR(255) NOT NULL +); diff --git a/snippets/tutorial/tutorial-series/part-1/language/de.xml b/snippets/tutorial/tutorial-series/part-1/language/de.xml new file mode 100644 index 00000000..c0468562 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/language/de.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + {$person} wirklich löschen?]]> + + + + + + + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-1/language/en.xml b/snippets/tutorial/tutorial-series/part-1/language/en.xml new file mode 100644 index 00000000..bff83cff --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/language/en.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + {$person}?]]> + + + + + + + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-1/menuItem.xml b/snippets/tutorial/tutorial-series/part-1/menuItem.xml new file mode 100644 index 00000000..378a2973 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/menuItem.xml @@ -0,0 +1,11 @@ + + + + + com.woltlab.wcf.MainMenu + Personen + People + com.woltlab.wcf.people.PersonList + + + diff --git a/snippets/tutorial/tutorial-series/part-1/package.xml b/snippets/tutorial/tutorial-series/part-1/package.xml new file mode 100644 index 00000000..5beddeda --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/package.xml @@ -0,0 +1,39 @@ + + + + WoltLab Suite Core Tutorial: People + Adds a simple management system for people as part of a tutorial to create packages. + 3.1.0 + 2018-03-30 + + + + WoltLab GmbH + http://www.woltlab.com + + + + com.woltlab.wcf + + + + com.woltlab.wcf + + + + + + + + + + + + + + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-1/page.xml b/snippets/tutorial/tutorial-series/part-1/page.xml new file mode 100644 index 00000000..3f80ef8b --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/page.xml @@ -0,0 +1,18 @@ + + + + + system + wcf\page\PersonListPage + Personen-Liste + Person List + + + Personen + + + People + + + + diff --git a/snippets/tutorial/tutorial-series/part-1/templates/personList.tpl b/snippets/tutorial/tutorial-series/part-1/templates/personList.tpl new file mode 100644 index 00000000..6cad7472 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/templates/personList.tpl @@ -0,0 +1,102 @@ +{capture assign='contentTitle'}{lang}wcf.person.list{/lang} {#$items}{/capture} + +{capture assign='headContent'} + {if $pageNo < $pages} + + {/if} + {if $pageNo > 1} + + {/if} + +{/capture} + +{capture assign='sidebarRight'} +
+
+

{lang}wcf.global.sorting{/lang}

+ +
+
+
+
+ + +
+
+ +
+ +
+
+
+
+{/capture} + +{include file='header'} + +{hascontent} +
+ {content} + {pages print=true assign=pagesLinks controller='PersonList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"} + {/content} +
+{/hascontent} + +{if $items} +
+
    + {foreach from=$objects item=person} +
  1. +
    + + +
    +
    +

    {$person}

    +
    + + {hascontent} +
      + {content}{event name='personData'}{/content} +
    + {/hascontent} + + {hascontent} +
    + {content}{event name='personStatistics'}{/content} +
    + {/hascontent} +
    +
    +
  2. + {/foreach} +
+
+{else} +

{lang}wcf.global.noItems{/lang}

+{/if} + +
+ {hascontent} +
+ {content}{@$pagesLinks}{/content} +
+ {/hascontent} + + {hascontent} + + {/hascontent} +
+ +{include file='footer'} diff --git a/snippets/tutorial/tutorial-series/part-1/userGroupOption.xml b/snippets/tutorial/tutorial-series/part-1/userGroupOption.xml new file mode 100644 index 00000000..2e041d38 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-1/userGroupOption.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-2/acptemplates/__personAddBirthday.tpl b/snippets/tutorial/tutorial-series/part-2/acptemplates/__personAddBirthday.tpl new file mode 100644 index 00000000..451466a1 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/acptemplates/__personAddBirthday.tpl @@ -0,0 +1,15 @@ + +
+
+ + {if $errorField == 'birthday'} + + {if $errorType == 'noValidSelection'} + {lang}wcf.global.form.error.noValidSelection{/lang} + {else} + {lang}wcf.acp.person.birthday.error.{$errorType}{/lang} + {/if} + + {/if} +
+ diff --git a/snippets/tutorial/tutorial-series/part-2/eventListener.xml b/snippets/tutorial/tutorial-series/part-2/eventListener.xml new file mode 100644 index 00000000..fb8631de --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/eventListener.xml @@ -0,0 +1,41 @@ + + + + + + admin + wcf\acp\page\PersonListPage + validateSortField + wcf\system\event\listener\BirthdaySortFieldPersonListPageListener + + + admin + wcf\acp\form\PersonAddForm + saved + wcf\system\event\listener\BirthdayPersonAddFormListener + + + admin + wcf\acp\form\PersonAddForm + assignVariables,readFormParameters,save,validate + wcf\system\event\listener\BirthdayPersonAddFormListener + 1 + + + admin + wcf\acp\form\PersonEditForm + readData + wcf\system\event\listener\BirthdayPersonAddFormListener + + + + + + user + wcf\page\PersonListPage + validateSortField + wcf\system\event\listener\BirthdaySortFieldPersonListPageListener + + + + diff --git a/snippets/tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php b/snippets/tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php new file mode 100644 index 00000000..df26e491 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdayPersonAddFormListener.class.php @@ -0,0 +1,90 @@ + + * @package WoltLabSuite\Core\System\Event\Listener + */ +class BirthdayPersonAddFormListener extends AbstractEventListener { + /** + * birthday of the created or edited person + * @var string + */ + protected $birthday = ''; + + /** + * @see IPage::assignVariables() + */ + protected function onAssignVariables() { + WCF::getTPL()->assign('birthday', $this->birthday); + } + + /** + * @see IPage::readData() + */ + protected function onReadData(PersonEditForm $form) { + if (empty($_POST)) { + $this->birthday = $form->person->birthday; + + if ($this->birthday === '0000-00-00') { + $this->birthday = ''; + } + } + } + + /** + * @see IForm::readFormParameters() + */ + protected function onReadFormParameters() { + if (isset($_POST['birthday'])) { + $this->birthday = StringUtil::trim($_POST['birthday']); + } + } + + /** + * @see IForm::save() + */ + protected function onSave(PersonAddForm $form) { + if ($this->birthday) { + $form->additionalFields['birthday'] = $this->birthday; + } + else { + $form->additionalFields['birthday'] = '0000-00-00'; + } + } + + /** + * @see IForm::saved() + */ + protected function onSaved() { + $this->birthday = ''; + } + + /** + * @see IForm::validate() + */ + protected function onValidate() { + if (empty($this->birthday)) { + return; + } + + if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $this->birthday, $match)) { + throw new UserInputException('birthday', 'noValidSelection'); + } + + if (!checkdate(intval($match[2]), intval($match[3]), intval($match[1]))) { + throw new UserInputException('birthday', 'noValidSelection'); + } + } +} diff --git a/snippets/tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php b/snippets/tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php new file mode 100644 index 00000000..c704048e --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/files/lib/system/event/listener/BirthdaySortFieldPersonListPageListener.class.php @@ -0,0 +1,22 @@ + + * @package WoltLabSuite\Core\System\Event\Listener + */ +class BirthdaySortFieldPersonListPageListener implements IParameterizedEventListener { + /** + * @inheritDoc + */ + public function execute($eventObj, $className, $eventName, array &$parameters) { + /** @var SortablePage $eventObj */ + + $eventObj->validSortFields[] = 'birthday'; + } +} diff --git a/snippets/tutorial/tutorial-series/part-2/install.sql b/snippets/tutorial/tutorial-series/part-2/install.sql new file mode 100644 index 00000000..5e121fcf --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/install.sql @@ -0,0 +1 @@ +ALTER TABLE wcf1_person ADD birthday DATE NOT NULL; diff --git a/snippets/tutorial/tutorial-series/part-2/language/de.xml b/snippets/tutorial/tutorial-series/part-2/language/de.xml new file mode 100644 index 00000000..4e49b140 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/language/de.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-2/language/en.xml b/snippets/tutorial/tutorial-series/part-2/language/en.xml new file mode 100644 index 00000000..133e822c --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/language/en.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-2/package.xml b/snippets/tutorial/tutorial-series/part-2/package.xml new file mode 100644 index 00000000..463253cb --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/package.xml @@ -0,0 +1,38 @@ + + + + WoltLab Suite Core Tutorial: People (Birthday) + Adds a birthday field to the people management system as part of a tutorial to create packages. + 3.1.0 + 2018-03-30 + + + + WoltLab GmbH + http://www.woltlab.com + + + + com.woltlab.wcf + com.woltlab.wcf.people + + + + com.woltlab.wcf + + + + + + + + + + + + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-2/templateListener.xml b/snippets/tutorial/tutorial-series/part-2/templateListener.xml new file mode 100644 index 00000000..565310eb --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/templateListener.xml @@ -0,0 +1,40 @@ + + + + + + columnHeads + admin + {lang}wcf.person.birthday{/lang}]]> + personList + + + columns + admin + {if $person->birthday !== '0000-00-00'}{@$person->birthday|strtotime|date}{/if}]]> + personList + + + dataFields + admin + + personAdd + + + + + + personStatistics + user + + personList + + + sortField + user + + personList + + + + diff --git a/snippets/tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl b/snippets/tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl new file mode 100644 index 00000000..cb393bb9 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/templates/__personListBirthday.tpl @@ -0,0 +1,4 @@ +{if $person->birthday !== '0000-00-00'} +
{lang}wcf.person.birthday{/lang}
+
{@$person->birthday|strtotime|date}
+{/if} diff --git a/snippets/tutorial/tutorial-series/part-2/templates/__personListBirthdaySortField.tpl b/snippets/tutorial/tutorial-series/part-2/templates/__personListBirthdaySortField.tpl new file mode 100644 index 00000000..9ce0acdd --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-2/templates/__personListBirthdaySortField.tpl @@ -0,0 +1 @@ + diff --git a/snippets/tutorial/tutorial-series/part-3/acpMenu.xml b/snippets/tutorial/tutorial-series/part-3/acpMenu.xml new file mode 100644 index 00000000..8ad2af2c --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/acpMenu.xml @@ -0,0 +1,19 @@ + + + + + wcf.acp.menu.link.content + + + wcf\acp\page\PersonListPage + wcf.acp.menu.link.person + admin.content.canManagePeople + + + wcf\acp\form\PersonAddForm + wcf.acp.menu.link.person.list + admin.content.canManagePeople + fa-plus + + + diff --git a/snippets/tutorial/tutorial-series/part-3/acptemplates/personAdd.tpl b/snippets/tutorial/tutorial-series/part-3/acptemplates/personAdd.tpl new file mode 100644 index 00000000..0e369071 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/acptemplates/personAdd.tpl @@ -0,0 +1,72 @@ +{include file='header' pageTitle='wcf.acp.person.'|concat:$action} + +
+
+

{lang}wcf.acp.person.{$action}{/lang}

+
+ + +
+ +{include file='formNotice'} + +
+
+ +
+
+ + {if $errorField == 'firstName'} + + {if $errorType == 'empty'} + {lang}wcf.global.form.error.empty{/lang} + {else} + {lang}wcf.acp.person.firstName.error.{$errorType}{/lang} + {/if} + + {/if} +
+ + + +
+
+ + {if $errorField == 'lastName'} + + {if $errorType == 'empty'} + {lang}wcf.global.form.error.empty{/lang} + {else} + {lang}wcf.acp.person.lastName.error.{$errorType}{/lang} + {/if} + + {/if} +
+ + +
+
+
+ + {lang}wcf.person.enableComments.description{/lang} +
+
+ + {event name='dataFields'} +
+ + {event name='sections'} + +
+ + {csrfToken} +
+
+ +{include file='footer'} diff --git a/snippets/tutorial/tutorial-series/part-3/acptemplates/personList.tpl b/snippets/tutorial/tutorial-series/part-3/acptemplates/personList.tpl new file mode 100644 index 00000000..39507ecd --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/acptemplates/personList.tpl @@ -0,0 +1,93 @@ +{include file='header' pageTitle='wcf.acp.person.list'} + +
+
+

{lang}wcf.acp.person.list{/lang}

+
+ + +
+ +{hascontent} +
+ {content}{pages print=true assign=pagesLinks controller="PersonList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content} +
+{/hascontent} + +{if $objects|count} +
+ + + + + + + + {event name='columnHeads'} + + + + + {foreach from=$objects item=person} + + + + + + + {event name='columns'} + + {/foreach} + +
{lang}wcf.global.objectID{/lang}{lang}wcf.person.firstName{/lang}{lang}wcf.person.lastName{/lang}
+ + + + {event name='rowButtons'} + {#$person->personID}{$person->firstName}{$person->lastName}
+
+ + +{else} +

{lang}wcf.global.noItems{/lang}

+{/if} + + + +{include file='footer'} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/acp/form/PersonAddForm.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/acp/form/PersonAddForm.class.php new file mode 100644 index 00000000..7e6de521 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/acp/form/PersonAddForm.class.php @@ -0,0 +1,123 @@ + + * @package WoltLabSuite\Core\Acp\Form + */ +class PersonAddForm extends AbstractForm { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person.add'; + + /** + * is `1` if comments are enabled for the person, otherwise `0` + * @var integer + */ + public $enableComments = 1; + + /** + * first name of the person + * @var string + */ + public $firstName = ''; + + /** + * last name of the person + * @var string + */ + public $lastName = ''; + + /** + * @inheritDoc + */ + public $neededPermissions = ['admin.content.canManagePeople']; + + /** + * @inheritDoc + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'action' => 'add', + 'enableComments' => $this->enableComments, + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]); + } + + /** + * @inheritDoc + */ + public function readFormParameters() { + parent::readFormParameters(); + + $this->enableComments = isset($_POST['enableComments']) ? 1 : 0; + if (isset($_POST['firstName'])) $this->firstName = StringUtil::trim($_POST['firstName']); + if (isset($_POST['lastName'])) $this->lastName = StringUtil::trim($_POST['lastName']); + } + + /** + * @inheritDoc + */ + public function save() { + parent::save(); + + $this->objectAction = new PersonAction([], 'create', [ + 'data' => array_merge($this->additionalFields, [ + 'enableComments' => $this->enableComments, + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]) + ]); + $returnValues = $this->objectAction->executeAction(); + + $this->saved(); + + // reset values + $this->enableComments = 1; + $this->firstName = ''; + $this->lastName = ''; + + // show success message + WCF::getTPL()->assign([ + 'success' => true, + 'objectEditLink' => LinkHandler::getInstance()->getControllerLink(PersonEditForm::class, ['id' => $returnValues['returnValues']->personID]), + ]); + } + + /** + * @inheritDoc + */ + public function validate() { + parent::validate(); + + // validate first name + if (empty($this->firstName)) { + throw new UserInputException('firstName'); + } + if (mb_strlen($this->firstName) > 255) { + throw new UserInputException('firstName', 'tooLong'); + } + + // validate last name + if (empty($this->lastName)) { + throw new UserInputException('lastName'); + } + if (mb_strlen($this->lastName) > 255) { + throw new UserInputException('lastName', 'tooLong'); + } + } +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/acp/form/PersonEditForm.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/acp/form/PersonEditForm.class.php new file mode 100644 index 00000000..1cb63335 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/acp/form/PersonEditForm.class.php @@ -0,0 +1,93 @@ + + * @package WoltLabSuite\Core\Acp\Form + */ +class PersonEditForm extends PersonAddForm { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person'; + + /** + * edited person object + * @var Person + */ + public $person = null; + + /** + * id of the edited person + * @var integer + */ + public $personID = 0; + + /** + * @inheritDoc + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'action' => 'edit', + 'person' => $this->person + ]); + } + + /** + * @inheritDoc + */ + public function readData() { + parent::readData(); + + if (empty($_POST)) { + $this->enableComments = $this->person->enableComments; + $this->firstName = $this->person->firstName; + $this->lastName = $this->person->lastName; + } + } + + /** + * @inheritDoc + */ + public function readParameters() { + parent::readParameters(); + + if (isset($_REQUEST['id'])) $this->personID = intval($_REQUEST['id']); + $this->person = new Person($this->personID); + if (!$this->person->personID) { + throw new IllegalLinkException(); + } + } + + /** + * @inheritDoc + */ + public function save() { + AbstractForm::save(); + + $this->objectAction = new PersonAction([$this->person], 'update', [ + 'data' => array_merge($this->additionalFields, [ + 'enableComments' => $this->enableComments, + 'firstName' => $this->firstName, + 'lastName' => $this->lastName + ]) + ]); + $this->objectAction->executeAction(); + + $this->saved(); + + // show success message + WCF::getTPL()->assign('success', true); + } +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/acp/page/PersonListPage.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/acp/page/PersonListPage.class.php new file mode 100644 index 00000000..a16f28dd --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/acp/page/PersonListPage.class.php @@ -0,0 +1,34 @@ + + * @package WoltLabSuite\Core\Acp\Page + */ +class PersonListPage extends SortablePage { + /** + * @inheritDoc + */ + public $activeMenuItem = 'wcf.acp.menu.link.person.list'; + + /** + * @inheritDoc + */ + public $neededPermissions = ['admin.content.canManagePeople']; + + /** + * @inheritDoc + */ + public $objectListClassName = PersonList::class; + + /** + * @inheritDoc + */ + public $validSortFields = ['personID', 'firstName', 'lastName']; +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/Person.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/Person.class.php new file mode 100644 index 00000000..eaeb1085 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/Person.class.php @@ -0,0 +1,48 @@ + + * @package WoltLabSuite\Core\Data\Person + * + * @property-read integer $personID unique id of the person + * @property-read string $firstName first name of the person + * @property-read string $lastName last name of the person + * @property-read integer $comments number of comments on the person + * @property-read integer $enableComments is `1` if comments are enabled for the person, otherwise `0` + */ +class Person extends DatabaseObject implements ILinkableObject, IRouteController { + /** + * Returns the first and last name of the person if a person object is treated as a string. + * + * @return string + */ + public function __toString() { + return $this->getTitle(); + } + + /** + * @inheritDoc + */ + public function getLink() { + return LinkHandler::getInstance()->getLink('Person', [ + 'forceFrontend' => true, + 'object' => $this + ]); + } + + /** + * @inheritDoc + */ + public function getTitle() { + return $this->firstName . ' ' . $this->lastName; + } +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonAction.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonAction.class.php new file mode 100644 index 00000000..d76b9267 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonAction.class.php @@ -0,0 +1,27 @@ + + * @package WoltLabSuite\Core\Data\Person + * + * @method Person create() + * @method PersonEditor[] getObjects() + * @method PersonEditor getSingleObject() + */ +class PersonAction extends AbstractDatabaseObjectAction { + /** + * @inheritDoc + */ + protected $permissionsDelete = ['admin.content.canManagePeople']; + + /** + * @inheritDoc + */ + protected $requireACP = ['delete']; +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonEditor.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonEditor.class.php new file mode 100644 index 00000000..3890bc10 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonEditor.class.php @@ -0,0 +1,22 @@ + + * @package WoltLabSuite\Core\Data\Person + * + * @method static Person create(array $parameters = []) + * @method Person getDecoratedObject() + * @mixin Person + */ +class PersonEditor extends DatabaseObjectEditor { + /** + * @inheritDoc + */ + protected static $baseClass = Person::class; +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonList.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonList.class.php new file mode 100644 index 00000000..bedf543c --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/data/person/PersonList.class.php @@ -0,0 +1,18 @@ + + * @package WoltLabSuite\Core\Data\Person + * + * @method Person current() + * @method Person[] getObjects() + * @method Person|null search($objectID) + * @property Person[] $objects + */ +class PersonList extends DatabaseObjectList {} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/page/PersonListPage.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/page/PersonListPage.class.php new file mode 100644 index 00000000..839b3e2a --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/page/PersonListPage.class.php @@ -0,0 +1,28 @@ + + * @package WoltLabSuite\Core\Page + */ +class PersonListPage extends SortablePage { + /** + * @inheritDoc + */ + public $defaultSortField = 'lastName'; + + /** + * @inheritDoc + */ + public $objectListClassName = PersonList::class; + + /** + * @inheritDoc + */ + public $validSortFields = ['personID', 'firstName', 'lastName']; +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php new file mode 100644 index 00000000..d68c0d73 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/page/PersonPage.class.php @@ -0,0 +1,89 @@ + + * @package WoltLabSuite\Core\Page + */ +class PersonPage extends AbstractPage { + /** + * list of comments + * @var StructuredCommentList + */ + public $commentList; + + /** + * person comment manager object + * @var PersonCommentManager + */ + public $commentManager; + + /** + * id of the person comment object type + * @var integer + */ + public $commentObjectTypeID = 0; + + /** + * shown person + * @var Person + */ + public $person; + + /** + * id of the shown person + * @var integer + */ + public $personID = 0; + + /** + * @inheritDoc + */ + public function assignVariables() { + parent::assignVariables(); + + WCF::getTPL()->assign([ + 'commentCanAdd' => WCF::getSession()->getPermission('user.person.canAddComment'), + 'commentList' => $this->commentList, + 'commentObjectTypeID' => $this->commentObjectTypeID, + 'lastCommentTime' => $this->commentList ? $this->commentList->getMinCommentTime() : 0, + 'likeData' => (MODULE_LIKE && $this->commentList) ? $this->commentList->getLikeData() : [], + 'person' => $this->person + ]); + } + + /** + * @inheritDoc + */ + public function readData() { + parent::readData(); + + if ($this->person->enableComments) { + $this->commentObjectTypeID = CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.person.personComment'); + $this->commentManager = CommentHandler::getInstance()->getObjectType($this->commentObjectTypeID)->getProcessor(); + $this->commentList = CommentHandler::getInstance()->getCommentList($this->commentManager, $this->commentObjectTypeID, $this->person->personID); + } + } + + /** + * @inheritDoc + */ + public function readParameters() { + parent::readParameters(); + + if (isset($_REQUEST['id'])) $this->personID = intval($_REQUEST['id']); + $this->person = new Person($this->personID); + if (!$this->person->personID) { + throw new IllegalLinkException(); + } + } +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php new file mode 100644 index 00000000..db63fdee --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/system/cache/runtime/PersonRuntimeCache.class.php @@ -0,0 +1,24 @@ + + * @package WoltLabSuite\Core\System\Cache\Runtime + * @since 3.0 + * + * @method Person[] getCachedObjects() + * @method Person getObject($objectID) + * @method Person[] getObjects(array $objectIDs) + */ +class PersonRuntimeCache extends AbstractRuntimeCache { + /** + * @inheritDoc + */ + protected $listClassName = PersonList::class; +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php new file mode 100644 index 00000000..0b35af23 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/system/comment/manager/PersonCommentManager.class.php @@ -0,0 +1,83 @@ + + * @package WoltLabSuite\Core\System\Comment\Manager + */ +class PersonCommentManager extends AbstractCommentManager { + /** + * @inheritDoc + */ + protected $permissionAdd = 'user.person.canAddComment'; + + /** + * @inheritDoc + */ + protected $permissionAddWithoutModeration = 'user.person.canAddCommentWithoutModeration'; + + /** + * @inheritDoc + */ + protected $permissionCanModerate = 'mod.person.canModerateComment'; + + /** + * @inheritDoc + */ + protected $permissionDelete = 'user.person.canDeleteComment'; + + /** + * @inheritDoc + */ + protected $permissionEdit = 'user.person.canEditComment'; + + /** + * @inheritDoc + */ + protected $permissionModDelete = 'mod.person.canDeleteComment'; + + /** + * @inheritDoc + */ + protected $permissionModEdit = 'mod.person.canEditComment'; + + /** + * @inheritDoc + */ + public function getLink($objectTypeID, $objectID) { + return PersonRuntimeCache::getInstance()->getObject($objectID)->getLink(); + } + + /** + * @inheritDoc + */ + public function isAccessible($objectID, $validateWritePermission = false) { + return PersonRuntimeCache::getInstance()->getObject($objectID) !== null; + } + + /** + * @inheritDoc + */ + public function getTitle($objectTypeID, $objectID, $isResponse = false) { + if ($isResponse) { + return WCF::getLanguage()->get('wcf.person.commentResponse'); + } + + return WCF::getLanguage()->getDynamicVariable('wcf.person.comment'); + } + + /** + * @inheritDoc + */ + public function updateCounter($objectID, $value) { + (new PersonEditor(new Person($objectID)))->updateCounters(['comments' => $value]); + } +} diff --git a/snippets/tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php b/snippets/tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php new file mode 100644 index 00000000..58819b63 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/files/lib/system/page/handler/PersonPageHandler.class.php @@ -0,0 +1,96 @@ + + * @package WoltLabSuite\Core\System\Page\Handler + */ +class PersonPageHandler extends AbstractLookupPageHandler implements IOnlineLocationPageHandler { + use TOnlineLocationPageHandler; + + /** + * @inheritDoc + */ + public function getLink($objectID) { + return PersonRuntimeCache::getInstance()->getObject($objectID)->getLink(); + } + + /** + * Returns the textual description if a user is currently online viewing this page. + * + * @see IOnlineLocationPageHandler::getOnlineLocation() + * + * @param Page $page visited page + * @param UserOnline $user user online object with request data + * @return string + */ + public function getOnlineLocation(Page $page, UserOnline $user) { + if ($user->pageObjectID === null) { + return ''; + } + + $person = PersonRuntimeCache::getInstance()->getObject($user->pageObjectID); + if ($person === null) { + return ''; + } + + return WCF::getLanguage()->getDynamicVariable('wcf.page.onlineLocation.'.$page->identifier, ['person' => $person]); + } + + /** + * @inheritDoc + */ + public function isValid($objectID = null) { + return PersonRuntimeCache::getInstance()->getObject($objectID) !== null; + } + + /** + * @inheritDoc + */ + public function lookup($searchString) { + $conditionBuilder = new PreparedStatementConditionBuilder(false, 'OR'); + $conditionBuilder->add('person.firstName LIKE ?', ['%' . $searchString . '%']); + $conditionBuilder->add('person.lastName LIKE ?', ['%' . $searchString . '%']); + + $personList = new PersonList(); + $personList->getConditionBuilder()->add($conditionBuilder, $conditionBuilder->getParameters()); + $personList->readObjects(); + + $results = []; + foreach ($personList as $person) { + $results[] = [ + 'image' => 'fa-user', + 'link' => $person->getLink(), + 'objectID' => $person->personID, + 'title' => $person->getTitle() + ]; + } + + return $results; + } + + /** + * Prepares fetching all necessary data for the textual description if a user is currently online + * viewing this page. + * + * @see IOnlineLocationPageHandler::prepareOnlineLocation() + * + * @param Page $page visited page + * @param UserOnline $user user online object with request data + */ + public function prepareOnlineLocation(/** @noinspection PhpUnusedParameterInspection */Page $page, UserOnline $user) { + if ($user->pageObjectID !== null) { + PersonRuntimeCache::getInstance()->cacheObjectID($user->pageObjectID); + } + } +} diff --git a/snippets/tutorial/tutorial-series/part-3/install.sql b/snippets/tutorial/tutorial-series/part-3/install.sql new file mode 100644 index 00000000..af5dfcb4 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/install.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS wcf1_person; +CREATE TABLE wcf1_person ( + personID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(255) NOT NULL, + lastName VARCHAR(255) NOT NULL, + comments SMALLINT(5) NOT NULL DEFAULT 0, + enableComments TINYINT(1) NOT NULL DEFAULT 1 +); diff --git a/snippets/tutorial/tutorial-series/part-3/language/de.xml b/snippets/tutorial/tutorial-series/part-3/language/de.xml new file mode 100644 index 00000000..1cf1af38 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/language/de.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + {$person} wirklich löschen?]]> + + + + + + + + getLink()}">{$person}]]> + + + + + + + + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-3/language/en.xml b/snippets/tutorial/tutorial-series/part-3/language/en.xml new file mode 100644 index 00000000..a7155b5f --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/language/en.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + {$person}?]]> + + + + + + + + getLink()}">{$person}]]> + + + + + + + + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-3/menuItem.xml b/snippets/tutorial/tutorial-series/part-3/menuItem.xml new file mode 100644 index 00000000..378a2973 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/menuItem.xml @@ -0,0 +1,11 @@ + + + + + com.woltlab.wcf.MainMenu + Personen + People + com.woltlab.wcf.people.PersonList + + + diff --git a/snippets/tutorial/tutorial-series/part-3/objectType.xml b/snippets/tutorial/tutorial-series/part-3/objectType.xml new file mode 100644 index 00000000..acce4314 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/objectType.xml @@ -0,0 +1,10 @@ + + + + + com.woltlab.wcf.person.personComment + com.woltlab.wcf.comment.commentableContent + wcf\system\comment\manager\PersonCommentManager + + + diff --git a/snippets/tutorial/tutorial-series/part-3/package.xml b/snippets/tutorial/tutorial-series/part-3/package.xml new file mode 100644 index 00000000..65191154 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/package.xml @@ -0,0 +1,40 @@ + + + + WoltLab Suite Core Tutorial: People + Adds a simple management system for people as part of a tutorial to create packages. + 3.1.0 + 2018-03-30 + + + + WoltLab GmbH + http://www.woltlab.com + + + + com.woltlab.wcf + + + + com.woltlab.wcf + + + + + + + + + + + + + + + + + + + + diff --git a/snippets/tutorial/tutorial-series/part-3/page.xml b/snippets/tutorial/tutorial-series/part-3/page.xml new file mode 100644 index 00000000..0e1ea88c --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/page.xml @@ -0,0 +1,27 @@ + + + + + system + wcf\page\PersonListPage + Personen-Liste + Person List + + + Personen + + + People + + + + system + wcf\page\PersonPage + wcf\system\page\handler\PersonPageHandler + Person + Person + 1 + com.woltlab.wcf.people.PersonList + + + diff --git a/snippets/tutorial/tutorial-series/part-3/templates/person.tpl b/snippets/tutorial/tutorial-series/part-3/templates/person.tpl new file mode 100644 index 00000000..8ccd8f90 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/templates/person.tpl @@ -0,0 +1,42 @@ +{capture assign='pageTitle'}{$person} - {lang}wcf.person.list{/lang}{/capture} + +{capture assign='contentTitle'}{$person}{/capture} + +{include file='header'} + +{if $person->enableComments} + {if $commentList|count || $commentCanAdd} +
+
+

{lang}wcf.person.comments{/lang}{if $person->comments} {#$person->comments}{/if}

+
+ + {include file='__commentJavaScript' commentContainerID='personCommentList'} + +
+
    + {include file='commentListAddComment' wysiwygSelector='personCommentListAddComment'} + {include file='commentList'} +
+
+
+ {/if} +{/if} + +
+ {hascontent} + + {/hascontent} +
+ +{include file='footer'} diff --git a/snippets/tutorial/tutorial-series/part-3/templates/personList.tpl b/snippets/tutorial/tutorial-series/part-3/templates/personList.tpl new file mode 100644 index 00000000..eb193f15 --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/templates/personList.tpl @@ -0,0 +1,109 @@ +{capture assign='contentTitle'}{lang}wcf.person.list{/lang} {#$items}{/capture} + +{capture assign='headContent'} + {if $pageNo < $pages} + + {/if} + {if $pageNo > 1} + + {/if} + +{/capture} + +{capture assign='sidebarRight'} +
+
+

{lang}wcf.global.sorting{/lang}

+ +
+
+
+
+ + +
+
+ +
+ +
+
+
+
+{/capture} + +{include file='header'} + +{hascontent} +
+ {content} + {pages print=true assign=pagesLinks controller='PersonList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"} + {/content} +
+{/hascontent} + +{if $items} +
+
    + {foreach from=$objects item=person} +
  1. +
    + + +
    +
    +

    {$person}

    +
    + + {hascontent} +
      + {content}{event name='personData'}{/content} +
    + {/hascontent} + + {hascontent} +
    + {content} + {if $person->enableComments} +
    {lang}wcf.person.comments{/lang}
    +
    {#$person->comments}
    + {/if} + + {event name='personStatistics'} + {/content} +
    + {/hascontent} +
    +
    +
  2. + {/foreach} +
+
+{else} +

{lang}wcf.global.noItems{/lang}

+{/if} + +
+ {hascontent} +
+ {content}{@$pagesLinks}{/content} +
+ {/hascontent} + + {hascontent} + + {/hascontent} +
+ +{include file='footer'} diff --git a/snippets/tutorial/tutorial-series/part-3/userGroupOption.xml b/snippets/tutorial/tutorial-series/part-3/userGroupOption.xml new file mode 100644 index 00000000..044394af --- /dev/null +++ b/snippets/tutorial/tutorial-series/part-3/userGroupOption.xml @@ -0,0 +1,79 @@ + + + + + + mod + + + + user + + + + + + + + + + + + + + + + + + + + + + + -- 2.20.1