In this part of our tutorial series, we will add a new front end page to our package that is dedicated to each person and shows their personal details.
To make good use of this new page and introduce a new API of WoltLab Suite, we will add the opportunity for users to comment on the person using WoltLab Suite’s reusable comment functionality.
We will not mention every code change between the first part and this part, as we only want to focus on the important, new parts of the code. For example, there is a new Person::getLink() method and new language items have been added. For all changes, please refer to the source code on GitHub.
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:
First, the system is told the names of the permissions via the $permission* properties.
More information about comment permissions can be found here.
The getLink() method returns the link to the person with the passed comment id.
As in isAccessible(), PersonRuntimeCache is used to potentially save database queries.
The isAccessible() method checks if the active user can access the relevant person.
As we do not have any special restrictions for accessing people, we only need to check if the person exists.
The getTitle() method returns the title used for comments and responses, which is just a generic language item in this case.
The updateCounter() updates the comments’ counter of the person.
We have added a new comments database table column to the wcf1_person database table in order to keep track on the number of comments.
Additionally, we have added a new enableComments database table column to the wcf1_person database table whose value can be set when creating or editing a person in the ACP.
With this option, comments on individual people can be disabled.
Liking comments is already built-in and only requires some extra code in the PersonPage class for showing the likes of pre-loaded comments.
<?phpnamespacewcf\page;usewcf\data\comment\StructuredCommentList;usewcf\data\person\Person;usewcf\system\comment\CommentHandler;usewcf\system\comment\manager\PersonCommentManager;usewcf\system\exception\IllegalLinkException;usewcf\system\WCF;/** * Shows the details of a certain person. * * @author Matthias Schmidt * @copyright 2001-2021 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\Core\Page */classPersonPageextendsAbstractPage{/** * list of comments * @var StructuredCommentList */public$commentList;/** * person comment manager object * @var PersonCommentManager */public$commentManager;/** * id of the person comment object type * @var int */public$commentObjectTypeID=0;/** * shown person * @var Person */public$person;/** * id of the shown person * @var int */public$personID=0;/** * @inheritDoc */publicfunctionassignVariables(){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 */publicfunctionreadData(){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 */publicfunctionreadParameters(){parent::readParameters();if(isset($_REQUEST['id'])){$this->personID=\intval($_REQUEST['id']);}$this->person=newPerson($this->personID);if(!$this->person->personID){thrownewIllegalLinkException();}}}
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.
In readData(), this list is fetched using CommentHandler::getCommentList() if comments are enabled for the person.
The assignVariables() method assigns some additional template variables like $commentCanAdd, which is 1 if the active person can add comments and is 0 otherwise, $lastCommentTime, which contains the UNIX timestamp of the last comment, and $likeData, which contains data related to the likes for the disabled comments.
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.
The ul#personCommentList elements has five additional data- attributes required by the JavaScript API for comments for loading more comments or creating new ones.
The commentListAddComment template adds the WYSIWYG support.
The attribute wysiwygSelector should be the id of the comment list personCommentList with an additional AddComment suffix.
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:
It has a <handler> element with a class name as value.
This aspect will be discussed in more detail in the next section.
There are no <content> elements because, both, the title and the content of the page are dynamically generated in the template.
The <requireObjectID> tells the system that this page requires an object id to properly work, in this case a valid person id.
This page has a <parent> page, the person list page.
In general, the details page for any type of object that is listed on a different page has the list page as its parent.
<?phpnamespacewcf\system\page\handler;usewcf\data\page\Page;usewcf\data\person\PersonList;usewcf\data\user\online\UserOnline;usewcf\system\cache\runtime\PersonRuntimeCache;usewcf\system\database\util\PreparedStatementConditionBuilder;usewcf\system\WCF;/** * Page handler implementation for person page. * * @author Matthias Schmidt * @copyright 2001-2022 WoltLab GmbH * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> * @package WoltLabSuite\Core\System\Page\Handler */finalclassPersonPageHandlerextendsAbstractLookupPageHandlerimplementsIOnlineLocationPageHandler{useTOnlineLocationPageHandler;/** * @inheritDoc */publicfunctiongetLink($objectID){returnPersonRuntimeCache::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 */publicfunctiongetOnlineLocation(Page$page,UserOnline$user){if($user->pageObjectID===null){return'';}$person=PersonRuntimeCache::getInstance()->getObject($user->pageObjectID);if($person===null){return'';}returnWCF::getLanguage()->getDynamicVariable('wcf.page.onlineLocation.'.$page->identifier,['person'=>$person]);}/** * @inheritDoc */publicfunctionisValid($objectID=null){returnPersonRuntimeCache::getInstance()->getObject($objectID)!==null;}/** * @inheritDoc */publicfunctionlookup($searchString){$conditionBuilder=newPreparedStatementConditionBuilder(false,'OR');$conditionBuilder->add('person.firstName LIKE ?',['%'.$searchString.'%']);$conditionBuilder->add('person.lastName LIKE ?',['%'.$searchString.'%']);$personList=newPersonList();$personList->getConditionBuilder()->add($conditionBuilder,$conditionBuilder->getParameters());$personList->readObjects();$results=[];foreach($personListas$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 */publicfunctionprepareOnlineLocation(Page$page,UserOnline$user){if($user->pageObjectID!==null){PersonRuntimeCache::getInstance()->cacheObjectID($user->pageObjectID);}}}
Like any page handler, the PersonPageHandler class has to implement the IMenuPageHandler interface, which should be done by extending the AbstractMenuPageHandler class.
As we want administrators to link to specific people in menus, for example, we have to also implement the ILookupPageHandler interface by extending the AbstractLookupPageHandler class.
For the ILookupPageHandler interface, we need to implement three methods:
getLink($objectID) returns the link to the person page with the given id.
In this case, we simply delegate this method call to the Person object returned by PersonRuntimeCache::getObject().
isValid($objectID) returns true if the person with the given id exists, otherwise false.
Here, we use PersonRuntimeCache::getObject() again and check if the return value is null, which is the case for non-existing people.
lookup($searchString) is used when setting up an internal link and when searching for the linked person.
This method simply searches the first and last name of the people and returns an array with the person data.
While the link, the objectID, and the title element are self-explanatory, the image element can either contain an HTML <img> tag, which is displayed next to the search result (WoltLab Suite uses an image tag for users showing their avatar, for example), or a FontAwesome icon class (starting with fa-).
Additionally, the class also implements IOnlineLocationPageHandler which is used to determine the online location of users.
To ensure upwards-compatibility if the IOnlineLocationPageHandler interface changes, the TOnlineLocationPageHandler trait is used.
The IOnlineLocationPageHandler interface requires two methods to be implemented:
getOnlineLocation(Page $page, UserOnline $user) returns the textual description of the online location.
The language item for the user online locations should use the pattern wcf.page.onlineLocation.{page identifier}.
prepareOnlineLocation(Page $page, UserOnline $user) is called for each user online before the getOnlineLocation() calls.
In this case, calling prepareOnlineLocation() first enables us to add all relevant person ids to the person runtime cache so that for all getOnlineLocation() calls combined, only one database query is necessary to fetch all person objects.
This concludes the third part of our tutorial series after which each person has a dedicated page on which people can comment on the person.
The complete source code of this part can be found on GitHub.