From: Marcel Werk Date: Wed, 25 May 2016 15:40:08 +0000 (+0200) Subject: Overhauled page tracking in sessions / user online locations X-Git-Tag: 3.0.0_Beta_1~1636 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=da5a61fbbd81a6f133b1becc6402650f084fcb28;p=GitHub%2FWoltLab%2FWCF.git Overhauled page tracking in sessions / user online locations --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 116194eaf8..0588dbf993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ * Scaled embedded youtube videos to maximum width * `\wcf\form\AbstractCaptchaForm`: added parameter to force captcha usage for registered users. * Added global disable switch for languages. +* Overhauled page tracking in sessions / user online locations #### CMS @@ -144,6 +145,7 @@ * Option `message_sidebar_enable_rank` removed. * Option `message_sidebar_enable_avatar` removed. * Removed obsolete `$activeMenuItem` in frontend forms/pages +* Obsolete interface `wcf\page\ITrackablePage` deprecated. #### Documentation diff --git a/wcfsetup/install/files/lib/data/user/UserProfileList.class.php b/wcfsetup/install/files/lib/data/user/UserProfileList.class.php index c0af048f16..00e9817490 100644 --- a/wcfsetup/install/files/lib/data/user/UserProfileList.class.php +++ b/wcfsetup/install/files/lib/data/user/UserProfileList.class.php @@ -43,7 +43,7 @@ class UserProfileList extends UserList { } // get current location - $this->sqlSelects .= ", session.controller, session.objectID AS locationObjectID, session.lastActivityTime AS sessionLastActivityTime"; + $this->sqlSelects .= ", session.pageID, session.pageObjectID, session.lastActivityTime AS sessionLastActivityTime"; $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_session session ON (session.userID = user_table.userID)"; } diff --git a/wcfsetup/install/files/lib/data/user/online/UserOnline.class.php b/wcfsetup/install/files/lib/data/user/online/UserOnline.class.php index 7a915c38a7..033a3ea369 100644 --- a/wcfsetup/install/files/lib/data/user/online/UserOnline.class.php +++ b/wcfsetup/install/files/lib/data/user/online/UserOnline.class.php @@ -62,42 +62,27 @@ class UserOnline extends UserProfile { */ public function setLocation($location = null) { if ($location === null) { - if ($this->controller) { - if ($this->controller === CmsPage::class) { - // create location based on cms page title - if ($this->objectType) { - $page = PageCache::getInstance()->getPageByIdentifier($this->objectType); - if ($page !== null) { - // TODO: check if active user may access the page - $pageContent = $page->getPageContentByLanguage(WCF::getLanguage()->languageID); - if (isset($pageContent['title'])) { - $this->location = $pageContent['title']; - - return false; - } - } + if ($this->pageID) { + $page = PageCache::getInstance()->getPage($this->pageID); + if ($page !== null) { + if ($page->getHandler() !== null && $page->getHandler() instanceof IOnlineLocationPageHandler) { + // refer to page handler + $this->location = $page->getHandler()->getOnlineLocation($page, $this); + return true; } - } - else { - $page = PageCache::getInstance()->getPageByController($this->controller); - if ($page !== null) { - if ($page->getHandler() !== null && $page->getHandler() instanceof IOnlineLocationPageHandler) { - // refer to page handler - $this->location = $page->getHandler()->getOnlineLocation($page, $this); - - return true; - } - else { - // check if static language item exists - $languageItem = 'wcf.page.onlineLocation.' . $page->identifier; - $languageItemValue = WCF::getLanguage()->get($languageItem); - - if ($languageItemValue !== $languageItem) { - $this->location = $languageItemValue; - - return true; + else { + // TODO: check if active user may access the page + $title = $page->getTitle(); + if (!empty($title)) { + if ($page->pageType != 'system') { + $this->location = '' . StringUtil::encodeHTML($title) . ''; + } + else { + $this->location = StringUtil::encodeHTML($title); } } + + return ($this->location != ''); } } } diff --git a/wcfsetup/install/files/lib/form/AccountManagementForm.class.php b/wcfsetup/install/files/lib/form/AccountManagementForm.class.php index f749874a92..c87e37a92d 100644 --- a/wcfsetup/install/files/lib/form/AccountManagementForm.class.php +++ b/wcfsetup/install/files/lib/form/AccountManagementForm.class.php @@ -23,11 +23,6 @@ use wcf\util\UserUtil; * @category Community Framework */ class AccountManagementForm extends AbstractForm { - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * @inheritDoc */ diff --git a/wcfsetup/install/files/lib/form/AvatarEditForm.class.php b/wcfsetup/install/files/lib/form/AvatarEditForm.class.php index f4471be47b..8fb7532c9a 100644 --- a/wcfsetup/install/files/lib/form/AvatarEditForm.class.php +++ b/wcfsetup/install/files/lib/form/AvatarEditForm.class.php @@ -21,11 +21,6 @@ use wcf\system\WCF; * @category Community Framework */ class AvatarEditForm extends AbstractForm { - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * @inheritDoc */ diff --git a/wcfsetup/install/files/lib/form/LoginForm.class.php b/wcfsetup/install/files/lib/form/LoginForm.class.php index 03e03def32..3b131d9d39 100644 --- a/wcfsetup/install/files/lib/form/LoginForm.class.php +++ b/wcfsetup/install/files/lib/form/LoginForm.class.php @@ -18,11 +18,6 @@ use wcf\util\HeaderUtil; class LoginForm extends \wcf\acp\form\LoginForm { const AVAILABLE_DURING_OFFLINE_MODE = true; - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * true enables the usage of cookies to save login credentials * @var boolean diff --git a/wcfsetup/install/files/lib/form/LostPasswordForm.class.php b/wcfsetup/install/files/lib/form/LostPasswordForm.class.php index 12422d523e..a908e026fc 100644 --- a/wcfsetup/install/files/lib/form/LostPasswordForm.class.php +++ b/wcfsetup/install/files/lib/form/LostPasswordForm.class.php @@ -23,11 +23,6 @@ use wcf\util\StringUtil; class LostPasswordForm extends AbstractCaptchaForm { const AVAILABLE_DURING_OFFLINE_MODE = true; - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * username * @var string diff --git a/wcfsetup/install/files/lib/form/NewPasswordForm.class.php b/wcfsetup/install/files/lib/form/NewPasswordForm.class.php index e488f9cf97..4915fe4742 100644 --- a/wcfsetup/install/files/lib/form/NewPasswordForm.class.php +++ b/wcfsetup/install/files/lib/form/NewPasswordForm.class.php @@ -24,11 +24,6 @@ use wcf\util\StringUtil; class NewPasswordForm extends AbstractForm { const AVAILABLE_DURING_OFFLINE_MODE = true; - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * user id * @var integer diff --git a/wcfsetup/install/files/lib/form/RegisterForm.class.php b/wcfsetup/install/files/lib/form/RegisterForm.class.php index ffb0c8521c..8b2da8ef5c 100644 --- a/wcfsetup/install/files/lib/form/RegisterForm.class.php +++ b/wcfsetup/install/files/lib/form/RegisterForm.class.php @@ -34,11 +34,6 @@ use wcf\util\UserRegistrationUtil; * @category Community Framework */ class RegisterForm extends UserAddForm { - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * true if external authentication is used * @var boolean diff --git a/wcfsetup/install/files/lib/form/SettingsForm.class.php b/wcfsetup/install/files/lib/form/SettingsForm.class.php index 9f745c8bd8..f8dfb7e4e2 100644 --- a/wcfsetup/install/files/lib/form/SettingsForm.class.php +++ b/wcfsetup/install/files/lib/form/SettingsForm.class.php @@ -25,11 +25,6 @@ use wcf\util\ArrayUtil; * @category Community Framework */ class SettingsForm extends AbstractForm { - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * @inheritDoc */ diff --git a/wcfsetup/install/files/lib/form/SignatureEditForm.class.php b/wcfsetup/install/files/lib/form/SignatureEditForm.class.php index 824cfeff1f..02758507cf 100644 --- a/wcfsetup/install/files/lib/form/SignatureEditForm.class.php +++ b/wcfsetup/install/files/lib/form/SignatureEditForm.class.php @@ -18,11 +18,6 @@ use wcf\system\WCF; * @category Community Framework */ class SignatureEditForm extends MessageForm { - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * @inheritDoc */ diff --git a/wcfsetup/install/files/lib/page/AbstractPage.class.php b/wcfsetup/install/files/lib/page/AbstractPage.class.php index a8e2d5c493..7ebd0f9419 100644 --- a/wcfsetup/install/files/lib/page/AbstractPage.class.php +++ b/wcfsetup/install/files/lib/page/AbstractPage.class.php @@ -24,7 +24,7 @@ use wcf\util\StringUtil; * @subpackage page * @category Community Framework */ -abstract class AbstractPage implements IPage, ITrackablePage { +abstract class AbstractPage implements IPage { /** * name of the active menu item * @var string @@ -43,12 +43,6 @@ abstract class AbstractPage implements IPage, ITrackablePage { */ public $canonicalURL = ''; - /** - * enables the tracking of this page - * @var boolean - */ - public $enableTracking = false; - /** * is true if canonical URL will be enforced even if POST data is represent * @var boolean @@ -324,46 +318,4 @@ abstract class AbstractPage implements IPage, ITrackablePage { } } } - - /** - * @inheritDoc - */ - public function isTracked() { - return $this->enableTracking; - } - - /** - * @inheritDoc - */ - public function getController() { - return get_class($this); - } - - /** - * @inheritDoc - */ - public function getParentObjectType() { - return ''; - } - - /** - * @inheritDoc - */ - public function getParentObjectID() { - return 0; - } - - /** - * @inheritDoc - */ - public function getObjectType() { - return ''; - } - - /** - * @inheritDoc - */ - public function getObjectID() { - return 0; - } } diff --git a/wcfsetup/install/files/lib/page/CmsPage.class.php b/wcfsetup/install/files/lib/page/CmsPage.class.php index 9485cd32e4..44874fa4f9 100644 --- a/wcfsetup/install/files/lib/page/CmsPage.class.php +++ b/wcfsetup/install/files/lib/page/CmsPage.class.php @@ -23,11 +23,6 @@ class CmsPage extends AbstractPage { */ public $content; - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * @var integer */ @@ -91,11 +86,4 @@ class CmsPage extends AbstractPage { 'pageID' => $this->pageID ]); } - - /** - * @inheritDoc - */ - public function getObjectType() { - return $this->page ? $this->page->identifier : ''; - } } diff --git a/wcfsetup/install/files/lib/page/ITrackablePage.class.php b/wcfsetup/install/files/lib/page/ITrackablePage.class.php index a7aeb8182c..819240ece6 100644 --- a/wcfsetup/install/files/lib/page/ITrackablePage.class.php +++ b/wcfsetup/install/files/lib/page/ITrackablePage.class.php @@ -10,6 +10,7 @@ namespace wcf\page; * @package com.woltlab.wcf * @subpackage page * @category Community Framework + * @deprecated 2.2 */ interface ITrackablePage { /** diff --git a/wcfsetup/install/files/lib/page/MembersListPage.class.php b/wcfsetup/install/files/lib/page/MembersListPage.class.php index 0f09a63b3e..604f59d032 100644 --- a/wcfsetup/install/files/lib/page/MembersListPage.class.php +++ b/wcfsetup/install/files/lib/page/MembersListPage.class.php @@ -39,11 +39,6 @@ class MembersListPage extends SortablePage { */ public $neededModules = ['MODULE_MEMBERS_LIST']; - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * @inheritDoc */ diff --git a/wcfsetup/install/files/lib/page/TeamPage.class.php b/wcfsetup/install/files/lib/page/TeamPage.class.php index ae65ee5063..812d4739f9 100644 --- a/wcfsetup/install/files/lib/page/TeamPage.class.php +++ b/wcfsetup/install/files/lib/page/TeamPage.class.php @@ -27,11 +27,6 @@ class TeamPage extends MultipleLinkPage { */ public $neededModules = ['MODULE_TEAM_PAGE']; - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * @inheritDoc */ diff --git a/wcfsetup/install/files/lib/page/UserPage.class.php b/wcfsetup/install/files/lib/page/UserPage.class.php index 78d3d67f97..a0cae9c944 100644 --- a/wcfsetup/install/files/lib/page/UserPage.class.php +++ b/wcfsetup/install/files/lib/page/UserPage.class.php @@ -29,11 +29,6 @@ use wcf\system\WCF; * @category Community Framework */ class UserPage extends AbstractPage { - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * edit profile on page load * @var boolean @@ -200,18 +195,4 @@ class UserPage extends AbstractPage { parent::show(); } - - /** - * @inheritDoc - */ - public function getObjectType() { - return 'com.woltlab.wcf.user'; - } - - /** - * @inheritDoc - */ - public function getObjectID() { - return $this->userID; - } } diff --git a/wcfsetup/install/files/lib/page/UsersOnlineListPage.class.php b/wcfsetup/install/files/lib/page/UsersOnlineListPage.class.php index 2527b5d6f1..826c92e535 100644 --- a/wcfsetup/install/files/lib/page/UsersOnlineListPage.class.php +++ b/wcfsetup/install/files/lib/page/UsersOnlineListPage.class.php @@ -27,11 +27,6 @@ class UsersOnlineListPage extends SortablePage { */ public $neededPermissions = ['user.profile.canViewUsersOnlineList']; - /** - * @inheritDoc - */ - public $enableTracking = true; - /** * @inheritDoc */ @@ -113,7 +108,7 @@ class UsersOnlineListPage extends SortablePage { // cache all necessary data for showing locations foreach ($this->objectList as $userOnline) { if ($userOnline->controller) { - $page = PageCache::getInstance()->getPageByController($userOnline->controller); + $page = PageCache::getInstance()->getPage($userOnline->pageID); if ($page !== null && $page->getHandler() !== null && $page->getHandler() instanceof IOnlineLocationPageHandler) { $page->getHandler()->prepareOnlineLocation($page, $userOnline); } diff --git a/wcfsetup/install/files/lib/system/exception/NamedUserException.class.php b/wcfsetup/install/files/lib/system/exception/NamedUserException.class.php index fb31c44da7..50e0cab3fa 100644 --- a/wcfsetup/install/files/lib/system/exception/NamedUserException.class.php +++ b/wcfsetup/install/files/lib/system/exception/NamedUserException.class.php @@ -1,5 +1,6 @@ disableTracking(); + WCF::getTPL()->assign([ 'name' => get_class($this), 'file' => $this->getFile(), diff --git a/wcfsetup/install/files/lib/system/exception/PermissionDeniedException.class.php b/wcfsetup/install/files/lib/system/exception/PermissionDeniedException.class.php index b707b413ef..9f4718a584 100644 --- a/wcfsetup/install/files/lib/system/exception/PermissionDeniedException.class.php +++ b/wcfsetup/install/files/lib/system/exception/PermissionDeniedException.class.php @@ -1,5 +1,6 @@ disableTracking(); + @header('HTTP/1.0 403 Forbidden'); WCF::getTPL()->assign([ diff --git a/wcfsetup/install/files/lib/system/page/PageLocationManager.class.php b/wcfsetup/install/files/lib/system/page/PageLocationManager.class.php index 2bd7323a6c..87f27ff31a 100644 --- a/wcfsetup/install/files/lib/system/page/PageLocationManager.class.php +++ b/wcfsetup/install/files/lib/system/page/PageLocationManager.class.php @@ -69,9 +69,10 @@ class PageLocationManager extends SingletonFactory { * @param string $identifier internal page identifier * @param integer $pageObjectID page object id * @param ITitledLinkObject $locationObject optional label for breadcrumbs usage + * @param boolean $useAsParentLocation * @throws SystemException */ - public function addParentLocation($identifier, $pageObjectID = 0, ITitledLinkObject $locationObject = null) { + public function addParentLocation($identifier, $pageObjectID = 0, ITitledLinkObject $locationObject = null, $useAsParentLocation = false) { $page = PageCache::getInstance()->getPageByIdentifier($identifier); if ($page === null) { throw new SystemException("Unknown page identifier '".$identifier."'."); @@ -98,7 +99,8 @@ class PageLocationManager extends SingletonFactory { 'link' => $link, 'pageID' => $page->pageID, 'pageObjectID' => $pageObjectID, - 'title' => $title + 'title' => $title, + 'useAsParentLocation' => $useAsParentLocation, ]; } diff --git a/wcfsetup/install/files/lib/system/page/handler/UserPageHandler.class.php b/wcfsetup/install/files/lib/system/page/handler/UserPageHandler.class.php index 87982f0f43..37f2299ac8 100644 --- a/wcfsetup/install/files/lib/system/page/handler/UserPageHandler.class.php +++ b/wcfsetup/install/files/lib/system/page/handler/UserPageHandler.class.php @@ -23,11 +23,11 @@ class UserPageHandler extends AbstractMenuPageHandler implements IOnlineLocation * @inheritDoc */ public function getOnlineLocation(Page $page, UserOnline $user) { - if ($user->objectID === null) { + if ($user->pageObjectID === null) { return ''; } - $visitedUser = UserRuntimeCache::getInstance()->getObject($user->objectID); + $visitedUser = UserRuntimeCache::getInstance()->getObject($user->pageObjectID); if ($visitedUser === null) { return ''; } @@ -39,8 +39,8 @@ class UserPageHandler extends AbstractMenuPageHandler implements IOnlineLocation * @inheritDoc */ public function prepareOnlineLocation(Page $page, UserOnline $user) { - if ($user->objectID !== null) { - UserRuntimeCache::getInstance()->cacheObjectID($user->objectID); + if ($user->pageObjectID !== null) { + UserRuntimeCache::getInstance()->cacheObjectID($user->pageObjectID); } } } diff --git a/wcfsetup/install/files/lib/system/session/SessionHandler.class.php b/wcfsetup/install/files/lib/system/session/SessionHandler.class.php index aa421c1e85..1cf6e0a027 100644 --- a/wcfsetup/install/files/lib/system/session/SessionHandler.class.php +++ b/wcfsetup/install/files/lib/system/session/SessionHandler.class.php @@ -9,14 +9,13 @@ use wcf\data\session\virtual\SessionVirtualEditor; use wcf\data\session\SessionEditor; use wcf\data\user\User; use wcf\data\user\UserEditor; -use wcf\page\ITrackablePage; use wcf\system\cache\builder\SpiderCacheBuilder; use wcf\system\cache\builder\UserGroupOptionCacheBuilder; use wcf\system\cache\builder\UserGroupPermissionCacheBuilder; use wcf\system\database\DatabaseException; use wcf\system\event\EventHandler; use wcf\system\exception\PermissionDeniedException; -use wcf\system\request\RequestHandler; +use wcf\system\page\PageLocationManager; use wcf\system\user\authentication\UserAuthenticationFactory; use wcf\system\user\storage\UserStorageHandler; use wcf\system\SingletonFactory; @@ -50,6 +49,12 @@ class SessionHandler extends SingletonFactory { */ protected $doNotUpdate = false; + /** + * disables page tracking + * @var boolean + */ + protected $disableTracking = false; + /** * various environment variables * @var array @@ -292,6 +297,13 @@ class SessionHandler extends SingletonFactory { $this->doNotUpdate = true; } + /** + * Disables page tracking. + */ + public function disableTracking() { + $this->disableTracking = true; + } + /** * Defines global wcf constants related to session. */ @@ -852,12 +864,22 @@ class SessionHandler extends SingletonFactory { 'requestMethod' => $this->requestMethod, 'lastActivityTime' => TIME_NOW ]; - if (!class_exists('wcf\system\CLIWCF', false) && PACKAGE_ID && RequestHandler::getInstance()->getActiveRequest() && RequestHandler::getInstance()->getActiveRequest()->getRequestObject() instanceof ITrackablePage && RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->isTracked()) { - $data['controller'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getController(); - $data['parentObjectType'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getParentObjectType(); - $data['parentObjectID'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getParentObjectID(); - $data['objectType'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getObjectType(); - $data['objectID'] = RequestHandler::getInstance()->getActiveRequest()->getRequestObject()->getObjectID(); + if (!class_exists('wcf\system\CLIWCF', false) && !$this->isACP && !$this->disableTracking) { + $pageLocations = PageLocationManager::getInstance()->getLocations(); + if (isset($pageLocations[0])) { + $data['pageID'] = $pageLocations[0]['pageID']; + $data['pageObjectID'] = ($pageLocations[0]['pageObjectID'] ?: null); + $data['parentPageID'] = null; + $data['parentPageObjectID'] = null; + + for ($i = 1; $i < count($pageLocations); $i++) { + if (!empty($pageLocations[$i]['useAsParentLocation'])) { + $data['parentPageID'] = $pageLocations[$i]['pageID']; + $data['parentPageObjectID'] = ($pageLocations[$i]['pageObjectID'] ?: null); + break; + } + } + } } // update session diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index 29f617b29a..e1075d5dbf 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -69,12 +69,7 @@ CREATE TABLE wcf1_acp_session ( userAgent VARCHAR(255) NOT NULL DEFAULT '', lastActivityTime INT(10) NOT NULL DEFAULT 0, requestURI VARCHAR(255) NOT NULL DEFAULT '', - requestMethod VARCHAR(7) NOT NULL DEFAULT '', - controller VARCHAR(255) NOT NULL DEFAULT '', - parentObjectType VARCHAR(255) NOT NULL DEFAULT '', - parentObjectID INT(10) NOT NULL DEFAULT 0, - objectType VARCHAR(255) NOT NULL DEFAULT '', - objectID INT(10) NOT NULL DEFAULT 0 + requestMethod VARCHAR(7) NOT NULL DEFAULT '' ); DROP TABLE IF EXISTS wcf1_acp_session_access_log; @@ -1043,13 +1038,14 @@ CREATE TABLE wcf1_session ( lastActivityTime INT(10) NOT NULL DEFAULT 0, requestURI VARCHAR(255) NOT NULL DEFAULT '', requestMethod VARCHAR(7) NOT NULL DEFAULT '', - controller VARCHAR(255) NOT NULL DEFAULT '', - parentObjectType VARCHAR(255) NOT NULL DEFAULT '', - parentObjectID INT(10) NOT NULL DEFAULT 0, - objectType VARCHAR(255) NOT NULL DEFAULT '', - objectID INT(10) NOT NULL DEFAULT 0, + pageID INT(10), + pageObjectID INT(10), + parentPageID INT(10), + parentPageObjectID INT(10), spiderID INT(10), KEY packageID (lastActivityTime, spiderID), + KEY pageID (pageID, pageObjectID), + KEY parentPageID (parentPageID, parentPageObjectID), UNIQUE KEY uniqueUserID (userID) ); @@ -1759,6 +1755,8 @@ ALTER TABLE wcf1_search ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) O ALTER TABLE wcf1_session ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE; ALTER TABLE wcf1_session ADD FOREIGN KEY (spiderID) REFERENCES wcf1_spider (spiderID) ON DELETE CASCADE; +ALTER TABLE wcf1_session ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL; +ALTER TABLE wcf1_session ADD FOREIGN KEY (parentPageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL; ALTER TABLE wcf1_session_virtual ADD FOREIGN KEY (sessionID) REFERENCES wcf1_session (sessionID) ON DELETE CASCADE ON UPDATE CASCADE;