<permissions>admin.user.canMailUser</permissions>
<showorder>2</showorder>
</acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.activityPoint">
+ <parent>wcf.acp.menu.link.user.management</parent>
+ <controller><![CDATA[wcf\acp\form\UserActivityPointOptionForm]]></controller>
+ <permissions>admin.user.canEditActivityPoints</permissions>
+ </acpmenuitem>
<!-- /users -->
<!-- user groups -->
</acpmenuitem>
<!-- /user options -->
+ <acpmenuitem name="wcf.acp.menu.link.user.rank">
+ <parent>wcf.acp.menu.link.user</parent>
+ <options>module_user_rank</options>
+ </acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.user.rank.list">
+ <parent>wcf.acp.menu.link.user.rank</parent>
+ <controller><![CDATA[wcf\acp\page\UserRankListPage]]></controller>
+ <permissions>admin.user.rank.canManageRank</permissions>
+ </acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.user.rank.add">
+ <parent>wcf.acp.menu.link.user.rank</parent>
+ <controller><![CDATA[wcf\acp\form\UserRankAddForm]]></controller>
+ <permissions>admin.user.rank.canManageRank</permissions>
+ </acpmenuitem>
+
<acpmenuitem name="wcf.acp.menu.link.display">
<showorder>3</showorder>
</acpmenuitem>
<parent>wcf.acp.menu.link.content</parent>
</acpmenuitem>
+ <acpmenuitem name="wcf.acp.menu.link.attachment.list">
+ <controller><![CDATA[wcf\acp\page\AttachmentListPage]]></controller>
+ <parent>wcf.acp.menu.link.attachment</parent>
+ <permissions>admin.attachment.canManageAttachment</permissions>
+ </acpmenuitem>
+
<acpmenuitem name="wcf.acp.menu.link.bbcode">
<parent>wcf.acp.menu.link.content</parent>
</acpmenuitem>
<showorder>4</showorder>
</acpmenuitem>
- <acpmenuitem name="wcf.acp.menu.link.attachment.list">
- <controller><![CDATA[wcf\acp\page\AttachmentListPage]]></controller>
- <parent>wcf.acp.menu.link.attachment</parent>
- <permissions>admin.attachment.canManageAttachment</permissions>
+ <acpmenuitem name="wcf.acp.menu.link.dashboard">
+ <parent>wcf.acp.menu.link.content</parent>
+ </acpmenuitem>
+
+ <acpmenuitem name="wcf.acp.menu.link.dashboard.list">
+ <parent>wcf.acp.menu.link.dashboard</parent>
+ <controller><![CDATA[wcf\acp\page\DashboardListPage]]></controller>
+ <permissions>admin.content.dashboard.canEditDashboard</permissions>
</acpmenuitem>
<acpmenuitem name="wcf.acp.menu.link.community">
<page><![CDATA[wcf\acp\page\UserListPage]]></page>
</pages>
</action>
+
+ <action name="merge">
+ <actionclassname><![CDATA[wcf\system\clipboard\action\UserExtendedClipboardAction]]></actionclassname>
+ <showorder>4</showorder>
+ <pages>
+ <page><![CDATA[wcf\acp\page\UserListPage]]></page>
+ </pages>
+ </action>
+ <action name="enable">
+ <actionclassname><![CDATA[wcf\system\clipboard\action\UserExtendedClipboardAction]]></actionclassname>
+ <showorder>5</showorder>
+ <pages>
+ <page><![CDATA[wcf\acp\page\UserListPage]]></page>
+ </pages>
+ </action>
</import>
</data>
<coreobject>
<objectname><![CDATA[wcf\system\bbcode\BBCodeHandler]]></objectname>
</coreobject>
+
+ <coreobject>
+ <objectname><![CDATA[wcf\system\user\UserProfileHandler]]></objectname>
+ </coreobject>
+
+ <coreobject>
+ <objectname><![CDATA[wcf\system\menu\user\profile\UserProfileMenu]]></objectname>
+ </coreobject>
+
+ <coreobject>
+ <objectname><![CDATA[wcf\system\menu\user\UserMenu]]></objectname>
+ </coreobject>
+
+ <coreobject>
+ <objectname><![CDATA[wcf\system\user\notification\UserNotificationHandler]]></objectname>
+ </coreobject>
+
+ <coreobject>
+ <objectname><![CDATA[wcf\system\user\object\watch\UserObjectWatchHandler]]></objectname>
+ </coreobject>
</import>
</data>
<canbeedited>1</canbeedited>
<canbedisabled>1</canbedisabled>
</cronjob>
+
+ <cronjob>
+ <classname>wcf\system\cronjob\LastActivityCronjob</classname>
+ <description><![CDATA[Updates last activity timestamp]]></description>
+ <description language="de"><![CDATA[Aktualisiert Zeitpunkt der letzten Aktivität]]></description>
+ <startminute>*/5</startminute>
+ <starthour>*</starthour>
+ <startdom>*</startdom>
+ <startmonth>*</startmonth>
+ <startdow>*</startdow>
+ <active>1</active>
+ <canbeedited>1</canbeedited>
+ <canbedisabled>1</canbedisabled>
+ </cronjob>
+
+ <cronjob>
+ <classname>wcf\system\cronjob\UserQuitCronjob</classname>
+ <description><![CDATA[Deletes canceled user accounts]]></description>
+ <description language="de"><![CDATA[Löscht gekündigte Benutzer-Accounts]]></description>
+ <startminute>0</startminute>
+ <starthour>0</starthour>
+ <startdom>*</startdom>
+ <startmonth>*</startmonth>
+ <startdow>*</startdow>
+ <active>1</active>
+ <canbeedited>1</canbeedited>
+ <canbedisabled>1</canbedisabled>
+ </cronjob>
+
+ <cronjob>
+ <classname>wcf\system\cronjob\DailyMailNotificationCronjob</classname>
+ <description><![CDATA[Sends daily mail notifications]]></description>
+ <description language="de"><![CDATA[Versendet tägliche E-Mail-Benachrichtigungen]]></description>
+ <startminute>*/30</startminute>
+ <starthour>*</starthour>
+ <startdom>*</startdom>
+ <startmonth>*</startmonth>
+ <startdow>*</startdow>
+ <active>1</active>
+ <canbeedited>1</canbeedited>
+ <canbedisabled>1</canbedisabled>
+ </cronjob>
</import>
</data>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/dashboardBox.xsd">
+ <import>
+ <dashboardbox name="com.woltlab.wcf.user.recentActivity">
+ <classname><![CDATA[wcf\system\dashboard\box\RecentActivityDashboardBox]]></classname>
+ <boxtype>content</boxtype>
+ </dashboardbox>
+
+ <dashboardbox name="com.woltlab.wcf.user.recentActivitySidebar">
+ <classname><![CDATA[wcf\system\dashboard\box\RecentActivitySidebarDashboardBox]]></classname>
+ <boxtype>sidebar</boxtype>
+ </dashboardbox>
+
+ <dashboardbox name="com.woltlab.wcf.user.registerButton">
+ <classname><![CDATA[wcf\system\dashboard\box\RegisterButtonDashboardBox]]></classname>
+ <boxtype>sidebar</boxtype>
+ </dashboardbox>
+
+ <dashboardbox name="com.woltlab.wcf.user.signedInAs">
+ <classname><![CDATA[wcf\system\dashboard\box\SignedInAsDashboardBox]]></classname>
+ <boxtype>sidebar</boxtype>
+ </dashboardbox>
+
+ <dashboardbox name="com.woltlab.wcf.user.statsSidebar">
+ <classname><![CDATA[wcf\system\dashboard\box\StatsSidebarDashboardBox]]></classname>
+ <boxtype>sidebar</boxtype>
+ </dashboardbox>
+
+ <dashboardbox name="com.woltlab.wcf.user.newestMembers">
+ <classname><![CDATA[wcf\system\dashboard\box\NewestMembersDashboardBox]]></classname>
+ <boxtype>sidebar</boxtype>
+ </dashboardbox>
+
+ <dashboardbox name="com.woltlab.wcf.user.mostActiveMembers">
+ <classname><![CDATA[wcf\system\dashboard\box\MostActiveMembersDashboardBox]]></classname>
+ <boxtype>sidebar</boxtype>
+ </dashboardbox>
+ </import>
+</data>
<inherit>1</inherit>
<listenerclassname>wcf\system\event\listener\SessionAccessLogListener</listenerclassname>
</eventlistener>
+ <eventlistener>
+ <eventclassname>wcf\system\bbcode\PreParser</eventclassname>
+ <eventname>beforeParsing</eventname>
+ <listenerclassname>wcf\system\event\listener\PreParserAtUserListener</listenerclassname>
+ <environment>user</environment>
+ </eventlistener>
</import>
</data>
\ No newline at end of file
<definitionname>com.woltlab.wcf.category</definitionname>
<classname>wcf\system\category\SmileyCategoryType</classname>
</type>
+
+ <type>
+ <name>com.woltlab.wcf.user.follow</name>
+ <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+ <classname>wcf\system\user\notification\object\type\UserFollowUserNotificationObjectType</classname>
+ <category>com.woltlab.wcf.user</category>
+ </type>
+
+ <type>
+ <name>com.woltlab.wcf.user.recentActivityEvent.follow</name>
+ <definitionname>com.woltlab.wcf.user.recentActivityEvent</definitionname>
+ <classname>wcf\system\user\activity\event\FollowUserActivityEvent</classname>
+ </type>
+
+ <type>
+ <name>com.woltlab.wcf.user.DashboardPage</name>
+ <definitionname>com.woltlab.wcf.user.dashboardContainer</definitionname>
+ <allowcontent>1</allowcontent>
+ <allowsidebar>1</allowsidebar>
+ </type>
+
+ <type>
+ <name>com.woltlab.wcf.user.MembersListPage</name>
+ <definitionname>com.woltlab.wcf.user.dashboardContainer</definitionname>
+ <allowcontent>0</allowcontent>
+ <allowsidebar>1</allowsidebar>
+ </type>
+
+ <!-- user online locations -->
+ <type>
+ <name>com.woltlab.wcf.user.UserPage</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <classname>wcf\system\user\online\location\UserLocation</classname>
+ <controller>wcf\page\UserPage</controller>
+ <languagevariable>wcf.user.usersOnline.location.UserPage</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.UsersOnlineListPage</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\page\UsersOnlineListPage</controller>
+ <languagevariable>wcf.user.usersOnline.location.UsersOnlineListPage</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.MembersListPage</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\page\MembersListPage</controller>
+ <languagevariable>wcf.user.usersOnline.location.MembersListPage</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.TeamPage</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\page\TeamPage</controller>
+ <languagevariable>wcf.user.usersOnline.location.TeamPage</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.DashboardPage</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\page\DashboardPage</controller>
+ <languagevariable>wcf.user.usersOnline.location.DashboardPage</languagevariable>
+ </type>
+
+ <type>
+ <name>com.woltlab.wcf.user.RegisterForm</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\form\RegisterForm</controller>
+ <languagevariable>wcf.user.usersOnline.location.RegisterForm</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.LostPasswordForm</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\form\LostPasswordForm</controller>
+ <languagevariable>wcf.user.usersOnline.location.LostPasswordForm</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.NewPasswordForm</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\form\NewPasswordForm</controller>
+ <languagevariable>wcf.user.usersOnline.location.LostPasswordForm</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.LoginForm</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\form\LoginForm</controller>
+ <languagevariable>wcf.user.usersOnline.location.LoginForm</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.AccountManagementForm</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\form\AccountManagementForm</controller>
+ <languagevariable>wcf.user.usersOnline.location.AccountManagementForm</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.AvatarEditForm</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\form\AvatarEditForm</controller>
+ <languagevariable>wcf.user.usersOnline.location.AvatarEditForm</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.SettingsForm</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\form\SettingsForm</controller>
+ <languagevariable>wcf.user.usersOnline.location.SettingsForm</languagevariable>
+ </type>
+ <type>
+ <name>com.woltlab.wcf.user.SignatureEditForm</name>
+ <definitionname>com.woltlab.wcf.user.online.location</definitionname>
+ <controller>wcf\form\SignatureEditForm</controller>
+ <languagevariable>wcf.user.usersOnline.location.SignatureEditForm</languagevariable>
+ </type>
</import>
</data>
\ No newline at end of file
<name>com.woltlab.wcf.message.quote</name>
<interfacename>wcf\system\message\IMessageQuoteHandler</interfacename>
</definition>
+
+ <definition>
+ <name>com.woltlab.wcf.user.recentActivityEvent</name>
+ <interfacename>wcf\system\user\activity\event\IUserActivityEvent</interfacename>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.user.activityPointEvent</name>
+ <interfacename>wcf\system\user\activity\point\IUserActivityPointObjectProcessor</interfacename>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.notification.notificationType</name>
+ <interfacename>wcf\system\user\notification\type\IUserNotificationType</interfacename>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.notification.objectType</name>
+ <interfacename>wcf\system\user\notification\object\type\IUserNotificationObjectType</interfacename>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.user.dashboardContainer</name>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.user.online.location</name>
+ <interfacename>wcf\system\user\online\location\IUserOnlineLocation</interfacename>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.user.objectWatch</name>
+ <interfacename>wcf\system\user\object\watch\IUserObjectWatch</interfacename>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.visitTracker.objectType</name>
+ <!-- <interfacename>wcf\system\visitTracker\IVisitTrackerObjectType</interfacename>-->
+ </definition>
</import>
</data>
<showorder>3</showorder>
</category>
- <category name="user.general">
- <parent>user</parent>
- </category>
+ <category name="user.general">
+ <parent>user</parent>
+ </category>
+
+ <category name="user.profile">
+ <parent>user</parent>
+ </category>
+ <category name="user.avatar">
+ <parent>user.profile</parent>
+ </category>
+ <category name="user.signature">
+ <parent>user.profile</parent>
+ </category>
+ <category name="user.title">
+ <parent>user.profile</parent>
+ </category>
+ <category name="user.cleanup">
+ <parent>user.profile</parent>
+ </category>
+
+ <category name="user.list">
+ <parent>user</parent>
+ </category>
+ <category name="user.list.members">
+ <parent>user.list</parent>
+ </category>
+ <category name="user.list.online">
+ <parent>user.list</parent>
+ </category>
+
+ <category name="user.register">
+ <parent>user</parent>
+ </category>
+ <category name="user.3rdPartyAuth">
+ <parent>user.register</parent>
+ </category>
+ <category name="user.password">
+ <parent>user.register</parent>
+ </category>
+ <category name="user.ban">
+ <parent>user.register</parent>
+ </category>
<!-- /user -->
<!-- security -->
<options>module_attachment</options>
</category>
<!-- /message -->
+
+ <category name="dashboard">
+ </category>
+ <category name="dashboard.content">
+ <parent>dashboard</parent>
+ </category>
+ <category name="dashboard.content.recentActivities">
+ <parent>dashboard.content</parent>
+ </category>
+
+ <category name="dashboard.sidebar">
+ <parent>dashboard</parent>
+ </category>
+ <category name="dashboard.sidebar.recentActivities">
+ <parent>dashboard.sidebar</parent>
+ </category>
</categories>
<options>
<defaultvalue>1</defaultvalue>
</option>
+ <option name="module_gravatar">
+ <categoryname>module.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="module_users_online">
+ <categoryname>module.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="module_user_rank">
+ <categoryname>module.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="module_user_signature">
+ <categoryname>module.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="module_team_page">
+ <categoryname>module.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="module_dashboard_page">
+ <categoryname>module.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+
<!-- general.page -->
<option name="page_title">
<categoryname>general.page</categoryname>
<optiontype>textarea</optiontype>
</option>
<!-- /message.censorship -->
+
+ <option name="register_enable_password_security_check">
+ <categoryname>user.password</categoryname>
+ <optiontype>boolean</optiontype>
+ <enableoptions><![CDATA[register_password_min_length,register_password_must_contain_lower_case,register_password_must_contain_upper_case,register_password_must_contain_digit,register_password_must_contain_special_char]]></enableoptions>
+ </option>
+ <option name="register_password_min_length">
+ <categoryname>user.password</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue><![CDATA[8]]></defaultvalue>
+ </option>
+ <option name="register_password_must_contain_lower_case">
+ <categoryname>user.password</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ </option>
+ <option name="register_password_must_contain_upper_case">
+ <categoryname>user.password</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ </option>
+ <option name="register_password_must_contain_digit">
+ <categoryname>user.password</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ </option>
+ <option name="register_password_must_contain_special_char">
+ <categoryname>user.password</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ </option>
+ <!-- /user.password -->
+
+ <!-- user.ban -->
+ <option name="register_forbidden_usernames">
+ <categoryname>user.ban</categoryname>
+ <optiontype>textarea</optiontype>
+ </option>
+ <option name="register_forbidden_emails">
+ <categoryname>user.ban</categoryname>
+ <optiontype>textarea</optiontype>
+ </option>
+ <option name="register_allowed_emails">
+ <categoryname>user.ban</categoryname>
+ <optiontype>textarea</optiontype>
+ </option>
+ <!-- /user.ban -->
+
+ <!-- user.register -->
+ <option name="register_username_min_length">
+ <categoryname>user.register</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>3</defaultvalue>
+ <minvalue>3</minvalue>
+ <maxvalue>255</maxvalue>
+ </option>
+ <option name="register_username_max_length">
+ <categoryname>user.register</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>25</defaultvalue>
+ <minvalue>3</minvalue>
+ <maxvalue>255</maxvalue>
+ </option>
+ <option name="register_username_force_ascii">
+ <categoryname>user.register</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+
+ <option name="register_disabled">
+ <categoryname>user.register</categoryname>
+ <optiontype>boolean</optiontype>
+ <enableoptions><![CDATA[!register_enable_disclaimer,!register_admin_notification,!register_activation_method]]></enableoptions>
+ </option>
+ <option name="register_enable_disclaimer">
+ <categoryname>user.register</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ </option>
+ <option name="register_admin_notification">
+ <categoryname>user.register</categoryname>
+ <optiontype>boolean</optiontype>
+ </option>
+ <option name="register_activation_method">
+ <categoryname>user.register</categoryname>
+ <optiontype>radioButton</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ <selectoptions><![CDATA[0:wcf.acp.option.register_activation_method.disabled
+1:wcf.acp.option.register_activation_method.byUser
+2:wcf.acp.option.register_activation_method.byAdmin]]></selectoptions>
+ </option>
+ <!-- /user.register -->
+
+ <!-- user.3rdPartyAuth -->
+ <option name="github_public_key">
+ <categoryname>user.3rdPartyAuth</categoryname>
+ <optiontype>text</optiontype>
+ <defaultvalue></defaultvalue>
+ </option>
+ <option name="github_private_key">
+ <categoryname>user.3rdPartyAuth</categoryname>
+ <optiontype>text</optiontype>
+ <defaultvalue></defaultvalue>
+ </option>
+ <option name="twitter_public_key">
+ <categoryname>user.3rdPartyAuth</categoryname>
+ <optiontype>text</optiontype>
+ <defaultvalue></defaultvalue>
+ </option>
+ <option name="twitter_private_key">
+ <categoryname>user.3rdPartyAuth</categoryname>
+ <optiontype>text</optiontype>
+ <defaultvalue></defaultvalue>
+ </option>
+ <option name="facebook_public_key">
+ <categoryname>user.3rdPartyAuth</categoryname>
+ <optiontype>text</optiontype>
+ <defaultvalue></defaultvalue>
+ </option>
+ <option name="facebook_private_key">
+ <categoryname>user.3rdPartyAuth</categoryname>
+ <optiontype>text</optiontype>
+ <defaultvalue></defaultvalue>
+ </option>
+ <option name="google_public_key">
+ <categoryname>user.3rdPartyAuth</categoryname>
+ <optiontype>text</optiontype>
+ <defaultvalue></defaultvalue>
+ </option>
+ <option name="google_private_key">
+ <categoryname>user.3rdPartyAuth</categoryname>
+ <optiontype>text</optiontype>
+ <defaultvalue></defaultvalue>
+ </option>
+ <!-- /user.3rdPartyAuth -->
+
+ <!-- user.security -->
+ <option name="register_use_captcha">
+ <categoryname>security.antispam</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ </option>
+ <option name="lost_password_use_captcha">
+ <categoryname>security.antispam</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ </option>
+ <option name="profile_mail_use_captcha">
+ <categoryname>security.antispam</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue><![CDATA[1]]></defaultvalue>
+ </option>
+ <!-- /user.security -->
+
+ <!-- user.avatar -->
+ <option name="max_avatar_width">
+ <categoryname>user.avatar</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>192</defaultvalue>
+ <minvalue>48</minvalue>
+ </option>
+ <option name="max_avatar_height">
+ <categoryname>user.avatar</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>192</defaultvalue>
+ <minvalue>48</minvalue>
+ </option>
+ <!-- /user.avatar -->
+
+ <!-- user.signature -->
+ <option name="signature_max_image_height">
+ <categoryname>user.signature</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>150</defaultvalue>
+ </option>
+ <!-- /user.signature -->
+
+ <!-- user.title -->
+ <option name="user_title_max_length">
+ <categoryname>user.title</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>25</defaultvalue>
+ <options>module_user_rank</options>
+ <minvalue>0</minvalue>
+ </option>
+ <option name="user_forbidden_titles">
+ <categoryname>user.title</categoryname>
+ <optiontype>textarea</optiontype>
+ <options>module_user_rank</options>
+ </option>
+ <!-- /user.title -->
+
+ <!-- user.profile -->
+ <option name="profile_show_old_username">
+ <categoryname>user.profile</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>182</defaultvalue>
+ <minvalue>0</minvalue>
+ </option>
+ <!-- /user.profile -->
+
+ <!-- user.list.members -->
+ <option name="members_list_users_per_page">
+ <categoryname>user.list.members</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>30</defaultvalue>
+ </option>
+ <option name="members_list_default_sort_field">
+ <categoryname>user.list.members</categoryname>
+ <optiontype>select</optiontype>
+ <defaultvalue><![CDATA[username]]></defaultvalue>
+ <selectoptions><![CDATA[username:wcf.user.username
+registrationDate:wcf.user.registrationDate
+activityPoints:wcf.user.activityPoint]]></selectoptions>
+ </option>
+ <option name="members_list_default_sort_order">
+ <categoryname>user.list.members</categoryname>
+ <optiontype>select</optiontype>
+ <defaultvalue><![CDATA[DESC]]></defaultvalue>
+ <selectoptions><![CDATA[ASC:wcf.global.sortOrder.ascending
+DESC:wcf.global.sortOrder.descending]]></selectoptions>
+ </option>
+ <!-- /user.list.members -->
+
+ <!-- user.list.online -->
+ <option name="users_online_show_guests">
+ <categoryname>user.list.online</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="users_online_show_robots">
+ <categoryname>user.list.online</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="users_online_default_sort_field">
+ <categoryname>user.list.online</categoryname>
+ <optiontype>select</optiontype>
+ <defaultvalue><![CDATA[lastActivityTime]]></defaultvalue>
+ <selectoptions><![CDATA[username:wcf.user.username
+lastActivityTime:wcf.user.usersOnline.lastActivity
+requestURI:wcf.user.usersOnline.location]]></selectoptions>
+ </option>
+ <option name="users_online_default_sort_order">
+ <categoryname>user.list.online</categoryname>
+ <optiontype>select</optiontype>
+ <defaultvalue><![CDATA[DESC]]></defaultvalue>
+ <selectoptions><![CDATA[ASC:wcf.global.sortOrder.ascending
+DESC:wcf.global.sortOrder.descending]]></selectoptions>
+ </option>
+ <option name="users_online_page_refresh">
+ <categoryname>user.list.online</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <minvalue>0</minvalue>
+ </option>
+ <!-- /user.list.online -->
+
+ <!-- user.cleanup -->
+ <option name="user_cleanup_notification_lifetime">
+ <categoryname>user.cleanup</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>60</defaultvalue>
+ <minvalue>0</minvalue>
+ </option>
+ <option name="user_cleanup_activity_event_lifetime">
+ <categoryname>user.cleanup</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>60</defaultvalue>
+ <minvalue>0</minvalue>
+ </option>
+ <option name="user_cleanup_profile_visitor_lifetime">
+ <categoryname>user.cleanup</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>60</defaultvalue>
+ <minvalue>0</minvalue>
+ </option>
+ <!-- /user.cleanup -->
+
+ <!-- dashboard -->
+ <option name="recent_activity_items">
+ <categoryname>dashboard.content.recentActivities</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>10</defaultvalue>
+ </option>
+
+ <option name="recent_activity_sidebar_items">
+ <categoryname>dashboard.sidebar.recentActivities</categoryname>
+ <optiontype>integer</optiontype>
+ <defaultvalue>5</defaultvalue>
+ </option>
+ <!-- /dashboard -->
</options>
</import>
</data>
<instruction type="objectType">objectType.xml</instruction>
<instruction type="acpSearchProvider">acpSearchProvider.xml</instruction>
<instruction type="style">woltlab-basic-style.tgz</instruction>
+ <instruction type="userOption">userOption.xml</instruction>
<instruction type="bbcode">bbcode.xml</instruction>
- <instruction type="smiley">smiley.xml</instruction>
+ <instruction type="smiley">smiley.xml</instruction>
+ <instruction type="pageMenu">pageMenu.xml</instruction>
+ <instruction type="sitemap">sitemap.xml</instruction>
+ <instruction type="dashboardBox">dashboardBox.xml</instruction>
+ <instruction type="userProfileMenu">userProfileMenu.xml</instruction>
+ <instruction type="userMenu">userMenu.xml</instruction>
+ <instruction type="userNotificationEvent">userNotificationEvent.xml</instruction>
</instructions>
<instructions type="update" fromversion="2.0.0 Alpha 1">
<pip name="style">wcf\system\package\plugin\StylePackageInstallationPlugin</pip>
<pip name="bbcode">wcf\system\package\plugin\BBCodePackageInstallationPlugin</pip>
<pip name="smiley">wcf\system\package\plugin\SmileyPackageInstallationPlugin</pip>
+ <pip name="userProfileMenu">wcf\system\package\plugin\UserProfileMenuPackageInstallationPlugin</pip>
+ <pip name="userMenu">wcf\system\package\plugin\UserMenuPackageInstallationPlugin</pip>
+ <pip name="userNotificationEvent">wcf\system\package\plugin\UserNotificationEventPackageInstallationPlugin</pip>
+ <pip name="dashboardBox">wcf\system\package\plugin\DashboardBoxPackageInstallationPlugin</pip>
</import>
</data>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/pageMenu.xsd">
+ <import>
+ <pagemenuitem name="wcf.user.dashboard">
+ <controller><![CDATA[wcf\page\DashboardPage]]></controller>
+ <position>header</position>
+ <options>module_dashboard_page</options>
+ </pagemenuitem>
+
+ <pagemenuitem name="wcf.user.members">
+ <controller><![CDATA[wcf\page\MembersListPage]]></controller>
+ <position>header</position>
+ <permissions>user.profile.canViewMembersList</permissions>
+ </pagemenuitem>
+
+ <pagemenuitem name="wcf.user.recentActivity">
+ <controller><![CDATA[wcf\page\RecentActivityListPage]]></controller>
+ <position>header</position>
+ <parent>wcf.user.members</parent>
+ </pagemenuitem>
+
+ <pagemenuitem name="wcf.user.usersOnline">
+ <controller><![CDATA[wcf\page\UsersOnlineListPage]]></controller>
+ <position>header</position>
+ <parent>wcf.user.members</parent>
+ <permissions>user.profile.canViewUsersOnlineList</permissions>
+ <options>module_users_online</options>
+ </pagemenuitem>
+
+ <pagemenuitem name="wcf.user.team">
+ <controller><![CDATA[wcf\page\TeamPage]]></controller>
+ <position>header</position>
+ <parent>wcf.user.members</parent>
+ <permissions>user.profile.canViewMembersList</permissions>
+ <options>module_team_page</options>
+ </pagemenuitem>
+
+ <pagemenuitem name="wcf.user.search">
+ <controller><![CDATA[wcf\form\UserSearchForm]]></controller>
+ <position>header</position>
+ <parent>wcf.user.members</parent>
+ <permissions>user.profile.canViewMembersList</permissions>
+ </pagemenuitem>
+ </import>
+</data>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/sitemap.xsd">
+ <import>
+ <sitemap name="userAccount">
+ <classname><![CDATA[wcf\system\sitemap\UserAccountSitemapProvider]]></classname>
+ </sitemap>
+ </import>
+</data>
--- /dev/null
+{if SIGNATURE_MAX_IMAGE_HEIGHT}
+ <style type="text/css">
+ .messageSignature img {
+ max-height: {@SIGNATURE_MAX_IMAGE_HEIGHT}px;
+ }
+ </style>
+{/if}
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.accountManagement{/lang} - {lang}wcf.user.usercp{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.accountManagement{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<p class="warning">{lang}wcf.user.accountManagement.warning{/lang}</p>
+
+{if $success|isset && $success|count > 0}
+ <div class="success">
+ {foreach from=$success item=successMessage}
+ <p>{lang}{@$successMessage}{/lang}</p>
+ {/foreach}
+ </div>
+{/if}
+
+{assign var=__authProvider value=$__wcf->getUserProfileHandler()->getAuthProvider()}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='AccountManagement'}{/link}">
+ <div class="container containerPadding marginTop">
+ {if !$__authProvider}
+ <fieldset>
+ <legend><label for="password">{lang}wcf.user.password{/lang}</label></legend>
+
+ <dl{if $errorField == 'password'} class="formError"{/if}>
+ <dt><label for="password">{lang}wcf.user.password{/lang}</label></dt>
+ <dd>
+ <input type="password" id="password" name="password" value="" required="required" class="medium" />
+ {if $errorField == 'password'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType == 'false'}{lang}wcf.user.password.error.false{/lang}{/if}
+ </small>
+ {/if}
+ <small>{lang}wcf.user.accountManagement.password.description{/lang}</small>
+ </dd>
+ </dl>
+
+ <dl>
+ <dd>
+ <ul class="buttonList">
+ <li><a class="button small" href="{link controller='LostPassword'}{/link}"><span>{lang}wcf.user.lostPassword{/lang}</span></a></li>
+ </ul>
+ </dd>
+ </dl>
+
+ {event name='passwordFields'}
+ </fieldset>
+ {/if}
+
+ {if $__wcf->getSession()->getPermission('user.profile.canRename')}
+ <fieldset>
+ <legend><label for="username">{lang}wcf.user.changeUsername{/lang}</label></legend>
+
+ <dl{if $errorField == 'username'} class="formError"{/if}>
+ <dt><label for="username">{lang}wcf.user.newUsername{/lang}</label></dt>
+ <dd>
+ <input type="text" id="username" name="username" value="{$username}" required="required" pattern="^[^,]{ldelim}{REGISTER_USERNAME_MIN_LENGTH},{REGISTER_USERNAME_MAX_LENGTH}}$" class="medium" />
+
+ {if $errorField == 'username'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType == 'notValid'}{lang}wcf.user.username.error.notValid{/lang}{/if}
+ {if $errorType == 'notUnique'}{lang}wcf.user.username.error.notUnique{/lang}{/if}
+ {if $errorType == 'alreadyRenamed'}{lang}wcf.user.username.error.alreadyRenamed{/lang}{/if}
+ </small>
+ {/if}
+ {if $renamePeriod > 0}
+ <small>{lang}wcf.user.changeUsername.description{/lang}</small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='changeUsernameFields'}
+ </fieldset>
+ {/if}
+
+ {if !$__authProvider}
+ <fieldset>
+ <legend><label for="newPassword">{lang}wcf.user.changePassword{/lang}</label></legend>
+
+ <dl{if $errorField == 'newPassword'} class="formError"{/if}>
+ <dt><label for="newPassword">{lang}wcf.user.newPassword{/lang}</label></dt>
+ <dd>
+ <input type="password" id="newPassword" name="newPassword" value="{$newPassword}" class="medium" />
+
+ {if $errorField == 'newPassword'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType == 'notSecure'}{lang}wcf.user.password.error.notSecure{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'confirmNewPassword'} class="formError"{/if}>
+ <dt><label for="confirmNewPassword">{lang}wcf.user.confirmPassword{/lang}</label></dt>
+ <dd>
+ <input type="password" id="confirmNewPassword" name="confirmNewPassword" value="{$confirmNewPassword}" class="medium" />
+
+ {if $errorField == 'confirmNewPassword'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType == 'notEqual'}{lang}wcf.user.confirmPassword.error.notEqual{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='changePasswordFields'}
+ </fieldset>
+ {/if}
+
+ {if $__wcf->getSession()->getPermission('user.profile.canChangeEmail')}
+ <fieldset>
+ <legend><label for="email">{lang}wcf.user.changeEmail{/lang}</label></legend>
+
+ <dl{if $errorField == 'email'} class="formError"{/if}>
+ <dt><label for="email">{lang}wcf.user.newEmail{/lang}</label></dt>
+ <dd>
+ <input type="email" id="email" name="email" value="{$email}" class="medium" />
+
+ {if $errorField == 'email'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType == 'notValid'}{lang}wcf.user.email.error.notValid{/lang}{/if}
+ {if $errorType == 'notUnique'}{lang}wcf.user.email.error.notUnique{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'confirmEmail'} class="formError"{/if}>
+ <dt><label for="confirmEmail">{lang}wcf.user.confirmEmail{/lang}</label></dt>
+ <dd>
+ <input type="email" id="confirmEmail" name="confirmEmail" value="{$confirmEmail}" class="medium" />
+
+ {if $errorField == 'confirmEmail'}
+ <small class="innerError">
+ {if $errorType == 'notEqual'}{lang}wcf.user.confirmEmail.error.notEqual{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='changeEmailFields'}
+
+ {if REGISTER_ACTIVATION_METHOD == 1 && $__wcf->getUser()->reactivationCode != 0}
+ <dl>
+ <dd>
+ <ul class="buttonList">
+ <li><a class="button small" href="{link controller='EmailActivation'}{/link}"><span>{lang}wcf.user.emailActivation{/lang}</span></a></li>
+ </ul>
+ </dd>
+ </dl>
+ {/if}
+ </fieldset>
+ {/if}
+
+ {if $__wcf->getSession()->getPermission('user.profile.canQuit')}
+ <fieldset>
+ <legend>{lang}wcf.user.quit{/lang}</legend>
+
+ {if $quitStarted}
+ <dl>
+ <dd>
+ <label><input type="checkbox" name="cancelQuit" value="1" {if $cancelQuit == 1}checked="checked" {/if}/> {lang}wcf.user.quit.cancel{/lang}</label>
+ </dd>
+ </dl>
+ {else}
+ <dl>
+ <dd>
+ <label><input type="checkbox" name="quit" value="1" {if $quit == 1}checked="checked" {/if}/> {lang}wcf.user.quit.sure{/lang}</label>
+ <small>{lang}wcf.user.quit.description{/lang}</small>
+ </dd>
+ </dl>
+ {/if}
+
+ {event name='quitFields'}
+ </fieldset>
+ {/if}
+
+ {hascontent}
+ <fieldset id="3rdParty">
+ <legend>{lang}wcf.user.3rdparty{/lang}</legend>
+
+ {content}
+ {if $__authProvider}
+ <dl>
+ <dt>{lang}wcf.user.3rdparty.{@$__authProvider}{/lang}</dt>
+ <dd>
+ <label><input type="checkbox" name="{@$__authProvider}Disconnect" value="1" /> {lang}wcf.user.3rdparty.{@$__authProvider}.disconnect{/lang}</label>
+ </dd>
+ </dl>
+ {else}
+ {if GITHUB_PUBLIC_KEY !== '' && GITHUB_PRIVATE_KEY !== ''}
+ <dl>
+ <dt>{lang}wcf.user.3rdparty.github{/lang}</dt>
+ <dd>
+ {if $__wcf->getSession()->getVar('__githubToken')}
+ <label><input type="checkbox" name="githubConnect" value="1"{if $githubConnect} checked="checked"{/if} /> {lang}wcf.user.3rdparty.github.connect{/lang}</label>
+ {else}
+ <a href="{link controller='GithubAuth'}{/link}" class="button small"><span class="icon icon16 icon-github"></span> <span>{lang}wcf.user.3rdparty.github.connect{/lang}</span></a>
+ {/if}
+ </dd>
+ </dl>
+ {/if}
+
+ {if TWITTER_PUBLIC_KEY !== '' && TWITTER_PRIVATE_KEY !== ''}
+ <dl>
+ <dt>{lang}wcf.user.3rdparty.twitter{/lang}</dt>
+ <dd>
+ {if $__wcf->getSession()->getVar('__twitterData')}
+ <label><input type="checkbox" name="twitterConnect" value="1"{if $twitterConnect} checked="checked"{/if} /> {lang}wcf.user.3rdparty.twitter.connect{/lang}</label>
+ {else}
+ <a href="{link controller='TwitterAuth'}{/link}" class="button small"><span class="icon icon16 icon-twitter"></span> <span>{lang}wcf.user.3rdparty.twitter.connect{/lang}</span></a>
+ {/if}
+ </dd>
+ </dl>
+ {/if}
+
+ {if FACEBOOK_PUBLIC_KEY !== '' && FACEBOOK_PRIVATE_KEY !== ''}
+ <dl>
+ <dt>{lang}wcf.user.3rdparty.facebook{/lang}</dt>
+ <dd>
+ {if $__wcf->getSession()->getVar('__facebookData')}
+ <label><input type="checkbox" name="facebookConnect" value="1"{if $facebookConnect} checked="checked"{/if} /> {lang}wcf.user.3rdparty.facebook.connect{/lang}</label>
+ {else}
+ <a href="{link controller='FacebookAuth'}{/link}" class="button small"><span class="icon icon16 icon-facebook"></span> <span>{lang}wcf.user.3rdparty.facebook.connect{/lang}</span></a>
+ {/if}
+ </dd>
+ </dl>
+ {/if}
+
+ {if GOOGLE_PUBLIC_KEY !== '' && GOOGLE_PRIVATE_KEY !== ''}
+ <dl>
+ <dt>{lang}wcf.user.3rdparty.google{/lang}</dt>
+ <dd>
+ {if $__wcf->getSession()->getVar('__googleData')}
+ <label><input type="checkbox" name="googleConnect" value="1"{if $googleConnect} checked="checked"{/if} /> {lang}wcf.user.3rdparty.google.connect{/lang}</label>
+ {else}
+ <a href="{link controller='GoogleAuth'}{/link}" class="button small"><span class="icon icon16 icon-google-plus"></span> <span>{lang}wcf.user.3rdparty.google.connect{/lang}</span></a>
+ {/if}
+ </dd>
+ </dl>
+ {/if}
+ {/if}
+
+ {event name='3rdpartyFields'}
+ {/content}
+ </fieldset>
+ {/hascontent}
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ {@SECURITY_TOKEN_INPUT_TAG}
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+<fieldset>
+ <legend class="invisible">{lang}wcf.user.author{/lang}</legend>
+
+ <div class="box96 framed">
+ {@$userProfile->getAvatar()->getImageTag(96)}
+
+ <div>
+ <div class="containerHeadline">
+ <h3><a href="{link controller='User' object=$userProfile}{/link}" rel="author">{$userProfile->username}</a></h3>
+ {if MODULE_USER_RANK && $userProfile->getUserTitle()}<p><span class="badge userTitleBadge{if $userProfile->getRank() && $userProfile->getRank()->cssClassName} {@$userProfile->getRank()->cssClassName}{/if}">{$userProfile->getUserTitle()}</span></p>{/if}
+ </div>
+
+ {include file='userInformationStatistics' user=$userProfile}
+ </div>
+ </div>
+</fieldset>
\ No newline at end of file
--- /dev/null
+<dl class="wide">
+ <dt>{lang}wcf.user.avatar.type.custom.crop{/lang}</dt>
+ <dd>
+ <div id="userAvatarCropSelection">
+ {@$avatar->getImageTag()}
+ <div id="userAvatarCropOverlay"></div>
+ <div id="userAvatarCropOverlaySelection"></div>
+ </div>
+
+ <small>{lang}wcf.user.avatar.type.custom.crop.description{/lang}</small>
+ </dd>
+</dl>
+
+<div class="formSubmit">
+ <button data-type="save" class="buttonPrimary">{lang}wcf.global.button.save{/lang}</button>
+</div>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.avatar.edit{/lang} - {lang}wcf.user.usercp{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.avatar.edit{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $__wcf->user->disableAvatar}
+ <p class="error">{lang}wcf.user.avatar.error.disabled{/lang}</p>
+{/if}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.edit{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='AvatarEdit'}{/link}" id="avatarForm">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.avatar{/lang}</legend>
+
+ <dl>
+ <dd>
+ <label><input type="radio" name="avatarType" value="none" {if $avatarType == 'none'}checked="checked" {/if}/> {lang}wcf.user.avatar.type.none{/lang}</label>
+ <small>{lang}wcf.user.avatar.type.none.description{/lang}</small>
+ </dd>
+ </dl>
+
+ <dl class="jsOnly{if $errorField == 'custom'} formError{/if}" id="avatarUpload">
+ <dt class="framed">
+ {if $avatarType == 'custom'}
+ {assign var='__customAvatar' value=$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(96)}
+ {if $__wcf->getUserProfileHandler()->getAvatar()->canCrop()}
+ {assign var='__customAvatar' value=$__customAvatar|substr:0:-2}
+ {assign var='__customAvatarTitle' value='wcf.user.avatar.type.custom.crop'|language}
+ {append var='__customAvatar' value='class="userAvatarCrop jsTooltip" title="'|concat:$__customAvatarTitle:'" />'}
+ {/if}
+ {@$__customAvatar}
+ {else}
+ <img src="{@$__wcf->getPath()}images/avatars/avatar-default.svg" alt="" class="icon96" />
+ {/if}
+ </dt>
+ <dd>
+ <label><input type="radio" name="avatarType" value="custom" {if $avatarType == 'custom'}checked="checked" {/if}/> {lang}wcf.user.avatar.type.custom{/lang}</label>
+ <small>{lang}wcf.user.avatar.type.custom.description{/lang}</small>
+
+ {* placeholder for upload button: *}
+ <div></div>
+
+ {if $errorField == 'custom'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {if MODULE_GRAVATAR}
+ <dl{if $errorField == 'gravatar'} class="formError"{/if}>
+ <dt class="framed"><img src="http://www.gravatar.com/avatar/{@$__wcf->user->email|strtolower|md5}?s=96" alt="" class="icon96" /></dt>
+ <dd>
+ <label><input type="radio" name="avatarType" value="gravatar" {if $avatarType == 'gravatar'}checked="checked" {/if}/> {lang}wcf.user.avatar.type.gravatar{/lang}</label>
+ {if $errorField == 'gravatar'}
+ <small class="innerError">
+ {if $errorType == 'notFound'}{lang}wcf.user.avatar.type.gravatar.error.notFound{/lang}{/if}
+ </small>
+ {/if}
+ <small>{lang}wcf.user.avatar.type.gravatar.description{/lang}</small>
+ </dd>
+ </dl>
+ {/if}
+
+ {event name='avatarFields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ {if !$__wcf->user->disableAvatar}
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+ {/if}
+</form>
+
+{include file='footer'}
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.avatar.type.custom.crop': '{lang}wcf.user.avatar.type.custom.crop{/lang}',
+ 'wcf.user.avatar.upload.error.invalidExtension': '{lang}wcf.user.avatar.upload.error.invalidExtension{/lang}',
+ 'wcf.user.avatar.upload.error.tooSmall': '{lang}wcf.user.avatar.upload.error.tooSmall{/lang}',
+ 'wcf.user.avatar.upload.error.tooLarge': '{lang}wcf.user.avatar.upload.error.tooLarge{/lang}',
+ 'wcf.user.avatar.upload.error.uploadFailed': '{lang}wcf.user.avatar.upload.error.uploadFailed{/lang}',
+ 'wcf.user.avatar.upload.error.badImage': '{lang}wcf.user.avatar.upload.error.badImage{/lang}',
+ 'wcf.user.avatar.upload.success': '{lang}wcf.user.avatar.upload.success{/lang}',
+ 'wcf.global.button.upload': '{lang}wcf.global.button.upload{/lang}'
+ });
+
+ {if !$__wcf->user->disableAvatar}
+ {if $__wcf->getUserProfileHandler()->getAvatar()->canCrop()}
+ new WCF.User.Avatar.Upload(0, new WCF.User.Avatar.Crop({@$__wcf->getUserProfileHandler()->getAvatar()->avatarID}));
+ {else}
+ new WCF.User.Avatar.Upload();
+ {/if}
+ {/if}
+ });
+ //]]>
+</script>
+
+</body>
+</html>
--- /dev/null
+<input type="number" id="{$option->optionName}_age_from" name="values[{$option->optionName}][ageFrom]" value="{@$valueAgeFrom}" placeholder="{lang}wcf.user.birthday.age.from{/lang}" min="0" max="120" class="tiny" />
+<input type="number" id="{$option->optionName}_age_to" name="values[{$option->optionName}][ageTo]" value="{@$valueAgeTo}" placeholder="{lang}wcf.user.birthday.age.to{/lang}" min="0" max="120" class="tiny" />
+
+<script type="text/javascript">
+//<![CDATA[
+$(function() {
+ $('#{$option->optionName}_age_from').parents('dl:eq(0)').find('> dt > label').text('{lang}wcf.user.birthday.age{/lang}').attr('for', '{$option->optionName}_age_from');
+});
+//]]>
+</script>
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{if $__wcf->getPageMenu()->getLandingPage()->menuItem != 'wcf.user.dashboard'}{lang}wcf.user.dashboard{/lang} - {/if}{PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <link rel="canonical" href="{link controller='Dashboard'}{/link}" />
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{if $__boxSidebar|isset && $__boxSidebar}
+ {capture assign='sidebar'}
+ {@$__boxSidebar}
+ {/capture}
+{/if}
+
+{include file='header' sidebarOrientation='right'}
+
+{if $__wcf->getPageMenu()->getLandingPage()->menuItem == 'wcf.user.dashboard'}
+ <header class="boxHeadline">
+ <h1>{PAGE_TITLE|language}</h1>
+ {hascontent}<p>{content}{PAGE_DESCRIPTION|language}{/content}</p>{/hascontent}
+ </header>
+{else}
+ <header class="boxHeadline">
+ <h1>{lang}wcf.user.dashboard{/lang}</h1>
+ </header>
+{/if}
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<section id="dashboard">
+ {if $__boxContent|isset}{@$__boxContent}{/if}
+</section>
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+<div class="dashboardBox">
+ {@$template}
+</div>
\ No newline at end of file
--- /dev/null
+<ul class="sidebarBoxList">
+ {foreach from=$mostActiveMembers item=activeMember}
+ <li class="box24">
+ <a href="{link controller='User' object=$activeMember}{/link}" class="framed">{@$activeMember->getAvatar()->getImageTag(24)}</a>
+
+ <div class="sidebarBoxHeadline">
+ <h3><a href="{link controller='User' object=$activeMember}{/link}" class="userLink" data-user-id="{@$activeMember->userID}">{$activeMember->username}</a></h3>
+ <small>{lang}wcf.dashboard.box.mostActiveMembers.points{/lang}</small>
+ </div>
+ </li>
+ {/foreach}
+</ul>
\ No newline at end of file
--- /dev/null
+<ul class="sidebarBoxList">
+ {foreach from=$newestMembers item=newMember}
+ <li class="box24">
+ <a href="{link controller='User' object=$newMember}{/link}" class="framed">{@$newMember->getAvatar()->getImageTag(24)}</a>
+
+ <div class="sidebarBoxHeadline">
+ <h3><a href="{link controller='User' object=$newMember}{/link}" class="userLink" data-user-id="{@$newMember->userID}">{$newMember->username}</a></h3>
+ <small>{@$newMember->registrationDate|time}</small>
+ </div>
+ </li>
+ {/foreach}
+</ul>
\ No newline at end of file
--- /dev/null
+<header class="boxHeadline boxSubHeadline">
+ <h2>{lang}wcf.user.recentActivity{/lang}</h2>
+ {if $filteredByFollowedUsers}<p>{lang}wcf.user.recentActivity.filteredByFollowedUsers{/lang}</p>{/if}
+</header>
+
+<div class="container marginTop">
+ <ul id="recentActivities" class="containerList recentActivityList" data-last-event-time="{@$lastEventTime}">
+ {include file='recentActivityListItem'}
+ </ul>
+</div>
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.recentActivity.more': '{lang}wcf.user.recentActivity.more{/lang}',
+ 'wcf.user.recentActivity.noMoreEntries': '{lang}wcf.user.recentActivity.noMoreEntries{/lang}'
+ });
+
+ new WCF.User.RecentActivityLoader(null, {if $filteredByFollowedUsers}true{else}false{/if});
+ });
+ //]]>
+</script>
\ No newline at end of file
--- /dev/null
+<ul class="sidebarBoxList">
+ {foreach from=$eventList item=event}
+ <li class="box24">
+ <a href="{link controller='User' object=$event->getUserProfile()}{/link}" title="{$event->getUserProfile()->username}" class="framed">{@$event->getUserProfile()->getAvatar()->getImageTag(24)}</a>
+
+ <div class="sidebarBoxHeadline">
+ <h3><a href="{link controller='User' object=$event->getUserProfile()}{/link}" class="userLink" data-user-id="{@$event->getUserProfile()->userID}">{$event->getUserProfile()->username}</a><small> - {@$event->time|time}</small></h3>
+ <small>{@$event->getTitle()}</small>
+ </div>
+ </li>
+ {/foreach}
+</ul>
--- /dev/null
+<fieldset class="dashboardBox dashboardBoxRegisterButton">
+ <div>
+ <a href="{link controller='Register'}{/link}" class="button">{lang}wcf.user.button.registerNow{/lang}</a>
+ </div>
+</fieldset>
--- /dev/null
+<fieldset class="dashboardBox">
+ <legend>{lang}wcf.dashboard.box.{$box->boxName}{/lang}</legend>
+
+ <div>
+ {@$template}
+ </div>
+</fieldset>
\ No newline at end of file
--- /dev/null
+<fieldset class="dashboardBox dashboardBoxSignedInAs">
+ <legend class="invisible">{lang}wcf.dashboard.box.{$box->boxName}{/lang}</legend>
+
+ <div class="box96 framed">
+ {@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(96)}
+
+ <div>
+ <div class="containerHeadline">
+ <h3><a href="{link controller='User' object=$__wcf->user}{/link}">{$__wcf->user->username}</a></h3>
+ {if MODULE_USER_RANK && $__wcf->getUserProfileHandler()->getUserTitle()}<p><span class="badge userTitleBadge{if $__wcf->getUserProfileHandler()->getRank() && $__wcf->getUserProfileHandler()->getRank()->cssClassName} {@$__wcf->getUserProfileHandler()->getRank()->cssClassName}{/if}">{$__wcf->getUserProfileHandler()->getUserTitle()}</span></p>{/if}
+ </div>
+
+ {include file='userInformationStatistics' user=$__wcf->user}
+ </div>
+ </div>
+</fieldset>
\ No newline at end of file
--- /dev/null
+<dl class="plain inlineDataList">
+ <dt>{lang}wcf.user.members{/lang}</dt>
+ <dd>{#$dashboardStats[members]}</dd>
+
+ {event name='stats'}
+
+ <dt>{lang}wcf.user.newestMember{/lang}</dt>
+ <dd><a href="{link controller='User' object=$dashboardStats[newestMember]}{/link}" class="userLink" data-user-id="{$dashboardStats[newestMember]->userID}">{$dashboardStats[newestMember]}</a></dd>
+</dl>
\ No newline at end of file
--- /dev/null
+<div id="userActivityPointListContainer" class="tabularBox marginTop">
+ <table class="table">
+ <thead>
+ <tr>
+ <th>{lang}wcf.user.activityPoint.objectType{/lang}</th>
+ <th>{lang}wcf.user.activityPoint.objects{/lang}</th>
+ <th>{lang}wcf.user.activityPoint.pointsPerObject{/lang}</th>
+ <th>{lang}wcf.user.activityPoint.sum{/lang}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {assign var='activityPointSum' value=0}
+ {foreach from=$activityPointObjectTypes item='objectType'}
+ {if $objectType->activityPoints > 0 && $objectType->points > 0}
+ <tr>
+ <td class="columnTitle">
+ {lang}wcf.user.activityPoint.objectType.{$objectType->objectType}{/lang}
+ </td>
+ <td class="columnDigits">
+ {#$objectType->activityPoints/$objectType->points}
+ </td>
+ <td class="columnDigits">
+ {#$objectType->points}
+ </td>
+ <td class="columnDigits">
+ {#$objectType->activityPoints}
+ </td>
+ {assign var='activityPointSum' value=$activityPointSum + $objectType->activityPoints}
+ </tr>
+ {/if}
+ {/foreach}
+
+ <tr>
+ <td class="columnTitle focus right" colspan="3">∑</td>
+ <td class="columnDigits focus"><span class="badge">{#$user->activityPoints}</span></td>
+ </tr>
+ </tbody>
+ </table>
+</div>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.register.disclaimer{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.register.disclaimer{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='Disclaimer'}{/link}">
+ <div class="container containerPadding marginTop htmlContent">
+ {lang}wcf.user.register.disclaimer.text{/lang}
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" name="accept" value="{lang}wcf.user.register.disclaimer.accept{/lang}" accesskey="s" />
+ <a class="button" href="{link}{/link}">{lang}wcf.user.register.disclaimer.decline{/lang}</a>
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.emailActivation{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.emailActivation{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='EmailActivation'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend><label for="userID">{lang}wcf.user.emailActivation{/lang}</label></legend>
+
+ <dl{if $errorField == 'u'} class="formError"{/if}>
+ <dt><label for="userID">{lang}wcf.user.userID{/lang}</label></dt>
+ <dd>
+ <input type="text" id="userID" name="u" value="{@$u}" required="required" class="medium" />
+ {if $errorField == 'u'}
+ <small class="innerError">
+ {if $errorType == 'notValid'}{lang}wcf.user.userID.error.invalid{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'a'} class="formError"{/if}>
+ <dt><label for="activationCode">{lang}wcf.user.activationCode{/lang}</label></dt>
+ <dd>
+ <input type="text" id="activationCode" maxlength="9" name="a" value="{@$a}" required="required" class="medium" />
+ {if $errorField == 'a'}
+ <small class="innerError">
+ {if $errorType == 'notValid'}{lang}wcf.user.activationCode.error.notValid{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='fields'}
+
+ <dl>
+ <dd>
+ <ul class="buttonList">
+ <li><a class="button small" href="{link controller='EmailNewActivationCode'}{/link}"><span>{lang}wcf.user.newActivationCode{/lang}</span></a></li>
+ {event name='buttons'}
+ </ul>
+ </dd>
+ </dl>
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.newActivationCode{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.newActivationCode{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='EmailNewActivationCode'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.newActivationCode{/lang}</legend>
+
+ <dl{if $errorField == 'username'} class="formError"{/if}>
+ <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
+ <dd>
+ <input type="text" id="username" name="username" value="{$username}" required="required" class="medium" />
+ {if $errorField == 'username'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {elseif $errorType == 'alreadyEnabled'}
+ {lang}wcf.user.registerActivation.error.userAlreadyEnabled{/lang}
+ {else}
+ {lang}wcf.user.username.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'password'} class="formError"{/if}>
+ <dt><label for="password">{lang}wcf.user.password{/lang}</label></dt>
+ <dd>
+ <input type="password" id="password" name="password" value="{$password}" required="required" class="medium" />
+ {if $errorField == 'password'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.user.password.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='fields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.following{/lang} - {lang}wcf.user.usercp{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Action.Delete('wcf\\data\\user\\follow\\UserFollowAction', '.jsFollowing');
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.following{/lang} <span class="badge">{#$items}</span></h1>
+</header>
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+ {pages print=true assign=pagesLinks controller='Following' link="pageNo=%d"}
+
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{if $objects|count}
+ <div class="container marginTop">
+ <ol class="containerList doubleColumned userList">
+ {foreach from=$objects item=user}
+ <li class="jsFollowing">
+ <div class="box48">
+ <a href="{link controller='User' object=$user}{/link}" title="{$user->username}" class="framed">{@$user->getAvatar()->getImageTag(48)}</a>
+
+ <div class="details userInformation">
+ {include file='userInformationHeadline'}
+
+ <nav class="jsMobileNavigation buttonGroupNavigation">
+ <ul class="buttonList jsOnly">
+ <li><span class="icon icon16 icon-remove pointer jsTooltip jsDeleteButton" title="{lang}wcf.user.button.unfollow{/lang}" data-object-id="{@$user->followID}"></span></li>
+ {event name='userButtons'}
+ </ul>
+ </nav>
+
+ {include file='userInformationStatistics'}
+ </div>
+ </div>
+ </li>
+ {/foreach}
+ </ol>
+ </div>
+
+ <div class="contentNavigation">
+ {@$pagesLinks}
+
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+ </div>
+{else}
+ <p class="info">{lang}wcf.user.following.noUsers{/lang}</p>
+{/if}
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{foreach from=$groupedUsers item=group}
+ {if $group}
+ <header class="boxHeadline">
+ <h1>{$group}</h1>
+ </header>
+ {/if}
+
+ {if $group|count}
+ <div class="container marginTop">
+ <ol class="containerList jsGroupedUserList">
+ {foreach from=$group item=user}
+ {include file='userListItem'}
+ {/foreach}
+ </ol>
+ </div>
+ {else}
+ <p class="marginTop">{$group->getNoUsersMessage()}</p>
+ {/if}
+{/foreach}
+
+<div class="contentNavigation"><div class="jsPagination"></div></div>
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.button.follow': '{lang}wcf.user.button.follow{/lang}',
+ 'wcf.user.button.ignore': '{lang}wcf.user.button.ignore{/lang}',
+ 'wcf.user.button.unfollow': '{lang}wcf.user.button.unfollow{/lang}',
+ 'wcf.user.button.unignore': '{lang}wcf.user.button.unignore{/lang}'
+ });
+
+ new WCF.User.Action.Follow($('.jsGroupedUserList > li'));
+ new WCF.User.Action.Ignore($('.jsGroupedUserList > li'));
+ });
+ //]]>
+</script>
\ No newline at end of file
WCF.User.init({@$__wcf->user->userID}, '{@$__wcf->user->username|encodeJS}');
//]]>
</script>
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.Message{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.User{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
{event name='javascriptInclude'}
<!-- Stylesheets -->
WCF.System.PageNavigation.init('.pageNavigation');
WCF.Date.Picker.init();
WCF.System.MobileNavigation.init();
+ new WCF.User.ProfilePreview();
{event name='javascriptInit'}
<div class="{if $__wcf->getStyleHandler()->getStyle()->getVariable('useFluidLayout')}layoutFluid{else}layoutFixed{/if} clearfix">
{hascontent}
<ul class="userPanelItems">
- {content}{event name='topMenu'}{/content}
+ {content}
+ {include file='userPanel'}
+ {event name='topMenu'}
+ {/content}
</ul>
{/hascontent}
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.ignoredUsers{/lang} - {lang}wcf.user.usercp{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Action.Delete('wcf\\data\\user\\ignore\\UserIgnoreAction', '.jsIgnoredUser');
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.ignoredUsers{/lang} <span class="badge">{#$items}</span></h1>
+</header>
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+ {pages print=true assign=pagesLinks controller='IgnoredUsers' link="pageNo=%d"}
+
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{if $objects|count}
+ <div class="container marginTop">
+ <ol class="containerList doubleColumned userList">
+ {foreach from=$objects item=user}
+ <li class="jsIgnoredUser">
+ <div class="box48">
+ <a href="{link controller='User' object=$user}{/link}" title="{$user->username}" class="framed">{@$user->getAvatar()->getImageTag(48)}</a>
+
+ <div class="details userInformation">
+ {include file='userInformationHeadline'}
+
+ <nav class="jsMobileNavigation buttonGroupNavigation">
+ <ul class="buttonList jsOnly">
+ <li><span class="icon icon16 icon-remove pointer jsTooltip jsDeleteButton" title="{lang}wcf.user.button.unignore{/lang}" data-object-id="{@$user->ignoreID}"></span></li>
+ {event name='userButtons'}
+ </ul>
+ </nav>
+
+ {include file='userInformationStatistics'}
+ </div>
+ </div>
+ </li>
+ {/foreach}
+ </ol>
+ </div>
+
+ <div class="contentNavigation">
+ {@$pagesLinks}
+
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+ </div>
+{else}
+ <p class="info">{lang}wcf.user.ignoredUsers.noUsers{/lang}</p>
+{/if}
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.login{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <script type="text/javascript" src="{@$__wcf->getPath('wcf')}js/WCF.User{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.User.Login(false);
+ })
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+{include file='header' __disableLoginLink=true}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.login{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='Login'}{/link}" id="loginForm">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.login.data{/lang}</legend>
+
+ <dl{if $errorField == 'username'} class="formError"{/if}>
+ <dt><label for="username">{lang}wcf.user.usernameOrEmail{/lang}</label></dt>
+ <dd>
+ <input type="text" id="username" name="username" value="{$username}" required="required" class="long" />
+ {if $errorField == 'username'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.user.username.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {if !REGISTER_DISABLED}
+ <dl>
+ <dt>{lang}wcf.user.login.action{/lang}</dt>
+ <dd><label><input type="radio" name="action" value="register" /> {lang}wcf.user.login.action.register{/lang}</label></dd>
+ <dd><label><input type="radio" name="action" value="login" checked="checked" /> {lang}wcf.user.login.action.login{/lang}</label></dd>
+ </dl>
+ {/if}
+
+ <dl{if $errorField == 'password'} class="formError"{/if}>
+ <dt><label for="password">{lang}wcf.user.password{/lang}</label></dt>
+ <dd>
+ <input type="password" id="password" name="password" value="{$password}" class="long" />
+ {if $errorField == 'password'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.user.password.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {if $supportsPersistentLogins}
+ <dl>
+ <dd>
+ <label for="useCookies"><input type="checkbox" id="useCookies" name="useCookies" value="1" {if $useCookies}checked="checked" {/if}/> {lang}wcf.user.useCookies{/lang}</label>
+ </dd>
+ </dl>
+ {/if}
+
+ {event name='fields'}
+
+ <dl>
+ <dd>
+ <ul class="buttonList">
+ <li><a class="button small" href="{link controller='LostPassword'}{/link}"><span>{lang}wcf.user.lostPassword{/lang}</span></a></li>
+ {if !REGISTER_DISABLED && REGISTER_ACTIVATION_METHOD == 1}<li><a class="button small" href="{link controller='RegisterActivation'}{/link}"><span>{lang}wcf.user.registerActivation{/lang}</span></a></li>{/if}
+ {event name='buttons'}
+ </ul>
+ </dd>
+ </dl>
+ </fieldset>
+
+ {capture assign='__3rdPartyButtons'}
+
+ {/capture}
+
+ {hascontent}
+ <fieldset>
+ <legend>{lang}wcf.user.login.3rdParty{/lang}</legend>
+
+ <dl>
+ <dd>
+ <ul class="buttonList">
+ {content}
+ {if GITHUB_PUBLIC_KEY !== '' && GITHUB_PRIVATE_KEY !== ''}
+ <li id="githubAuth" class="3rdPartyAuth">
+ <a href="{link controller='GithubAuth'}{/link}" class="button small"><span class="icon icon16 icon-github"></span> <span>{lang}wcf.user.3rdparty.github.login{/lang}</span></a>
+ </li>
+ {/if}
+
+ {if TWITTER_PUBLIC_KEY !== '' && TWITTER_PRIVATE_KEY !== ''}
+ <li id="twitterAuth" class="3rdPartyAuth">
+ <a href="{link controller='TwitterAuth'}{/link}" class="button small"><span class="icon icon16 icon-twitter"></span> <span>{lang}wcf.user.3rdparty.twitter.login{/lang}</span></a>
+ </li>
+ {/if}
+
+ {if FACEBOOK_PUBLIC_KEY !== '' && FACEBOOK_PRIVATE_KEY !== ''}
+ <li id="facebookAuth" class="3rdPartyAuth">
+ <a href="{link controller='FacebookAuth'}{/link}" class="button small"><span class="icon icon16 icon-facebook"></span> <span>{lang}wcf.user.3rdparty.facebook.login{/lang}</span></a>
+ </li>
+ {/if}
+
+ {if GOOGLE_PUBLIC_KEY !== '' && GOOGLE_PRIVATE_KEY !== ''}
+ <li id="googleAuth" class="3rdPartyAuth">
+ <a href="{link controller='GoogleAuth'}{/link}" class="button small"><span class="icon icon16 icon-google-plus"></span> <span>{lang}wcf.user.3rdparty.google.login{/lang}</span></a>
+ </li>
+ {/if}
+ {/content}
+ </ul>
+ </dd>
+ </dl>
+ </fieldset>
+ {/hascontent}
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ <input type="hidden" name="url" value="{@$url}" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file="documentHeader"}
+
+<head>
+ <title>{lang}wcf.user.lostPassword{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+
+ <script type="text/javascript" src="{@$__wcf->getPath('wcf')}js/WCF.User{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.User.Registration.LostPassword();
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.lostPassword{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+<p class="info">{lang}wcf.user.lostPassword.description{/lang}</p>
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='LostPassword'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.lostPassword{/lang}</legend>
+
+ <dl id="usernameDiv"{if $errorField == 'username'} class="formError"{/if}>
+ <dt>
+ <label for="usernameInput">{lang}wcf.user.username{/lang}</label>
+ </dt>
+ <dd>
+ <input type="text" id="usernameInput" name="username" value="{$username}" class="medium" />
+ {if $errorField == 'username'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType == 'notFound'}{lang}wcf.user.error.username.notFound{/lang}{/if}
+ {if $errorType == '3rdParty'}{lang}wcf.user.error.username.3rdParty{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl id="emailDiv"{if $errorField == 'email'} class="formError"{/if}>
+ <dt>
+ <label for="emailInput">{lang}wcf.user.email{/lang}</label>
+ </dt>
+ <dd>
+ <input type="email" id="emailInput" name="email" value="{$email}" class="medium" />
+ {if $errorField == 'email'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType == 'notFound'}{lang}wcf.user.lostPassword.error.email.notFound{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='fields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+
+ {if $useCaptcha}{include file='recaptcha'}{/if}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.mail.title{/lang} - {lang}wcf.user.profile{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.mail.title{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='Mail' object=$user}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.mail.information{/lang}</legend>
+
+ <dl{if $errorField == 'subject'} class="formError"{/if}>
+ <dt><label for="subject">{lang}wcf.user.mail.subject{/lang}</label></dt>
+ <dd>
+ <input type="text" id="subject" name="subject" value="{$subject}" required="required" class="long" />
+ {if $errorField == 'subject'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.user.mail.subject.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {if $__wcf->user->userID}
+ <dl>
+ <dd><label><input type="checkbox" name="showAddress" value="1" {if $showAddress == 1} checked="checked"{/if}/> {lang}wcf.user.mail.showAddress{/lang}</label></dd>
+ </dl>
+ {else}
+ <dl{if $errorField == 'email'} class="formError"{/if}>
+ <dt><label for="email">{lang}wcf.user.mail.senderEmail{/lang}</label></dt>
+ <dd>
+ <input type="email" id="email" name="email" value="{$email}" required="required" class="medium" />
+ {if $errorField == 'email'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {elseif $errorType == 'invalid'}
+ {lang}wcf.user.email.error.notValid{/lang}
+ {else}
+ {lang}wcf.user.mail.senderEmail.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {/if}
+
+ {event name='informationFields'}
+ </fieldset>
+
+ <fieldset>
+ <legend><label for="message">{lang}wcf.user.mail.message{/lang}</label></legend>
+
+ <dl class="wide{if $errorField == 'message'} formError{/if}">
+ <dd>
+ <textarea rows="15" cols="40" name="message" id="message" required="required">{$message}</textarea>
+ {if $errorField == 'message'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.user.mail.message.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='messageFields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+
+ {if $useCaptcha}
+ {include file='recaptcha'}
+ {/if}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+<fieldset>
+ <dl class="wide">
+ <dd>
+ <label><input type="radio" name="subscribe" value="1"{if $userObjectWatch} checked="checked"{/if} /> {lang}wcf.user.objectWatch.subscribe.{@$objectType->objectType}{/lang}</label>
+
+ <small><label><input type="checkbox" name="enableNotification" value="1"{if $userObjectWatch && $userObjectWatch->notification} checked="checked"{/if} /> {lang}wcf.user.objectWatch.enableNotification.{@$objectType->objectType}{/lang}</label></small>
+ </dd>
+ </dl>
+ <dl class="wide">
+ <dd>
+ <label><input type="radio" name="subscribe" value="0"{if !$userObjectWatch} checked="checked"{/if} /> {lang}wcf.user.objectWatch.unsubscribe.{@$objectType->objectType}{/lang}</label>
+ </dd>
+ </dl>
+
+ {event name='fields'}
+</fieldset>
+
+<div class="formSubmit">
+ <button class="jsButtonSave buttonPrimary">{lang}wcf.global.button.save{/lang}</button>
+</div>
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{if $searchID}{lang}wcf.user.search.results{/lang}{else}{lang}wcf.user.members{/lang}{/if} {if $pageNo > 1}- {lang}wcf.page.pageNo{/lang} {/if}- {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ {capture assign='canonicalURLParameters'}sortField={@$sortField}&sortOrder={@$sortOrder}{if $letter}&letter={@$letter|rawurlencode}{/if}{/capture}
+ {if $pageNo < $pages}
+ <link rel="next" href="{link controller='MembersList'}pageNo={@$pageNo+1}&{@$canonicalURLParameters}{/link}" />
+ {/if}
+ {if $pageNo > 1}
+ <link rel="prev" href="{link controller='MembersList'}{if $pageNo > 2}pageNo={@$pageNo-1}&{/if}{@$canonicalURLParameters}{/link}" />
+ {/if}
+ <link rel="canonical" href="{link controller='MembersList'}{if $pageNo > 1}pageNo={@$pageNo}&{/if}{@$canonicalURLParameters}{/link}" />
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.button.follow': '{lang}wcf.user.button.follow{/lang}',
+ 'wcf.user.button.ignore': '{lang}wcf.user.button.ignore{/lang}',
+ 'wcf.user.button.unfollow': '{lang}wcf.user.button.unfollow{/lang}',
+ 'wcf.user.button.unignore': '{lang}wcf.user.button.unignore{/lang}'
+ });
+
+ new WCF.User.Action.Follow($('.userList > li'));
+ new WCF.User.Action.Ignore($('.userList > li'));
+
+ new WCF.Search.User('#searchUsername', function(data) {
+ var $link = '{link controller='User' id=2147483646 title='wcfTitlePlaceholder' encode=false}{/link}';
+ window.location = $link.replace('2147483646', data.objectID).replace('wcfTitlePlaceholder', data.label);
+ }, false, [ ], false);
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{capture assign='sidebar'}
+ {assign var=encodedLetter value=$letter|rawurlencode}
+ <div class="jsOnly">
+ <form method="get" action="{if $searchID}{link controller='MembersList' id=$searchID}{/link}{else}{link controller='MembersList'}{/link}{/if}">
+ <fieldset>
+ <legend><label for="searchUsername">{lang}wcf.user.search{/lang}</label></legend>
+
+ <dl>
+ <dd>
+ <input type="text" id="searchUsername" name="username" class="long" placeholder="{lang}wcf.user.username{/lang}" />
+ </dd>
+ </dl>
+ </fieldset>
+ </form>
+ </div>
+
+ <fieldset>
+ <legend>{lang}wcf.user.members.sort.letters{/lang}</legend>
+
+ <ul class="buttonList letters">
+ {foreach from=$letters item=__letter}
+ <li><a href="{if $searchID}{link controller='MembersList' id=$searchID}sortField={$sortField}&sortOrder={$sortOrder}&letter={$__letter|rawurlencode}{/link}{else}{link controller='MembersList'}sortField={$sortField}&sortOrder={$sortOrder}&letter={$__letter|rawurlencode}{/link}{/if}" class="button small{if $letter == $__letter} active{/if}">{$__letter}</a></li>
+ {/foreach}
+ {if !$letter|empty}<li><a href="{if $searchID}{link controller='MembersList' id=$searchID}sortField={$sortField}&sortOrder={$sortOrder}{/link}{else}{link controller='MembersList'}sortField={$sortField}&sortOrder={$sortOrder}{/link}{/if}" class="button small">{lang}wcf.user.members.sort.letters.all{/lang}</a></li>{/if}
+ </ul>
+ </fieldset>
+
+ <div>
+ <form method="get" action="{if $searchID}{link controller='MembersList' id=$searchID}{/link}{else}{link controller='MembersList'}{/link}{/if}">
+ <fieldset>
+ <legend><label for="sortField">{lang}wcf.user.members.sort{/lang}</label></legend>
+
+ <dl>
+ <dd>
+ <select id="sortField" name="sortField">
+ <option value="username"{if $sortField == 'username'} selected="selected"{/if}>{lang}wcf.user.username{/lang}</option>
+ <option value="registrationDate"{if $sortField == 'registrationDate'} selected="selected"{/if}>{lang}wcf.user.registrationDate{/lang}</option>
+ <option value="activityPoints"{if $sortField == 'activityPoints'} selected="selected"{/if}>{lang}wcf.user.activityPoint{/lang}</option>
+ {event name='sortField'}
+ </select>
+ <select name="sortOrder">
+ <option value="ASC"{if $sortOrder == 'ASC'} selected="selected"{/if}>{lang}wcf.global.sortOrder.ascending{/lang}</option>
+ <option value="DESC"{if $sortOrder == 'DESC'} selected="selected"{/if}>{lang}wcf.global.sortOrder.descending{/lang}</option>
+ </select>
+ </dd>
+ </dl>
+ </fieldset>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ <input type="hidden" name="letter" value="{$letter}" />
+ </div>
+ </form>
+ </div>
+
+ {@$__boxSidebar}
+{/capture}
+
+{include file='header' sidebarOrientation='right'}
+
+<header class="boxHeadline">
+ <h1>{if $searchID}{lang}wcf.user.search.results{/lang}{else}{lang}wcf.user.members{/lang}{/if} <span class="badge">{#$items}</span></h1>
+</header>
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+ {if $searchID}
+ {pages print=true assign=pagesLinks controller='MembersList' id=$searchID link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder&letter=$encodedLetter"}
+ {else}
+ {pages print=true assign=pagesLinks controller='MembersList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder&letter=$encodedLetter"}
+ {/if}
+
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{if $items}
+ <div class="container marginTop">
+ <ol class="containerList doubleColumned userList">
+ {foreach from=$objects item=user}
+ {include file='userListItem'}
+ {/foreach}
+ </ol>
+ </div>
+{else}
+ <p class="info">{lang}wcf.user.members.noMembers{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {@$pagesLinks}
+
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+<textarea id="{$option->optionName}" name="values[{$option->optionName}]" cols="40" rows="10">{$value}</textarea>
+{include file='wysiwyg' wysiwygSelector=$option->optionName}
+
+<script type="text/javascript">
+//<![CDATA[
+$(function() {
+ $('#{$option->optionName}').parents('dl:eq(0)').addClass('wide');
+});
+//]]>
+</script>
\ No newline at end of file
--- /dev/null
+{hascontent}
+ <div id="{$option->optionName}">
+ {content}
+ {@$value}
+ {/content}
+ </div>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ $('#{$option->optionName}').parents('dl:eq(0)').addClass('wide');
+ });
+ //]]>
+ </script>
+{/hascontent}
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.newPassword{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.newPassword{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='NewPassword'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.newPassword{/lang}</legend>
+
+ <dl{if $errorField == 'userID'} class="formError"{/if}>
+ <dt>
+ <label for="userID">{lang}wcf.user.userID{/lang}</label>
+ </dt>
+ <dd>
+ <input type="text" id="userID" name="u" value="{@$userID}" required="required" class="medium" />
+ {if $errorField == 'userID'}
+ <small class="innerError">
+ {if $errorType == 'invalid'}{lang}wcf.user.userID.error.invalid{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'lostPasswordKey'} class="formError"{/if}>
+ <dt>
+ <label for="lostPasswordKey">{lang}wcf.user.lostPasswordKey{/lang}</label>
+ </dt>
+ <dd>
+ <input type="text" id="lostPasswordKey" name="k" value="{$lostPasswordKey}" required="required" class="medium" />
+ {if $errorField == 'lostPasswordKey'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType == 'invalid'}{lang}wcf.user.lostPasswordKey.error.invalid{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='fields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.notification.notifications{/lang} - {lang}wcf.user.usercp{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.notification.markAsConfirmed': '{lang}wcf.user.notification.markAsConfirmed{/lang}'
+ });
+
+ new WCF.Notification.List();
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.notification.notifications{/lang} <span class="badge jsNotificationsBadge">{#$__wcf->getUserNotificationHandler()->getNotificationCount()}</span></h1>
+</header>
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+ {pages print=true assign=pagesLinks controller='NotificationList' link="pageNo=%d"}
+
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {if $notifications[notifications]}<li class="jsOnly"><a class="button jsMarkAllAsConfirmed"><span class="icon icon16 icon-remove"></span> <span>{lang}wcf.user.notification.markAllAsConfirmed{/lang}</span></a></li>{/if}
+
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{if $notifications[notifications]}
+ <div class="container marginTop">
+ <ul class="containerList">
+ {foreach from=$notifications[notifications] item=$notification}
+ <li class="jsNotificationItem" data-notification-id="{@$notification[notificationID]}" data-link="{$notification[event]->getLink()}">
+ <div class="box48">
+ <a href="{link controller='User' object=$notification[event]->getAuthor()}{/link}" title="{$notification[event]->getAuthor()->username}" class="framed">{@$notification[event]->getAuthor()->getAvatar()->getImageTag(48)}</a>
+
+ <div class="details">
+ <div class="containerHeadline">
+ <h3><a href="{link controller='User' object=$notification[event]->getAuthor()}{/link}" class="userLink" data-user-id="{@$notification[event]->getAuthor()->userID}">{$notification[event]->getAuthor()->username}</a></h3>
+ <small>{@$notification[time]|time}</small>
+ </div>
+
+ <p>{@$notification[event]->getMessage()}</p>
+
+ <nav class="jsMobileNavigation buttonGroupNavigation">
+ <ul class="buttonList jsOnly">
+ <li><a class="jsMarkAsConfirmed jsTooltip" title="{lang}wcf.user.notification.markAsConfirmed{/lang}"><span class="icon icon16 icon-remove"></span></a></li>
+ </ul>
+ </nav>
+ </div>
+ </div>
+ </li>
+ {/foreach}
+ </ul>
+ </div>
+
+ <div class="contentNavigation">
+ {@$pagesLinks}
+
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+ </div>
+{else}
+ <p class="info">{lang}wcf.user.notification.noNotifications{/lang}</p>
+{/if}
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{foreach from=$notifications[notifications] item=notification}
+ <li class="jsNotificationItem" data-link="{$notification[event]->getLink()}" data-notification-id="{@$notification[notificationID]}">
+ <a class="box24">
+ <div class="framed">
+ {@$notification[event]->getAuthor()->getAvatar()->getImageTag(24)}
+ </div>
+ <div>
+ <h3>{$notification[event]->getMessage()}</h3>
+ <small>{$notification[event]->getAuthor()->username} - {@$notification[time]|time}</small>
+ </div>
+ </a>
+ </li>
+{/foreach}
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.menu.settings{/lang}: {lang}wcf.user.notification.notifications{/lang} - {lang}wcf.user.menu.settings{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ $('#notificationSettings > fieldset > dl > dd > label > input').each(function(index, value) {
+ var $input = $(value);
+ $input.on('click', function(event) {
+ var $input = $(event.currentTarget);
+ $input.parents('dd').find('.jsMailNotificationType').toggle();
+ });
+ if (!$input.is(':checked')) {
+ $input.parents('dd').find('.jsMailNotificationType').hide();
+ }
+ });
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.menu.settings{/lang}: {lang}wcf.user.notification.notifications{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.edit{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='NotificationSettings'}{/link}">
+ <div class="container containerPadding marginTop" id="notificationSettings">
+ {foreach from=$events key='eventCategory' item='eventList'}
+ <fieldset>
+ <legend>{lang}wcf.user.notification.{$eventCategory}{/lang}</legend>
+
+ <dl>
+ {foreach from=$eventList item='event'}
+ <dd>
+ <label><input type="checkbox" name="settings[{@$event->eventID}][enabled]" value="1"{if $settings[$event->eventID][enabled]} checked="checked"{/if} /> {lang}wcf.user.notification.{$event->objectType}.{$event->eventName}{/lang}</label>
+ {hascontent}<small>{content}{lang __optional=true}wcf.user.notification.{$event->objectType}.{$event->eventName}.description{/lang}{/content}</small>{/hascontent}
+ <small class="jsMailNotificationType">
+ <select name="settings[{@$event->eventID}][mailNotificationType]">
+ <option value="none">{lang}wcf.user.notification.mailNotificationType.none{/lang}</option>
+ <option value="instant"{if $settings[$event->eventID][mailNotificationType] == 'instant'} selected="selected"{/if}>{lang}wcf.user.notification.mailNotificationType.instant{/lang}</option>
+ <option value="daily"{if $settings[$event->eventID][mailNotificationType] == 'daily'} selected="selected"{/if}>{lang}wcf.user.notification.mailNotificationType.daily{/lang}</option>
+ </select>
+ </small>
+ </dd>
+ {/foreach}
+ </dl>
+ </fieldset>
+ {/foreach}
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{hascontent}
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.recentActivity.more': '{lang}wcf.user.recentActivity.more{/lang}',
+ 'wcf.user.recentActivity.noMoreEntries': '{lang}wcf.user.recentActivity.noMoreEntries{/lang}'
+ });
+
+ new WCF.User.RecentActivityLoader({@$userID});
+ });
+ //]]>
+ </script>
+
+ <ul id="recentActivities" class="containerList recentActivityList" data-last-event-time="{@$lastEventTime}">
+ {content}
+ {include file='recentActivityListItem'}
+ {/content}
+ </ul>
+{hascontentelse}
+ <div class="containerPadding">
+ {if $placeholder|isset}{$placeholder}{/if}
+ </div>
+{/hascontent}
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.recentActivity{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <link rel="canonical" href="{link controller='RecentActivityList'}{/link}" />
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.recentActivity.more': '{lang}wcf.user.recentActivity.more{/lang}',
+ 'wcf.user.recentActivity.noMoreEntries': '{lang}wcf.user.recentActivity.noMoreEntries{/lang}'
+ });
+
+ new WCF.User.RecentActivityLoader(null);
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{capture assign='sidebar'}
+ {@$__boxSidebar}
+{/capture}
+
+{include file='header' sidebarOrientation='right'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.recentActivity{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{if $eventList|count}
+ <div class="container marginTop">
+ <ul id="recentActivities" class="containerList recentActivityList" data-last-event-time="{@$lastEventTime}">
+ {include file='recentActivityListItem'}
+ </ul>
+ </div>
+
+ <div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+ </div>
+{else}
+ <p class="info">{lang}wcf.user.recentActivity.noEntries{/lang}</p>
+{/if}
+
+
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{foreach from=$eventList item=event}
+ <li>
+ <div class="box48">
+ <a href="{link controller='User' object=$event->getUserProfile()}{/link}" title="{$event->getUserProfile()->username}" class="framed">{@$event->getUserProfile()->getAvatar()->getImageTag(48)}</a>
+
+ <div>
+ <div class="containerHeadline">
+ <h3><a href="{link controller='User' object=$event->getUserProfile()}{/link}" class="userLink" data-user-id="{@$event->getUserProfile()->userID}">{$event->getUserProfile()->username}</a><small> - {@$event->time|time}</small></h3>
+ <p><strong>{@$event->getTitle()}</strong></p>
+ <small class="containerContentType">{lang}wcf.user.recentActivity.{@$event->getObjectTypeName()}{/lang}</small>
+ </div>
+
+ <div>{@$event->getDescription()}</div>
+ </div>
+ </div>
+ </li>
+{/foreach}
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.register{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+
+ <script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.User{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.global.form.error.empty': '{lang}wcf.global.form.error.empty{/lang}',
+ 'wcf.user.username.error.notValid': '{lang}wcf.user.username.error.notValid{/lang}',
+ 'wcf.user.username.error.notUnique': '{lang}wcf.user.username.error.notUnique{/lang}',
+ 'wcf.user.email.error.notValid' : '{lang}wcf.user.email.error.notValid{/lang}',
+ 'wcf.user.email.error.notUnique' : '{lang}wcf.user.email.error.notUnique{/lang}',
+ 'wcf.user.confirmEmail.error.notEqual' : '{lang}wcf.user.confirmEmail.error.notEqual{/lang}',
+ 'wcf.user.password.error.notSecure' : '{lang}wcf.user.password.error.notSecure{/lang}',
+ 'wcf.user.confirmPassword.error.notEqual' : '{lang}wcf.user.confirmPassword.error.notEqual{/lang}'
+ });
+
+ new WCF.User.Registration.Validation.EmailAddress($('#email'), $('#confirmEmail'), null);
+ new WCF.User.Registration.Validation.Password($('#password'), $('#confirmPassword'), null);
+ new WCF.User.Registration.Validation.Username($('#username', null, {
+ minlength: {@REGISTER_USERNAME_MIN_LENGTH},
+ maxlength: {@REGISTER_USERNAME_MAX_LENGTH}
+ }));
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+{include file='header' __disableLoginLink=true}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.register{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $isExternalAuthentication}
+ {if $__wcf->session->getVar('__githubToken')}
+ <p class="info">{lang}wcf.user.3rdparty.github.register{/lang}</p>
+ {elseif $__wcf->session->getVar('__twitterData')}
+ <p class="info">{lang}wcf.user.3rdparty.twitter.register{/lang}</p>
+ {elseif $__wcf->session->getVar('__facebookData')}
+ <p class="info">{lang}wcf.user.3rdparty.facebook.register{/lang}</p>
+ {elseif $__wcf->session->getVar('__googleData')}
+ <p class="info">{lang}wcf.user.3rdparty.google.register{/lang}</p>
+ {/if}
+{/if}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='Register'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.username{/lang}</legend>
+
+ <dl{if $errorType.username|isset} class="formError"{/if}>
+ <dt>
+ <label for="username">{lang}wcf.user.username{/lang}</label>
+ </dt>
+ <dd>
+ <input type="text" id="username" name="username" value="{$username}" required="required" class="medium" />
+ {if $errorType.username|isset}
+ <small class="innerError">
+ {if $errorType.username == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType.username == 'notValid'}{lang}wcf.user.username.error.notValid{/lang}{/if}
+ {if $errorType.username == 'notUnique'}{lang}wcf.user.username.error.notUnique{/lang}{/if}
+ </small>
+ {/if}
+ <small>{lang}wcf.user.username.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='usernameFields'}
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.user.email{/lang}</legend>
+
+ <dl{if $errorType.email|isset} class="formError"{/if}>
+ <dt>
+ <label for="email">{lang}wcf.user.email{/lang}</label>
+ </dt>
+ <dd>
+ <input type="email" id="email" name="email" value="{$email}" required="required" class="medium" />
+ {if $errorType.email|isset}
+ <small class="innerError">
+ {if $errorType.email == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType.email == 'notValid'}{lang}wcf.user.email.error.notValid{/lang}{/if}
+ {if $errorType.email == 'notUnique'}{lang}wcf.user.email.error.notUnique{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorType.confirmEmail|isset} class="formError"{/if}>
+ <dt>
+ <label for="confirmEmail">{lang}wcf.user.confirmEmail{/lang}</label>
+ </dt>
+ <dd>
+ <input type="email" id="confirmEmail" name="confirmEmail" value="{$confirmEmail}" required="required" class="medium" />
+ {if $errorType.confirmEmail|isset}
+ <small class="innerError">
+ {if $errorType.confirmEmail == 'notEqual'}{lang}wcf.user.confirmEmail.error.notEqual{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='emailFields'}
+ </fieldset>
+
+ {if !$isExternalAuthentication}
+ <fieldset>
+ <legend>{lang}wcf.user.password{/lang}</legend>
+
+ <dl{if $errorType.password|isset} class="formError"{/if}>
+ <dt>
+ <label for="password">{lang}wcf.user.password{/lang}</label>
+ </dt>
+ <dd>
+ <input type="password" id="password" name="password" value="{$password}" required="required" class="medium" />
+ {if $errorType.password|isset}
+ <small class="innerError">
+ {if $errorType.password == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ {if $errorType.password == 'notSecure'}{lang}wcf.user.password.error.notSecure{/lang}{/if}
+ </small>
+ {/if}
+ <small>{lang}wcf.user.password.description{/lang}</small>
+ </dd>
+ </dl>
+
+ <dl{if $errorType.confirmPassword|isset} class="formError"{/if}>
+ <dt>
+ <label for="confirmPassword">{lang}wcf.user.confirmPassword{/lang}</label>
+ </dt>
+ <dd>
+ <input type="password" id="confirmPassword" name="confirmPassword" value="{$confirmPassword}" required="required" class="medium" />
+ {if $errorType.confirmPassword|isset}
+ <small class="innerError">
+ {if $errorType.confirmPassword == 'notEqual'}{lang}wcf.user.confirmPassword.error.notEqual{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='passwordFields'}
+ </fieldset>
+ {/if}
+
+ {if $availableLanguages|count > 1}
+ <fieldset>
+ <legend>{lang}wcf.user.language{/lang}</legend>
+
+ <dl>
+ <dt><label for="languageID">{lang}wcf.user.language{/lang}</label></dt>
+ <dd>
+ <select id="languageID" name="languageID">
+ {foreach from=$availableLanguages item=language}
+ <option value="{@$language->languageID}"{if $language->languageID == $languageID} selected="selected"{/if}>{$language}</option>
+ {/foreach}
+ </select>
+ <small>{lang}wcf.user.language.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {hascontent}
+ <dl>
+ <dt><label>{lang}wcf.user.visibleLanguages{/lang}</label></dt>
+ <dd class="floated">
+ {content}
+ {foreach from=$availableContentLanguages item=language}
+ <label><input name="visibleLanguages[]" type="checkbox" value="{@$language->languageID}"{if $language->languageID|in_array:$visibleLanguages} checked="checked"{/if} /> {$language}</label>
+ {/foreach}
+ {/content}
+ <small>{lang}wcf.user.visibleLanguages.description{/lang}</small></dd>
+ </dl>
+ {/hascontent}
+
+ {event name='languageFields'}
+ </fieldset>
+ {/if}
+
+ {foreach from=$optionTree item=category}
+ <fieldset>
+ <legend>{lang}wcf.user.option.category.{@$category[object]->categoryName}{/lang}</legend>
+
+ {include file='userOptionFieldList' options=$category[options] langPrefix='wcf.user.option.'}
+ </fieldset>
+ {/foreach}
+
+ {event name='fieldsets'}
+
+ {if $useCaptcha}
+ {if $errorType.recaptchaString|isset}
+ {assign var=errorField value='recaptchaString'}
+ {assign var=errorType value=$errorType.recaptchaString}
+ {/if}
+ {include file='recaptcha'}
+ {/if}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.registerActivation{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.registerActivation{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $__wcf->user->userID && $__wcf->user->activationCode}<p class="info">{lang}wcf.user.registerActivation.info{/lang}</p>{/if}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='RegisterActivation'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend><label for="userID">{lang}wcf.user.registerActivation{/lang}</label></legend>
+
+ <dl{if $errorField == 'username'} class="formError"{/if}>
+ <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
+ <dd>
+ <input type="text" id="username" name="username" value="{@$username}" required="required" class="medium" />
+ {if $errorField == 'username'}
+ <small class="innerError">
+ {if $errorType == 'notFound'}{lang}wcf.user.username.error.notFound{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'activationCode'} class="formError"{/if}>
+ <dt><label for="activationCode">{lang}wcf.user.activationCode{/lang}</label></dt>
+ <dd>
+ <input type="text" id="activationCode" maxlength="9" name="activationCode" value="{@$activationCode}" required="required" class="medium" />
+ {if $errorField == 'activationCode'}
+ <small class="innerError">
+ {if $errorType == 'notValid'}{lang}wcf.user.activationCode.error.notValid{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='fields'}
+
+ <dl>
+ <dd>
+ <ul class="buttonList">
+ <li><a class="button small" href="{link controller='RegisterNewActivationCode'}{/link}"><span>{lang}wcf.user.newActivationCode{/lang}</span></a></li>
+ {event name='buttons'}
+ </ul>
+ </dd>
+ </dl>
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.newActivationCode{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.newActivationCode{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='RegisterNewActivationCode'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.newActivationCode{/lang}</legend>
+
+ <dl{if $errorField == 'username'} class="formError"{/if}>
+ <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
+ <dd>
+ <input type="text" id="username" name="username" value="{$username}" required="required" class="medium" />
+ {if $errorField == 'username'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {elseif $errorType == 'alreadyEnabled'}
+ {lang}wcf.user.registerActivation.error.userAlreadyEnabled{/lang}
+ {else}
+ {lang}wcf.user.username.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'password'} class="formError"{/if}>
+ <dt><label for="password">{lang}wcf.user.password{/lang}</label></dt>
+ <dd>
+ <input type="password" id="password" name="password" value="{$password}" required="required" class="medium" />
+ {if $errorField == 'password'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.user.password.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'email'} class="formError"{/if}>
+ <dt><label for="email">{lang}wcf.user.email{/lang}</label></dt>
+ <dd>
+ <input type="email" id="email" name="email" value="{$email}" class="medium" />
+ {if $errorField == 'email'}
+ <small class="innerError">
+ {lang}wcf.user.email.error.{@$errorType}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.user.registerNewActivationCode.email.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='fields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.option.category.settings.{$category}{/lang} - {lang}wcf.user.menu.settings{/lang} - {PAGE_TITLE|language}</title>
+ {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.menu.settings{/lang}: {lang}wcf.user.option.category.settings.{$category}{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.edit{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='Settings'}{/link}">
+ <div class="container containerPadding marginTop">
+ {if $category == 'general'}
+ <fieldset>
+ <legend>{lang}wcf.user.language{/lang}</legend>
+
+ <dl>
+ <dt><label>{lang}wcf.user.language{/lang}</label></dt>
+ <dd id="languageIDContainer">
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ var $languages = {
+ {implode from=$availableLanguages item=language}
+ '{@$language->languageID}': {
+ iconPath: '{@$language->getIconPath()}',
+ languageName: '{$language}'
+ }
+ {/implode}
+ };
+
+ new WCF.Language.Chooser('languageIDContainer', 'languageID', {@$languageID}, $languages);
+ });
+ //]]>
+ </script>
+ <noscript>
+ <select name="languageID" id="languageID">
+ {foreach from=$availableLanguages item=language}
+ <option value="{@$language->languageID}"{if $language->languageID == $languageID} selected="selected"{/if}>{$language}</option>
+ {/foreach}
+ </select>
+ </noscript>
+ </dd>
+ </dl>
+
+ {hascontent}
+ <dl>
+ <dt><label>{lang}wcf.user.visibleLanguages{/lang}</label></dt>
+ <dd class="floated">
+ {content}
+ {foreach from=$availableContentLanguages item=language}
+ <label><input name="contentLanguageIDs[]" type="checkbox" value="{@$language->languageID}"{if $language->languageID|in_array:$contentLanguageIDs} checked="checked"{/if} /> {$language}</label>
+ {/foreach}
+ {/content}
+ <small>{lang}wcf.user.visibleLanguages.description{/lang}</small></dd>
+ </dl>
+ {/hascontent}
+
+ {event name='languageFields'}
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.user.style{/lang}</legend>
+
+ <dl>
+ <dt><label for="styleID">{lang}wcf.user.style{/lang}</label></dt>
+ <dd>
+ <!-- TODO: Add some fancy JavaScript to display preview images, this should be common enough to use it in boardAdd.tpl too! -->
+ <select id="styleID" name="styleID">
+ <option value="0"></option>
+ {foreach from=$availableStyles item=style}
+ <option value="{@$style->styleID}"{if $style->styleID == $styleID} selected="selected"{/if}>{$style->styleName}</option>
+ {/foreach}
+ </select>
+ <small>{lang}wcf.user.style.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='styleFields'}
+ </fieldset>
+ {/if}
+
+ {foreach from=$optionTree[0][categories][0][categories] item=optionCategory}
+ <fieldset>
+ <legend>{lang}wcf.user.option.category.{@$optionCategory[object]->categoryName}{/lang}</legend>
+
+ {include file='userProfileOptionFieldList' options=$optionCategory[options] langPrefix='wcf.user.option.'}
+ </fieldset>
+ {/foreach}
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ {if $category != 'general'}<input type="hidden" name="category" value="{$category}" />{/if}
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.signature.edit{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.global.preview': '{lang}wcf.global.preview{/lang}'
+ });
+
+ new WCF.User.SignaturePreview('wcf\\data\\user\\UserProfileAction', 'text', 'previewButton');
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.signature.edit{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.edit{/lang}</p>
+{/if}
+
+{if $__wcf->user->disableSignature}
+ <p class="error">{lang}wcf.user.signature.error.disabled{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='SignatureEdit'}{/link}">
+ <div class="container containerPadding marginTop">
+ {if $signatureCache}
+ <fieldset>
+ <legend>{lang}wcf.user.signature.current{/lang}</legend>
+
+ {@$signatureCache}
+ </fieldset>
+ {/if}
+
+ {if !$__wcf->user->disableSignature}
+ <fieldset id="signatureContainer">
+ <legend>{lang}wcf.user.signature{/lang}</legend>
+
+ <dl class="wide{if $errorField == 'text'} formError{/if}">
+ <dt><label for="text">{lang}wcf.user.signature{/lang}</label></dt>
+ <dd>
+ <textarea id="text" name="text" rows="20" cols="40">{$text}</textarea>
+ {if $errorField == 'text'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {elseif $errorType == 'tooLong'}
+ {lang}wcf.message.error.tooLong{/lang}
+ {elseif $errorType == 'censoredWordsFound'}
+ {lang}wcf.message.error.censoredWordsFound{/lang}
+ {elseif $errorType == 'disallowedBBCodes'}
+ {lang}wcf.message.error.disallowedBBCodes{/lang}
+ {else}
+ {lang}wcf.user.signature.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {event name='fields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+
+ {include file='messageFormTabs'}
+ {/if}
+ </div>
+
+ {if !$__wcf->user->disableSignature}
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ <button id="previewButton" class="jsOnly" accesskey="p">{lang}wcf.global.button.preview{/lang}</button>
+ </div>
+ {/if}
+</form>
+
+{include file='footer'}
+{include file='wysiwyg'}
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+<ul class="sitemapList">
+ {if $__wcf->getUser()->userID}
+ {assign var=__userMenuActiveItems value=$__wcf->getUserMenu()->getActiveMenuItems()}
+ {foreach from=$__wcf->getUserMenu()->getMenuItems('') item=menuCategory}
+ <li>
+ <h3>{lang}{$menuCategory->menuItem}{/lang}</h3>
+ <ul>
+ {foreach from=$__wcf->getUserMenu()->getMenuItems($menuCategory->menuItem) item=menuItem}
+ <li><a href="{$menuItem->getLink()}">{lang}{$menuItem->menuItem}{/lang}</a></li>
+ {/foreach}
+ </ul>
+ </li>
+ {/foreach}
+ {else}
+ <li>
+ <a href="{link controller='Login'}{/link}">{lang}wcf.user.login{/lang}</a>
+ </li>
+ <li>
+ <a href="{link controller='Register'}{/link}">{lang}wcf.user.register{/lang}</a>
+ </li>
+ {/if}
+</ul>
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.team{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <link rel="canonical" href="{link controller='Team'}{/link}" />
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.button.follow': '{lang}wcf.user.button.follow{/lang}',
+ 'wcf.user.button.ignore': '{lang}wcf.user.button.ignore{/lang}',
+ 'wcf.user.button.unfollow': '{lang}wcf.user.button.unfollow{/lang}',
+ 'wcf.user.button.unignore': '{lang}wcf.user.button.unignore{/lang}'
+ });
+
+ new WCF.User.Action.Follow($('.userList > li'));
+ new WCF.User.Action.Ignore($('.userList > li'));
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{capture assign='sidebar'}
+ {@$__boxSidebar}
+{/capture}
+
+{include file='header' sidebarOrientation='right'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.team{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{foreach from=$objects->getTeams() item=team}
+ <header class="boxHeadline boxSubHeadline">
+ <h2>{$team->groupName|language} <span class="badge">{#$team->getMembers()|count}</span></h2>
+ </header>
+
+ <div class="container marginTop">
+ <ol class="containerList doubleColumned userList">
+ {foreach from=$team->getMembers() item=user}
+ {include file='userListItem'}
+ {/foreach}
+ </ol>
+ </div>
+{/foreach}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.profile{/lang} - {lang}wcf.user.members{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <link rel="canonical" href="{link controller='User' object=$user}{/link}" />
+
+ <script type="text/javascript" src="{@$__wcf->getPath('wcf')}js/WCF.User{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+ {event name='javascriptInclude'}
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ {if $__wcf->getUser()->userID && $__wcf->getUser()->userID != $user->userID}
+ WCF.Language.addObject({
+ 'wcf.user.activityPoint': '{lang}wcf.user.activityPoint{/lang}',
+ 'wcf.user.button.follow': '{lang}wcf.user.button.follow{/lang}',
+ 'wcf.user.button.unfollow': '{lang}wcf.user.button.unfollow{/lang}',
+ 'wcf.user.button.ignore': '{lang}wcf.user.button.ignore{/lang}',
+ 'wcf.user.button.unignore': '{lang}wcf.user.button.unignore{/lang}'
+ });
+
+ {if !$user->getPermission('user.profile.cannotBeIgnored')}
+ new WCF.User.Profile.IgnoreUser({@$user->userID}, {if $__wcf->getUserProfileHandler()->isIgnoredUser($user->userID)}true{else}false{/if});
+ {/if}
+
+ new WCF.User.Profile.Follow({@$user->userID}, {if $__wcf->getUserProfileHandler()->isFollowing($user->userID)}true{else}false{/if});
+ {/if}
+
+ new WCF.User.Profile.TabMenu({@$user->userID});
+
+ WCF.TabMenu.init();
+
+ {if $user->canEdit() || ($__wcf->getUser()->userID == $user->userID && $user->canEditOwnProfile())}
+ WCF.Language.addObject({
+ 'wcf.user.editProfile': '{lang}wcf.user.editProfile{/lang}',
+ });
+
+ new WCF.User.Profile.Editor({@$user->userID}, {if $editOnInit}true{else}false{/if});
+ {/if}
+
+ {if $user->activityPoints}
+ WCF.Language.addObject({
+ 'wcf.user.activityPoint': '{lang}wcf.user.activityPoint{/lang}'
+ });
+
+ WCF.User.Profile.ActivityPointList.init();
+ {/if}
+
+ {if $followingCount > 10}
+ var $followingList = null;
+ $('#followingAll').click(function() {
+ if ($followingList === null) {
+ $followingList = new WCF.User.List('wcf\\data\\user\\follow\\UserFollowingAction', '{lang}wcf.user.profile.following{/lang}', { userID: {@$user->userID} });
+ }
+
+ $followingList.open();
+ });
+ {/if}
+ {if $followerCount > 10}
+ var $followerList = null;
+ $('#followerAll').click(function() {
+ if ($followerList === null) {
+ $followerList = new WCF.User.List('wcf\\data\\user\\follow\\UserFollowAction', '{lang}wcf.user.profile.followers{/lang}', { userID: {@$user->userID} });
+ }
+
+ $followerList.open();
+ });
+ {/if}
+ {if $visitorCount > 10}
+ var $visitorList = null;
+ $('#visitorAll').click(function() {
+ if ($visitorList === null) {
+ $visitorList = new WCF.User.List('wcf\\data\\user\\profile\\visitor\\UserProfileVisitorAction', '{lang}wcf.user.profile.visitors{/lang}', { userID: {@$user->userID} });
+ }
+
+ $visitorList.open();
+ });
+ {/if}
+
+ {event name='javascriptInit'}
+ });
+ //]]>
+ </script>
+
+ <noscript>
+ <style type="text/css">
+ #profileContent > .tabMenu > ul > li:not(:first-child) {
+ display: none !important;
+ }
+
+ #profileContent > .tabMenuContent:not(:first-of-type) {
+ display: none !important;
+ }
+ </style>
+ </noscript>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{include file='userSidebar' assign='sidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline userHeadline">
+ <span class="framed invisible">{@$user->getAvatar()->getImageTag(48)}</span>
+
+ <h1>{$user->username}{if MODULE_USER_RANK && $user->getUserTitle()} <span class="badge userTitleBadge{if $user->getRank() && $user->getRank()->cssClassName} {@$user->getRank()->cssClassName}{/if}">{$user->getUserTitle()}</span>{/if}</h1>
+
+ <ul class="dataList">
+ {if $user->gender}<li>{lang}wcf.user.gender.{if $user->gender == 1}male{else}female{/if}{/lang}</li>{/if}
+ {if $user->getAge()}<li>{@$user->getAge()}</li>{/if}
+ {if $user->location}<li>{lang}wcf.user.membersList.location{/lang}</li>{/if}
+ {if $user->getOldUsername()}<li>{lang}wcf.user.profile.oldUsername{/lang}</li>{/if}
+ <li>{lang}wcf.user.membersList.registrationDate{/lang}</li>
+ {event name='userDataRow1'}
+ </ul>
+ {if $user->canViewOnlineStatus() && $user->getLastActivityTime()}
+ <dl class="plain inlineDataList">
+ <dt>{lang}wcf.user.usersOnline.lastActivity{/lang}</dt>
+ <dd>{@$user->getLastActivityTime()|time}{if $user->getCurrentLocation()}, {@$user->getCurrentLocation()}{/if}</dd>
+ {event name='userDataRow2'}
+ </dl>
+ {/if}
+ <nav class="jsMobileNavigation buttonGroupNavigation">
+ <ul id="profileButtonContainer" class="buttonGroup">{*
+ *}{if $user->userID != $__wcf->user->userID}{if $user->isAccessible('canViewEmailAddress')}<li><a class="button jsTooltip" href="mailto:{@$user->getEncodedEmail()}" title="{lang}wcf.user.button.mail{/lang}"><span class="icon icon16 icon-envelope-alt"></span> <span class="invisible">{lang}wcf.user.button.mail{/lang}</span></a></li>{elseif $user->isAccessible('canMail') && $__wcf->session->getPermission('user.profile.canMail')}<li><a class="button jsTooltip" href="{link controller='Mail' object=$user}{/link}" title="{lang}wcf.user.button.mail{/lang}"><span class="icon icon16 icon-envelope-alt"></span> <span class="invisible">{lang}wcf.user.button.mail{/lang}</span></a></li>{/if}{/if}{*
+ *}{event name='buttons'}{*
+ *}</ul>
+ </nav>
+</header>
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<section id="profileContent" class="marginTop tabMenuContainer" data-active="{$__wcf->getUserProfileMenu()->getActiveMenuItem()->getIdentifier()}">
+ <nav class="tabMenu">
+ <ul>
+ {foreach from=$__wcf->getUserProfileMenu()->getMenuItems() item=menuItem}
+ {if $menuItem->getContentManager()->isVisible($userID)}
+ <li><a href="{$__wcf->getAnchor($menuItem->getIdentifier())}">{lang}wcf.user.profile.menu.{@$menuItem->menuItem}{/lang}</a></li>
+ {/if}
+ {/foreach}
+ </ul>
+ </nav>
+
+ {foreach from=$__wcf->getUserProfileMenu()->getMenuItems() item=menuItem}
+ {if $menuItem->getContentManager()->isVisible($userID)}
+ <div id="{$menuItem->getIdentifier()}" class="container tabMenuContent" data-menu-item="{$menuItem->menuItem}">
+ {if $menuItem === $__wcf->getUserProfileMenu()->getActiveMenuItem()}
+ {@$profileContent}
+ {/if}
+ </div>
+ {/if}
+ {/foreach}
+</section>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+{include file='userInformationHeadline'}
+
+{include file='userInformationButtons'}
+
+{include file='userInformationStatistics'}
\ No newline at end of file
--- /dev/null
+{hascontent}
+ <nav class="jsMobileNavigation buttonGroupNavigation">
+ <ul class="buttonList">
+ {content}
+ {if $user->homepage}
+ <li><a class="jsTooltip" href="{@$user->homepage}" title="{lang}wcf.user.option.homepage{/lang}"{if EXTERNAL_LINK_REL_NOFOLLOW} rel="nofollow"{/if}{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}><span class="icon icon16 icon-home"></span> <span class="invisible">{lang}wcf.user.option.homepage{/lang}</span></a></li>
+ {/if}
+
+ {if $user->userID != $__wcf->user->userID}
+ {if $user->isAccessible('canViewEmailAddress')}
+ <li><a class="jsTooltip" href="mailto:{@$user->getEncodedEmail()}" title="{lang}wcf.user.button.mail{/lang}"><span class="icon icon16 icon-envelope-alt"></span> <span class="invisible">{lang}wcf.user.button.mail{/lang}</span></a></li>
+ {elseif $user->isAccessible('canMail') && $__wcf->session->getPermission('user.profile.canMail')}
+ <li><a class="jsTooltip" href="{link controller='Mail' object=$user}{/link}" title="{lang}wcf.user.button.mail{/lang}"><span class="icon icon16 icon-envelope-alt"></span> <span class="invisible">{lang}wcf.user.button.mail{/lang}</span></a></li>
+ {/if}
+ {/if}
+
+ {if $__wcf->user->userID && $user->userID != $__wcf->user->userID}
+ {if $__wcf->getUserProfileHandler()->isFollowing($user->userID)}
+ <li class="jsOnly"><a data-following="1" data-object-id="{@$user->userID}" class="jsFollowButton jsTooltip" title="{lang}wcf.user.button.unfollow{/lang}"><span class="icon icon16 icon-minus"></span> <span class="invisible">{lang}wcf.user.button.unfollow{/lang}</span></a></li>
+ {else}
+ <li class="jsOnly"><a data-following="0" data-object-id="{@$user->userID}" class="jsFollowButton jsTooltip" title="{lang}wcf.user.button.follow{/lang}"><span class="icon icon16 icon-plus"></span> <span class="invisible">{lang}wcf.user.button.follow{/lang}</span></a></li>
+ {/if}
+
+ {*if !$user->getPermission('user.profile.cannotBeIgnored')*}{*disabled for performance reasons*}
+ {if $__wcf->getUserProfileHandler()->isIgnoredUser($user->userID)}
+ <li class="jsOnly"><a data-ignored="1" data-object-id="{@$user->userID}" class="jsIgnoreButton jsTooltip" title="{lang}wcf.user.button.unignore{/lang}"><span class="icon icon16 icon-circle-blank"></span> <span class="invisible">{lang}wcf.user.button.unignore{/lang}</span></a></li>
+ {else}
+ <li class="jsOnly"><a data-ignored="0" data-object-id="{@$user->userID}" class="jsIgnoreButton jsTooltip" title="{lang}wcf.user.button.ignore{/lang}"><span class="icon icon16 icon-off"></span> <span class="invisible">{lang}wcf.user.button.ignore{/lang}</span></a></li>
+ {/if}
+ {*/if*}
+ {/if}
+
+ {event name='buttons'}
+ {/content}
+ </ul>
+ </nav>
+{/hascontent}
\ No newline at end of file
--- /dev/null
+<div class="containerHeadline">
+ <h3><a href="{link controller='User' object=$user}{/link}">{$user->username}</a>{if MODULE_USER_RANK && $user->getUserTitle()} <span class="badge userTitleBadge{if $user->getRank() && $user->getRank()->cssClassName} {@$user->getRank()->cssClassName}{/if}">{$user->getUserTitle()}</span>{/if}</h3>
+</div>
+<ul class="dataList userFacts">
+ {if $user->gender}<li>{lang}wcf.user.gender.{if $user->gender == 1}male{else}female{/if}{/lang}</li>{/if}
+ {if $user->getAge()}<li>{@$user->getAge()}</li>{/if}
+ {if $user->location}<li>{lang}wcf.user.membersList.location{/lang}</li>{/if}
+ <li>{lang}wcf.user.membersList.registrationDate{/lang}</li>
+
+ {event name='userData'}
+</ul>
\ No newline at end of file
--- /dev/null
+<dl class="plain inlineDataList userStats">
+ {event name='statistics'}
+
+ <dt>{lang}wcf.user.activityPoint{/lang}</dt>
+ <dd>{#$user->activityPoints}</dd>
+</dl>
\ No newline at end of file
--- /dev/null
+<li>
+ <div class="box48">
+ <a href="{link controller='User' object=$user}{/link}" title="{$user->username}" class="framed">{@$user->getAvatar()->getImageTag(48)}</a>
+
+ <div class="details userInformation">
+ {include file='userInformation'}
+ </div>
+ </div>
+</li>
\ No newline at end of file
--- /dev/null
+{capture assign='sidebar'}
+ {assign var=__userMenuActiveItems value=$__wcf->getUserMenu()->getActiveMenuItems()}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ // mobile safari hover workaround
+ if ($(window).width() <= 800) {
+ $('.sidebar').addClass('mobileSidebar').hover(function() { });
+ }
+ });
+ //]]>
+ </script>
+
+ {foreach from=$__wcf->getUserMenu()->getMenuItems('') item=menuCategory}
+ <fieldset>
+ <legend>{lang}{$menuCategory->menuItem}{/lang}</legend>
+
+ <nav>
+ <ul>
+ {foreach from=$__wcf->getUserMenu()->getMenuItems($menuCategory->menuItem) item=menuItem}
+ <li{if $menuItem->menuItem|in_array:$__userMenuActiveItems} class="active"{/if}><a href="{$menuItem->getProcessor()->getLink()}">{@$menuItem}</a></li>
+ {/foreach}
+ </ul>
+ </nav>
+ </fieldset>
+ {/foreach}
+{/capture}
</div>
{/if}
+ {if $__wcf->user->activationCode && REGISTER_ACTIVATION_METHOD == 1}
+ <p class="warning">{lang}wcf.user.register.needActivation{/lang}</p>
+ {/if}
+
{event name='userNotice'}
</div>
\ No newline at end of file
--- /dev/null
+<li class="userNotificationItem">
+ <a class="box24">
+ <div class="framed">{@$author->getAvatar()->getImageTag(24)}</div>
+ <div>
+ <h3>{$label}</h3>
+ <small>{@$time|time}</small>
+ </div>
+ </a>
+</li>
\ No newline at end of file
--- /dev/null
+{foreach from=$options item=optionData}
+ {assign var=option value=$optionData[object]}
+ <dl class="{$option->optionName}Input{if $errorType|is_array && $errorType[$option->optionName]|isset} formError{/if}">
+ <dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}><label for="{$option->optionName}">{lang}{@$langPrefix}{$option->optionName}{/lang}</label></dt>
+ <dd>{@$optionData[html]}
+ <small>{lang __optional=true}{@$langPrefix}{$option->optionName}.description{/lang}</small>
+
+ {if $errorType|is_array && $errorType[$option->optionName]|isset}
+ <small class="innerError">
+ {if $errorType[$option->optionName] == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}{@$langPrefix}error.{$errorType[$option->optionName]}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+{/foreach}
--- /dev/null
+{if $__wcf->user->userID}
+ <!-- user menu -->
+ <li id="userMenu" class="dropdown">
+ <a class="dropdownToggle framed" data-toggle="userMenu" href="{link controller='User' object=$__wcf->user}{/link}">{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(24)} <span>{lang}wcf.user.userNote{/lang}</span></a>
+ <ul class="dropdownMenu">
+ <li><a href="{link controller='User' object=$__wcf->user}{/link}" class="box32">
+ <div class="framed">{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(32)}</div>
+
+ <div class="containerHeadline">
+ <h3>{$__wcf->user->username}</h3>
+ <small>{lang}wcf.user.myProfile{/lang}</small>
+ </div>
+ </a></li>
+ {if $__wcf->getUserProfileHandler()->canEditOwnProfile()}<li><a href="{link controller='User' object=$__wcf->user}editOnInit=true#about{/link}">{lang}wcf.user.editProfile{/lang}</a></li>{/if}
+ <li><a href="{link controller='Settings'}{/link}">{lang}wcf.user.menu.settings{/lang}</a></li>
+
+ {event name='userMenuItems'}
+
+ {if $__wcf->session->getPermission('admin.general.canUseAcp')}
+ <li class="dropdownDivider"></li>
+ <li><a href="{link isACP=true}{/link}">{lang}wcf.global.acp.short{/lang}</a></li>
+ {/if}
+ <li class="dropdownDivider"></li>
+ <li><a href="{link controller='Logout'}t={@SECURITY_TOKEN}{/link}" onclick="WCF.System.Confirmation.show('{lang}wcf.user.logout.sure{/lang}', $.proxy(function (action) { if (action == 'confirm') window.location.href = $(this).attr('href'); }, this)); return false;">{lang}wcf.user.logout{/lang}</a></li>
+ </ul>
+ </li>
+
+ <li><a href="{link controller='Settings'}{/link}" class="noJsOnly" style="display: none"><span class="icon icon16 icon-cogs"></span> <span>{lang}wcf.user.menu.settings{/lang}</span></a></li>
+
+ <!-- user notifications -->
+ {if !$__hideUserMenu|isset}
+ <li id="userNotifications" data-count="{#$__wcf->getUserNotificationHandler()->getNotificationCount()}">
+ <a href="{link controller='NotificationList'}{/link}"><span class="icon icon16 icon-bell-alt"></span> <span>{lang}wcf.user.notification.notifications{/lang}</span>{if $__wcf->getUserNotificationHandler()->getNotificationCount()} <span class="badge badgeInverse">{#$__wcf->getUserNotificationHandler()->getNotificationCount()}</span>{/if}</a>
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.notification.count': '{lang}wcf.user.notification.count{/lang}',
+ 'wcf.user.notification.markAllAsConfirmed': '{lang}wcf.user.notification.markAllAsConfirmed{/lang}',
+ 'wcf.user.notification.markAllAsConfirmed.confirmMessage': '{lang}wcf.user.notification.markAllAsConfirmed.confirmMessage{/lang}',
+ 'wcf.user.notification.noMoreNotifications': '{lang}wcf.user.notification.noMoreNotifications{/lang}',
+ 'wcf.user.notification.showAll': '{lang}wcf.user.notification.showAll{/lang}'
+ });
+
+ new WCF.Notification.UserPanel('{link controller='NotificationList'}{/link}');
+ });
+ //]]>
+ </script>
+ </li>
+ {/if}
+{else}
+ {if !$__disableLoginLink|isset}
+ <!-- login box -->
+ <li>
+ <a class="loginLink" href="{link controller='Login'}{/link}">{lang}wcf.user.loginOrRegister{/lang}</a>
+ <div id="loginForm" style="display: none;">
+ {capture assign='__3rdPartyButtons'}
+ {if GITHUB_PUBLIC_KEY !== '' && GITHUB_PRIVATE_KEY !== ''}
+ <li id="githubAuth" class="3rdPartyAuth">
+ <a href="{link controller='GithubAuth'}{/link}" class="button"><span class="icon icon16 icon-github"></span> <span>{lang}wcf.user.3rdparty.github.login{/lang}</span></a>
+ </li>{*
+ *}{/if}{*
+
+ *}{if TWITTER_PUBLIC_KEY !== '' && TWITTER_PRIVATE_KEY !== ''}{*
+ *}<li id="twitterAuth" class="3rdPartyAuth">
+ <a href="{link controller='TwitterAuth'}{/link}" class="button"><span class="icon icon16 icon-twitter"></span> <span>{lang}wcf.user.3rdparty.twitter.login{/lang}</span></a>
+ </li>{*
+ *}{/if}{*
+
+ *}{if FACEBOOK_PUBLIC_KEY !== '' && FACEBOOK_PRIVATE_KEY !== ''}{*
+ *}<li id="facebookAuth" class="3rdPartyAuth">
+ <a href="{link controller='FacebookAuth'}{/link}" class="button"><span class="icon icon16 icon-facebook"></span> <span>{lang}wcf.user.3rdparty.facebook.login{/lang}</span></a>
+ </li>{*
+ *}{/if}{*
+
+ *}{if GOOGLE_PUBLIC_KEY !== '' && GOOGLE_PRIVATE_KEY !== ''}{*
+ *}<li id="googleAuth" class="3rdPartyAuth">
+ <a href="{link controller='GoogleAuth'}{/link}" class="button"><span class="icon icon16 icon-google-plus"></span> <span>{lang}wcf.user.3rdparty.google.login{/lang}</span></a>
+ </li>
+ {/if}
+ {/capture}
+
+ <form method="post" action="{link controller='Login'}{/link}">
+ <fieldset>
+ {if $__3rdPartyButtons|trim}<legend>{lang}wcf.user.login{/lang}</legend>{/if}
+
+ <dl>
+ <dt><label for="username">{lang}wcf.user.usernameOrEmail{/lang}</label></dt>
+ <dd>
+ <input type="text" id="username" name="username" value="" required="required" class="long" />
+ </dd>
+ </dl>
+
+ {if !REGISTER_DISABLED}
+ <dl>
+ <dt>{lang}wcf.user.login.action{/lang}</dt>
+ <dd>
+ <label><input type="radio" name="action" value="register" /> {lang}wcf.user.login.action.register{/lang}</label>
+ <label><input type="radio" name="action" value="login" checked="checked" /> {lang}wcf.user.login.action.login{/lang}</label>
+ </dd>
+ </dl>
+ {/if}
+
+ <dl>
+ <dt><label for="password">{lang}wcf.user.password{/lang}</label></dt>
+ <dd>
+ <input type="password" id="password" name="password" value="" class="long" />
+ </dd>
+ </dl>
+
+ {if $__wcf->getUserAuthenticationFactory()->getUserAuthentication()->supportsPersistentLogins()}
+ <dl>
+ <dd><label><input type="checkbox" id="useCookies" name="useCookies" value="1" checked="checked" /> {lang}wcf.user.useCookies{/lang}</label></dd>
+ </dl>
+ {/if}
+
+ {event name='loginFields'}
+
+ <div class="formSubmit">
+ <input type="submit" id="loginSubmitButton" name="submitButton" value="{lang}wcf.user.button.login{/lang}" accesskey="s" />
+ <input type="hidden" name="url" value="{$__wcf->session->requestURI}" />
+ </div>
+ </fieldset>
+
+ {if $__3rdPartyButtons|trim}
+ <fieldset>
+ <legend>{lang}wcf.user.login.3rdParty{/lang}</legend>
+ <ul class="buttonGroup" style="text-align: center;"> {* todo: inline CSS *}
+ {@$__3rdPartyButtons}
+ </ul>
+ </fieldset>
+ {/if}
+ </form>
+ </div>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.button.login': '{lang}wcf.user.button.login{/lang}',
+ 'wcf.user.button.register': '{lang}wcf.user.button.register{/lang}',
+ 'wcf.user.login': '{lang}wcf.user.login{/lang}'
+ });
+ new WCF.User.Login(true);
+ });
+ //]]>
+ </script>
+ </li>
+ {/if}
+ <!-- language switcher -->
+ <li id="pageLanguageContainer">
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ var $languages = {
+ {implode from=$__wcf->getLanguage()->getLanguages() item=language}
+ '{@$language->languageID}': {
+ iconPath: '{@$language->getIconPath()}',
+ languageName: '{$language}'
+ }
+ {/implode}
+ };
+
+ new WCF.Language.Chooser('pageLanguageContainer', 'languageID', {@$__wcf->getLanguage()->languageID}, $languages, function(item) {
+ var $location = window.location.toString().replace(/#.*/, '').replace(/(\?|&)l=[0-9]+/g, '');
+ var $delimiter = ($location.indexOf('?') == -1) ? '?' : '&';
+
+ window.location = $location + $delimiter + 'l=' + item.data('languageID') + window.location.hash;
+ });
+ });
+ //]]>
+ </script>
+ </li>
+{/if}
+
+{if !$__hideUserMenu|isset}
+ {event name='menuItems'}
+{/if}
+
+{if $__wcf->user->userID}
+ <li><a href="{link controller='Logout'}t={@SECURITY_TOKEN}{/link}" class="noJsOnly" style="display: none"><span class="icon icon16 icon-signout"></span> <span>{lang}wcf.user.logout{/lang}</span></a></li>
+{/if}
--- /dev/null
+<div class="containerPadding">
+ {hascontent}
+ {content}
+ {foreach from=$options item=category}
+ {foreach from=$category[categories] item=optionCategory}
+ <fieldset>
+ <legend>{lang}wcf.user.option.category.{@$optionCategory[object]->categoryName}{/lang}</legend>
+
+ <dl>
+ {foreach from=$optionCategory[options] item=userOption}
+ <dt>{lang}wcf.user.option.{@$userOption[object]->optionName}{/lang}</dt>
+ <dd>{@$userOption[object]->optionValue}</dd>
+ {/foreach}
+ </dl>
+ </fieldset>
+ {/foreach}
+ {/foreach}
+ {/content}
+ {hascontentelse}
+ {lang}wcf.user.profile.content.about.noPublicData{/lang}
+ {/hascontent}
+</div>
\ No newline at end of file
--- /dev/null
+<div class="containerPadding">
+ {foreach from=$optionTree item=categoryLevel1}
+ {foreach from=$categoryLevel1[categories] item=categoryLevel2}
+ <fieldset>
+ <legend>{lang}wcf.user.option.category.{@$categoryLevel2[object]->categoryName}{/lang}</legend>
+
+ {if $categoryLevel2[object]->categoryName == 'profile.personal' && MODULE_USER_RANK && $__wcf->session->getPermission('user.profile.canEditUserTitle')}
+ <dl>
+ <dt><label for="__userTitle">{lang}wcf.user.userTitle{/lang}</label></dt>
+ <dd>
+ <input type="text" id="__userTitle" name="values[__userTitle]" value="{$__userTitle}" class="long" maxlength="{@USER_TITLE_MAX_LENGTH}" />
+ {if $errorType[__userTitle]|isset}
+ <small class="innerError">
+ {lang}wcf.user.userTitle.error.{@$errorType[__userTitle]}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.user.userTitle.description{/lang}</small>
+ </dd>
+ </dl>
+ {/if}
+
+ {include file='userProfileOptionFieldList' options=$categoryLevel2[options] langPrefix='wcf.user.option.'}
+ </fieldset>
+ {/foreach}
+ {/foreach}
+
+ <div class="formSubmit">
+ <button class="buttonPrimary" accesskey="s" data-type="save">{lang}wcf.global.button.save{/lang}</button>
+ <button data-type="restore">{lang}wcf.global.button.cancel{/lang}</button>
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+{foreach from=$options item=optionData}
+ {assign var=option value=$optionData[object]}
+ {if $errorType|is_array && $errorType[$option->optionName]|isset}
+ {assign var=error value=$errorType[$option->optionName]}
+ {else}
+ {assign var=error value=''}
+ {/if}
+ <dl class="{$option->optionName}Input{if $error} formError{/if}">
+ <dt{if $optionData[cssClassName]} class="{$optionData[cssClassName]}"{/if}><label for="{$option->optionName}">{lang}{@$langPrefix}{$option->optionName}{/lang}</label></dt>
+ <dd>{@$optionData[html]}
+ {if $error}
+ <small class="innerError">
+ {if $error == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}{@$langPrefix}error.{$error}{/lang}
+ {/if}
+ </small>
+ {/if}
+ <small>{lang __optional=true}{@$langPrefix}{$option->optionName}.description{/lang}</small>
+ </dd>
+ </dl>
+{/foreach}
--- /dev/null
+<div class="box128 userProfilePreview">
+ <a href="{link controller='User' object=$user}{/link}" title="{$user->username}">{@$user->getAvatar()->getImageTag(128)}</a>
+
+ {if $__wcf->getUser()->userID && $__wcf->getUser()->userID != $user->userID}
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.button.follow': '{lang}wcf.user.button.follow{/lang}',
+ 'wcf.user.button.ignore': '{lang}wcf.user.button.ignore{/lang}',
+ 'wcf.user.button.unfollow': '{lang}wcf.user.button.unfollow{/lang}',
+ 'wcf.user.button.unignore': '{lang}wcf.user.button.unignore{/lang}'
+ });
+
+ new WCF.User.Action.Follow($('.userInformation'));
+
+ {if !$user->getPermission('user.profile.cannotBeIgnored')}
+ new WCF.User.Action.Ignore($('.userInformation'));
+ {/if}
+ });
+ //]]>
+ </script>
+ {/if}
+
+ <div class="userInformation">
+ {include file='userInformation'}
+
+ {if $user->canViewOnlineStatus() && $user->getLastActivityTime()}
+ <dl class="plain inlineDataList userStats">
+ <dt>{lang}wcf.user.usersOnline.lastActivity{/lang}</dt>
+ <dd>{@$user->getLastActivityTime()|time}{if $user->getCurrentLocation()}, {@$user->getCurrentLocation()}{/if}</dd>
+ </dl>
+ {/if}
+
+ {hascontent}
+ <dl class="plain inlineDataList userFields">
+ {content}
+ {if $user->occupation}
+ <dt>{lang}wcf.user.option.occupation{/lang}</dt>
+ <dd>{$user->occupation}</dd>
+ {/if}
+ {if $user->hobbies}
+ <dt>{lang}wcf.user.option.hobbies{/lang}</dt>
+ <dd>{$user->hobbies}</dd>
+ {/if}
+ {event name='userFields'}
+ {/content}
+ </dl>
+ {/hascontent}
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.search{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Search.User('#username', null, false, [ ], false);
+ });
+ //]]>
+ </script>
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{capture assign='sidebar'}
+ {@$__boxSidebar}
+{/capture}
+
+{include file='header' sidebarOrientation='right'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.user.search{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+{if $errorField == 'search'}
+ <p class="error">{lang}wcf.user.search.error.noMatches{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='UserSearch'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.acp.user.search.conditions.general{/lang}</legend>
+
+ <dl>
+ <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
+ <dd>
+ <input type="text" id="username" name="username" value="{$username}" class="medium" />
+ </dd>
+ </dl>
+
+ {event name='generalFields'}
+ </fieldset>
+
+ {foreach from=$optionTree[0][categories] item=category}
+ <fieldset>
+ <legend>{lang}wcf.user.option.category.{@$category[object]->categoryName}{/lang}</legend>
+ {hascontent}<p>{content}{lang __optional=true}wcf.user.option.category.{@$category[object]->categoryName}.description{/lang}{/content}</p>{/hascontent}
+
+ {include file='userOptionFieldList' options=$category[options] langPrefix='wcf.user.option.'}
+ </fieldset>
+ {/foreach}
+ </div>
+
+ {event name='fieldsets'}
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
+
+</body>
+</html>
--- /dev/null
+<fieldset>
+ <legend class="invisible">{lang}wcf.user.avatar{/lang}</legend>
+
+ <div class="userAvatar">
+ {if $user->userID == $__wcf->user->userID}
+ <a href="{link controller='AvatarEdit'}{/link}" class="framed jsTooltip" title="{lang}wcf.user.avatar.edit{/lang}">{@$user->getAvatar()->getImageTag()}</a>
+ {else}
+ <span class="framed">{@$user->getAvatar()->getImageTag()}</span>
+ {/if}
+ </div>
+</fieldset>
+
+<fieldset>
+ <legend class="invisible">{lang}wcf.user.stats{/lang}</legend>
+
+ <dl class="plain statsDataList">
+ {event name='statistics'}
+
+ <dt>{if $user->activityPoints}<a class="activityPointsDisplay jsTooltip" title="{lang}wcf.user.activityPoint.showDetails{/lang}" data-user-id="{@$user->userID}">{lang}wcf.user.activityPoint{/lang}</a>{else}{lang}wcf.user.activityPoint{/lang}{/if}</dt>
+ <dd>{#$user->activityPoints}</dd>
+
+ <dt>{lang}wcf.user.profileHits{/lang}</dt>
+ <dd{if $user->getProfileAge() > 1} title="{lang}wcf.user.profileHits.hitsPerDay{/lang}"{/if}>{#$user->profileHits}</dd>
+ </dl>
+</fieldset>
+
+{if $followingCount}
+ <fieldset>
+ <legend>{lang}wcf.user.profile.following{/lang} <span class="badge">{#$followingCount}</span></legend>
+
+ <div>
+ <ul class="framedIconList">
+ {foreach from=$following item=followingUser}
+ <li><a href="{link controller='User' object=$followingUser}{/link}" title="{$followingUser->username}" class="framed jsTooltip">{@$followingUser->getAvatar()->getImageTag(48)}</a></li>
+ {/foreach}
+ </ul>
+
+ {if $followingCount > 10}
+ <a id="followingAll" class="button small more jsOnly">{lang}wcf.user.profile.userList.showAll{/lang}</a>
+ {/if}
+ </div>
+ </fieldset>
+{/if}
+
+{if $followerCount}
+ <fieldset>
+ <legend>{lang}wcf.user.profile.followers{/lang} <span class="badge">{#$followerCount}</span></legend>
+
+ <div>
+ <ul class="framedIconList">
+ {foreach from=$followers item=follower}
+ <li><a href="{link controller='User' object=$follower}{/link}" title="{$follower->username}" class="framed jsTooltip">{@$follower->getAvatar()->getImageTag(48)}</a></li>
+ {/foreach}
+ </ul>
+
+ {if $followerCount > 10}
+ <a id="followerAll" class="button small more jsOnly">{lang}wcf.user.profile.userList.showAll{/lang}</a>
+ {/if}
+ </div>
+ </fieldset>
+{/if}
+
+{if $visitorCount}
+ <fieldset>
+ <legend>{lang}wcf.user.profile.visitors{/lang} <span class="badge">{#$visitorCount}</span></legend>
+
+ <div>
+ <ul class="framedIconList">
+ {foreach from=$visitors item=visitor}
+ <li><a href="{link controller='User' object=$visitor}{/link}" title="{$visitor->username} ({@$visitor->time|plainTime})" class="framed jsTooltip">{@$visitor->getAvatar()->getImageTag(48)}</a></li>
+ {/foreach}
+ </ul>
+
+ {if $visitorCount > 10}
+ <a id="visitorAll" class="button small more jsOnly">{lang}wcf.user.profile.userList.showAll{/lang}</a>
+ {/if}
+ </div>
+ </fieldset>
+{/if}
+
+{event name='boxes'}
--- /dev/null
+{include file='documentHeader'}
+
+<head>
+ <title>{lang}wcf.user.usersOnline{/lang} - {PAGE_TITLE|language}</title>
+
+ {include file='headInclude'}
+
+ <link rel="canonical" href="{link controller='UsersOnlineList'}{/link}" />
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.user.button.follow': '{lang}wcf.user.button.follow{/lang}',
+ 'wcf.user.button.ignore': '{lang}wcf.user.button.ignore{/lang}',
+ 'wcf.user.button.unfollow': '{lang}wcf.user.button.unfollow{/lang}',
+ 'wcf.user.button.unignore': '{lang}wcf.user.button.unignore{/lang}'
+ });
+
+ new WCF.User.Action.Follow($('.userList > li'));
+ new WCF.User.Action.Ignore($('.userList > li'));
+ });
+ //]]>
+ </script>
+
+ {if USERS_ONLINE_PAGE_REFRESH > 0}
+ <meta http-equiv="refresh" content="{@USERS_ONLINE_PAGE_REFRESH}; url={link controller='UsersOnlineList'}sortField={@$sortField}&sortOrder={@$sortOrder}{/link}" />
+ {/if}
+</head>
+
+<body id="tpl{$templateName|ucfirst}">
+
+{capture assign='sidebar'}
+ <div>
+ <form method="get" action="{link controller='UsersOnlineList'}{/link}">
+ <fieldset>
+ <legend><label for="sortField">{lang}wcf.user.members.sort{/lang}</label></legend>
+
+ <dl>
+ <dd>
+ <select id="sortField" name="sortField">
+ <option value="username"{if $sortField == 'username'} selected="selected"{/if}>{lang}wcf.user.username{/lang}</option>
+ <option value="lastActivityTime"{if $sortField == 'lastActivityTime'} selected="selected"{/if}>{lang}wcf.user.usersOnline.lastActivity{/lang}</option>
+ <option value="requestURI"{if $sortField == 'requestURI'} selected="selected"{/if}>{lang}wcf.user.usersOnline.location{/lang}</option>
+
+ {if $__wcf->session->getPermission('admin.user.canViewIpAddress')}
+ <option value="ipAddress"{if $sortField == 'ipAddress'} selected="selected"{/if}>{lang}wcf.user.usersOnline.ipAddress{/lang}</option>
+ <option value="userAgent"{if $sortField == 'userAgent'} selected="selected"{/if}>{lang}wcf.user.usersOnline.userAgent{/lang}</option>
+ {/if}
+ </select>
+ <select name="sortOrder">
+ <option value="ASC"{if $sortOrder == 'ASC'} selected="selected"{/if}>{lang}wcf.global.sortOrder.ascending{/lang}</option>
+ <option value="DESC"{if $sortOrder == 'DESC'} selected="selected"{/if}>{lang}wcf.global.sortOrder.descending{/lang}</option>
+ </select>
+ </dd>
+ </dl>
+ </fieldset>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+ </form>
+ </div>
+
+ {@$__boxSidebar}
+{/capture}
+
+{include file='header' sidebarOrientation='right'}
+
+{include file='userNotice'}
+
+{assign var=usersOnlineList value=''}
+{assign var=usersOnline value=0}
+{assign var=robotsOnlineList value=''}
+{assign var=robotsOnline value=0}
+{assign var=guestsOnlineList value=''}
+{assign var=guestsOnline value=0}
+{foreach from=$objects item=user}
+ {capture assign=locationData}
+ <p>
+ {if $user->getLocation()}{@$user->getLocation()}{else}{lang}wcf.user.usersOnline.location.unknown{/lang}{/if} <small>- {@$user->lastActivityTime|time}</small>
+ </p>
+ {/capture}
+
+ {capture assign=sessionData}
+ {if $__wcf->session->getPermission('admin.user.canViewIpAddress')}
+ <dl class="plain inlineDataList">
+ <dt>{lang}wcf.user.usersOnline.ipAddress{/lang}</dt>
+ <dd title="{$user->getFormattedIPAddress()}">{$user->getFormattedIPAddress()|truncate:30}</dd>
+ <dt>{lang}wcf.user.usersOnline.userAgent{/lang}</dt>
+ <dd title="{$user->userAgent}">{$user->getBrowser()|truncate:30}</dd>
+ </dl>
+ {/if}
+ {/capture}
+
+ {if $user->userID}
+ {* member *}
+ {capture append=usersOnlineList}
+ <li>
+ <div class="box48">
+ <a href="{link controller='User' object=$user}{/link}" title="{$user->username}" class="framed">{@$user->getAvatar()->getImageTag(48)}</a>
+
+ <div class="details userInformation">
+ <div class="containerHeadline">
+ <h3><a href="{link controller='User' object=$user}{/link}">{@$user->getFormattedUsername()}</a>{if MODULE_USER_RANK && $user->getUserTitle()} <span class="badge userTitleBadge{if $user->getRank() && $user->getRank()->cssClassName} {@$user->getRank()->cssClassName}{/if}">{$user->getUserTitle()}</span>{/if}</h3>
+ {@$locationData}
+ </div>
+
+ {@$sessionData}
+
+ {include file='userInformationButtons'}
+ </div>
+ </div>
+ </li>
+ {/capture}
+
+ {assign var=usersOnline value=$usersOnline+1}
+ {elseif $user->spiderID}
+ {* search robot *}
+ {capture append=robotsOnlineList}
+ <li>
+ <div class="box48">
+ {*todo: we need an avatar placeholder for search robots here*}
+ <p class="framed"><img src="{$__wcf->getPath()}images/avatars/avatar-default.svg" alt="" class="icon48" /></p>
+
+ <div class="details userInformation">
+ <div class="containerHeadline">
+ <h3>{if $user->getSpider()->spiderURL}<a href="{$user->getSpider()->spiderURL}" class="externalURL"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}>{$user->getSpider()->spiderName}</a>{else}{$user->getSpider()->spiderName}{/if}</h3>
+ {@$locationData}
+ </div>
+
+ {@$sessionData}
+ </div>
+ </div>
+ </li>
+ {/capture}
+
+ {assign var=robotsOnline value=$robotsOnline+1}
+ {else}
+ {* unregistered *}
+ {capture append=guestsOnlineList}
+ <li>
+ <div class="box48">
+ {*todo: we need an avatar placeholder for guests here*}
+ <p class="framed"><img src="{$__wcf->getPath()}images/avatars/avatar-default.svg" alt="" class="icon48" /></p>
+
+ <div class="details userInformation">
+ <div class="containerHeadline">
+ <h3>{lang}wcf.user.guest{/lang}</h3>
+ {@$locationData}
+ </div>
+
+ {@$sessionData}
+ </div>
+ </div>
+ </li>
+ {/capture}
+
+ {assign var=guestsOnline value=$guestsOnline+1}
+ {/if}
+{/foreach}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{if $usersOnline}
+ <header class="boxHeadline">
+ <h1>{lang}wcf.user.usersOnline{/lang} <span class="badge">{#$usersOnline}</span></h1>
+ </header>
+
+ <div class="container marginTop">
+ <ol class="containerList doubleColumned userList">
+ {@$usersOnlineList}
+ </ol>
+ </div>
+{/if}
+
+{if $guestsOnline && USERS_ONLINE_SHOW_GUESTS}
+ <header class="boxHeadline">
+ <h1>{lang}wcf.user.usersOnline.guests{/lang} <span class="badge">{#$guestsOnline}</span></h1>
+ </header>
+
+ <div class="container marginTop">
+ <ol class="containerList doubleColumned">
+ {@$guestsOnlineList}
+ </ol>
+ </div>
+{/if}
+
+{if $robotsOnline && USERS_ONLINE_SHOW_ROBOTS}
+ <header class="boxHeadline">
+ <h1>{lang}wcf.user.usersOnline.robots{/lang} <span class="badge">{#$robotsOnline}</span></h1>
+ </header>
+
+ <div class="container marginTop">
+ <ol class="containerList doubleColumned">
+ {@$robotsOnlineList}
+ </ol>
+ </div>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{include file='footer'}
+
+</body>
+</html>
<category name="user.message.attachment">
<parent>user.message</parent>
</category>
+ <category name="user.profile">
+ <parent>user</parent>
+ </category>
+
+ <category name="user.signature">
+ <parent>user.profile</parent>
+ </category>
+
+ <category name="user.profile.avatar">
+ <parent>user.profile</parent>
+ </category>
<category name="mod"></category>
<category name="mod.general">
<category name="admin.user.option">
<parent>admin.user</parent>
</category>
+ <category name="admin.user.rank">
+ <parent>admin.user</parent>
+ </category>
<category name="admin.display">
<parent>admin</parent>
<category name="admin.content.smiley">
<parent>admin.content</parent>
</category>
+ <category name="admin.content.dashboard">
+ <parent>admin.content</parent>
+ </category>
<category name="admin.community">
<parent>admin</parent>
<defaultvalue>all</defaultvalue>
</option>
<!-- /user.message -->
+
+ <option name="admin.user.canViewInvisible">
+ <categoryname>admin.general</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+ <option name="admin.user.canViewIpAddress">
+ <categoryname>admin.general</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+ <option name="admin.user.canEnableUser">
+ <categoryname>admin.user.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+ <option name="admin.user.canEditActivityPoints">
+ <categoryname>admin.user.user</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+
+ <option name="admin.content.dashboard.canEditDashboard">
+ <categoryname>admin.content.dashboard</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+ <option name="admin.user.rank.canManageRank">
+ <categoryname>admin.user.rank</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+
+ <!-- user.signature -->
+ <option name="user.signature.canUseSmilies">
+ <categoryname>user.signature</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.signature.canUseHtml">
+ <categoryname>user.signature</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+ <option name="user.signature.canUseBBCodes">
+ <categoryname>user.signature</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.signature.allowedBBCodes">
+ <categoryname>user.signature</categoryname>
+ <optiontype>BBCodeSelect</optiontype>
+ <defaultvalue>all</defaultvalue>
+ </option>
+ <!-- /user.signature -->
+
+ <!-- user.profile -->
+ <option name="user.profile.canMail">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.profile.canChangeEmail">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.profile.canQuit">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ </option>
+ <option name="user.profile.canRename">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ <enableoptions>user.profile.renamePeriod</enableoptions>
+ </option>
+ <option name="user.profile.renamePeriod">
+ <categoryname>user.profile</categoryname>
+ <optiontype>inverseInteger</optiontype>
+ <defaultvalue>182</defaultvalue>
+ </option>
+ <option name="user.profile.canEditUserTitle">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+ <option name="user.profile.canViewMembersList">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.profile.canViewUsersOnlineList">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ <options>module_users_online</options>
+ </option>
+ <option name="user.profile.canViewUserProfile">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.profile.cannotBeIgnored">
+ <categoryname>user.profile</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+
+ <option name="user.profile.avatar.canUploadAvatar">
+ <categoryname>user.profile.avatar</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ <enableoptions>user.profile.avatar.maxSize,user.profile.avatar.allowedFileExtensions</enableoptions>
+ </option>
+ <option name="user.profile.avatar.maxSize">
+ <categoryname>user.profile.avatar</categoryname>
+ <optiontype>fileSize</optiontype>
+ <defaultvalue>100000</defaultvalue>
+ <minvalue>10000</minvalue>
+ </option>
+ <option name="user.profile.avatar.allowedFileExtensions">
+ <categoryname>user.profile.avatar</categoryname>
+ <optiontype>textarea</optiontype>
+ <defaultvalue><![CDATA[gif
+jpg
+jpeg
+png]]></defaultvalue>
+ </option>
+ <!-- /user.profile -->
</options>
</import>
</data>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/userMenu.xsd">
+ <import>
+ <!-- profile -->
+ <usermenuitem name="wcf.user.menu.profile">
+ <showorder>1</showorder>
+ </usermenuitem>
+
+ <usermenuitem name="wcf.user.menu.profile.accountManagement">
+ <controller><![CDATA[wcf\form\AccountManagementForm]]></controller>
+ <parent>wcf.user.menu.profile</parent>
+ <showorder>1</showorder>
+ </usermenuitem>
+ <usermenuitem name="wcf.user.menu.profile.avatar">
+ <controller><![CDATA[wcf\form\AvatarEditForm]]></controller>
+ <parent>wcf.user.menu.profile</parent>
+ <showorder>2</showorder>
+ </usermenuitem>
+ <usermenuitem name="wcf.user.menu.profile.signature">
+ <controller><![CDATA[wcf\form\SignatureEditForm]]></controller>
+ <parent>wcf.user.menu.profile</parent>
+ <showorder>3</showorder>
+ <options>module_user_signature</options>
+ </usermenuitem>
+ <!-- /profile -->
+
+ <!-- settings -->
+ <usermenuitem name="wcf.user.menu.settings">
+ <showorder>2</showorder>
+ </usermenuitem>
+
+ <usermenuitem name="wcf.user.menu.settings.notification">
+ <controller><![CDATA[wcf\form\NotificationSettingsForm]]></controller>
+ <parent>wcf.user.menu.settings</parent>
+ </usermenuitem>
+ <!-- /settings -->
+
+ <!-- community -->
+ <usermenuitem name="wcf.user.menu.community">
+ <showorder>3</showorder>
+ </usermenuitem>
+
+ <usermenuitem name="wcf.user.menu.community.notification">
+ <controller><![CDATA[wcf\page\NotificationListPage]]></controller>
+ <parent>wcf.user.menu.community</parent>
+ <showorder>1</showorder>
+ </usermenuitem>
+ <usermenuitem name="wcf.user.menu.community.following">
+ <controller><![CDATA[wcf\page\FollowingPage]]></controller>
+ <parent>wcf.user.menu.community</parent>
+ <showorder>2</showorder>
+ </usermenuitem>
+ <usermenuitem name="wcf.user.menu.community.ignoredUsers">
+ <controller><![CDATA[wcf\page\IgnoredUsersPage]]></controller>
+ <parent>wcf.user.menu.community</parent>
+ <showorder>3</showorder>
+ </usermenuitem>
+ <!-- /community -->
+ </import>
+</data>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/userNotificationEvent.xsd">
+ <import>
+ <event>
+ <name>following</name>
+ <objecttype>com.woltlab.wcf.user.follow</objecttype>
+ <classname>wcf\system\user\notification\event\UserFollowFollowingUserNotificationEvent</classname>
+ </event>
+ </import>
+</data>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/userOption.xsd">
+ <import>
+ <categories>
+ <category name="profile">
+ <showorder>1</showorder>
+ </category>
+
+ <!-- profile -->
+ <category name="profile.aboutMe">
+ <parent>profile</parent>
+ </category>
+ <category name="profile.personal">
+ <parent>profile</parent>
+ </category>
+ <category name="profile.contact">
+ <parent>profile</parent>
+ </category>
+ <!-- /profile -->
+
+ <category name="settings">
+ <showorder>2</showorder>
+ </category>
+
+ <!-- settings -->
+ <category name="settings.general">
+ <showorder>1</showorder>
+ <parent>settings</parent>
+ </category>
+ <category name="settings.general.appearance">
+ <showorder>1</showorder>
+ <parent>settings.general</parent>
+ </category>
+ <category name="settings.general.interface">
+ <showorder>2</showorder>
+ <parent>settings.general</parent>
+ </category>
+ <category name="settings.general.date">
+ <showorder>3</showorder>
+ <parent>settings.general</parent>
+ </category>
+
+ <category name="settings.privacy">
+ <showorder>2</showorder>
+ <parent>settings</parent>
+ </category>
+ <category name="settings.privacy.content">
+ <parent>settings.privacy</parent>
+ </category>
+ <category name="settings.privacy.messaging">
+ <parent>settings.privacy</parent>
+ </category>
+ <!-- /settings -->
+ </categories>
+
+ <options>
+ <!-- profile -->
+ <option name="aboutMe">
+ <categoryname>profile.aboutMe</categoryname>
+ <optiontype>message</optiontype>
+ <outputclass>wcf\system\option\user\MessageUserOptionOutput</outputclass>
+ <visible>15</visible>
+ <editable>3</editable>
+ <maxlength>1000</maxlength>
+ </option>
+ <option name="birthday">
+ <categoryname>profile.personal</categoryname>
+ <optiontype>birthday</optiontype>
+ <outputclass>wcf\system\option\user\BirthdayUserOptionOutput</outputclass>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="birthdayShowYear">
+ <categoryname>profile.personal</categoryname>
+ <optiontype>boolean</optiontype>
+ <visible>0</visible>
+ <editable>3</editable>
+ <defaultvalue>1</defaultvalue>
+ </option>
+
+ <option name="gender">
+ <categoryname>profile.personal</categoryname>
+ <optiontype>radioButton</optiontype>
+ <outputclass>wcf\system\option\user\SelectOptionsUserOptionOutput</outputclass>
+ <selectoptions>
+ <![CDATA[0:wcf.global.noDeclaration
+1:wcf.user.gender.male
+2:wcf.user.gender.female]]>
+ </selectoptions>
+ <defaultvalue>0</defaultvalue>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="location">
+ <categoryname>profile.personal</categoryname>
+ <optiontype>text</optiontype>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="occupation">
+ <categoryname>profile.personal</categoryname>
+ <optiontype>text</optiontype>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="hobbies">
+ <categoryname>profile.personal</categoryname>
+ <optiontype>text</optiontype>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="adminComment">
+ <categoryname>profile.personal</categoryname>
+ <optiontype>textarea</optiontype>
+ <editable>3</editable>
+ <outputclass>wcf\system\option\user\NewlineToBreakUserOptionOutput</outputclass>
+ <visible>2</visible>
+ <editable>2</editable>
+ </option>
+ <option name="homepage">
+ <categoryname>profile.contact</categoryname>
+ <optiontype>text</optiontype>
+ <outputclass>wcf\system\option\user\URLUserOptionOutput</outputclass>
+ <searchable>1</searchable>
+ <validationpattern>^$|^https?://</validationpattern>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="icq">
+ <categoryname>profile.contact</categoryname>
+ <optiontype>text</optiontype>
+ <searchable>1</searchable>
+ <validationpattern>^$|^([0-9](-| )?)+[0-9]$</validationpattern>
+ <contentpattern>^(\d{3})-(\d{3})-(\d{3})$</contentpattern>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="skype">
+ <categoryname>profile.contact</categoryname>
+ <optiontype>text</optiontype>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="facebook">
+ <categoryname>profile.contact</categoryname>
+ <optiontype>text</optiontype>
+ <outputclass>wcf\system\option\user\FacebookUserOptionOutput</outputclass>
+ <validationpattern>^$|^[A-Za-z\d.]{5,}$</validationpattern>
+ <contentpattern>^https?://www.facebook.com/([A-Za-z\d.]{5,})$</contentpattern>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="twitter">
+ <categoryname>profile.contact</categoryname>
+ <optiontype>text</optiontype>
+ <outputclass>wcf\system\option\user\TwitterUserOptionOutput</outputclass>
+ <validationpattern>^$|^[A-Za-z0-9_]+$</validationpattern>
+ <contentpattern>^https?://twitter.com/([A-Za-z0-9_]+)$</contentpattern>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <option name="googlePlus">
+ <categoryname>profile.contact</categoryname>
+ <optiontype>text</optiontype>
+ <outputclass>wcf\system\option\user\GooglePlusUserOptionOutput</outputclass>
+ <validationpattern>^$|^[0-9]{21}$</validationpattern>
+ <contentpattern>^https?://plus.google.com/([0-9]{21})$</contentpattern>
+ <searchable>1</searchable>
+ <visible>15</visible>
+ <editable>3</editable>
+ </option>
+ <!-- /profile -->
+
+ <!-- settings -->
+ <option name="timezone">
+ <categoryname>settings.general.date</categoryname>
+ <optiontype>timezone</optiontype>
+ <editable>3</editable>
+ </option>
+ <option name="showSignature">
+ <categoryname>settings.general.appearance</categoryname>
+ <optiontype>boolean</optiontype>
+ <editable>3</editable>
+ <defaultvalue>1</defaultvalue>
+ <options>module_user_signature</options>
+ </option>
+
+ <!-- settings.privacy.content -->
+ <option name="canViewOnlineStatus">
+ <categoryname>settings.privacy.content</categoryname>
+ <optiontype>select</optiontype>
+ <editable>3</editable>
+ <selectoptions><![CDATA[0:wcf.user.access.everyone
+1:wcf.user.access.registered
+2:wcf.user.access.following
+3:wcf.user.access.nobody]]></selectoptions>
+ <defaultvalue>0</defaultvalue>
+ </option>
+ <option name="canViewProfile">
+ <categoryname>settings.privacy.content</categoryname>
+ <optiontype>select</optiontype>
+ <editable>3</editable>
+ <selectoptions><![CDATA[0:wcf.user.access.everyone
+1:wcf.user.access.registered
+2:wcf.user.access.following
+3:wcf.user.access.nobody]]></selectoptions>
+ <defaultvalue>0</defaultvalue>
+ </option>
+
+ <!-- settings.privacy.messaging -->
+ <option name="canViewEmailAddress">
+ <categoryname>settings.privacy.messaging</categoryname>
+ <optiontype>select</optiontype>
+ <editable>3</editable>
+ <selectoptions><![CDATA[0:wcf.user.access.everyone
+1:wcf.user.access.registered
+2:wcf.user.access.following
+3:wcf.user.access.nobody]]></selectoptions>
+ <defaultvalue>3</defaultvalue>
+ </option>
+ <option name="canMail">
+ <categoryname>settings.privacy.messaging</categoryname>
+ <optiontype>select</optiontype>
+ <editable>3</editable>
+ <selectoptions><![CDATA[0:wcf.user.access.everyone
+1:wcf.user.access.registered
+2:wcf.user.access.following
+3:wcf.user.access.nobody]]></selectoptions>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="adminCanMail">
+ <categoryname>settings.privacy.messaging</categoryname>
+ <optiontype>boolean</optiontype>
+ <editable>3</editable>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <!-- /settings -->
+ </options>
+ </import>
+</data>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/userProfileMenu.xsd">
+ <import>
+ <userprofilemenuitem name="about">
+ <classname>wcf\system\menu\user\profile\content\AboutUserProfileMenuContent</classname>
+ <showorder>2</showorder>
+ </userprofilemenuitem>
+
+ <userprofilemenuitem name="recentActivity">
+ <classname>wcf\system\menu\user\profile\content\RecentActivityUserProfileMenuContent</classname>
+ <showorder>1</showorder>
+ </userprofilemenuitem>
+ </import>
+</data>
<?php
namespace wcf\acp;
use wcf\data\language\LanguageEditor;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserProfileAction;
use wcf\system\cache\CacheHandler;
+use wcf\system\dashboard\DashboardHandler;
use wcf\system\session\SessionHandler;
use wcf\system\template\ACPTemplateEngine;
use wcf\system\WCF;
$statement->execute(array($timezone, 'timezone'));
}
}
+
+// set dashboard default values
+DashboardHandler::setDefaultValues('com.woltlab.wcf.user.DashboardPage', array(
+ // content
+ 'com.woltlab.wcf.user.recentActivity' => 1,
+ // sidebar
+ 'com.woltlab.wcf.user.registerButton' => 1,
+ 'com.woltlab.wcf.user.signedInAs' => 2,
+ 'com.woltlab.wcf.user.statsSidebar' => 3
+));
+DashboardHandler::setDefaultValues('com.woltlab.wcf.user.MembersListPage', array(
+ 'com.woltlab.wcf.user.newestMembers' => 1,
+ 'com.woltlab.wcf.user.mostActiveMembers' => 2
+));
+
+// update administrator user rank and user online marking
+$editor = new UserEditor(WCF::getUser());
+$action = new UserProfileAction(array($editor), 'updateUserRank');
+$action->executeAction();
+$action = new UserProfileAction(array($editor), 'updateUserOnlineMarking');
+$action->executeAction();
--- /dev/null
+/**
+ * Generic implementation to enable users.
+ */
+WCF.ACP.User.EnableHandler = {
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * Initializes WCF.ACP.User.EnableHandler on first use.
+ */
+ init: function() {
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ $('.jsEnableButton').click($.proxy(function(event) {
+ var $button = $(event.currentTarget);
+ if ($button.data('enabled')) {
+ this.disable([ $button.data('objectID') ]);
+ }
+ else {
+ this.enable([ $button.data('objectID') ]);
+ }
+ }, this));
+
+ // bind listener
+ $('.jsClipboardEditor').each($.proxy(function(index, container) {
+ var $container = $(container);
+ var $types = eval($container.data('types'));
+ if (WCF.inArray('com.woltlab.wcf.user', $types)) {
+ $container.on('clipboardAction', $.proxy(this._execute, this));
+ return false;
+ }
+ }, this));
+ },
+
+ /**
+ * Handles clipboard actions.
+ *
+ * @param object event
+ * @param string type
+ * @param string actionName
+ * @param object parameters
+ */
+ _execute: function(event, type, actionName, parameters) {
+ if (actionName == 'com.woltlab.wcf.user.enable') {
+ this.enable(parameters.objectIDs);
+ }
+ },
+
+ /**
+ * Disables users.
+ *
+ * @param array<integer> userIDs
+ */
+ disable: function(userIDs) {
+ this._proxy.setOption('data', {
+ actionName: 'disable',
+ className: 'wcf\\data\\user\\ExtendedUserAction',
+ objectIDs: userIDs
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Enables users.
+ *
+ * @param array<integer> userIDs
+ */
+ enable: function(userIDs) {
+ this._proxy.setOption('data', {
+ actionName: 'enable',
+ className: 'wcf\\data\\user\\ExtendedUserAction',
+ objectIDs: userIDs
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles successful AJAX calls.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ $('.jsEnableButton').each(function(index, button) {
+ var $button = $(button);
+ if (WCF.inArray($button.data('objectID'), data.objectIDs)) {
+ if (data.actionName == 'disable') {
+ $button.data('enabled', false).data('tooltip', $button.data('enableMessage')).removeClass('icon-circle-blank').addClass('icon-off');
+ }
+ else {
+ $button.data('enabled', true).data('tooltip', $button.data('disableMessage')).removeClass('icon-off').addClass('icon-circle-blank');
+ }
+ }
+ });
+
+ var $notification = new WCF.System.Notification();
+ $notification.show();
+
+ WCF.Clipboard.reload();
+ }
+};
--- /dev/null
+<input type="number" id="{$option->optionName}_age_from" name="values[{$option->optionName}][ageFrom]" value="{@$valueAgeFrom}" placeholder="{lang}wcf.user.birthday.age.from{/lang}" min="0" max="120" class="tiny" />
+<input type="number" id="{$option->optionName}_age_to" name="values[{$option->optionName}][ageTo]" value="{@$valueAgeTo}" placeholder="{lang}wcf.user.birthday.age.to{/lang}" min="0" max="120" class="tiny" />
+
+<script type="text/javascript">
+//<![CDATA[
+$(function() {
+ $('#{$option->optionName}_age_from').parents('dl:eq(0)').find('> dt > label').text('{lang}wcf.user.birthday.age{/lang}').attr('for', '{$option->optionName}_age_from');
+});
+//]]>
+</script>
\ No newline at end of file
--- /dev/null
+{include file='header' pageTitle='wcf.acp.dashboard.list'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.dashboard.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsTop'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<div class="tabularBox tabularBoxTitle marginTop">
+ <header>
+ <h2>{lang}wcf.acp.dashboard.list{/lang} <span class="badge badgeInverse">{#$objectTypes|count}</span></h2>
+ </header>
+
+ <table class="table">
+ <thead>
+ <tr>
+ <th colspan="2" class="columnID">{lang}wcf.global.objectID{/lang}</th>
+ <th class="columnText">{lang}wcf.dashboard.objectType{/lang}</th>
+
+ {event name='columnHeads'}
+ </tr>
+ </thead>
+
+ <tbody>
+ {foreach from=$objectTypes item=$objectType}
+ <tr>
+ <td class="columnIcon">
+ <a href="{link controller='DashboardOption' id=$objectType->objectTypeID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
+
+ {event name='rowButtons'}
+ </td>
+ <td class="columnID">{#$objectType->objectTypeID}</td>
+ <td class="columnText"><a href="{link controller='DashboardOption' id=$objectType->objectTypeID}{/link}">{lang}wcf.dashboard.objectType.{$objectType->objectType}{/lang}</a></td>
+
+ {event name='columns'}
+ </tr>
+ {/foreach}
+ </tbody>
+ </table>
+</div>
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtonsBottom'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+{include file='footer'}
\ No newline at end of file
--- /dev/null
+{include file='header' pageTitle='wcf.acp.dashboard.option'}
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.TabMenu.init();
+ });
+ //]]>
+</script>
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.dashboard.option{/lang}</h1>
+ <p>{lang}wcf.dashboard.objectType.{$objectType->objectType}{/lang}</p>
+</header>
+
+<p class="info">{lang}wcf.acp.dashboard.box.sort{/lang}</p>
+
+<div class="contentNavigation">
+ <nav>
+ <ul>
+ <li><a href="{link controller='DashboardList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.dashboard.list{/lang}</span></a></li>
+
+ {event name='contentNavigationButtons'}
+ </ul>
+ </nav>
+</div>
+
+<div class="tabMenuContainer">
+ <nav class="tabMenu">
+ <ul>
+ {if $objectType->allowcontent}
+ <li><a href="{@$__wcf->getAnchor('dashboard-content')}">{lang}wcf.dashboard.boxType.content{/lang}</a></li>
+ {/if}
+ {if $objectType->allowsidebar}
+ <li><a href="{@$__wcf->getAnchor('dashboard-sidebar')}">{lang}wcf.dashboard.boxType.sidebar{/lang}</a></li>
+ {/if}
+
+ {event name='tabMenuTabs'}
+ </ul>
+ </nav>
+
+ {if $objectType->allowcontent}
+ <div id="dashboard-content" class="container containerPadding tabMenuContent hidden">
+ <fieldset>
+ <legend>{lang}wcf.dashboard.box.enabledBoxes{/lang}</legend>
+
+ <div class="container containerPadding sortableListContainer">
+ <ol class="sortableList simpleSortableList" data-object-id="0">
+ {foreach from=$enabledBoxes item=boxID}
+ {if $boxes[$boxID]->boxType == 'content'}
+ <li class="sortableNode" data-object-id="{@$boxID}">
+ <span class="sortableNodeLabel">{lang}wcf.dashboard.box.{$boxes[$boxID]->boxName}{/lang}</span>
+ </li>
+ {/if}
+ {/foreach}
+ </ol>
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.dashboard.box.availableBoxes{/lang}</legend>
+
+ <div class="container containerPadding sortableListContainer">
+ <ol class="sortableList simpleSortableList">
+ {foreach from=$boxes item=box}
+ {if $box->boxType == 'content' && !$box->boxID|in_array:$enabledBoxes}
+ <li class="sortableNode" data-object-id="{@$box->boxID}">
+ <span class="sortableNodeLabel">{lang}wcf.dashboard.box.{$box->boxName}{/lang}</span>
+ </li>
+ {/if}
+ {/foreach}
+ </ol>
+ </div>
+ </fieldset>
+
+ <div class="formSubmit">
+ <button data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
+ </div>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Sortable.List('dashboard-content', 'wcf\\data\\dashboard\\box\\DashboardBoxAction', 0, { }, true, { boxType: 'content', objectTypeID: {@$objectTypeID} });
+ });
+ //]]>
+ </script>
+ </div>
+ {/if}
+
+ {if $objectType->allowsidebar}
+ <div id="dashboard-sidebar" class="container containerPadding tabMenuContent hidden">
+ <fieldset>
+ <legend>{lang}wcf.dashboard.box.enabledBoxes{/lang}</legend>
+
+ <div class="container containerPadding sortableListContainer">
+ <ol class="sortableList simpleSortableList" data-object-id="0">
+ {foreach from=$enabledBoxes item=boxID}
+ {if $boxes[$boxID]->boxType == 'sidebar'}
+ <li class="sortableNode" data-object-id="{@$boxID}">
+ <span class="sortableNodeLabel">{lang}wcf.dashboard.box.{$boxes[$boxID]->boxName}{/lang}</span>
+ </li>
+ {/if}
+ {/foreach}
+ </ol>
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.dashboard.box.availableBoxes{/lang}</legend>
+
+ <div id="dashboard-sidebar-enabled" class="container containerPadding sortableListContainer">
+ <ol class="sortableList simpleSortableList">
+ {foreach from=$boxes item=box}
+ {if $box->boxType == 'sidebar' && !$box->boxID|in_array:$enabledBoxes}
+ <li class="sortableNode" data-object-id="{@$box->boxID}">
+ <span class="sortableNodeLabel">{lang}wcf.dashboard.box.{$box->boxName}{/lang}</span>
+ </li>
+ {/if}
+ {/foreach}
+ </ol>
+ </div>
+ </fieldset>
+
+ <div class="formSubmit">
+ <button data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
+ </div>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Sortable.List('dashboard-sidebar', 'wcf\\data\\dashboard\\box\\DashboardBoxAction', 0, { }, true, { boxType: 'sidebar', objectTypeID: {@$objectTypeID} });
+ });
+ //]]>
+ </script>
+ </div>
+ {/if}
+
+ {event name='tabMenuContents'}
+</div>
+
+{include file='footer'}
\ No newline at end of file
<div class="layoutFluid clearfix">
<ul class="userPanelItems">
<li id="userMenu" class="dropdown">
- <a class="dropdownToggle framed" data-toggle="userMenu">{event name='userAvatar'} {lang}wcf.user.userNote{/lang}</a>
+ <a class="dropdownToggle framed" data-toggle="userMenu">{@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(24)} {lang}wcf.user.userNote{/lang}</a>
<ul class="dropdownMenu">
{if PACKAGE_ID > 1}
<li><a href="{@$__wcf->getPageMenu()->getLandingPage()->getLink()}">{lang}wcf.global.toLandingPage{/lang}</a></li>
<h1>{lang}wcf.global.acp{/lang}</h1>
</header>
+{if $usersAwaitingApproval}
+ <p class="info">{lang}wcf.acp.user.usersAwaitingApprovalInfo{/lang}</p>
+{/if}
+
{event name='userNotice'}
<div class="tabMenuContainer" data-active="{if ENABLE_WOLTLAB_NEWS}news{else}system{/if}" data-store="activeTabMenuItem">
--- /dev/null
+<textarea id="{$option->optionName}" name="values[{$option->optionName}]" cols="40" rows="10">{$value}</textarea>
--- /dev/null
+{include file='header' pageTitle='wcf.acp.user.activityPoint.option'}
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ $('#updateCache').click(function () {
+ new WCF.ACP.Worker('cache', 'wcf\\system\\worker\\UserActivityPointUpdateCacheWorker', '{lang}wcf.acp.user.activityPoint.updateCache{/lang}');
+ });
+ $('#updateEvents').click(function () {
+ new WCF.ACP.Worker('events', 'wcf\\system\\worker\\UserActivityPointUpdateEventsWorker', '{lang}wcf.acp.user.activityPoint.updateEvents{/lang}');
+ });
+ });
+ //]]>
+</script>
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.user.activityPoint.option{/lang}</h1>
+</header>
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.edit{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ <nav>
+ <ul>
+ <li><a id="updateCache" class="button"><span class="icon icon16 icon-repeat"></span> <span>{lang}wcf.acp.user.activityPoint.updateCache{/lang}</span></a></li>
+ <li><a id="updateEvents" class="button"><span class="icon icon16 icon-repeat"></span> <span>{lang}wcf.acp.user.activityPoint.updateEvents{/lang}</span></a></li>
+
+ {event name='contentNavigationButtons'}
+ </ul>
+ </nav>
+</div>
+
+<form method="post" action="{link controller='UserActivityPointOption'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.user.activityPoint.pointsPerObject{/lang}</legend>
+ {foreach from=$objectTypes item='objectType'}
+ <dl{if $errorField == $objectType->objectTypeID} class="formError"{/if}>
+ <dt><label for="{$objectType->objectType}">{lang}wcf.user.activityPoint.objectType.{$objectType->objectType}{/lang}</label></dt>
+ <dd>
+ <input type="number" id="{$objectType->objectType}" name="points[{$objectType->objectTypeID}]" value="{$points[$objectType->objectTypeID]}" required="required" min="0" class="tiny" />
+ {if $errorField == $objectType->objectTypeID}
+ <small class="innerError">
+ {lang}wcf.acp.user.activityPoint.option.invalid{/lang}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {/foreach}
+ </fieldset>
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
\ No newline at end of file
<li><a href="{@$__wcf->getAnchor($categoryLevel1[object]->categoryName)}">{lang}wcf.user.option.category.{@$categoryLevel1[object]->categoryName}{/lang}</a></li>
{/foreach}
+ {if MODULE_USER_SIGNATURE}
+ <li><a href="{@$__wcf->getAnchor('signatureManagement')}">{lang}wcf.user.signature{/lang}</a></li>
+ {/if}
+
+ {if $action == 'edit'}
+ <li><a href="{@$__wcf->getAnchor('avatarForm')}">{lang}wcf.user.avatar{/lang}</a></li>
+ {/if}
+
{event name='tabMenuTabs'}
</ul>
</nav>
{/if}
{/if}
+ {if $categoryLevel2[object]->categoryName == 'profile.personal' && MODULE_USER_RANK}
+ <dl>
+ <dt><label for="userTitle">{lang}wcf.user.userTitle{/lang}</label></dt>
+ <dd>
+ <input type="text" id="userTitle" name="userTitle" value="{$userTitle}" class="long" maxlength="{@USER_TITLE_MAX_LENGTH}" />
+ {if $errorType[userTitle]|isset}
+ <small class="innerError">
+ {lang}wcf.user.userTitle.error.{@$errorType[userTitle]}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.user.userTitle.description{/lang}</small>
+ </dd>
+ </dl>
+ {/if}
+
{event name='categoryFields'}
{include file='optionFieldList' options=$categoryLevel2[options] langPrefix='wcf.user.option.'}
</div>
{/foreach}
+ {if MODULE_USER_SIGNATURE}
+ <div id="signatureManagement" class="container containerPadding tabMenuContent hidden">
+ <fieldset>
+ <legend>{lang}wcf.user.signature{/lang}</legend>
+
+ <dl>
+ <dt><label for="signature">{lang}wcf.user.signature{/lang}</label></dt>
+ <dd>
+ <textarea name="signature" id="signature" cols="40" rows="10">{$signature}</textarea>
+ </dd>
+ </dl>
+
+ <dl>
+ <dt>{lang}wcf.message.settings{/lang}</dt>
+ <dd>
+ <label><input id="signatureEnableSmilies" name="signatureEnableSmilies" type="checkbox" value="1"{if $signatureEnableSmilies} checked="checked"{/if} /> {lang}wcf.message.settings.enableSmilies{/lang}</label>
+ <label><input id="signatureEnableBBCodes" name="signatureEnableBBCodes" type="checkbox" value="1"{if $signatureEnableBBCodes} checked="checked"{/if} /> {lang}wcf.message.settings.enableBBCodes{/lang}</label>
+ <label><input id="signatureEnableHtml" name="signatureEnableHtml" type="checkbox" value="1"{if $signatureEnableHtml} checked="checked"{/if} /> {lang}wcf.message.settings.enableHtml{/lang}</label>
+ </dd>
+ </dl>
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.acp.user.disableSignature{/lang}</legend>
+
+ <dl>
+ <dd>
+ <label><input type="checkbox" id="disableSignature" name="disableSignature" value="1" {if $disableSignature == 1}checked="checked" {/if}/> {lang}wcf.acp.user.disableSignature{/lang}</label>
+ </dd>
+ </dl>
+
+ <dl>
+ <dt><label for="disableSignatureReason">{lang}wcf.acp.user.disableSignatureReason{/lang}</label></dt>
+ <dd>
+ <textarea name="disableSignatureReason" id="disableSignatureReason" cols="40" rows="10">{$disableSignatureReason}</textarea>
+ </dd>
+ </dl>
+ </fieldset>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $('#disableSignature').change(function (event) {
+ if ($('#disableSignature').is(':checked')) {
+ $('#disableSignatureReason').attr('readonly', false);
+ }
+ else {
+ $('#disableSignatureReason').attr('readonly', true);
+ }
+ });
+ $('#disableSignature').change();
+ //]]>
+ </script>
+ </div>
+ {/if}
+
+ {if $action == 'edit'}
+ <div id="avatarForm" class="container containerPadding tabMenuContent hidden">
+ <fieldset>
+ <legend>{lang}wcf.user.avatar{/lang}</legend>
+
+ <dl>
+ <dd>
+ <label><input type="radio" name="avatarType" value="none" {if $avatarType == 'none'}checked="checked" {/if}/> {lang}wcf.user.avatar.type.none{/lang}</label>
+ </dd>
+ </dl>
+
+ <dl class="jsOnly{if $errorType[customAvatar]|isset} formError{/if}" id="avatarUpload">
+ <dt class="framed">{if $avatarType == 'custom'}{@$userAvatar->getImageTag(96)}{else}<img src="{@$__wcf->getPath()}images/avatars/avatar-default.svg" alt="" class="icon96" />{/if}</dt>
+ <dd>
+ <label><input type="radio" name="avatarType" value="custom" {if $avatarType == 'custom'}checked="checked" {/if}/> {lang}wcf.user.avatar.type.custom{/lang}</label>
+
+ {* placeholder for upload button: *}
+ <div></div>
+
+ {if $errorType[customAvatar]|isset}
+ <small class="innerError">
+ {if $errorType[customAvatar] == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+
+ {if MODULE_GRAVATAR}
+ <dl{if $errorType[gravatar]|isset} class="formError"{/if}>
+ <dt class="framed"><img src="http://www.gravatar.com/avatar/{@$user->email|strtolower|md5}?s=96" alt="" class="icon96" /></dt>
+ <dd>
+ <label><input type="radio" name="avatarType" value="gravatar" {if $avatarType == 'gravatar'}checked="checked" {/if}/> {lang}wcf.user.avatar.type.gravatar{/lang}</label>
+
+ {if $errorType[gravatar]|isset}
+ <small class="innerError">
+ {if $errorType[gravatar] == 'notFound'}{lang}wcf.user.avatar.type.gravatar.error.notFound{/lang}{/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {/if}
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.acp.user.disableAvatar{/lang}</legend>
+
+ <dl>
+ <dd>
+ <label><input type="checkbox" id="disableAvatar" name="disableAvatar" value="1" {if $disableAvatar == 1}checked="checked" {/if}/> {lang}wcf.acp.user.disableAvatar{/lang}</label>
+ </dd>
+ </dl>
+
+ <dl>
+ <dt><label for="disableAvatarReason">{lang}wcf.acp.user.disableAvatarReason{/lang}</label></dt>
+ <dd>
+ <textarea name="disableAvatarReason" id="disableAvatarReason" cols="40" rows="10">{$disableAvatarReason}</textarea>
+ </dd>
+ </dl>
+ </fieldset>
+
+ <script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.Message{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+ <script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.User{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ $('#disableAvatar').change(function (event) {
+ if ($('#disableAvatar').is(':checked')) {
+ $('#disableAvatarReason').attr('readonly', false);
+ }
+ else {
+ $('#disableAvatarReason').attr('readonly', true);
+ }
+ });
+ $('#disableAvatar').change();
+
+ WCF.Language.addObject({
+ 'wcf.user.avatar.upload.error.invalidExtension': '{lang}wcf.user.avatar.upload.error.invalidExtension{/lang}',
+ 'wcf.user.avatar.upload.error.tooSmall': '{lang}wcf.user.avatar.upload.error.tooSmall{/lang}',
+ 'wcf.user.avatar.upload.error.tooLarge': '{lang}wcf.user.avatar.upload.error.tooLarge{/lang}',
+ 'wcf.user.avatar.upload.error.uploadFailed': '{lang}wcf.user.avatar.upload.error.uploadFailed{/lang}',
+ 'wcf.user.avatar.upload.error.badImage': '{lang}wcf.user.avatar.upload.error.badImage{/lang}',
+ 'wcf.user.avatar.upload.success': '{lang}wcf.user.avatar.upload.success{/lang}',
+ 'wcf.global.button.upload': '{lang}wcf.global.button.upload{/lang}'
+ });
+
+ new WCF.User.Avatar.Upload({@$user->userID});
+ });
+ //]]>
+ </script>
+ </div>
+ {/if}
+
{event name='tabMenuContent'}
</div>
</dd>
</dl>
+ <dl>
+ <dt><label for="lastActivityTimeStart">{lang}wcf.user.lastActivityTime{/lang}</label></dt>
+ <dd>
+ <input type="date" id="lastActivityTimeStart" name="lastActivityTimeStart" value="{$lastActivityTimeStart}" placeholder="{lang}wcf.date.period.start{/lang}" />
+ <input type="date" id="lastActivityTimeEnd" name="lastActivityTimeEnd" value="{$lastActivityTimeEnd}" placeholder="{lang}wcf.date.period.end{/lang}" />
+ </dd>
+ </dl>
+
{event name='conditionFields'}
</fieldset>
<dd>
<label><input type="checkbox" name="banned" value="1" {if $banned == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.banned{/lang}</label>
<label><input type="checkbox" name="notBanned" value="1" {if $notBanned == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.notBanned{/lang}</label>
+ <label><input type="checkbox" name="enabled" value="1" {if $enabled == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.enabled{/lang}</label>
+ <label><input type="checkbox" name="disabled" value="1" {if $disabled == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.disabled{/lang}</label>
{event name='states'}
</dd>
</dd>
</dl>
+ <dl{if $errorType.priority|isset} class="formError"{/if}>
+ <dt><label for="priority">{lang}wcf.acp.group.priority{/lang}</label></dt>
+ <dd>
+ <input type="number" id="priority" name="priority" value="{@$priority}" class="medium" />
+ {if $errorType.priority|isset}
+ <small class="innerError">
+ {lang}wcf.acp.group.priority.error.{@$errorType.priority}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.group.priority.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {if MODULE_USERS_ONLINE}
+ <dl{if $errorType.userOnlineMarking|isset} class="formError"{/if}>
+ <dt><label for="userOnlineMarking">{lang}wcf.acp.group.userOnlineMarking{/lang}</label></dt>
+ <dd>
+ <input type="text" id="userOnlineMarking" name="userOnlineMarking" value="{$userOnlineMarking}" class="long" />
+ {if $errorType.userOnlineMarking|isset}
+ <small class="innerError">
+ {lang}wcf.acp.group.userOnlineMarking.error.{@$errorType.priority}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.group.userOnlineMarking.description{/lang}</small>
+ </dd>
+ </dl>
+ {/if}
+
+ {if MODULE_TEAM_PAGE && ($action == 'add' || $group->groupType > 3)}
+ <dl>
+ <dd>
+ <label><input type="checkbox" id="showOnTeamPage" name="showOnTeamPage" value="1" {if $showOnTeamPage}checked="checked" {/if}/> {lang}wcf.acp.group.showOnTeamPage{/lang}</label>
+ </dd>
+ </dl>
+ {/if}
+
{event name='dataFields'}
</fieldset>
<th class="columnID columnGroupID{if $sortField == 'groupID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='UserGroupList'}pageNo={@$pageNo}&sortField=groupID&sortOrder={if $sortField == 'groupID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
<th class="columnTitle columnGroupName{if $sortField == 'groupName'} active {@$sortOrder}{/if}"><a href="{link controller='UserGroupList'}pageNo={@$pageNo}&sortField=groupName&sortOrder={if $sortField == 'groupName' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.name{/lang}</a></th>
<th class="columnDigits columnMembers{if $sortField == 'members'} active {@$sortOrder}{/if}"><a href="{link controller='UserGroupList'}pageNo={@$pageNo}&sortField=members&sortOrder={if $sortField == 'members' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.group.members{/lang}</a></th>
+ <th class="columnDigits columnPriority{if $sortField == 'priority'} active {@$sortOrder}{/if}"><a href="{link controller='UserGroupList'}pageNo={@$pageNo}&sortField=priority&sortOrder={if $sortField == 'priority' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.group.priority{/lang}</a></th>
{event name='columnHeads'}
</tr>
<a class="jsTooltip" title="{lang}wcf.acp.group.showMembers{/lang}" href="{link controller='UserSearch'}groupID={@$group->groupID}{/link}">{#$group->members}</a>
{/if}
</td>
+ <td class="columnDigits columnPriority">{#$group->priority}</td>
{event name='columns'}
</tr>
{include file='header'}
{event name='javascriptInclude'}
+<script type="text/javascript" src="{@$__wcf->getPath()}acp/js/WCF.ACP.User{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
<script type="text/javascript">
//<![CDATA[
$(function() {
'wcf.acp.user.ban.sure': '{lang}wcf.acp.user.ban.sure{/lang}'
});
WCF.ACP.User.BanHandler.init();
+ {if $__wcf->session->getPermission('admin.user.canEnableUser')}WCF.ACP.User.EnableHandler.init();{/if}
{event name='javascriptInit'}
});
{else}
<span class="icon icon16 icon-{if $user->banned}lock{else}unlock{/if} disabled" title="{lang}wcf.acp.user.{if $user->banned}unban{else}ban{/if}{/lang}"></span>
{/if}
+ {if $__wcf->session->getPermission('admin.user.canEnableUser')}
+ {if $user->userID != $__wcf->user->userID}
+ <span class="icon icon16 icon-{if !$user->activationCode}circle-blank{else}off{/if} jsEnableButton jsTooltip pointer" title="{lang}wcf.acp.user.{if !$user->activationCode}disable{else}enable{/if}{/lang}" data-object-id="{@$user->userID}" data-enable-message="{lang}wcf.acp.user.enable{/lang}" data-disable-message="{lang}wcf.acp.user.disable{/lang}" data-enabled="{if !$user->activationCode}true{else}false{/if}"></span>
+ {else}
+ <span class="icon icon16 icon-{if !$user->activationCode}circle-blank{else}off{/if} disabled" title="{lang}wcf.acp.user.{if !$user->activationCode}disable{else}enable{/if}{/lang}"></span>
+ {/if}
+ {/if}
{event name='rowButtons'}
</td>
--- /dev/null
+{include file='header' pageTitle='wcf.acp.user.merge'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.user.merge{/lang}</h1>
+</header>
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ {hascontent}
+ <nav>
+ <ul>
+ {content}
+ {event name='contentNavigationButtons'}
+ {/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</div>
+
+<form method="post" action="{link controller='UserMerge'}{/link}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.acp.user.merge.markedUsers{/lang}</legend>
+
+ <div>
+ {implode from=$users item='user'}<a href="{link controller='UserEdit' id=$user->userID}{/link}">{$user}</a>{/implode}
+ </div>
+
+ {event name='markedUserFields'}
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.acp.user.merge.destination{/lang}</legend>
+
+ <dl{if $errorField == 'destinationUserID'} class="formError"{/if}>
+ <dt><label for="destinationUserID">{lang}wcf.acp.user.merge.destination{/lang}</label></dt>
+ <dd>
+ <select name="destinationUserID" id="destinationUserID">
+ <option value="0"></option>
+ {foreach from=$users item=user}
+ <option value="{@$user->userID}">{$user->username}</option>
+ {/foreach}
+ </select>
+
+ {if $errorField == 'destinationUserID'}
+ <small class="innerError">
+ {if $errorType == 'empty'}{lang}wcf.global.form.error.empty{/lang}{/if}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.user.merge.destination.description{/lang}</small>
+ <dd>
+ </dl>
+
+ {event name='mergeFields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+{include file='footer'}
--- /dev/null
+{include file='header'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.user.rank.{$action}{/lang}</h1>
+</header>
+
+{if $errorField}
+ <p class="error">{lang}wcf.global.form.error{/lang}</p>
+{/if}
+
+{if $success|isset}
+ <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+ <nav>
+ <ul>
+ <li><a href="{link controller='UserRankList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.user.rank.list{/lang}</span></a></li>
+
+ {event name='contentNavigationButtons'}
+ </ul>
+ </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='UserRankAdd'}{/link}{else}{link controller='UserRankEdit' id=$rankID}{/link}{/if}">
+ <div class="container containerPadding marginTop">
+ <fieldset>
+ <legend>{lang}wcf.global.form.data{/lang}</legend>
+
+ <dl{if $errorField == 'rankTitle'} class="formError"{/if}>
+ <dt><label for="rankTitle">{lang}wcf.acp.user.rank.title{/lang}</label></dt>
+ <dd>
+ <input type="text" id="rankTitle" name="rankTitle" value="{$rankTitle}" required="required" autofocus="autofocus" class="long" />
+ {if $errorField == 'rankTitle'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {elseif $errorType == 'multilingual'}
+ {lang}wcf.global.form.error.multilingual{/lang}
+ {else}
+ {lang}wcf.acp.user.rank.title.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ </dd>
+ </dl>
+ {include file='multipleLanguageInputJavascript' elementIdentifier='rankTitle' forceSelection=false}
+
+ <dl{if $errorField == 'cssClassName'} class="formError"{/if}>
+ <dt><label for="cssClassName">{lang}wcf.acp.user.rank.cssClassName{/lang}</label></dt>
+ <dd>
+ <ul id="labelList">
+ {foreach from=$availableCssClassNames item=className}
+ {if $className == 'custom'}
+ <li class="labelCustomClass"><label><input type="radio" name="cssClassName" value="custom"{if $cssClassName == 'custom'} checked="checked"{/if} /> <span><input type="text" id="customCssClassName" name="customCssClassName" value="{$customCssClassName}" class="long" /></span></label></li>
+ {else}
+ <li><label><input type="radio" name="cssClassName" value="{$className}"{if $cssClassName == $className} checked="checked"{/if} /> <span class="badge label{if $className != 'none'} {$className}{/if}">{lang}wcf.acp.user.rank.title{/lang}</span></label></li>
+ {/if}
+ {/foreach}
+ </ul>
+
+ {if $errorField == 'cssClassName'}
+ <small class="innerError">
+ {lang}wcf.acp.user.rank.cssClassName.error.{@$errorType}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.user.rank.cssClassName.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='dataFields'}
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.acp.user.rank.image{/lang}</legend>
+
+ <dl{if $errorField == 'rankImage'} class="formError"{/if}>
+ <dt><label for="rankImage">{lang}wcf.acp.user.rank.image{/lang}</label></dt>
+ <dd>
+ <input type="text" id="rankImage" name="rankImage" value="{$rankImage}" class="long" />
+ {if $errorField == 'rankImage'}
+ <small class="innerError">
+ {lang}wcf.acp.user.rank.image.error.{@$errorType}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.user.rank.rankImage.description{/lang}</small>
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'repeatImage'} class="formError"{/if}>
+ <dt><label for="repeatImage">{lang}wcf.acp.user.rank.repeatImage{/lang}</label></dt>
+ <dd>
+ <input type="number" id="repeatImage" name="repeatImage" value="{@$repeatImage}" min="1" class="tiny" />
+ {if $errorField == 'rankImage'}
+ <small class="innerError">
+ {lang}wcf.acp.user.rank.repeatImage.error.{@$errorType}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.user.rank.repeatImage.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {if $action == 'edit' && $rank->rankImage}
+ <dl>
+ <dt><label>{lang}wcf.acp.user.rank.currentImage{/lang}</label></dt>
+ <dd>{@$rank->getImage()}</dd>
+ </dl>
+ {/if}
+
+ {event name='imageFields'}
+ </fieldset>
+
+ <fieldset>
+ <legend>{lang}wcf.acp.user.rank.requirement{/lang}</legend>
+
+ <dl{if $errorField == 'groupID'} class="formError"{/if}>
+ <dt><label for="groupID">{lang}wcf.user.group{/lang}</label></dt>
+ <dd>
+ <select id="groupID" name="groupID">
+ {foreach from=$availableGroups item=group}
+ <option value="{@$group->groupID}"{if $group->groupID == $groupID} selected="selected"{/if}>{$group->groupName|language}</option>
+ {/foreach}
+ </select>
+ {if $errorField == 'groupID'}
+ <small class="innerError">
+ {if $errorType == 'empty'}
+ {lang}wcf.global.form.error.empty{/lang}
+ {else}
+ {lang}wcf.acp.user.rank.userGroup.error.{@$errorType}{/lang}
+ {/if}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.user.rank.userGroup.description{/lang}</small>
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'requiredGender'} class="formError"{/if}>
+ <dt><label for="requiredGender">{lang}wcf.user.option.gender{/lang}</label></dt>
+ <dd>
+ <select id="requiredGender" name="requiredGender">
+ <option value="0"></option>
+ <option value="1"{if $requiredGender == 1} selected="selected"{/if}>{lang}wcf.user.gender.male{/lang}</option>
+ <option value="2"{if $requiredGender == 2} selected="selected"{/if}>{lang}wcf.user.gender.female{/lang}</option>
+ </select>
+ {if $errorField == 'requiredGender'}
+ <small class="innerError">
+ {lang}wcf.acp.user.rank.requiredGender.error.{@$errorType}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.user.rank.requiredGender.description{/lang}</small>
+ </dd>
+ </dl>
+
+ <dl{if $errorField == 'requiredPoints'} class="formError"{/if}>
+ <dt><label for="requiredPoints">{lang}wcf.acp.user.rank.requiredPoints{/lang}</label></dt>
+ <dd>
+ <input type="number" id="requiredPoints" name="requiredPoints" value="{@$requiredPoints}" min="0" class="tiny" />
+ {if $errorField == 'requiredPoints'}
+ <small class="innerError">
+ {lang}wcf.acp.user.rank.requiredPoints.error.{@$errorType}{/lang}
+ </small>
+ {/if}
+ <small>{lang}wcf.acp.user.rank.requiredPoints.description{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='requirementFields'}
+ </fieldset>
+
+ {event name='fieldsets'}
+ </div>
+
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+ </div>
+</form>
+
+
+{include file='footer'}
--- /dev/null
+{include file='header' pageTitle='wcf.acp.user.rank.list'}
+
+<header class="boxHeadline">
+ <h1>{lang}wcf.acp.user.rank.list{/lang}</h1>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Action.Delete('wcf\\data\\user\\rank\\UserRankAction', '.jsUserRankRow');
+
+ $('#updateUserRanks').click(function () {
+ $('#updateUserRanks').unbind('click');
+ new WCF.ACP.Worker('updateUserRanks', 'wcf\\system\\worker\\UserRankUpdateWorker');
+ });
+ });
+ //]]>
+ </script>
+</header>
+
+<div class="contentNavigation">
+ {pages print=true assign=pagesLinks controller="UserRankList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+
+ <nav>
+ <ul>
+ <li><a id="updateUserRanks" class="button"><span class="icon icon16 icon-repeat"></span> <span>{lang}wcf.acp.user.rank.updateRanks{/lang}</span></a></li>
+ <li><a href="{link controller='UserRankAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.user.rank.add{/lang}</span></a></li>
+
+ {event name='contentNavigationButtonsTop'}
+ </ul>
+ </nav>
+</div>
+
+{hascontent}
+ <div class="tabularBox tabularBoxTitle marginTop">
+ <header>
+ <h2>{lang}wcf.acp.user.rank.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+ </header>
+
+ <table class="table">
+ <thead>
+ <tr>
+ <th class="columnID columnRankID{if $sortField == 'rankID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=rankID&sortOrder={if $sortField == 'rankID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+ <th class="columnTitle columnRankTitle{if $sortField == 'rankTitle'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=rankTitle&sortOrder={if $sortField == 'rankTitle' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.user.rank.title{/lang}</a></th>
+ <th class="columnText columnRankImage{if $sortField == 'rankImage'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=rankImage&sortOrder={if $sortField == 'rankImage' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.user.rank.image{/lang}</a></th>
+ <th class="columnText columnGroupID{if $sortField == 'groupID'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=groupID&sortOrder={if $sortField == 'groupID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.user.group{/lang}</a></th>
+ <th class="columnText columnRequiredGender{if $sortField == 'requiredGender'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=requiredGender&sortOrder={if $sortField == 'requiredGender' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.user.option.gender{/lang}</a></th>
+ <th class="columnDigits columnRequiredPoints{if $sortField == 'requiredPoints'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=requiredPoints&sortOrder={if $sortField == 'requiredPoints' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.user.rank.requiredPoints{/lang}</a></th>
+
+ {event name='columnHeads'}
+ </tr>
+ </thead>
+
+ <tbody>
+ {content}
+ {foreach from=$objects item=userRank}
+ <tr class="jsUserRankRow">
+ <td class="columnIcon">
+ <a href="{link controller='UserRankEdit' id=$userRank->rankID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
+ <span class="icon icon16 icon-remove jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$userRank->rankID}" data-confirm-message="{lang}wcf.acp.user.rank.delete.sure{/lang}"></span>
+
+ {event name='rowButtons'}
+ </td>
+ <td class="columnID columnRankID">{@$userRank->rankID}</td>
+ <td class="columnTitle columnRankTitle"><a href="{link controller='UserRankEdit' id=$userRank->rankID}{/link}" title="{lang}wcf.acp.user.rank.edit{/lang}" class="badge label{if $userRank->cssClassName} {$userRank->cssClassName}{/if}">{$userRank->rankTitle|language}</a></td>
+ <td class="columnText columnRankImage">{if $userRank->rankImage}{@$userRank->getImage()}{/if}</td>
+ <td class="columnText columnGroupID">{$userRank->groupName|language}</td>
+ <td class="columnText columnRequiredGender">{if $userRank->requiredGender}{if $userRank->requiredGender == 1}{lang}wcf.user.gender.male{/lang}{else}{lang}wcf.user.gender.female{/lang}{/if}{/if}</td>
+ <td class="columnDigits columnRequiredPoints">{#$userRank->requiredPoints}</td>
+
+ {event name='columns'}
+ </tr>
+ {/foreach}
+ {/content}
+ </tbody>
+ </table>
+
+ </div>
+
+ <div class="contentNavigation">
+ {@$pagesLinks}
+
+ <nav>
+ <ul>
+ <li><a href="{link controller='UserRankAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.user.rank.add{/lang}</span></a></li>
+
+ {event name='contentNavigationButtonsBottom'}
+ </ul>
+ </nav>
+ </div>
+{hascontentelse}
+ <p class="info">{lang}wcf.acp.user.rank.noneAvailable{/lang}</p>
+{/hascontent}
+
+{include file='footer'}
<ul class="dropdownMenu">
<li><a href="{link controller='UserQuickSearch'}mode=banned{/link}">{lang}wcf.acp.user.quickSearch.banned{/lang}</a></li>
<li><a href="{link controller='UserQuickSearch'}mode=newest{/link}">{lang}wcf.acp.user.quickSearch.newest{/lang}</a></li>
-
+ <li><a href="{link controller='UserQuickSearch'}mode=disabled{/link}">{lang}wcf.acp.user.quickSearch.disabled{/lang}</a></li>
+ <li><a href="{link controller='UserQuickSearch'}mode=disabledAvatars{/link}">{lang}wcf.acp.user.quickSearch.disabledAvatars{/lang}</a></li>
+ <li><a href="{link controller='UserQuickSearch'}mode=disabledSignatures{/link}">{lang}wcf.acp.user.quickSearch.disabledSignatures{/lang}</a></li>
+
{event name='quickSearchItems'}
</ul>
</li>
</dd>
</dl>
+ <dl>
+ <dt><label for="lastActivityTimeStart">{lang}wcf.user.lastActivityTime{/lang}</label></dt>
+ <dd>
+ <input type="date" id="lastActivityTimeStart" name="lastActivityTimeStart" value="{$lastActivityTimeStart}" placeholder="{lang}wcf.date.period.start{/lang}" />
+ <input type="date" id="lastActivityTimeEnd" name="lastActivityTimeEnd" value="{$lastActivityTimeEnd}" placeholder="{lang}wcf.date.period.end{/lang}" />
+ </dd>
+ </dl>
+
{event name='conditionFields'}
</fieldset>
<dd>
<label><input type="checkbox" name="banned" value="1" {if $banned == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.banned{/lang}</label>
<label><input type="checkbox" name="notBanned" value="1" {if $notBanned == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.notBanned{/lang}</label>
+ <label><input type="checkbox" name="enabled" value="1" {if $enabled == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.enabled{/lang}</label>
+ <label><input type="checkbox" name="disabled" value="1" {if $disabled == 1}checked="checked" {/if}/> {lang}wcf.acp.user.search.conditions.state.disabled{/lang}</label>
{event name='states'}
</dd>
--- /dev/null
+{foreach from=$availableOptions item=availableOption}
+ <label><input type="checkbox" name="values[{$option->optionName}][]" value="{$availableOption}" {if $availableOption|in_array:$value}checked="checked" {/if}/> {lang}wcf.user.option.{$availableOption}{/lang}</label>
+{/foreach}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
+ <title>Default Avatar</title>
+ <desc>Default Avatar for WCF 2.0</desc>
+
+ <!--
+ @author Alexander Ebert
+ @copyright 2001-2012 WoltLab GmbH
+ @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ -->
+
+ <defs>
+ <style type="text/css">
+ <![CDATA[
+ .surface { fill: #fff; }
+ .shadow { fill: #bbb; }
+ ]]>
+ </style>
+ </defs>
+ <rect x="0" class="shadow" width="16" height="16"/>
+ <path class="surface" d="M3.528,16C3.776,11.499,5.684,8,8,8s4.224,3.499,4.473,7.998"/>
+ <circle class="surface" cx="8" cy="6" r="3.5"/>
+</svg>
--- /dev/null
+/**
+ * User-related classes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+
+/**
+ * User login
+ *
+ * @param boolean isQuickLogin
+ */
+WCF.User.Login = Class.extend({
+ /**
+ * login button
+ * @var jQuery
+ */
+ _loginSubmitButton: null,
+
+ /**
+ * password input
+ * @var jQuery
+ */
+ _password: null,
+
+ /**
+ * password input container
+ * @var jQuery
+ */
+ _passwordContainer: null,
+
+ /**
+ * cookie input
+ * @var jQuery
+ */
+ _useCookies: null,
+
+ /**
+ * cookie input container
+ * @var jQuery
+ */
+ _useCookiesContainer: null,
+
+ /**
+ * Initializes the user login
+ *
+ * @param boolean isQuickLogin
+ */
+ init: function(isQuickLogin) {
+ this._loginSubmitButton = $('#loginSubmitButton');
+ this._password = $('#password'),
+ this._passwordContainer = this._password.parents('dl');
+ this._useCookies = $('#useCookies');
+ this._useCookiesContainer = this._useCookies.parents('dl');
+
+ var $loginForm = $('#loginForm');
+ $loginForm.find('input[name=action]').change($.proxy(this._change, this));
+
+ if (isQuickLogin) {
+ WCF.User.QuickLogin.init();
+ }
+ },
+
+ /**
+ * Handle toggle between login and register.
+ *
+ * @param object event
+ */
+ _change: function(event) {
+ if ($(event.currentTarget).val() === 'register') {
+ this._setState(false, WCF.Language.get('wcf.user.button.register'));
+ }
+ else {
+ this._setState(true, WCF.Language.get('wcf.user.button.login'));
+ }
+ },
+
+ /**
+ * Sets form states.
+ *
+ * @param boolean enable
+ * @param string buttonTitle
+ */
+ _setState: function(enable, buttonTitle) {
+ if (enable) {
+ this._password.enable();
+ this._passwordContainer.removeClass('disabled');
+ this._useCookies.enable();
+ this._useCookiesContainer.removeClass('disabled');
+ }
+ else {
+ this._password.disable();
+ this._passwordContainer.addClass('disabled');
+ this._useCookies.disable();
+ this._useCookiesContainer.addClass('disabled');
+ }
+
+ this._loginSubmitButton.val(buttonTitle);
+ }
+});
+
+/**
+ * Quick login box
+ */
+WCF.User.QuickLogin = {
+ /**
+ * dialog overlay
+ * @var jQuery
+ */
+ _dialog: null,
+
+ /**
+ * login message container
+ * @var jQuery
+ */
+ _loginMessage: null,
+
+ /**
+ * Initializes the quick login box
+ */
+ init: function() {
+ $('.loginLink').click($.proxy(this._render, this));
+ },
+
+ /**
+ * Displays the quick login box with a info message
+ *
+ * @param string message
+ */
+ show: function(message) {
+ if (message) {
+ if (this._loginMessage === null) {
+ this._loginMessage = $('<p class="info" />').hide().prependTo($('#loginForm > form'));
+ }
+
+ this._loginMessage.show().text(message);
+ }
+ else if (this._loginMessage !== null) {
+ this._loginMessage.hide();
+ }
+
+ this._render();
+ },
+
+ /**
+ * Renders the dialog
+ *
+ * @param jQuery.Event event
+ */
+ _render: function(event) {
+ if (event !== undefined) {
+ event.preventDefault();
+ }
+
+ if (this._dialog === null) {
+ this._dialog = $('#loginForm').wcfDialog({
+ title: WCF.Language.get('wcf.user.login')
+ });
+ this._dialog.find('#username').focus();
+ }
+ else {
+ this._dialog.wcfDialog('open');
+ }
+ }
+};
+
+/**
+ * UserProfile namespace
+ */
+WCF.User.Profile = {};
+
+/**
+ * Shows the activity point list for users.
+ */
+WCF.User.Profile.ActivityPointList = {
+ /**
+ * list of cached templates
+ * @var object
+ */
+ _cache: { },
+
+ /**
+ * dialog overlay
+ * @var jQuery
+ */
+ _dialog: null,
+
+ /**
+ * initialization state
+ * @var boolean
+ */
+ _didInit: false,
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * Initializes the WCF.User.Profile.ActivityPointList class.
+ */
+ init: function() {
+ if (this._didInit) {
+ return;
+ }
+
+ this._cache = { };
+ this._dialog = null;
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ this._init();
+
+ WCF.DOMNodeInsertedHandler.addCallback('WCF.User.Profile.ActivityPointList', $.proxy(this._init, this));
+
+ this._didInit = true;
+ },
+
+ /**
+ * Initializes display for activity points.
+ */
+ _init: function() {
+ $('.activityPointsDisplay').removeClass('activityPointsDisplay').click($.proxy(this._click, this));
+ },
+
+ /**
+ * Shows or loads the activity point for selected user.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var $userID = $(event.currentTarget).data('userID');
+
+ if (this._cache[$userID] === undefined) {
+ this._proxy.setOption('data', {
+ actionName: 'getDetailedActivityPointList',
+ className: 'wcf\\data\\user\\UserProfileAction',
+ objectIDs: [ $userID ]
+ });
+ this._proxy.sendRequest();
+ }
+ else {
+ this._show($userID);
+ }
+ },
+
+ /**
+ * Displays activity points for given user.
+ *
+ * @param integer userID
+ */
+ _show: function(userID) {
+ if (this._dialog === null) {
+ this._dialog = $('<div>' + this._cache[userID] + '</div>').hide().appendTo(document.body);
+ this._dialog.wcfDialog({
+ title: WCF.Language.get('wcf.user.activityPoint')
+ });
+ }
+ else {
+ this._dialog.html(this._cache[userID]);
+ this._dialog.wcfDialog('open');
+ }
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ this._cache[data.returnValues.userID] = data.returnValues.template;
+ this._show(data.returnValues.userID);
+ }
+};
+
+/**
+ * Provides methods to follow an user.
+ *
+ * @param integer userID
+ * @param boolean following
+ */
+WCF.User.Profile.Follow = Class.extend({
+ /**
+ * follow button
+ * @var jQuery
+ */
+ _button: null,
+
+ /**
+ * true if following current user
+ * @var boolean
+ */
+ _following: false,
+
+ /**
+ * action proxy object
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * user id
+ * @var integer
+ */
+ _userID: 0,
+
+ /**
+ * Creates a new follow object.
+ *
+ * @param integer userID
+ * @param boolean following
+ */
+ init: function (userID, following) {
+ this._following = following;
+ this._userID = userID;
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ this._createButton();
+ this._showButton();
+ },
+
+ /**
+ * Creates the (un-)follow button
+ */
+ _createButton: function () {
+ this._button = $('<li id="followUser"><a class="button jsTooltip" title="'+WCF.Language.get('wcf.user.button.'+(this._following ? 'un' : '')+'follow')+'"><span class="icon icon16 icon-plus"></span> <span class="invisible">'+WCF.Language.get('wcf.user.button.'+(this._following ? 'un' : '')+'follow')+'</span></a></li>').prependTo($('#profileButtonContainer'));
+ this._button.click($.proxy(this._execute, this));
+ },
+
+ /**
+ * Follows or unfollows an user.
+ */
+ _execute: function () {
+ var $actionName = (this._following) ? 'unfollow' : 'follow';
+ this._proxy.setOption('data', {
+ 'actionName': $actionName,
+ 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
+ 'parameters': {
+ data: {
+ userID: this._userID
+ }
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Displays current follow state.
+ */
+ _showButton: function () {
+ if (this._following) {
+ this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.unfollow')).addClass('active').children('.icon').removeClass('icon-plus').addClass('icon-minus');
+ }
+ else {
+ this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.follow')).removeClass('active').children('.icon').removeClass('icon-minus').addClass('icon-plus');
+ }
+ },
+
+ /**
+ * Update object state on success.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function (data, textStatus, jqXHR) {
+ this._following = data.returnValues.following;
+ this._showButton();
+
+ var $notification = new WCF.System.Notification();
+ $notification.show();
+ }
+});
+
+/**
+ * Provides methods to manage ignored users.
+ *
+ * @param integer userID
+ * @param boolean isIgnoredUser
+ */
+WCF.User.Profile.IgnoreUser = Class.extend({
+ /**
+ * ignore button
+ * @var jQuery
+ */
+ _button: null,
+
+ /**
+ * ignore state
+ * @var boolean
+ */
+ _isIgnoredUser: false,
+
+ /**
+ * ajax proxy object
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * target user id
+ * @var integer
+ */
+ _userID: 0,
+
+ /**
+ * Initializes methods to manage an ignored user.
+ *
+ * @param integer userID
+ * @param boolean isIgnoredUser
+ */
+ init: function(userID, isIgnoredUser) {
+ this._userID = userID;
+ this._isIgnoredUser = isIgnoredUser;
+
+ // initialize proxy
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ // handle button
+ this._updateButton();
+ this._button.click($.proxy(this._click, this));
+ },
+
+ /**
+ * Handle clicks, might cause 'ignore' or 'unignore' to be triggered.
+ */
+ _click: function() {
+ var $action = (this._isIgnoredUser) ? 'unignore' : 'ignore';
+
+ this._proxy.setOption('data', {
+ actionName: $action,
+ className: 'wcf\\data\\user\\ignore\\UserIgnoreAction',
+ parameters: {
+ data: {
+ ignoreUserID: this._userID
+ }
+ }
+ });
+
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Updates button label and function upon successful request.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ this._isIgnoredUser = data.returnValues.isIgnoredUser;
+ this._updateButton();
+
+ var $notification = new WCF.System.Notification();
+ $notification.show();
+ },
+
+ /**
+ * Updates button label and inserts it if not exists.
+ */
+ _updateButton: function() {
+ if (this._button === null) {
+ this._button = $('<li id="ignoreUser"><a class="button jsTooltip" title="'+WCF.Language.get('wcf.user.button.'+(this._following ? 'un' : '')+'ignore')+'"><span class="icon icon16 icon-off"></span> <span class="invisible">'+WCF.Language.get('wcf.user.button.'+(this._following ? 'un' : '')+'ignore')+'</span></a></li>').prependTo($('#profileButtonContainer'));
+ }
+
+ this._button.find('.button').data('tooltip', WCF.Language.get('wcf.user.button.' + (this._isIgnoredUser ? 'un' : '') + 'ignore'));
+ if (this._isIgnoredUser) this._button.find('.button').addClass('active').children('.icon').removeClass('icon-off').addClass('icon-circle-blank');
+ else this._button.find('.button').removeClass('active').children('.icon').removeClass('icon-circle-blank').addClass('icon-off');
+ }
+});
+
+/**
+ * Provides methods to load tab menu content upon request.
+ */
+WCF.User.Profile.TabMenu = Class.extend({
+ /**
+ * list of containers
+ * @var object
+ */
+ _hasContent: { },
+
+ /**
+ * profile content
+ * @var jQuery
+ */
+ _profileContent: null,
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * target user id
+ * @var integer
+ */
+ _userID: 0,
+
+ /**
+ * Initializes the tab menu loader.
+ *
+ * @param integer userID
+ */
+ init: function(userID) {
+ this._profileContent = $('#profileContent');
+ this._userID = userID;
+
+ var $activeMenuItem = this._profileContent.data('active');
+ var $enableProxy = false;
+
+ // fetch content state
+ this._profileContent.find('div.tabMenuContent').each($.proxy(function(index, container) {
+ var $containerID = $(container).wcfIdentify();
+
+ if ($activeMenuItem === $containerID) {
+ this._hasContent[$containerID] = true;
+ }
+ else {
+ this._hasContent[$containerID] = false;
+ $enableProxy = true;
+ }
+ }, this));
+
+ // enable loader if at least one container is empty
+ if ($enableProxy) {
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ this._profileContent.bind('wcftabsbeforeactivate', $.proxy(this._loadContent, this));
+ }
+ },
+
+ /**
+ * Prepares to load content once tabs are being switched.
+ *
+ * @param object event
+ * @param object ui
+ */
+ _loadContent: function(event, ui) {
+ var $panel = $(ui.newPanel);
+ var $containerID = $panel.attr('id');
+
+ if (!this._hasContent[$containerID]) {
+ this._proxy.setOption('data', {
+ actionName: 'getContent',
+ className: 'wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction',
+ parameters: {
+ data: {
+ containerID: $containerID,
+ menuItem: $panel.data('menuItem'),
+ userID: this._userID
+ }
+ }
+ });
+ this._proxy.sendRequest();
+ }
+ },
+
+ /**
+ * Shows previously requested content.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ var $containerID = data.returnValues.containerID;
+ this._hasContent[$containerID] = true;
+
+ // insert content
+ var $content = this._profileContent.find('#' + $containerID);
+ $('<div>' + data.returnValues.template + '</div>').hide().appendTo($content);
+
+ // slide in content
+ $content.children('div').wcfBlindIn();
+ }
+});
+
+/**
+ * User profile inline editor.
+ *
+ * @param integer userID
+ * @param boolean editOnInit
+ */
+WCF.User.Profile.Editor = Class.extend({
+ /**
+ * current action
+ * @var string
+ */
+ _actionName: '',
+
+ /**
+ * list of interface buttons
+ * @var object
+ */
+ _buttons: { },
+
+ /**
+ * cached tab content
+ * @var string
+ */
+ _cachedTemplate: '',
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * tab object
+ * @var jQuery
+ */
+ _tab: null,
+
+ /**
+ * target user id
+ * @var integer
+ */
+ _userID: 0,
+
+ /**
+ * Initializes the WCF.User.Profile.Editor object.
+ *
+ * @param integer userID
+ * @param boolean editOnInit
+ */
+ init: function(userID, editOnInit) {
+ this._actionName = '';
+ this._cachedTemplate = '';
+ this._tab = $('#about');
+ this._userID = userID;
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ this._initButtons();
+
+ // begin editing on page load
+ if (editOnInit) {
+ this._beginEdit();
+ }
+ },
+
+ /**
+ * Initializes interface buttons.
+ */
+ _initButtons: function() {
+ var $buttonContainer = $('#profileButtonContainer');
+
+ // create buttons
+ this._buttons = {
+ beginEdit: $('<li><a class="button"><span class="icon icon16 icon-pencil" /> <span>' + WCF.Language.get('wcf.user.editProfile') + '</span></a></li>').click($.proxy(this._beginEdit, this)).appendTo($buttonContainer)
+ };
+ },
+
+ /**
+ * Begins editing.
+ */
+ _beginEdit: function() {
+ this._actionName = 'beginEdit';
+ this._buttons.beginEdit.hide();
+ $('#profileContent').wcfTabs('select', 'about');
+
+ // load form
+ this._proxy.setOption('data', {
+ actionName: 'beginEdit',
+ className: 'wcf\\data\\user\\UserProfileAction',
+ objectIDs: [ this._userID ]
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Saves input values.
+ */
+ _save: function() {
+ this._actionName = 'save';
+
+ // collect values
+ var $regExp = /values\[([a-zA-Z0-9._-]+)\]/;
+ var $values = { };
+ this._tab.find('input, textarea, select').each(function(index, element) {
+ var $element = $(element);
+
+ if ($element.getTagName() === 'input') {
+ var $type = $element.attr('type');
+
+ if (($type === 'radio' || $type === 'checkbox') && !$element.prop('checked')) {
+ return;
+ }
+ }
+
+ var $name = $element.attr('name');
+ if ($regExp.test($name)) {
+ $values[RegExp.$1] = $element.val();
+ }
+ });
+
+ this._proxy.setOption('data', {
+ actionName: 'save',
+ className: 'wcf\\data\\user\\UserProfileAction',
+ objectIDs: [ this._userID ],
+ parameters: {
+ values: $values
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Restores back to default view.
+ */
+ _restore: function() {
+ this._actionName = 'restore';
+ this._buttons.beginEdit.show();
+
+ this._destroyCKEditor();
+
+ this._tab.html(this._cachedTemplate).children().css({ height: 'auto' });
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ switch (this._actionName) {
+ case 'beginEdit':
+ this._prepareEdit(data);
+ break;
+
+ case 'save':
+ // save was successful, show parsed template
+ if (data.returnValues.success) {
+ this._cachedTemplate = data.returnValues.template;
+ this._restore();
+ }
+ else {
+ this._prepareEdit(data, true);
+ }
+ break;
+ }
+ },
+
+ /**
+ * Prepares editing mode.
+ *
+ * @param object data
+ * @param boolean disableCache
+ */
+ _prepareEdit: function(data, disableCache) {
+ this._destroyCKEditor();
+
+ // update template
+ var self = this;
+ this._tab.html(function(index, oldHTML) {
+ if (disableCache !== true) {
+ self._cachedTemplate = oldHTML;
+ }
+
+ return data.returnValues.template;
+ });
+
+ // block autocomplete
+ this._tab.find('input[type=text]').attr('autocomplete', 'off');
+
+ // bind event listener
+ this._tab.find('.formSubmit > button[data-type=save]').click($.proxy(this._save, this));
+ this._tab.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore, this));
+ this._tab.find('input').keyup(function(event) {
+ if (event.which === 13) { // Enter
+ self._save();
+
+ event.preventDefault();
+ return false;
+ }
+ });
+ },
+
+ /**
+ * Destroys all CKEditor instances within current tab.
+ */
+ _destroyCKEditor: function() {
+ // destroy all CKEditor instances
+ this._tab.find('textarea + .cke').each(function(index, container) {
+ var $instanceName = $(container).attr('id').replace(/cke_/, '');
+ if (CKEDITOR.instances[$instanceName]) {
+ CKEDITOR.instances[$instanceName].destroy();
+ }
+ });
+ }
+});
+
+
+/**
+ * Namespace for registration functions.
+ */
+WCF.User.Registration = {};
+
+/**
+ * Validates the password.
+ *
+ * @param jQuery element
+ * @param jQuery confirmElement
+ * @param object options
+ */
+WCF.User.Registration.Validation = Class.extend({
+ /**
+ * action name
+ * @var string
+ */
+ _actionName: '',
+
+ /**
+ * class name
+ * @var string
+ */
+ _className: '',
+
+ /**
+ * confirmation input element
+ * @var jQuery
+ */
+ _confirmElement: null,
+
+ /**
+ * input element
+ * @var jQuery
+ */
+ _element: null,
+
+ /**
+ * list of error messages
+ * @var object
+ */
+ _errorMessages: { },
+
+ /**
+ * list of additional options
+ * @var object
+ */
+ _options: { },
+
+ /**
+ * AJAX proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * Initializes the validation.
+ *
+ * @param jQuery element
+ * @param jQuery confirmElement
+ * @param object options
+ */
+ init: function(element, confirmElement, options) {
+ this._element = element;
+ this._element.blur($.proxy(this._blur, this));
+ this._confirmElement = confirmElement || null;
+
+ if (this._confirmElement !== null) {
+ this._confirmElement.blur($.proxy(this._blurConfirm, this));
+ }
+
+ options = options || { };
+ this._setOptions(options);
+
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this),
+ showLoadingOverlay: false
+ });
+
+ this._setErrorMessages();
+ },
+
+ /**
+ * Sets additional options
+ */
+ _setOptions: function(options) { },
+
+ /**
+ * Sets error messages.
+ */
+ _setErrorMessages: function() {
+ this._errorMessages = {
+ ajaxError: '',
+ notEqual: ''
+ };
+ },
+
+ /**
+ * Validates once focus on input is lost.
+ *
+ * @param object event
+ */
+ _blur: function(event) {
+ var $value = this._element.val();
+ if (!$value) {
+ return this._showError(this._element, WCF.Language.get('wcf.global.form.error.empty'));
+ }
+
+ if (this._confirmElement !== null) {
+ var $confirmValue = this._confirmElement.val();
+ if ($confirmValue != '' && $value != $confirmValue) {
+ return this._showError(this._confirmElement, this._errorMessages.notEqual);
+ }
+ }
+
+ if (!this._validateOptions()) {
+ return;
+ }
+
+ this._proxy.setOption('data', {
+ actionName: this._actionName,
+ className: this._className,
+ parameters: this._getParameters()
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Returns a list of parameters.
+ *
+ * @return object
+ */
+ _getParameters: function() {
+ return { };
+ },
+
+ /**
+ * Validates input by options.
+ *
+ * @return boolean
+ */
+ _validateOptions: function() {
+ return true;
+ },
+
+ /**
+ * Validates value once confirmation input focus is lost.
+ *
+ * @param object event
+ */
+ _blurConfirm: function(event) {
+ var $value = this._confirmElement.val();
+ if (!$value) {
+ return this._showError(this._confirmElement, WCF.Language.get('wcf.global.form.error.empty'));
+ }
+
+ this._blur(event);
+ },
+
+ /**
+ * Handles AJAX responses.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ if (data.returnValues.isValid) {
+ this._showSuccess(this._element);
+ if (this._confirmElement !== null && this._confirmElement.val()) {
+ this._showSuccess(this._confirmElement);
+ }
+ }
+ else {
+ this._showError(this._element, WCF.Language.get(this._errorMessages.ajaxError + data.returnValues.error));
+ }
+ },
+
+ /**
+ * Shows an error message.
+ *
+ * @param jQuery element
+ * @param string message
+ */
+ _showError: function(element, message) {
+ element.parent().parent().addClass('formError').removeClass('formSuccess');
+
+ var $innerError = element.parent().find('small.innerError');
+ if (!$innerError.length) {
+ $innerError = $('<small />').addClass('innerError').insertAfter(element);
+ }
+
+ $innerError.text(message);
+ },
+
+ /**
+ * Displays a success message.
+ *
+ * @param jQuery element
+ */
+ _showSuccess: function(element) {
+ element.parent().parent().addClass('formSuccess').removeClass('formError');
+ element.next('small.innerError').remove();
+ }
+});
+
+/**
+ * Username validation for registration.
+ *
+ * @see WCF.User.Registration.Validation
+ */
+WCF.User.Registration.Validation.Username = WCF.User.Registration.Validation.extend({
+ /**
+ * @see WCF.User.Registration.Validation._actionName
+ */
+ _actionName: 'validateUsername',
+
+ /**
+ * @see WCF.User.Registration.Validation._className
+ */
+ _className: 'wcf\\data\\user\\UserRegistrationAction',
+
+ /**
+ * @see WCF.User.Registration.Validation._setOptions()
+ */
+ _setOptions: function(options) {
+ this._options = $.extend(true, {
+ minlength: 3,
+ maxlength: 25
+ }, options);
+ },
+
+ /**
+ * @see WCF.User.Registration.Validation._setErrorMessages()
+ */
+ _setErrorMessages: function() {
+ this._errorMessages = {
+ ajaxError: 'wcf.user.username.error.'
+ };
+ },
+
+ /**
+ * @see WCF.User.Registration.Validation._validateOptions()
+ */
+ _validateOptions: function() {
+ var $value = this._element.val();
+ if ($value.length < this._options.minlength || $value.length > this._options.maxlength) {
+ this._showError(this._element, WCF.Language.get('wcf.user.username.error.notValid'));
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * @see WCF.User.Registration.Validation._getParameters()
+ */
+ _getParameters: function() {
+ return {
+ username: this._element.val()
+ };
+ }
+});
+
+/**
+ * Email validation for registration.
+ *
+ * @see WCF.User.Registration.Validation
+ */
+WCF.User.Registration.Validation.EmailAddress = WCF.User.Registration.Validation.extend({
+ /**
+ * @see WCF.User.Registration.Validation._actionName
+ */
+ _actionName: 'validateEmailAddress',
+
+ /**
+ * @see WCF.User.Registration.Validation._className
+ */
+ _className: 'wcf\\data\\user\\UserRegistrationAction',
+
+ /**
+ * @see WCF.User.Registration.Validation._getParameters()
+ */
+ _getParameters: function() {
+ return {
+ email: this._element.val()
+ };
+ },
+
+ /**
+ * @see WCF.User.Registration.Validation._setErrorMessages()
+ */
+ _setErrorMessages: function() {
+ this._errorMessages = {
+ ajaxError: 'wcf.user.email.error.',
+ notEqual: WCF.Language.get('wcf.user.confirmEmail.error.notEqual')
+ };
+ }
+});
+
+/**
+ * Password validation for registration.
+ *
+ * @see WCF.User.Registration.Validation
+ */
+WCF.User.Registration.Validation.Password = WCF.User.Registration.Validation.extend({
+ /**
+ * @see WCF.User.Registration.Validation._actionName
+ */
+ _actionName: 'validatePassword',
+
+ /**
+ * @see WCF.User.Registration.Validation._className
+ */
+ _className: 'wcf\\data\\user\\UserRegistrationAction',
+
+ /**
+ * @see WCF.User.Registration.Validation._getParameters()
+ */
+ _getParameters: function() {
+ return {
+ password: this._element.val()
+ };
+ },
+
+ /**
+ * @see WCF.User.Registration.Validation._setErrorMessages()
+ */
+ _setErrorMessages: function() {
+ this._errorMessages = {
+ ajaxError: 'wcf.user.password.error.',
+ notEqual: WCF.Language.get('wcf.user.confirmPassword.error.notEqual')
+ };
+ }
+});
+
+/**
+ * Toggles input fields for lost password form.
+ */
+WCF.User.Registration.LostPassword = Class.extend({
+ /**
+ * email input
+ * @var jQuery
+ */
+ _email: null,
+
+ /**
+ * username input
+ * @var jQuery
+ */
+ _username: null,
+
+ /**
+ * Initializes LostPassword-form class.
+ */
+ init: function() {
+ // bind input fields
+ this._email = $('#emailInput');
+ this._username = $('#usernameInput');
+
+ // bind event listener
+ this._email.keyup($.proxy(this._checkEmail, this));
+ this._username.keyup($.proxy(this._checkUsername, this));
+
+ // toggle fields on init
+ this._checkEmail();
+ this._checkUsername();
+ },
+
+ /**
+ * Checks for content in email field and toggles username.
+ */
+ _checkEmail: function() {
+ if (this._email.val() == '') {
+ this._username.enable();
+ this._username.parents('dl:eq(0)').removeClass('disabled');
+ }
+ else {
+ this._username.disable();
+ this._username.parents('dl:eq(0)').addClass('disabled');
+ }
+ },
+
+ /**
+ * Checks for content in username field and toggles email.
+ */
+ _checkUsername: function() {
+ if (this._username.val() == '') {
+ this._email.enable();
+ this._email.parents('dl:eq(0)').removeClass('disabled');
+ }
+ else {
+ this._email.disable();
+ this._email.parents('dl:eq(0)').addClass('disabled');
+ }
+ }
+});
+
+/**
+ * Notification system for WCF.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+WCF.Notification = {};
+
+/**
+ * Loads notification for the user panel.
+ *
+ * @see WCF.UserPanel
+ */
+WCF.Notification.UserPanel = WCF.UserPanel.extend({
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * link to show all notifications
+ * @var string
+ */
+ _showAllLink: '',
+
+ /**
+ * @see WCF.UserPanel.init()
+ */
+ init: function(showAllLink) {
+ this._noItems = 'wcf.user.notification.noMoreNotifications';
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+ this._showAllLink = showAllLink;
+
+ this._super('userNotifications');
+
+ // update page title
+ if (this._container.data('count')) {
+ document.title = '(' + this._container.data('count') + ') ' + document.title;
+ }
+ },
+
+ /**
+ * @see WCF.UserPanel._addDefaultItems()
+ */
+ _addDefaultItems: function(dropdownMenu) {
+ this._addDivider(dropdownMenu);
+ $('<li><a href="' + this._showAllLink + '">' + WCF.Language.get('wcf.user.notification.showAll') + '</a></li>').appendTo(dropdownMenu);
+ this._addDivider(dropdownMenu);
+ $('<li id="userNotificationsMarkAllAsConfirmed"><a>' + WCF.Language.get('wcf.user.notification.markAllAsConfirmed') + '</a></li>').click($.proxy(this._markAllAsConfirmed, this)).appendTo(dropdownMenu);
+ },
+
+ /**
+ * @see WCF.UserPanel._getParameters()
+ */
+ _getParameters: function() {
+ return {
+ actionName: 'getOutstandingNotifications',
+ className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+ };
+ },
+
+ /**
+ * @see WCF.UserPanel._after()
+ */
+ _after: function(dropdownMenu) {
+ this._container.find('li.jsNotificationItem').click($.proxy(this._markAsConfirmed, this));
+ },
+
+ /**
+ * Marks a notification as confirmed.
+ *
+ * @param object event
+ */
+ _markAsConfirmed: function(event) {
+ this._proxy.setOption('data', {
+ actionName: 'markAsConfirmed',
+ className: 'wcf\\data\\user\\notification\\UserNotificationAction',
+ parameters: {
+ notificationID: $(event.currentTarget).data('notificationID')
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Marks all notifications as confirmed.
+ */
+ _markAllAsConfirmed: function() {
+ WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), $.proxy(function(action) {
+ if (action === 'confirm') {
+ this._proxy.setOption('data', {
+ actionName: 'markAllAsConfirmed',
+ className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+ });
+ this._proxy.sendRequest();
+ }
+ }, this));
+ },
+
+ /**
+ * @see WCF.UserPanel._success()
+ */
+ _success: function(data, textStatus, jqXHR) {
+ switch (data.actionName) {
+ case 'markAllAsConfirmed':
+ $('.jsNotificationItem').remove();
+ // remove notification count
+ document.title = document.title.replace(/^\(([0-9]+)\) /, '');
+ // fall through
+ case 'getOutstandingNotifications':
+ if (!data.returnValues || !data.returnValues.template) {
+ $('#userNotificationsMarkAllAsConfirmed').prev('.dropdownDivider').remove();
+ $('#userNotificationsMarkAllAsConfirmed').remove();
+ }
+
+ this._super(data, textStatus, jqXHR);
+ break;
+
+ case 'markAsConfirmed':
+ this._container.find('li.jsNotificationItem').each(function(index, item) {
+ var $item = $(item);
+ if (data.returnValues.notificationID == $item.data('notificationID')) {
+ window.location = $item.data('link');
+ return false;
+ }
+ });
+ break;
+ }
+ }
+});
+
+/**
+ * Handles notification list actions.
+ */
+WCF.Notification.List = Class.extend({
+ /**
+ * notification count
+ * @var jQuery
+ */
+ _badge: null,
+
+ /**
+ * list of notification items
+ * @var object
+ */
+ _items: { },
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * Initializes the notification list.
+ */
+ init: function() {
+ var $containers = $('li.jsNotificationItem');
+ if (!$containers.length) {
+ return;
+ }
+
+ $containers.each($.proxy(function(index, container) {
+ var $container = $(container);
+ this._items[$container.data('notificationID')] = $container;
+
+ $container.find('.jsMarkAsConfirmed').data('notificationID', $container.data('notificationID')).click($.proxy(this._click, this));
+ $container.find('p').html(function(index, oldHTML) {
+ return '<a>' + oldHTML + '</a>';
+ }).children('a').click($.proxy(this._click, this));
+ }, this));
+
+ this._badge = $('.jsNotificationsBadge:eq(0)');
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ // mark all as confirmed button
+ $('.contentNavigation .jsMarkAllAsConfirmed').click($.proxy(this._markAllAsConfirmed, this));
+ },
+
+ /**
+ * Handles button actions.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var $notificationID = $(event.currentTarget).data('notificationID');
+
+ this._proxy.setOption('data', {
+ actionName: 'markAsConfirmed',
+ className: 'wcf\\data\\user\\notification\\UserNotificationAction',
+ parameters: {
+ notificationID: $notificationID
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Marks all notifications as confirmed.
+ */
+ _markAllAsConfirmed: function() {
+ WCF.System.Confirmation.show(WCF.Language.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), $.proxy(function(action) {
+ if (action === 'confirm') {
+ this._proxy.setOption('data', {
+ actionName: 'markAllAsConfirmed',
+ className: 'wcf\\data\\user\\notification\\UserNotificationAction'
+ });
+ this._proxy.sendRequest();
+ }
+ }, this));
+ },
+
+ /**
+ * Handles successful button actions.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ switch (data.actionName) {
+ case 'markAllAsConfirmed':
+ window.location.reload();
+ break;
+
+ case 'markAsConfirmed':
+ this._items[data.returnValues.notificationID].remove();
+ delete this._items[data.returnValues.notificationID];
+
+ // reduce badge count
+ this._badge.html(data.returnValues.totalCount);
+
+ // remove previous notification count
+ document.title = document.title.replace(/^\(([0-9]+)\) /, '');
+
+ // update page title
+ if (data.returnValues.totalCount > 0) {
+ document.title = '(' + data.returnValues.totalCount + ') ' + document.title;
+ }
+ break;
+ }
+ }
+});
+
+/**
+ * Signature preview.
+ *
+ * @see WCF.Message.Preview
+ */
+WCF.User.SignaturePreview = WCF.Message.Preview.extend({
+ /**
+ * @see WCF.Message.Preview._handleResponse()
+ */
+ _handleResponse: function(data) {
+ // get preview container
+ var $preview = $('#previewContainer');
+ if (!$preview.length) {
+ $preview = $('<fieldset id="previewContainer"><legend>' + WCF.Language.get('wcf.global.preview') + '</legend><div></div></fieldset>').insertBefore($('#signatureContainer')).wcfFadeIn();
+ }
+
+ $preview.children('div').first().html(data.returnValues.message);
+ }
+});
+
+/**
+ * Loads recent activity events once the user scrolls to the very bottom.
+ *
+ * @param integer userID
+ */
+WCF.User.RecentActivityLoader = Class.extend({
+ /**
+ * container object
+ * @var jQuery
+ */
+ _container: null,
+
+ /**
+ * true if list should be filtered by followed users
+ * @var boolean
+ */
+ _filteredByFollowedUsers: false,
+
+ /**
+ * button to load next events
+ * @var jQuery
+ */
+ _loadButton: null,
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * user id
+ * @var integer
+ */
+ _userID: 0,
+
+ /**
+ * Initializes a new RecentActivityLoader object.
+ *
+ * @param integer userID
+ * @param boolean filteredByFollowedUsers
+ */
+ init: function(userID, filteredByFollowedUsers) {
+ this._container = $('#recentActivities');
+ this._filteredByFollowedUsers = (filteredByFollowedUsers === true);
+ this._userID = userID;
+
+ if (this._userID !== null && !this._userID) {
+ console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
+ return;
+ }
+
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ this._loadButton = $('<li class="recentActivitiesMore"><button class="small">' + WCF.Language.get('wcf.user.recentActivity.more') + '</button></li>').appendTo(this._container);
+ this._loadButton = this._loadButton.children('button').click($.proxy(this._click, this));
+ },
+
+ /**
+ * Loads next activity events.
+ */
+ _click: function() {
+ this._loadButton.enable();
+
+ var $parameters = {
+ lastEventTime: this._container.data('lastEventTime')
+ };
+ if (this._userID) {
+ $parameters.userID = this._userID;
+ }
+ else if (this._filteredByFollowedUsers) {
+ $parameters.filteredByFollowedUsers = 1;
+ }
+
+ this._proxy.setOption('data', {
+ actionName: 'load',
+ className: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction',
+ parameters: $parameters
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ if (data.returnValues.template) {
+ $(data.returnValues.template).insertBefore(this._loadButton.parent());
+
+ this._container.data('lastEventTime', data.returnValues.lastEventTime);
+ this._loadButton.enable();
+ }
+ else {
+ $('<small>' + WCF.Language.get('wcf.user.recentActivity.noMoreEntries') + '</small>').appendTo(this._loadButton.parent());
+ this._loadButton.remove();
+ }
+ }
+});
+
+/**
+ * Loads user profile previews.
+ *
+ * @see WCF.Popover
+ */
+WCF.User.ProfilePreview = WCF.Popover.extend({
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * list of user profiles
+ * @var object
+ */
+ _userProfiles: { },
+
+ /**
+ * @see WCF.Popover.init()
+ */
+ init: function() {
+ this._super('.userLink');
+
+ this._proxy = new WCF.Action.Proxy({
+ showLoadingOverlay: false
+ });
+ },
+
+ /**
+ * @see WCF.Popover._loadContent()
+ */
+ _loadContent: function() {
+ var $element = $('#' + this._activeElementID);
+ var $userID = $element.data('userID');
+
+ if (this._userProfiles[$userID]) {
+ // use cached user profile
+ this._insertContent(this._activeElementID, this._userProfiles[$userID], true);
+ }
+ else {
+ this._proxy.setOption('data', {
+ actionName: 'getUserProfile',
+ className: 'wcf\\data\\user\\UserProfileAction',
+ objectIDs: [ $userID ]
+ });
+
+ var $elementID = this._activeElementID;
+ var self = this;
+ this._proxy.setOption('success', function(data, textStatus, jqXHR) {
+ // cache user profile
+ self._userProfiles[$userID] = data.returnValues.template;
+
+ // show user profile
+ self._insertContent($elementID, data.returnValues.template, true);
+ });
+ this._proxy.sendRequest();
+ }
+ }
+});
+
+/**
+ * Initalizes WCF.User.Action namespace.
+ */
+WCF.User.Action = {};
+
+/**
+ * Handles user follow and unfollow links.
+ */
+WCF.User.Action.Follow = Class.extend({
+ /**
+ * list with elements containing follow and unfollow buttons
+ * @var array
+ */
+ _containerList: null,
+
+ /**
+ * CSS selector for follow buttons
+ * @var string
+ */
+ _followButtonSelector: '.jsFollowButton',
+
+ /**
+ * id of the user that is currently being followed/unfollowed
+ * @var integer
+ */
+ _userID: 0,
+
+ /**
+ * Initializes new WCF.User.Action.Follow object.
+ *
+ * @param array containerList
+ * @param string followButtonSelector
+ */
+ init: function(containerList, followButtonSelector) {
+ if (!containerList.length) {
+ return;
+ }
+ this._containerList = containerList;
+
+ if (followButtonSelector) {
+ this._followButtonSelector = followButtonSelector;
+ }
+
+ // initialize proxy
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ // bind event listeners
+ this._containerList.each($.proxy(function(index, container) {
+ $(container).find(this._followButtonSelector).click($.proxy(this._click, this));
+ }, this));
+ },
+
+ /**
+ * Handles a click on a follow or unfollow button.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var link = $(event.target);
+ if (!link.is('a')) {
+ link = link.closest('a');
+ }
+ this._userID = link.data('objectID');
+
+ this._proxy.setOption('data', {
+ 'actionName': link.data('following') ? 'unfollow' : 'follow',
+ 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
+ 'parameters': {
+ data: {
+ userID: this._userID
+ }
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles the successful (un)following of a user.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ this._containerList.each($.proxy(function(index, container) {
+ var button = $(container).find(this._followButtonSelector).get(0);
+
+ if (button && $(button).data('objectID') == this._userID) {
+ button = $(button);
+
+ // toogle icon title
+ if (data.returnValues.following) {
+ button.data('tooltip', WCF.Language.get('wcf.user.button.unfollow')).children('.icon').removeClass('icon-plus').addClass('icon-minus');
+ }
+ else {
+ button.data('tooltip', WCF.Language.get('wcf.user.button.follow')).children('.icon').removeClass('icon-minus').addClass('icon-plus');
+ }
+
+ button.data('following', data.returnValues.following);
+
+ return false;
+ }
+ }, this));
+
+ var $notification = new WCF.System.Notification();
+ $notification.show();
+ }
+});
+
+/**
+ * Handles user ignore and unignore links.
+ */
+WCF.User.Action.Ignore = Class.extend({
+ /**
+ * list with elements containing ignore and unignore buttons
+ * @var array
+ */
+ _containerList: null,
+
+ /**
+ * CSS selector for ignore buttons
+ * @var string
+ */
+ _ignoreButtonSelector: '.jsIgnoreButton',
+
+ /**
+ * id of the user that is currently being ignored/unignored
+ * @var integer
+ */
+ _userID: 0,
+
+ /**
+ * Initializes new WCF.User.Action.Ignore object.
+ *
+ * @param array containerList
+ * @param string ignoreButtonSelector
+ */
+ init: function(containerList, ignoreButtonSelector) {
+ if (!containerList.length) {
+ return;
+ }
+ this._containerList = containerList;
+
+ if (ignoreButtonSelector) {
+ this._ignoreButtonSelector = ignoreButtonSelector;
+ }
+
+ // initialize proxy
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ // bind event listeners
+ this._containerList.each($.proxy(function(index, container) {
+ $(container).find(this._ignoreButtonSelector).click($.proxy(this._click, this));
+ }, this));
+ },
+
+ /**
+ * Handles a click on a ignore or unignore button.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var link = $(event.target);
+ if (!link.is('a')) {
+ link = link.closest('a');
+ }
+ this._userID = link.data('objectID');
+
+ this._proxy.setOption('data', {
+ 'actionName': link.data('ignored') ? 'unignore' : 'ignore',
+ 'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
+ 'parameters': {
+ data: {
+ ignoreUserID: this._userID
+ }
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles the successful (un)ignoring of a user.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ this._containerList.each($.proxy(function(index, container) {
+ var button = $(container).find(this._ignoreButtonSelector).get(0);
+
+ if (button && $(button).data('objectID') == this._userID) {
+ button = $(button);
+
+ // toogle icon title
+ if (data.returnValues.isIgnoredUser) {
+ button.data('tooltip', WCF.Language.get('wcf.user.button.unignore')).children('.icon').removeClass('icon-off').addClass('icon-circle-blank');
+ }
+ else {
+ button.data('tooltip', WCF.Language.get('wcf.user.button.ignore')).children('.icon').removeClass('icon-circle-blank').addClass('icon-off');
+ }
+
+ button.data('ignored', data.returnValues.isIgnoredUser);
+
+ return false;
+ }
+ }, this));
+
+ var $notification = new WCF.System.Notification();
+ $notification.show();
+ }
+});
+
+/**
+ * Namespace for avatar functions.
+ */
+WCF.User.Avatar = {};
+
+/**
+ * Handles cropping an avatar.
+ */
+WCF.User.Avatar.Crop = Class.extend({
+ /**
+ * current crop setting in x-direction
+ * @var integer
+ */
+ _cropX: 0,
+
+ /**
+ * current crop setting in y-direction
+ * @var integer
+ */
+ _cropY: 0,
+
+ /**
+ * avatar crop dialog
+ * @var jQuery
+ */
+ _dialog: null,
+
+ /**
+ * action proxy to send the crop AJAX requests
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * maximum size of thumbnails
+ * @var integer
+ */
+ MAX_THUMBNAIL_SIZE: 128,
+
+ /**
+ * Creates a new instance of WCF.User.Avatar.Crop.
+ *
+ * @param integer avatarID
+ */
+ init: function(avatarID) {
+ this._avatarID = avatarID;
+
+ if (this._dialog) {
+ this.destroy();
+ }
+ this._dialog = null;
+
+ // check if object already had been initialized
+ if (!this._proxy) {
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+ }
+
+ $('.userAvatarCrop').click($.proxy(this._showCropDialog, this));
+ },
+
+ /**
+ * Destroys the avatar crop interface.
+ */
+ destroy: function() {
+ this._dialog.remove();
+ },
+
+ /**
+ * Sends AJAX request to crop avatar.
+ *
+ * @param object event
+ */
+ _crop: function(event) {
+ this._proxy.setOption('data', {
+ actionName: 'cropAvatar',
+ className: 'wcf\\data\\user\\avatar\\UserAvatarAction',
+ objectIDs: [ this._avatarID ],
+ parameters: {
+ cropX: this._cropX,
+ cropY: this._cropY
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Initializes the dialog after a successful 'getCropDialog' request.
+ *
+ * @param object data
+ */
+ _getCropDialog: function(data) {
+ if (!this._dialog) {
+ this._dialog = $('<div />').hide().appendTo(document.body);
+ this._dialog.wcfDialog({
+ title: WCF.Language.get('wcf.user.avatar.type.custom.crop')
+ });
+ }
+
+ this._dialog.html(data.returnValues.template);
+ this._dialog.find('button[data-type="save"]').click($.proxy(this._crop, this));
+
+ this._cropX = data.returnValues.cropX;
+ this._cropY = data.returnValues.cropY;
+
+ var $image = $('#userAvatarCropSelection > img');
+ $('#userAvatarCropSelection').css({
+ height: $image.height() + 'px',
+ width: $image.width() + 'px'
+ });
+ $('#userAvatarCropOverlaySelection').css({
+ 'background-image': 'url(' + $image.attr('src') + ')',
+ 'background-position': -this._cropX + 'px ' + -this._cropY + 'px',
+ 'left': this._cropX + 'px',
+ 'top': this._cropY + 'px'
+ }).draggable({
+ containment: 'parent',
+ drag : $.proxy(this._updateSelection, this),
+ stop : $.proxy(this._updateSelection, this)
+ });
+
+ this._dialog.find('button[data-type="save"]').click($.proxy(this._save, this));
+
+ this._dialog.wcfDialog('render');
+ },
+
+ /**
+ * Shows the cropping dialog.
+ */
+ _showCropDialog: function() {
+ if (!this._dialog) {
+ this._proxy.setOption('data', {
+ actionName: 'getCropDialog',
+ className: 'wcf\\data\\user\\avatar\\UserAvatarAction',
+ objectIDs: [ this._avatarID ]
+ });
+ this._proxy.sendRequest();
+ }
+ else {
+ this._dialog.wcfDialog('open');
+ }
+ },
+
+ /**
+ * Handles successful AJAX request.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ switch (data.actionName) {
+ case 'getCropDialog':
+ this._getCropDialog(data);
+ break;
+
+ case 'cropAvatar':
+ WCF.DOMNodeInsertedHandler.enable();
+ $('#avatarUpload > dt > img').replaceWith($('<img src="' + data.returnValues.url + '" alt="" class="userAvatarCrop jsTooltip" title="' + WCF.Language.get('wcf.user.avatar.type.custom.crop') + '" />').css({
+ width: '96px',
+ height: '96px'
+ }).click($.proxy(this._showCropDialog, this)));
+ WCF.DOMNodeInsertedHandler.disable();
+
+ this._dialog.wcfDialog('close');
+
+ var $notification = new WCF.System.Notification();
+ $notification.show();
+ break;
+ }
+ },
+
+ /**
+ * Updates the current crop selection if the selection overlay is dragged.
+ *
+ * @param object event
+ * @param object ui
+ */
+ _updateSelection: function(event, ui) {
+ this._cropX = ui.position.left;
+ this._cropY = ui.position.top;
+
+ $('#userAvatarCropOverlaySelection').css({
+ 'background-position': -ui.position.left + 'px ' + -ui.position.top + 'px'
+ });
+ }
+});
+
+/**
+ * Avatar upload function
+ *
+ * @see WCF.Upload
+ */
+WCF.User.Avatar.Upload = WCF.Upload.extend({
+ /**
+ * handles cropping the avatar
+ * @var WCF.User.Avatar.Crop
+ */
+ _avatarCrop: null,
+
+ /**
+ * user id of avatar owner
+ * @var integer
+ */
+ _userID: 0,
+
+ /**
+ * Initalizes a new WCF.User.Avatar.Upload object.
+ *
+ * @param integer userID
+ * @param WCF.User.Avatar.Crop avatarCrop
+ */
+ init: function(userID, avatarCrop) {
+ this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
+ this._userID = userID || 0;
+ this._avatarCrop = avatarCrop;
+
+ $('#avatarForm input[type=radio]').change(function() {
+ if ($(this).val() == 'custom') {
+ $('#avatarUpload > dd > div').show();
+ }
+ else {
+ $('#avatarUpload > dd > div').hide();
+ }
+ });
+ if (!$('#avatarForm input[type=radio][value=custom]:checked').length) {
+ $('#avatarUpload > dd > div').hide();
+ }
+ },
+
+ /**
+ * @see WCF.Upload._initFile()
+ */
+ _initFile: function(file) {
+ return $('#avatarUpload > dt > img');
+ },
+
+ /**
+ * @see WCF.Upload._success()
+ */
+ _success: function(uploadID, data) {
+ if (data.returnValues.url) {
+ this._updateImage(data.returnValues.url, data.returnValues.canCrop);
+
+ if (data.returnValues.canCrop) {
+ if (!this._avatarCrop) {
+ this._avatarCrop = new WCF.User.Avatar.Crop(data.returnValues.avatarID);
+ }
+ else {
+ this._avatarCrop.init(data.returnValues.avatarID);
+ }
+ }
+ else if (this._avatarCrop) {
+ this._avatarCrop.destroy();
+ this._avatarCrop = null;
+ }
+
+ // hide error
+ $('#avatarUpload > dd > .innerError').remove();
+
+ // show success message
+ var $notification = new WCF.System.Notification(WCF.Language.get('wcf.user.avatar.upload.success'));
+ $notification.show();
+ }
+ else if (data.returnValues.errorType) {
+ // show error
+ this._getInnerErrorElement().text(WCF.Language.get('wcf.user.avatar.upload.error.' + data.returnValues.errorType));
+ }
+ },
+
+ /**
+ * Updates the displayed avatar image.
+ *
+ * @param string url
+ * @param boolean canCrop
+ */
+ _updateImage: function(url, canCrop) {
+ WCF.DOMNodeInsertedHandler.enable();
+ $('#avatarUpload > dt > img').remove();
+ var $image = $('<img src="' + url + '" alt="" />').css({
+ 'height': 'auto',
+ 'max-height': '96px',
+ 'max-width': '96px',
+ 'width': 'auto'
+ });
+ if (canCrop) {
+ $image.addClass('userAvatarCrop').addClass('jsTooltip');
+ $image.attr('title', WCF.Language.get('wcf.user.avatar.type.custom.crop'));
+ }
+
+ $('#avatarUpload > dt').prepend($image);
+ WCF.DOMNodeInsertedHandler.disable();
+ },
+
+ /**
+ * Returns the inner error element.
+ *
+ * @return jQuery
+ */
+ _getInnerErrorElement: function() {
+ var $span = $('#avatarUpload > dd > .innerError');
+ if (!$span.length) {
+ $span = $('<small class="innerError"></span>');
+ $('#avatarUpload > dd').append($span);
+ }
+
+ return $span;
+ },
+
+ /**
+ * @see WCF.Upload._getParameters()
+ */
+ _getParameters: function() {
+ return {
+ userID: this._userID
+ };
+ },
+});
+
+/**
+ * Generic implementation for grouped user lists.
+ *
+ * @param string className
+ * @param string dialogTitle
+ * @param object additionalParameters
+ */
+WCF.User.List = Class.extend({
+ /**
+ * list of additional parameters
+ * @var object
+ */
+ _additionalParameters: { },
+
+ /**
+ * list of cached pages
+ * @var object
+ */
+ _cache: { },
+
+ /**
+ * action class name
+ * @var string
+ */
+ _className: '',
+
+ /**
+ * dialog overlay
+ * @var jQuery
+ */
+ _dialog: null,
+
+ /**
+ * dialog title
+ * @var string
+ */
+ _dialogTitle: '',
+
+ /**
+ * page count
+ * @var integer
+ */
+ _pageCount: 0,
+
+ /**
+ * current page no
+ * @var integer
+ */
+ _pageNo: 1,
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * Initializes a new grouped user list.
+ *
+ * @param string className
+ * @param string dialogTitle
+ * @param object additionalParameters
+ */
+ init: function(className, dialogTitle, additionalParameters) {
+ this._additionalParameters = additionalParameters || { };
+ this._cache = { };
+ this._className = className;
+ this._dialog = null;
+ this._dialogTitle = dialogTitle;
+ this._pageCount = 0;
+ this._pageNo = 1;
+
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+ },
+
+ /**
+ * Opens the dialog overlay.
+ */
+ open: function() {
+ this._pageNo = 1;
+ this._showPage();
+ },
+
+ /**
+ * Displays the specified page.
+ *
+ * @param object event
+ * @param object data
+ */
+ _showPage: function(event, data) {
+ if (data && data.activePage) {
+ this._pageNo = data.activePage;
+ }
+
+ if (this._pageCount != 0 && (this._pageNo < 1 || this._pageNo > this._pageCount)) {
+ console.debug("[WCF.User.List] Cannot access page " + this._pageNo + " of " + this._pageCount);
+ return;
+ }
+
+ if (this._cache[this._pageNo]) {
+ var $dialogCreated = false;
+ if (this._dialog === null) {
+ this._dialog = $('<div id="userList' + this._className.hashCode() + '" style="min-width: 600px;" />').hide().appendTo(document.body);
+ $dialogCreated = true;
+ }
+
+ // remove current view
+ this._dialog.empty();
+
+ // insert HTML
+ this._dialog.html(this._cache[this._pageNo]);
+
+ // add pagination
+ if (this._pageCount > 1) {
+ this._dialog.find('.jsPagination').wcfPages({
+ activePage: this._pageNo,
+ maxPage: this._pageCount
+ }).bind('wcfpagesswitched', $.proxy(this._showPage, this));
+ }
+
+ // show dialog
+ if ($dialogCreated) {
+ this._dialog.wcfDialog({
+ title: this._dialogTitle
+ });
+ }
+ else {
+ this._dialog.wcfDialog('open').wcfDialog('render');
+ }
+ }
+ else {
+ this._additionalParameters.pageNo = this._pageNo;
+
+ // load template via AJAX
+ this._proxy.setOption('data', {
+ actionName: 'getGroupedUserList',
+ className: this._className,
+ interfaceName: 'wcf\\data\\IGroupedUserListAction',
+ parameters: this._additionalParameters
+ });
+ this._proxy.sendRequest();
+ }
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ if (data.returnValues.pageCount) {
+ this._pageCount = data.returnValues.pageCount;
+ }
+
+ this._cache[this._pageNo] = data.returnValues.template;
+ this._showPage();
+ }
+});
+
+/**
+ * Namespace for object watch functions.
+ */
+WCF.User.ObjectWatch = {};
+
+/**
+ * Handles subscribe/unsubscribe links.
+ */
+WCF.User.ObjectWatch.Subscribe = Class.extend({
+ /**
+ * CSS selector for subscribe buttons
+ * @var string
+ */
+ _buttonSelector: '.jsSubscribeButton',
+
+ /**
+ * list of buttons
+ * @var object
+ */
+ _buttons: { },
+
+ /**
+ * dialog overlay
+ * @var object
+ */
+ _dialog: null,
+
+ /**
+ * system notification
+ * @var WCF.System.Notification
+ */
+ _notification: null,
+
+ /**
+ * WCF.User.ObjectWatch.Subscribe object.
+ */
+ init: function() {
+ this._buttons = { };
+ this._notification = null;
+
+ // initialize proxy
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ // bind event listeners
+ $(this._buttonSelector).each($.proxy(function(index, button) {
+ var $button = $(button);
+ var $objectID = $button.data('objectID');
+ this._buttons[$objectID] = $button.click($.proxy(this._click, this));
+ }, this));
+ },
+
+ /**
+ * Handles a click on a subscribe button.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var $button = $(event.currentTarget);
+
+ this._proxy.setOption('data', {
+ actionName: 'manageSubscription',
+ className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
+ parameters: {
+ objectID: $button.data('objectID'),
+ objectType: $button.data('objectType')
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ if (data.actionName === 'manageSubscription') {
+ if (this._dialog === null) {
+ this._dialog = $('<div>' + data.returnValues.template + '</div>').hide().appendTo(document.body);
+ this._dialog.wcfDialog({
+ title: WCF.Language.get('wcf.user.objectWatch.manageSubscription')
+ });
+ }
+ else {
+ this._dialog.html(data.returnValues.template);
+ this._dialog.wcfDialog('open');
+ }
+
+ // bind event listener
+ this._dialog.find('.formSubmit > .jsButtonSave').data('objectID', data.returnValues.objectID).click($.proxy(this._save, this));
+ var $enableNotification = this._dialog.find('input[name=enableNotification]').disable();
+
+ // toggle subscription
+ this._dialog.find('input[name=subscribe]').change(function(event) {
+ var $input = $(event.currentTarget);
+ if ($input.val() == 1) {
+ $enableNotification.enable();
+ }
+ else {
+ $enableNotification.disable();
+ }
+ });
+
+ // setup
+ var $selectedOption = this._dialog.find('input[name=subscribe]:checked');
+ if ($selectedOption.length && $selectedOption.val() == 1) {
+ $enableNotification.enable();
+ }
+ }
+ else if (data.actionName === 'saveSubscription' && this._dialog.is(':visible')) {
+ this._dialog.wcfDialog('close');
+
+ if (this._notification === null) {
+ this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
+ }
+
+ this._notification.show();
+ }
+ },
+
+ /**
+ * Saves the subscription.
+ *
+ * @param object event
+ */
+ _save: function(event) {
+ var $button = this._buttons[$(event.currentTarget).data('objectID')];
+ var $subscribe = this._dialog.find('input[name=subscribe]:checked').val();
+ var $enableNotification = (this._dialog.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
+
+ this._proxy.setOption('data', {
+ actionName: 'saveSubscription',
+ className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
+ parameters: {
+ enableNotification: $enableNotification,
+ objectID: $button.data('objectID'),
+ objectType: $button.data('objectType'),
+ subscribe: $subscribe
+ }
+ });
+ this._proxy.sendRequest();
+ }
+});
+
+/**
+ * Enables notifications for subscriptions.
+ */
+WCF.User.ObjectWatch.Notification = Class.extend({
+ /**
+ * CSS selector for buttons
+ * @var string
+ */
+ _buttonSelector: '.jsObjectWatchNotificationButton',
+
+ /**
+ * watch id
+ * @var integer
+ */
+ _watchID: 0,
+
+ /**
+ * WCF.User.ObjectWatch.Notification object.
+ */
+ init: function() {
+ // initialize proxy
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ // bind event listeners
+ $(this._buttonSelector).click($.proxy(this._click, this));
+ },
+
+ /**
+ * Handles a click on a button.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var link = $(event.target);
+ if (!link.is('a')) {
+ link = link.closest('a');
+ }
+ this._watchID = link.data('watchID');
+
+ this._proxy.setOption('data', {
+ actionName: link.data('notification') ? 'disableNotification' : 'enableNotification',
+ className: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
+ objectIDs: [ this._watchID ]
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles the successful action.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ $(this._buttonSelector).each($.proxy(function(index, container) {
+ var button = $(container);
+
+ if (button.data('watchID') == this._watchID) {
+ // toogle icon title
+ if (button.data('notification')) {
+ button.children('img').removeClass('disabled');
+ button.data('tooltip', WCF.Language.get('wcf.user.watchedObjects.enableNotification'));
+ }
+ else {
+ button.children('img').addClass('disabled');
+ button.data('tooltip', WCF.Language.get('wcf.user.watchedObjects.disableNotification'));
+ }
+
+ button.data('notification', !button.data('notification'));
+
+ return false;
+ }
+ }, this));
+ }
+});
--- /dev/null
+WCF.User.Login=Class.extend({_loginSubmitButton:null,_password:null,_passwordContainer:null,_useCookies:null,_useCookiesContainer:null,init:function(b){this._loginSubmitButton=$("#loginSubmitButton");this._password=$("#password"),this._passwordContainer=this._password.parents("dl");this._useCookies=$("#useCookies");this._useCookiesContainer=this._useCookies.parents("dl");var a=$("#loginForm");a.find("input[name=action]").change($.proxy(this._change,this));if(b){WCF.User.QuickLogin.init()}},_change:function(a){if($(a.currentTarget).val()==="register"){this._setState(false,WCF.Language.get("wcf.user.button.register"))}else{this._setState(true,WCF.Language.get("wcf.user.button.login"))}},_setState:function(b,a){if(b){this._password.enable();this._passwordContainer.removeClass("disabled");this._useCookies.enable();this._useCookiesContainer.removeClass("disabled")}else{this._password.disable();this._passwordContainer.addClass("disabled");this._useCookies.disable();this._useCookiesContainer.addClass("disabled")}this._loginSubmitButton.val(a)}});WCF.User.QuickLogin={_dialog:null,_loginMessage:null,init:function(){$(".loginLink").click($.proxy(this._render,this))},show:function(a){if(a){if(this._loginMessage===null){this._loginMessage=$('<p class="info" />').hide().prependTo($("#loginForm > form"))}this._loginMessage.show().text(a)}else{if(this._loginMessage!==null){this._loginMessage.hide()}}this._render()},_render:function(a){if(a!==undefined){a.preventDefault()}if(this._dialog===null){this._dialog=$("#loginForm").wcfDialog({title:WCF.Language.get("wcf.user.login")});this._dialog.find("#username").focus()}else{this._dialog.wcfDialog("open")}}};WCF.User.Profile={};WCF.User.Profile.ActivityPointList={_cache:{},_dialog:null,_didInit:false,_proxy:null,init:function(){if(this._didInit){return}this._cache={};this._dialog=null;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._init();WCF.DOMNodeInsertedHandler.addCallback("WCF.User.Profile.ActivityPointList",$.proxy(this._init,this));this._didInit=true},_init:function(){$(".activityPointsDisplay").removeClass("activityPointsDisplay").click($.proxy(this._click,this))},_click:function(b){var a=$(b.currentTarget).data("userID");if(this._cache[a]===undefined){this._proxy.setOption("data",{actionName:"getDetailedActivityPointList",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[a]});this._proxy.sendRequest()}else{this._show(a)}},_show:function(a){if(this._dialog===null){this._dialog=$("<div>"+this._cache[a]+"</div>").hide().appendTo(document.body);this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.activityPoint")})}else{this._dialog.html(this._cache[a]);this._dialog.wcfDialog("open")}},_success:function(b,c,a){this._cache[b.returnValues.userID]=b.returnValues.template;this._show(b.returnValues.userID)}};WCF.User.Profile.Follow=Class.extend({_button:null,_following:false,_proxy:null,_userID:0,init:function(a,b){this._following=b;this._userID=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._createButton();this._showButton()},_createButton:function(){this._button=$('<li id="followUser"><a class="button jsTooltip" title="'+WCF.Language.get("wcf.user.button."+(this._following?"un":"")+"follow")+'"><span class="icon icon16 icon-plus"></span> <span class="invisible">'+WCF.Language.get("wcf.user.button."+(this._following?"un":"")+"follow")+"</span></a></li>").prependTo($("#profileButtonContainer"));this._button.click($.proxy(this._execute,this))},_execute:function(){var a=(this._following)?"unfollow":"follow";this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\user\\follow\\UserFollowAction",parameters:{data:{userID:this._userID}}});this._proxy.sendRequest()},_showButton:function(){if(this._following){this._button.find(".button").data("tooltip",WCF.Language.get("wcf.user.button.unfollow")).addClass("active").children(".icon").removeClass("icon-plus").addClass("icon-minus")}else{this._button.find(".button").data("tooltip",WCF.Language.get("wcf.user.button.follow")).removeClass("active").children(".icon").removeClass("icon-minus").addClass("icon-plus")}},_success:function(b,d,a){this._following=b.returnValues.following;this._showButton();var c=new WCF.System.Notification();c.show()}});WCF.User.Profile.IgnoreUser=Class.extend({_button:null,_isIgnoredUser:false,_proxy:null,_userID:0,init:function(b,a){this._userID=b;this._isIgnoredUser=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._updateButton();this._button.click($.proxy(this._click,this))},_click:function(){var a=(this._isIgnoredUser)?"unignore":"ignore";this._proxy.setOption("data",{actionName:a,className:"wcf\\data\\user\\ignore\\UserIgnoreAction",parameters:{data:{ignoreUserID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._isIgnoredUser=b.returnValues.isIgnoredUser;this._updateButton();var c=new WCF.System.Notification();c.show()},_updateButton:function(){if(this._button===null){this._button=$('<li id="ignoreUser"><a class="button jsTooltip" title="'+WCF.Language.get("wcf.user.button."+(this._following?"un":"")+"ignore")+'"><span class="icon icon16 icon-off"></span> <span class="invisible">'+WCF.Language.get("wcf.user.button."+(this._following?"un":"")+"ignore")+"</span></a></li>").prependTo($("#profileButtonContainer"))}this._button.find(".button").data("tooltip",WCF.Language.get("wcf.user.button."+(this._isIgnoredUser?"un":"")+"ignore"));if(this._isIgnoredUser){this._button.find(".button").addClass("active").children(".icon").removeClass("icon-off").addClass("icon-circle-blank")}else{this._button.find(".button").removeClass("active").children(".icon").removeClass("icon-circle-blank").addClass("icon-off")}}});WCF.User.Profile.TabMenu=Class.extend({_hasContent:{},_profileContent:null,_proxy:null,_userID:0,init:function(a){this._profileContent=$("#profileContent");this._userID=a;var c=this._profileContent.data("active");var b=false;this._profileContent.find("div.tabMenuContent").each($.proxy(function(e,d){var f=$(d).wcfIdentify();if(c===f){this._hasContent[f]=true}else{this._hasContent[f]=false;b=true}},this));if(b){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._profileContent.bind("wcftabsbeforeactivate",$.proxy(this._loadContent,this))}},_loadContent:function(b,c){var d=$(c.newPanel);var a=d.attr("id");if(!this._hasContent[a]){this._proxy.setOption("data",{actionName:"getContent",className:"wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction",parameters:{data:{containerID:a,menuItem:d.data("menuItem"),userID:this._userID}}});this._proxy.sendRequest()}},_success:function(d,e,c){var b=d.returnValues.containerID;this._hasContent[b]=true;var a=this._profileContent.find("#"+b);$("<div>"+d.returnValues.template+"</div>").hide().appendTo(a);a.children("div").wcfBlindIn()}});WCF.User.Profile.Editor=Class.extend({_actionName:"",_buttons:{},_cachedTemplate:"",_proxy:null,_tab:null,_userID:0,init:function(a,b){this._actionName="";this._cachedTemplate="";this._tab=$("#about");this._userID=a;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._initButtons();if(b){this._beginEdit()}},_initButtons:function(){var a=$("#profileButtonContainer");this._buttons={beginEdit:$('<li><a class="button"><span class="icon icon16 icon-pencil" /> <span>'+WCF.Language.get("wcf.user.editProfile")+"</span></a></li>").click($.proxy(this._beginEdit,this)).appendTo(a)}},_beginEdit:function(){this._actionName="beginEdit";this._buttons.beginEdit.hide();$("#profileContent").wcfTabs("select","about");this._proxy.setOption("data",{actionName:"beginEdit",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID]});this._proxy.sendRequest()},_save:function(){this._actionName="save";var b=/values\[([a-zA-Z0-9._-]+)\]/;var a={};this._tab.find("input, textarea, select").each(function(e,f){var d=$(f);if(d.getTagName()==="input"){var c=d.attr("type");if((c==="radio"||c==="checkbox")&&!d.prop("checked")){return}}var g=d.attr("name");if(b.test(g)){a[RegExp.$1]=d.val()}});this._proxy.setOption("data",{actionName:"save",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[this._userID],parameters:{values:a}});this._proxy.sendRequest()},_restore:function(){this._actionName="restore";this._buttons.beginEdit.show();this._destroyCKEditor();this._tab.html(this._cachedTemplate).children().css({height:"auto"})},_success:function(b,c,a){switch(this._actionName){case"beginEdit":this._prepareEdit(b);break;case"save":if(b.returnValues.success){this._cachedTemplate=b.returnValues.template;this._restore()}else{this._prepareEdit(b,true)}break}},_prepareEdit:function(b,c){this._destroyCKEditor();var a=this;this._tab.html(function(e,d){if(c!==true){a._cachedTemplate=d}return b.returnValues.template});this._tab.find("input[type=text]").attr("autocomplete","off");this._tab.find(".formSubmit > button[data-type=save]").click($.proxy(this._save,this));this._tab.find(".formSubmit > button[data-type=restore]").click($.proxy(this._restore,this));this._tab.find("input").keyup(function(d){if(d.which===13){a._save();d.preventDefault();return false}})},_destroyCKEditor:function(){this._tab.find("textarea + .cke").each(function(c,b){var a=$(b).attr("id").replace(/cke_/,"");if(CKEDITOR.instances[a]){CKEDITOR.instances[a].destroy()}})}});WCF.User.Registration={};WCF.User.Registration.Validation=Class.extend({_actionName:"",_className:"",_confirmElement:null,_element:null,_errorMessages:{},_options:{},_proxy:null,init:function(b,c,a){this._element=b;this._element.blur($.proxy(this._blur,this));this._confirmElement=c||null;if(this._confirmElement!==null){this._confirmElement.blur($.proxy(this._blurConfirm,this))}a=a||{};this._setOptions(a);this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this),showLoadingOverlay:false});this._setErrorMessages()},_setOptions:function(a){},_setErrorMessages:function(){this._errorMessages={ajaxError:"",notEqual:""}},_blur:function(b){var a=this._element.val();if(!a){return this._showError(this._element,WCF.Language.get("wcf.global.form.error.empty"))}if(this._confirmElement!==null){var c=this._confirmElement.val();if(c!=""&&a!=c){return this._showError(this._confirmElement,this._errorMessages.notEqual)}}if(!this._validateOptions()){return}this._proxy.setOption("data",{actionName:this._actionName,className:this._className,parameters:this._getParameters()});this._proxy.sendRequest()},_getParameters:function(){return{}},_validateOptions:function(){return true},_blurConfirm:function(b){var a=this._confirmElement.val();if(!a){return this._showError(this._confirmElement,WCF.Language.get("wcf.global.form.error.empty"))}this._blur(b)},_success:function(b,c,a){if(b.returnValues.isValid){this._showSuccess(this._element);if(this._confirmElement!==null&&this._confirmElement.val()){this._showSuccess(this._confirmElement)}}else{this._showError(this._element,WCF.Language.get(this._errorMessages.ajaxError+b.returnValues.error))}},_showError:function(a,b){a.parent().parent().addClass("formError").removeClass("formSuccess");var c=a.parent().find("small.innerError");if(!c.length){c=$("<small />").addClass("innerError").insertAfter(a)}c.text(b)},_showSuccess:function(a){a.parent().parent().addClass("formSuccess").removeClass("formError");a.next("small.innerError").remove()}});WCF.User.Registration.Validation.Username=WCF.User.Registration.Validation.extend({_actionName:"validateUsername",_className:"wcf\\data\\user\\UserRegistrationAction",_setOptions:function(a){this._options=$.extend(true,{minlength:3,maxlength:25},a)},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.username.error."}},_validateOptions:function(){var a=this._element.val();if(a.length<this._options.minlength||a.length>this._options.maxlength){this._showError(this._element,WCF.Language.get("wcf.user.username.error.notValid"));return false}return true},_getParameters:function(){return{username:this._element.val()}}});WCF.User.Registration.Validation.EmailAddress=WCF.User.Registration.Validation.extend({_actionName:"validateEmailAddress",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{email:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.email.error.",notEqual:WCF.Language.get("wcf.user.confirmEmail.error.notEqual")}}});WCF.User.Registration.Validation.Password=WCF.User.Registration.Validation.extend({_actionName:"validatePassword",_className:"wcf\\data\\user\\UserRegistrationAction",_getParameters:function(){return{password:this._element.val()}},_setErrorMessages:function(){this._errorMessages={ajaxError:"wcf.user.password.error.",notEqual:WCF.Language.get("wcf.user.confirmPassword.error.notEqual")}}});WCF.User.Registration.LostPassword=Class.extend({_email:null,_username:null,init:function(){this._email=$("#emailInput");this._username=$("#usernameInput");this._email.keyup($.proxy(this._checkEmail,this));this._username.keyup($.proxy(this._checkUsername,this));this._checkEmail();this._checkUsername()},_checkEmail:function(){if(this._email.val()==""){this._username.enable();this._username.parents("dl:eq(0)").removeClass("disabled")}else{this._username.disable();this._username.parents("dl:eq(0)").addClass("disabled")}},_checkUsername:function(){if(this._username.val()==""){this._email.enable();this._email.parents("dl:eq(0)").removeClass("disabled")}else{this._email.disable();this._email.parents("dl:eq(0)").addClass("disabled")}}});WCF.Notification={};WCF.Notification.UserPanel=WCF.UserPanel.extend({_proxy:null,_showAllLink:"",init:function(a){this._noItems="wcf.user.notification.noMoreNotifications";this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._showAllLink=a;this._super("userNotifications");if(this._container.data("count")){document.title="("+this._container.data("count")+") "+document.title}},_addDefaultItems:function(a){this._addDivider(a);$('<li><a href="'+this._showAllLink+'">'+WCF.Language.get("wcf.user.notification.showAll")+"</a></li>").appendTo(a);this._addDivider(a);$('<li id="userNotificationsMarkAllAsConfirmed"><a>'+WCF.Language.get("wcf.user.notification.markAllAsConfirmed")+"</a></li>").click($.proxy(this._markAllAsConfirmed,this)).appendTo(a)},_getParameters:function(){return{actionName:"getOutstandingNotifications",className:"wcf\\data\\user\\notification\\UserNotificationAction"}},_after:function(a){this._container.find("li.jsNotificationItem").click($.proxy(this._markAsConfirmed,this))},_markAsConfirmed:function(a){this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",parameters:{notificationID:$(a.currentTarget).data("notificationID")}});this._proxy.sendRequest()},_markAllAsConfirmed:function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.user.notification.markAllAsConfirmed.confirmMessage"),$.proxy(function(a){if(a==="confirm"){this._proxy.setOption("data",{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"});this._proxy.sendRequest()}},this))},_success:function(b,c,a){switch(b.actionName){case"markAllAsConfirmed":$(".jsNotificationItem").remove();document.title=document.title.replace(/^\(([0-9]+)\) /,"");case"getOutstandingNotifications":if(!b.returnValues||!b.returnValues.template){$("#userNotificationsMarkAllAsConfirmed").prev(".dropdownDivider").remove();$("#userNotificationsMarkAllAsConfirmed").remove()}this._super(b,c,a);break;case"markAsConfirmed":this._container.find("li.jsNotificationItem").each(function(e,f){var d=$(f);if(b.returnValues.notificationID==d.data("notificationID")){window.location=d.data("link");return false}});break}}});WCF.Notification.List=Class.extend({_badge:null,_items:{},_proxy:null,init:function(){var a=$("li.jsNotificationItem");if(!a.length){return}a.each($.proxy(function(c,b){var d=$(b);this._items[d.data("notificationID")]=d;d.find(".jsMarkAsConfirmed").data("notificationID",d.data("notificationID")).click($.proxy(this._click,this));d.find("p").html(function(f,e){return"<a>"+e+"</a>"}).children("a").click($.proxy(this._click,this))},this));this._badge=$(".jsNotificationsBadge:eq(0)");this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(".contentNavigation .jsMarkAllAsConfirmed").click($.proxy(this._markAllAsConfirmed,this))},_click:function(a){var b=$(a.currentTarget).data("notificationID");this._proxy.setOption("data",{actionName:"markAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction",parameters:{notificationID:b}});this._proxy.sendRequest()},_markAllAsConfirmed:function(){WCF.System.Confirmation.show(WCF.Language.get("wcf.user.notification.markAllAsConfirmed.confirmMessage"),$.proxy(function(a){if(a==="confirm"){this._proxy.setOption("data",{actionName:"markAllAsConfirmed",className:"wcf\\data\\user\\notification\\UserNotificationAction"});this._proxy.sendRequest()}},this))},_success:function(b,c,a){switch(b.actionName){case"markAllAsConfirmed":window.location.reload();break;case"markAsConfirmed":this._items[b.returnValues.notificationID].remove();delete this._items[b.returnValues.notificationID];this._badge.html(b.returnValues.totalCount);document.title=document.title.replace(/^\(([0-9]+)\) /,"");if(b.returnValues.totalCount>0){document.title="("+b.returnValues.totalCount+") "+document.title}break}}});WCF.User.SignaturePreview=WCF.Message.Preview.extend({_handleResponse:function(b){var a=$("#previewContainer");if(!a.length){a=$('<fieldset id="previewContainer"><legend>'+WCF.Language.get("wcf.global.preview")+"</legend><div></div></fieldset>").insertBefore($("#signatureContainer")).wcfFadeIn()}a.children("div").first().html(b.returnValues.message)}});WCF.User.RecentActivityLoader=Class.extend({_container:null,_filteredByFollowedUsers:false,_loadButton:null,_proxy:null,_userID:0,init:function(b,a){this._container=$("#recentActivities");this._filteredByFollowedUsers=(a===true);this._userID=b;if(this._userID!==null&&!this._userID){console.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");return}this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._loadButton=$('<li class="recentActivitiesMore"><button class="small">'+WCF.Language.get("wcf.user.recentActivity.more")+"</button></li>").appendTo(this._container);this._loadButton=this._loadButton.children("button").click($.proxy(this._click,this))},_click:function(){this._loadButton.enable();var a={lastEventTime:this._container.data("lastEventTime")};if(this._userID){a.userID=this._userID}else{if(this._filteredByFollowedUsers){a.filteredByFollowedUsers=1}}this._proxy.setOption("data",{actionName:"load",className:"wcf\\data\\user\\activity\\event\\UserActivityEventAction",parameters:a});this._proxy.sendRequest()},_success:function(b,c,a){if(b.returnValues.template){$(b.returnValues.template).insertBefore(this._loadButton.parent());this._container.data("lastEventTime",b.returnValues.lastEventTime);this._loadButton.enable()}else{$("<small>"+WCF.Language.get("wcf.user.recentActivity.noMoreEntries")+"</small>").appendTo(this._loadButton.parent());this._loadButton.remove()}}});WCF.User.ProfilePreview=WCF.Popover.extend({_proxy:null,_userProfiles:{},init:function(){this._super(".userLink");this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false})},_loadContent:function(){var a=$("#"+this._activeElementID);var c=a.data("userID");if(this._userProfiles[c]){this._insertContent(this._activeElementID,this._userProfiles[c],true)}else{this._proxy.setOption("data",{actionName:"getUserProfile",className:"wcf\\data\\user\\UserProfileAction",objectIDs:[c]});var d=this._activeElementID;var b=this;this._proxy.setOption("success",function(f,g,e){b._userProfiles[c]=f.returnValues.template;b._insertContent(d,f.returnValues.template,true)});this._proxy.sendRequest()}}});WCF.User.Action={};WCF.User.Action.Follow=Class.extend({_containerList:null,_followButtonSelector:".jsFollowButton",_userID:0,init:function(b,a){if(!b.length){return}this._containerList=b;if(a){this._followButtonSelector=a}this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._containerList.each($.proxy(function(d,c){$(c).find(this._followButtonSelector).click($.proxy(this._click,this))},this))},_click:function(b){var a=$(b.target);if(!a.is("a")){a=a.closest("a")}this._userID=a.data("objectID");this._proxy.setOption("data",{actionName:a.data("following")?"unfollow":"follow",className:"wcf\\data\\user\\follow\\UserFollowAction",parameters:{data:{userID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._containerList.each($.proxy(function(f,e){var g=$(e).find(this._followButtonSelector).get(0);if(g&&$(g).data("objectID")==this._userID){g=$(g);if(b.returnValues.following){g.data("tooltip",WCF.Language.get("wcf.user.button.unfollow")).children(".icon").removeClass("icon-plus").addClass("icon-minus")}else{g.data("tooltip",WCF.Language.get("wcf.user.button.follow")).children(".icon").removeClass("icon-minus").addClass("icon-plus")}g.data("following",b.returnValues.following);return false}},this));var c=new WCF.System.Notification();c.show()}});WCF.User.Action.Ignore=Class.extend({_containerList:null,_ignoreButtonSelector:".jsIgnoreButton",_userID:0,init:function(a,b){if(!a.length){return}this._containerList=a;if(b){this._ignoreButtonSelector=b}this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._containerList.each($.proxy(function(d,c){$(c).find(this._ignoreButtonSelector).click($.proxy(this._click,this))},this))},_click:function(b){var a=$(b.target);if(!a.is("a")){a=a.closest("a")}this._userID=a.data("objectID");this._proxy.setOption("data",{actionName:a.data("ignored")?"unignore":"ignore",className:"wcf\\data\\user\\ignore\\UserIgnoreAction",parameters:{data:{ignoreUserID:this._userID}}});this._proxy.sendRequest()},_success:function(b,d,a){this._containerList.each($.proxy(function(f,e){var g=$(e).find(this._ignoreButtonSelector).get(0);if(g&&$(g).data("objectID")==this._userID){g=$(g);if(b.returnValues.isIgnoredUser){g.data("tooltip",WCF.Language.get("wcf.user.button.unignore")).children(".icon").removeClass("icon-off").addClass("icon-circle-blank")}else{g.data("tooltip",WCF.Language.get("wcf.user.button.ignore")).children(".icon").removeClass("icon-circle-blank").addClass("icon-off")}g.data("ignored",b.returnValues.isIgnoredUser);return false}},this));var c=new WCF.System.Notification();c.show()}});WCF.User.Avatar={};WCF.User.Avatar.Crop=Class.extend({_cropX:0,_cropY:0,_dialog:null,_proxy:null,MAX_THUMBNAIL_SIZE:128,init:function(a){this._avatarID=a;if(this._dialog){this.destroy()}this._dialog=null;if(!this._proxy){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})}$(".userAvatarCrop").click($.proxy(this._showCropDialog,this))},destroy:function(){this._dialog.remove()},_crop:function(a){this._proxy.setOption("data",{actionName:"cropAvatar",className:"wcf\\data\\user\\avatar\\UserAvatarAction",objectIDs:[this._avatarID],parameters:{cropX:this._cropX,cropY:this._cropY}});this._proxy.sendRequest()},_getCropDialog:function(b){if(!this._dialog){this._dialog=$("<div />").hide().appendTo(document.body);this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.avatar.type.custom.crop")})}this._dialog.html(b.returnValues.template);this._dialog.find('button[data-type="save"]').click($.proxy(this._crop,this));this._cropX=b.returnValues.cropX;this._cropY=b.returnValues.cropY;var a=$("#userAvatarCropSelection > img");$("#userAvatarCropSelection").css({height:a.height()+"px",width:a.width()+"px"});$("#userAvatarCropOverlaySelection").css({"background-image":"url("+a.attr("src")+")","background-position":-this._cropX+"px "+-this._cropY+"px",left:this._cropX+"px",top:this._cropY+"px"}).draggable({containment:"parent",drag:$.proxy(this._updateSelection,this),stop:$.proxy(this._updateSelection,this)});this._dialog.find('button[data-type="save"]').click($.proxy(this._save,this));this._dialog.wcfDialog("render")},_showCropDialog:function(){if(!this._dialog){this._proxy.setOption("data",{actionName:"getCropDialog",className:"wcf\\data\\user\\avatar\\UserAvatarAction",objectIDs:[this._avatarID]});this._proxy.sendRequest()}else{this._dialog.wcfDialog("open")}},_success:function(b,d,a){switch(b.actionName){case"getCropDialog":this._getCropDialog(b);break;case"cropAvatar":WCF.DOMNodeInsertedHandler.enable();$("#avatarUpload > dt > img").replaceWith($('<img src="'+b.returnValues.url+'" alt="" class="userAvatarCrop jsTooltip" title="'+WCF.Language.get("wcf.user.avatar.type.custom.crop")+'" />').css({width:"96px",height:"96px"}).click($.proxy(this._showCropDialog,this)));WCF.DOMNodeInsertedHandler.disable();this._dialog.wcfDialog("close");var c=new WCF.System.Notification();c.show();break}},_updateSelection:function(a,b){this._cropX=b.position.left;this._cropY=b.position.top;$("#userAvatarCropOverlaySelection").css({"background-position":-b.position.left+"px "+-b.position.top+"px"})}});WCF.User.Avatar.Upload=WCF.Upload.extend({_avatarCrop:null,_userID:0,init:function(b,a){this._super($("#avatarUpload > dd > div"),undefined,"wcf\\data\\user\\avatar\\UserAvatarAction");this._userID=b||0;this._avatarCrop=a;$("#avatarForm input[type=radio]").change(function(){if($(this).val()=="custom"){$("#avatarUpload > dd > div").show()}else{$("#avatarUpload > dd > div").hide()}});if(!$("#avatarForm input[type=radio][value=custom]:checked").length){$("#avatarUpload > dd > div").hide()}},_initFile:function(a){return $("#avatarUpload > dt > img")},_success:function(c,a){if(a.returnValues.url){this._updateImage(a.returnValues.url,a.returnValues.canCrop);if(a.returnValues.canCrop){if(!this._avatarCrop){this._avatarCrop=new WCF.User.Avatar.Crop(a.returnValues.avatarID)}else{this._avatarCrop.init(a.returnValues.avatarID)}}else{if(this._avatarCrop){this._avatarCrop.destroy();this._avatarCrop=null}}$("#avatarUpload > dd > .innerError").remove();var b=new WCF.System.Notification(WCF.Language.get("wcf.user.avatar.upload.success"));b.show()}else{if(a.returnValues.errorType){this._getInnerErrorElement().text(WCF.Language.get("wcf.user.avatar.upload.error."+a.returnValues.errorType))}}},_updateImage:function(b,a){WCF.DOMNodeInsertedHandler.enable();$("#avatarUpload > dt > img").remove();var c=$('<img src="'+b+'" alt="" />').css({height:"auto","max-height":"96px","max-width":"96px",width:"auto"});if(a){c.addClass("userAvatarCrop").addClass("jsTooltip");c.attr("title",WCF.Language.get("wcf.user.avatar.type.custom.crop"))}$("#avatarUpload > dt").prepend(c);WCF.DOMNodeInsertedHandler.disable()},_getInnerErrorElement:function(){var a=$("#avatarUpload > dd > .innerError");if(!a.length){a=$('<small class="innerError"></span>');$("#avatarUpload > dd").append(a)}return a},_getParameters:function(){return{userID:this._userID}},});WCF.User.List=Class.extend({_additionalParameters:{},_cache:{},_className:"",_dialog:null,_dialogTitle:"",_pageCount:0,_pageNo:1,_proxy:null,init:function(c,a,b){this._additionalParameters=b||{};this._cache={};this._className=c;this._dialog=null;this._dialogTitle=a;this._pageCount=0;this._pageNo=1;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},open:function(){this._pageNo=1;this._showPage()},_showPage:function(b,c){if(c&&c.activePage){this._pageNo=c.activePage}if(this._pageCount!=0&&(this._pageNo<1||this._pageNo>this._pageCount)){console.debug("[WCF.User.List] Cannot access page "+this._pageNo+" of "+this._pageCount);return}if(this._cache[this._pageNo]){var a=false;if(this._dialog===null){this._dialog=$('<div id="userList'+this._className.hashCode()+'" style="min-width: 600px;" />').hide().appendTo(document.body);a=true}this._dialog.empty();this._dialog.html(this._cache[this._pageNo]);if(this._pageCount>1){this._dialog.find(".jsPagination").wcfPages({activePage:this._pageNo,maxPage:this._pageCount}).bind("wcfpagesswitched",$.proxy(this._showPage,this))}if(a){this._dialog.wcfDialog({title:this._dialogTitle})}else{this._dialog.wcfDialog("open").wcfDialog("render")}}else{this._additionalParameters.pageNo=this._pageNo;this._proxy.setOption("data",{actionName:"getGroupedUserList",className:this._className,interfaceName:"wcf\\data\\IGroupedUserListAction",parameters:this._additionalParameters});this._proxy.sendRequest()}},_success:function(b,c,a){if(b.returnValues.pageCount){this._pageCount=b.returnValues.pageCount}this._cache[this._pageNo]=b.returnValues.template;this._showPage()}});WCF.User.ObjectWatch={};WCF.User.ObjectWatch.Subscribe=Class.extend({_buttonSelector:".jsSubscribeButton",_buttons:{},_dialog:null,_notification:null,init:function(){this._buttons={};this._notification=null;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(this._buttonSelector).each($.proxy(function(a,b){var d=$(b);var c=d.data("objectID");this._buttons[c]=d.click($.proxy(this._click,this))},this))},_click:function(a){var b=$(a.currentTarget);this._proxy.setOption("data",{actionName:"manageSubscription",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",parameters:{objectID:b.data("objectID"),objectType:b.data("objectType")}});this._proxy.sendRequest()},_success:function(c,e,b){if(c.actionName==="manageSubscription"){if(this._dialog===null){this._dialog=$("<div>"+c.returnValues.template+"</div>").hide().appendTo(document.body);this._dialog.wcfDialog({title:WCF.Language.get("wcf.user.objectWatch.manageSubscription")})}else{this._dialog.html(c.returnValues.template);this._dialog.wcfDialog("open")}this._dialog.find(".formSubmit > .jsButtonSave").data("objectID",c.returnValues.objectID).click($.proxy(this._save,this));var a=this._dialog.find("input[name=enableNotification]").disable();this._dialog.find("input[name=subscribe]").change(function(f){var g=$(f.currentTarget);if(g.val()==1){a.enable()}else{a.disable()}});var d=this._dialog.find("input[name=subscribe]:checked");if(d.length&&d.val()==1){a.enable()}}else{if(c.actionName==="saveSubscription"&&this._dialog.is(":visible")){this._dialog.wcfDialog("close");if(this._notification===null){this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.edit"))}this._notification.show()}}},_save:function(b){var d=this._buttons[$(b.currentTarget).data("objectID")];var c=this._dialog.find("input[name=subscribe]:checked").val();var a=(this._dialog.find("input[name=enableNotification]").is(":checked"))?1:0;this._proxy.setOption("data",{actionName:"saveSubscription",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",parameters:{enableNotification:a,objectID:d.data("objectID"),objectType:d.data("objectType"),subscribe:c}});this._proxy.sendRequest()}});WCF.User.ObjectWatch.Notification=Class.extend({_buttonSelector:".jsObjectWatchNotificationButton",_watchID:0,init:function(){this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$(this._buttonSelector).click($.proxy(this._click,this))},_click:function(b){var a=$(b.target);if(!a.is("a")){a=a.closest("a")}this._watchID=a.data("watchID");this._proxy.setOption("data",{actionName:a.data("notification")?"disableNotification":"enableNotification",className:"wcf\\data\\user\\object\\watch\\UserObjectWatchAction",objectIDs:[this._watchID]});this._proxy.sendRequest()},_success:function(b,c,a){$(this._buttonSelector).each($.proxy(function(e,d){var f=$(d);if(f.data("watchID")==this._watchID){if(f.data("notification")){f.children("img").removeClass("disabled");f.data("tooltip",WCF.Language.get("wcf.user.watchedObjects.enableNotification"))}else{f.children("img").addClass("disabled");f.data("tooltip",WCF.Language.get("wcf.user.watchedObjects.disableNotification"))}f.data("notification",!f.data("notification"));return false}},this))}});
\ No newline at end of file
$this->matches[] = $row['userID'];
}
break;
+
+ case 'disabled':
+ $this->sortField = 'registrationDate';
+ $this->sortOrder = 'DESC';
+ $sql = "SELECT user_table.userID
+ FROM wcf".WCF_N."_user user_table
+ LEFT JOIN wcf".WCF_N."_user_option_value option_value
+ ON (option_value.userID = user_table.userID)
+ WHERE activationCode <> ?
+ ORDER BY user_table.registrationDate DESC";
+ $statement = WCF::getDB()->prepareStatement($sql, $this->maxResults);
+ $statement->execute(array(0));
+ while ($row = $statement->fetchArray()) {
+ $this->matches[] = $row['userID'];
+ }
+ break;
+
+ case 'disabledAvatars':
+ $sql = "SELECT user_table.userID
+ FROM wcf".WCF_N."_user user_table
+ LEFT JOIN wcf".WCF_N."_user_option_value option_value
+ ON (option_value.userID = user_table.userID)
+ WHERE disableAvatar = ?";
+ $statement = WCF::getDB()->prepareStatement($sql, $this->maxResults);
+ $statement->execute(array(1));
+ while ($row = $statement->fetchArray()) {
+ $this->matches[] = $row['userID'];
+ }
+ break;
+
+ case 'disabledSignatures':
+ $sql = "SELECT user_table.userID
+ FROM wcf".WCF_N."_user user_table
+ LEFT JOIN wcf".WCF_N."_user_option_value option_value
+ ON (option_value.userID = user_table.userID)
+ WHERE disableSignature = ?";
+ $statement = WCF::getDB()->prepareStatement($sql, $this->maxResults);
+ $statement->execute(array(1));
+ while ($row = $statement->fetchArray()) {
+ $this->matches[] = $row['userID'];
+ }
+ break;
}
if (empty($this->matches)) {
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\dashboard\box\DashboardBoxList;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * Provides the dashboard option form.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class DashboardOptionForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.dashboard';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.content.dashboard.canEditDashboard');
+
+ /**
+ * list of available dashboard boxes
+ * @var array<wcf\data\dashboard\box\DashboardBox>
+ */
+ public $boxes = array();
+
+ /**
+ * list of enabled box ids
+ * @var array<integer>
+ */
+ public $enabledBoxes = array();
+
+ /**
+ * object type object
+ * @var wcf\data\object\type\ObjectType
+ */
+ public $objectType = null;
+
+ /**
+ * object type id
+ * @var integer
+ */
+ public $objectTypeID = 0;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) $this->objectTypeID = intval($_REQUEST['id']);
+
+ // load object type
+ $objectTypeDefinition = ObjectTypeCache::getInstance()->getDefinitionByName('com.woltlab.wcf.user.dashboardContainer');
+ $this->objectType = ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID);
+ if ($this->objectType === null || $this->objectType->definitionID != $objectTypeDefinition->definitionID) {
+ throw new IllegalLinkException();
+ }
+
+ // load available boxes
+ $allowedBoxTypes = array();
+ if ($this->objectType->allowcontent) $allowedBoxTypes[] = 'content';
+ if ($this->objectType->allowsidebar) $allowedBoxTypes[] = 'sidebar';
+ if (empty($allowedBoxTypes)) {
+ // this should not happen unless you go full retard
+ throw new IllegalLinkException();
+ }
+
+ $boxList = new DashboardBoxList();
+ $boxList->getConditionBuilder()->add("dashboard_box.boxType IN (?)", array($allowedBoxTypes));
+ $boxList->readObjects();
+ $this->boxes = $boxList->getObjects();
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['enabledBoxes']) && is_array($_POST['enabledBoxes'])) $this->enabledBoxes = ArrayUtil::toIntegerArray($_POST['enabledBoxes']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ $this->validateEnabledBoxes();
+ }
+
+ /**
+ * Validates dashboard options.
+ */
+ protected function validateEnabledBoxes() {
+ foreach ($this->enabledBoxes as $boxID) {
+ if (!isset($this->boxes[$boxID])) {
+ throw new IllegalLinkException();
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST)) {
+ // load settings
+ $sql = "SELECT boxID
+ FROM wcf".WCF_N."_dashboard_option
+ WHERE objectTypeID = ?
+ ORDER BY showOrder ASC";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->objectTypeID
+ ));
+ while ($row = $statement->fetchArray()) {
+ $this->enabledBoxes[] = $row['boxID'];
+ }
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // remove previous settings
+ $sql = "DELETE FROM wcf".WCF_N."_dashboard_option
+ WHERE objectTypeID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->objectTypeID
+ ));
+
+ // insert new settings
+ if (!empty($this->enabledBoxes)) {
+ $sql = "INSERT INTO wcf".WCF_N."_dashboard_option
+ (objectTypeID, boxID, showOrder)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ WCF::getDB()->beginTransaction();
+ $showOrder = 1;
+ foreach ($this->enabledBoxes as $boxID) {
+ $statement->execute(array(
+ $this->objectTypeID,
+ $boxID,
+ $showOrder
+ ));
+
+ $showOrder++;
+ }
+ WCF::getDB()->commitTransaction();
+ }
+
+ $this->saved();
+
+ WCF::getTPL()->assign('success', true);
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'boxes' => $this->boxes,
+ 'enabledBoxes' => $this->enabledBoxes,
+ 'objectType' => $this->objectType,
+ 'objectTypeID' => $this->objectTypeID
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\object\type\ObjectTypeEditor;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * Provides the user activity point option form.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class UserActivityPointOptionForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.activityPoint';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.user.canEditActivityPoints');
+
+ /**
+ * points to objectType
+ * @var array<integer>
+ */
+ public $points = array();
+
+ /**
+ * valid object types
+ * @var array<wcf\data\object\type\ObjectType>
+ */
+ public $objectTypes = array();
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['points']) && is_array($_POST['points'])) $this->points = ArrayUtil::toIntegerArray($_POST['points']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ foreach ($this->points as $objectTypeID => $points) {
+ if ($points < 0) throw new UserInputException($objectTypeID, 'invalid');
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.activityPointEvent');
+ if (empty($_POST)) {
+ foreach ($this->objectTypes as $objectType) {
+ $this->points[$objectType->objectTypeID] = $objectType->points;
+ }
+ }
+
+ parent::readData();
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ foreach ($this->objectTypes as $objectType) {
+ if (!isset($this->points[$objectType->objectTypeID])) continue;
+ $editor = new ObjectTypeEditor($objectType);
+ $data = $objectType->additionalData;
+ $data['points'] = $this->points[$objectType->objectTypeID];
+ $editor->update(array('additionalData' => serialize($data)));
+ }
+
+ ObjectTypeEditor::resetCache();
+
+ $this->saved();
+
+ WCF::getTPL()->assign('success', true);
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'objectTypes' => $this->objectTypes,
+ 'points' => $this->points
+ ));
+ }
+}
namespace wcf\acp\form;
use wcf\data\user\group\UserGroup;
use wcf\data\user\UserAction;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserProfileAction;
use wcf\form\AbstractForm;
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\exception\UserInputException;
*/
public $additionalFields = array();
+ /**
+ * title of the user
+ * @var string
+ */
+ protected $userTitle = '';
+
+ /**
+ * signature text
+ * @var string
+ */
+ public $signature = '';
+
+ /**
+ * enables smilies
+ * @var boolean
+ */
+ public $signatureEnableSmilies = 1;
+
+ /**
+ * enables bbcodes
+ * @var boolean
+ */
+ public $signatureEnableBBCodes = 1;
+
+ /**
+ * enables html
+ * @var boolean
+ */
+ public $signatureEnableHtml = 0;
+
+ /**
+ * true to disable this signature
+ * @var boolean
+ */
+ public $disableSignature = 0;
+
+ /**
+ * reason
+ * @var string
+ */
+ public $disableSignatureReason = '';
+
/**
* @see wcf\form\IForm::readFormParameters()
*/
if (isset($_POST['groupIDs']) && is_array($_POST['groupIDs'])) $this->groupIDs = ArrayUtil::toIntegerArray($_POST['groupIDs']);
if (isset($_POST['visibleLanguages']) && is_array($_POST['visibleLanguages'])) $this->visibleLanguages = ArrayUtil::toIntegerArray($_POST['visibleLanguages']);
if (isset($_POST['languageID'])) $this->languageID = intval($_POST['languageID']);
+ if (isset($_POST['userTitle'])) $this->userTitle = $_POST['userTitle'];
+
+ if (isset($_POST['signature'])) $this->signature = StringUtil::trim($_POST['signature']);
+ if (isset($_POST['disableSignatureReason'])) $this->disableSignatureReason = StringUtil::trim($_POST['disableSignatureReason']);
+
+ $this->signatureEnableBBCodes = $this->signatureEnableSmilies = 0;
+ if (!empty($_POST['signatureEnableBBCodes'])) $this->signatureEnableBBCodes = 1;
+ if (!empty($_POST['signatureEnableSmilies'])) $this->signatureEnableSmilies = 1;
+ if (!empty($_POST['signatureEnableHtml'])) $this->signatureEnableHtml = 1;
+ if (!empty($_POST['disableSignature'])) $this->disableSignature = 1;
}
/**
$this->visibleLanguages[] = $this->languageID;
}
+ // validate user title
+ try {
+ if (StringUtil::length($this->userTitle) > USER_TITLE_MAX_LENGTH) {
+ throw new UserInputException('userTitle', 'tooLong');
+ }
+ if (!StringUtil::executeWordFilter($this->userTitle, USER_FORBIDDEN_TITLES)) {
+ throw new UserInputException('userTitle', 'forbidden');
+ }
+ }
+ catch (UserInputException $e) {
+ $this->errorType[$e->getField()] = $e->getType();
+ }
+
// validate dynamic options
parent::validate();
}
'username' => $this->username,
'email' => $this->email,
'password' => $this->password,
+ 'userTitle' => $this->userTitle,
+ 'signature' => $this->signature,
+ 'signatureEnableBBCodes' => $this->signatureEnableBBCodes,
+ 'signatureEnableSmilies' => $this->signatureEnableSmilies,
+ 'signatureEnableHtml' => $this->signatureEnableHtml,
+ 'disableSignature' => $this->disableSignature,
+ 'disableSignatureReason' => $this->disableSignatureReason
)),
'groups' => $this->groupIDs,
'languages' => $this->visibleLanguages,
);
$this->objectAction = new UserAction(array(), 'create', $data);
$this->objectAction->executeAction();
+ $returnValues = $this->objectAction->getReturnValues();
+
+ // set user rank
+ $editor = new UserEditor($returnValues['returnValues']);
+ if (MODULE_USER_RANK) {
+ $action = new UserProfileAction(array($editor), 'updateUserRank');
+ $action->executeAction();
+ }
+ if (MODULE_USERS_ONLINE) {
+ $action = new UserProfileAction(array($editor), 'updateUserOnlineMarking');
+ $action->executeAction();
+ }
$this->saved();
// show empty add form
'languageID' => $this->languageID,
'visibleLanguages' => $this->visibleLanguages,
'availableContentLanguages' => LanguageFactory::getInstance()->getContentLanguages(),
- 'action' => 'add'
+ 'action' => 'add',
+ 'userTitle' => $this->userTitle,
+ 'signature' => $this->signature,
+ 'signatureEnableBBCodes' => $this->signatureEnableBBCodes,
+ 'signatureEnableSmilies' => $this->signatureEnableSmilies,
+ 'signatureEnableHtml' => $this->signatureEnableHtml,
+ 'disableSignature' => $this->disableSignature,
+ 'disableSignatureReason' => $this->disableSignatureReason
));
}
*/
public $notBanned = 0;
+ /**
+ * last activity start time
+ * @var string
+ */
+ public $lastActivityTimeStart = '';
+
+ /**
+ * last activity end time
+ * @var string
+ */
+ public $lastActivityTimeEnd = '';
+
+ /**
+ * enabled state
+ * @var boolean
+ */
+ public $enabled = 0;
+
+ /**
+ * disabled state
+ * @var boolean
+ */
+ public $disabled = 0;
+
// assign to group
public $assignToGroupIDs = array();
if (isset($_POST['registrationDateEnd'])) $this->registrationDateEnd = $_POST['registrationDateEnd'];
if (isset($_POST['banned'])) $this->banned = intval($_POST['banned']);
if (isset($_POST['notBanned'])) $this->notBanned = intval($_POST['notBanned']);
+ if (isset($_POST['lastActivityTimeStart'])) $this->lastActivityTimeStart = $_POST['lastActivityTimeStart'];
+ if (isset($_POST['lastActivityTimeEnd'])) $this->lastActivityTimeEnd = $_POST['lastActivityTimeEnd'];
+ if (isset($_POST['enabled'])) $this->enabled = intval($_POST['enabled']);
+ if (isset($_POST['disabled'])) $this->disabled = intval($_POST['disabled']);
// assign to group
if (isset($_POST['assignToGroupIDs']) && is_array($_POST['assignToGroupIDs'])) $this->assignToGroupIDs = ArrayUtil::toIntegerArray($_POST['assignToGroupIDs']);
$this->conditions->add('user_table.banned = ?', array(0));
}
+ // last activity time
+ if ($startDate = @strtotime($this->lastActivityTimeStart)) {
+ $this->conditions->add('user_table.lastActivityTime >= ?', array($startDate));
+ }
+ if ($endDate = @strtotime($this->lastActivityTimeEnd)) {
+ $this->conditions->add('user_table.lastActivityTime <= ?', array($endDate));
+ }
+
+ if ($this->enabled) {
+ $this->conditions->add('user_table.activationCode = ?', array(0));
+ }
+ if ($this->disabled) {
+ $this->conditions->add('user_table.activationCode <> ?', array(0));
+ }
+
// dynamic fields
foreach ($this->activeOptions as $name => $option) {
$value = isset($this->values[$option['optionName']]) ? $this->values[$option['optionName']] : null;
'registrationDateEnd' => $this->registrationDateEnd,
'banned' => $this->banned,
'notBanned' => $this->notBanned,
-
+ 'lastActivityTimeStart' => $this->lastActivityTimeStart,
+ 'lastActivityTimeEnd' => $this->lastActivityTimeEnd,
+ 'enabled' => $this->enabled,
+ 'disabled' => $this->disabled,
+
'availableGroups' => $this->availableGroups,
'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
'options' => $this->options,
<?php
namespace wcf\acp\form;
+use wcf\data\user\avatar\UserAvatarAction;
+
+use wcf\data\user\avatar\Gravatar;
+
+use wcf\system\exception\UserInputException;
+
+use wcf\data\user\avatar\UserAvatar;
+
use wcf\data\user\group\UserGroup;
use wcf\data\user\User;
use wcf\data\user\UserAction;
use wcf\data\user\UserEditor;
+use wcf\data\user\UserProfileAction;
use wcf\form\AbstractForm;
use wcf\system\exception\IllegalLinkException;
use wcf\system\exception\PermissionDeniedException;
*/
public $banReason = '';
+ /**
+ * user avatar object
+ * @var wcf\data\user\avatar\UserAvatar
+ */
+ public $userAvatar = null;
+
+ /**
+ * avatar type
+ * @var string
+ */
+ public $avatarType = 'none';
+
+ /**
+ * true to disable this avatar
+ * @var boolean
+ */
+ public $disableAvatar = 0;
+
+ /**
+ * reason
+ * @var string
+ */
+ public $disableAvatarReason = '';
+
/**
* @see wcf\page\IPage::readParameters()
*/
if (!empty($_POST['banned'])) $this->banned = 1;
if (isset($_POST['banReason'])) $this->banReason = StringUtil::trim($_POST['banReason']);
+ if (isset($_POST['avatarType'])) $this->avatarType = $_POST['avatarType'];
+ if (!empty($_POST['disableAvatar'])) $this->disableAvatar = 1;
+ if (isset($_POST['disableAvatarReason'])) $this->disableAvatarReason = StringUtil::trim($_POST['disableAvatarReason']);
}
/**
$this->readDefaultValues();
}
+ // get avatar object
+ if ($this->avatarType == 'custom') {
+ $this->userAvatar = new UserAvatar($this->user->avatarID);
+ }
+
parent::readData();
}
$this->languageID = $this->user->languageID;
$this->banned = $this->user->banned;
$this->banReason = $this->user->banReason;
+ $this->userTitle = $this->user->userTitle;
+
+ $this->signature = $this->user->signature;
+ $this->signatureEnableBBCodes = $this->user->signatureEnableBBCodes;
+ $this->signatureEnableSmilies = $this->user->signatureEnableSmilies;
+ $this->signatureEnableHtml = $this->user->signatureEnableHtml;
+ $this->disableSignature = $this->user->disableSignature;
+ $this->disableSignatureReason = $this->user->disableSignatureReason;
+ $this->disableAvatar = $this->user->disableAvatar;
+ $this->disableAvatarReason = $this->user->disableAvatarReason;
+
+ if ($this->user->avatarID) $this->avatarType = 'custom';
+ else if (MODULE_GRAVATAR && $this->user->enableGravatar) $this->avatarType = 'gravatar';
}
/**
'markedUsers' => 0,
'user' => $this->user,
'banned' => $this->banned,
- 'banReason' => $this->banReason
+ 'banReason' => $this->banReason,
+ 'avatarType' => $this->avatarType,
+ 'disableAvatar' => $this->disableAvatar,
+ 'disableAvatarReason' => $this->disableAvatarReason,
+ 'userAvatar' => $this->userAvatar
));
}
public function save() {
AbstractForm::save();
+ // handle avatar
+ if ($this->avatarType != 'custom') {
+ // delete custom avatar
+ if ($this->user->avatarID) {
+ $action = new UserAvatarAction(array($this->user->avatarID), 'delete');
+ $action->executeAction();
+ }
+ }
+ switch ($this->avatarType) {
+ case 'none':
+ $avatarData = array(
+ 'avatarID' => null,
+ 'enableGravatar' => 0
+ );
+ break;
+
+ case 'custom':
+ $avatarData = array(
+ 'avatarID' => null,
+ 'enableGravatar' => 0
+ );
+ break;
+
+ case 'gravatar':
+ $avatarData = array(
+ 'avatarID' => null,
+ 'enableGravatar' => 1
+ );
+ break;
+ }
+ $avatarData['disableAvatar'] = $this->disableAvatar;
+ $avatarData['disableAvatarReason'] = $this->disableAvatarReason;
+ $this->additionalFields = array_merge($this->additionalFields, $avatarData);
+
// add default groups
$defaultGroups = UserGroup::getAccessibleGroups(array(UserGroup::GUESTS, UserGroup::EVERYONE, UserGroup::USERS));
$oldGroupIDs = $this->user->getGroupIDs();
'email' => $this->email,
'password' => $this->password,
'banned' => $this->banned,
- 'banReason' => $this->banReason
+ 'banReason' => $this->banReason,
+ 'userTitle' => $this->userTitle,
+ 'signature' => $this->signature,
+ 'signatureEnableBBCodes' => $this->signatureEnableBBCodes,
+ 'signatureEnableSmilies' => $this->signatureEnableSmilies,
+ 'signatureEnableHtml' => $this->signatureEnableHtml,
+ 'disableSignature' => $this->disableSignature,
+ 'disableSignatureReason' => $this->disableSignatureReason
)),
'groups' => $this->groupIDs,
'languages' => $this->visibleLanguages,
$this->objectAction = new UserAction(array($this->userID), 'update', $data);
$this->objectAction->executeAction();
+ // update user rank
+ $editor = new UserEditor(new User($this->userID));
+ if (MODULE_USER_RANK) {
+ $action = new UserProfileAction(array($editor), 'updateUserRank');
+ $action->executeAction();
+ }
+ if (MODULE_USERS_ONLINE) {
+ $action = new UserProfileAction(array($editor), 'updateUserOnlineMarking');
+ $action->executeAction();
+ }
$this->saved();
// reset password
parent::validatePassword($password, $confirmPassword);
}
}
+
+ /**
+ * Validates the user avatar.
+ */
+ protected function validateAvatar() {
+ if ($this->avatarType != 'custom' && $this->avatarType != 'gravatar') $this->avatarType = 'none';
+
+ try {
+ switch ($this->avatarType) {
+ case 'custom':
+ if (!$this->user->avatarID) {
+ throw new UserInputException('customAvatar');
+ }
+ break;
+
+ case 'gravatar':
+ if (!MODULE_GRAVATAR) {
+ $this->avatarType = 'none';
+ break;
+ }
+
+ // test gravatar
+ if (!Gravatar::test($this->user->email)) {
+ throw new UserInputException('gravatar', 'notFound');
+ }
+ }
+ }
+ catch (UserInputException $e) {
+ $this->errorType[$e->getField()] = $e->getType();
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ $this->validateAvatar();
+
+ parent::validate();
+ }
}
use wcf\system\menu\acp\ACPMenu;
use wcf\system\WCF;
use wcf\system\WCFACP;
+use wcf\util\StringUtil;
/**
* Shows the group add form.
*/
public $defaultValues = array();
+ /**
+ * group priority
+ * @var integer
+ */
+ protected $priority = 0;
+
+ /**
+ * user online marking string
+ * @var string
+ */
+ protected $userOnlineMarking = '%s';
+
+ /**
+ * shows the members of this group on the team page
+ * @var integer
+ */
+ protected $showOnTeamPage = 0;
+
/**
* @see wcf\page\IPage::readParameters()
*/
I18nHandler::getInstance()->readValues();
if (I18nHandler::getInstance()->isPlainValue('groupName')) $this->groupName = I18nHandler::getInstance()->getValue('groupName');
+
+ if (isset($_POST['priority'])) $this->priority = intval($_POST['priority']);
+ if (isset($_POST['userOnlineMarking'])) $this->userOnlineMarking = StringUtil::trim($_POST['userOnlineMarking']);
+ if (isset($_POST['showOnTeamPage'])) $this->showOnTeamPage = intval($_POST['showOnTeamPage']);
}
/**
}
$data = array(
- 'data' => array_merge($this->additionalFields, array('groupName' => $this->groupName)),
+ 'data' => array_merge($this->additionalFields, array(
+ 'groupName' => $this->groupName,
+ 'priority' => $this->priority,
+ 'userOnlineMarking' => $this->userOnlineMarking,
+ 'showOnTeamPage' => $this->showOnTeamPage
+ )),
'options' => $saveOptions
);
$this->objectAction = new UserGroupAction(array(), 'create', $data);
WCF::getTPL()->assign(array(
'groupName' => $this->groupName,
'optionTree' => $this->optionTree,
- 'action' => 'add'
+ 'action' => 'add',
+ 'priority' => $this->priority,
+ 'userOnlineMarking' => $this->userOnlineMarking,
+ 'showOnTeamPage' => $this->showOnTeamPage
));
}
if (empty($_POST)) {
I18nHandler::getInstance()->setOptions('groupName', 1, $this->group->groupName, 'wcf.acp.group.group\d+');
$this->groupName = $this->group->groupName;
+ $this->priority = $this->group->priority;
+ $this->userOnlineMarking = $this->group->userOnlineMarking;
+ $this->showOnTeamPage = $this->group->showOnTeamPage;
$options = $this->optionHandler->getCategoryOptions();
// get default values
}
$data = array(
- 'data' => array_merge(array('groupName' => $this->groupName), $this->additionalFields),
+ 'data' => array_merge(array(
+ 'groupName' => $this->groupName,
+ 'priority' => $this->priority,
+ 'userOnlineMarking' => $this->userOnlineMarking,
+ 'showOnTeamPage' => $this->showOnTeamPage
+ ), $this->additionalFields),
'options' => $saveOptions
);
$this->objectAction = new UserGroupAction(array($this->groupID), 'update', $data);
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\user\UserAction;
+use wcf\form\AbstractForm;
+use wcf\system\clipboard\ClipboardHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\session\SessionHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the user merge form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class UserMergeForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.user.canEditUser');
+
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.user.management';
+
+ /**
+ * ids of the relevant users
+ * @var array<integer>
+ */
+ public $userIDs = array();
+
+ /**
+ * relevant users
+ * @var array<wcf\data\user\User>
+ */
+ public $users = array();
+
+ /**
+ * destination user id
+ * @var integer
+ */
+ public $destinationUserID = 0;
+
+ /**
+ * ids of merge users (without destination user)
+ * @var array<integer>
+ */
+ public $mergedUserIDs = array();
+
+ /**
+ * id of the user clipboard item object type
+ * @var integer
+ */
+ protected $objectTypeID = null;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ // get object type id
+ $this->objectTypeID = ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.user');
+ // get user
+ $this->users = ClipboardHandler::getInstance()->getMarkedItems($this->objectTypeID);
+ if (empty($this->users) || count($this->users) < 2) {
+ throw new IllegalLinkException();
+ }
+ $this->userIDs = array_keys($this->users);
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['destinationUserID'])) $this->destinationUserID = intval($_POST['destinationUserID']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ if (!isset($this->users[$this->destinationUserID])) {
+ throw new UserInputException('destinationUserID');
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ foreach ($this->userIDs as $userID) {
+ if ($userID != $this->destinationUserID) $this->mergedUserIDs[] = $userID;
+ }
+
+ parent::save();
+
+ // user_follow (userID)
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($this->mergedUserIDs));
+ $conditions->add("followUserID <> ?", array($this->destinationUserID));
+ $sql = "UPDATE IGNORE wcf".WCF_N."_user_follow
+ SET userID = ?
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+ // user_follow (followUserID)
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("followUserID IN (?)", array($this->mergedUserIDs));
+ $conditions->add("userID <> ?", array($this->destinationUserID));
+ $sql = "UPDATE IGNORE wcf".WCF_N."_user_follow
+ SET followUserID = ?
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+
+ // user_ignore (userID)
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($this->mergedUserIDs));
+ $conditions->add("ignoreUserID <> ?", array($this->destinationUserID));
+ $sql = "UPDATE IGNORE wcf".WCF_N."_user_ignore
+ SET userID = ?
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+ // user_ignore (ignoreUserID)
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("ignoreUserID IN (?)", array($this->mergedUserIDs));
+ $conditions->add("userID <> ?", array($this->destinationUserID));
+ $sql = "UPDATE IGNORE wcf".WCF_N."_user_ignore
+ SET ignoreUserID = ?
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+
+ // user_object_watch
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($this->mergedUserIDs));
+ $sql = "UPDATE IGNORE wcf".WCF_N."_user_object_watch
+ SET userID = ?
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+
+ // user_activity_event
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($this->mergedUserIDs));
+ $sql = "UPDATE wcf".WCF_N."_user_activity_event
+ SET userID = ?
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+
+ // attachments
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($this->mergedUserIDs));
+ $sql = "UPDATE wcf".WCF_N."_attachment
+ SET userID = ?
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+
+ // modification_log
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($this->mergedUserIDs));
+ $sql = "UPDATE wcf".WCF_N."_modification_log
+ SET userID = ?
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array_merge(array($this->destinationUserID), $conditions->getParameters()));
+
+ // delete merged users
+ $action = new UserAction($this->mergedUserIDs, 'delete');
+ $action->executeAction();
+
+ // reset clipboard
+ ClipboardHandler::getInstance()->removeItems($this->objectTypeID);
+ SessionHandler::resetSessions($this->userIDs);
+ $this->saved();
+
+ // show success message
+ WCF::getTPL()->assign('message', 'wcf.global.success');
+ WCF::getTPL()->display('success');
+ exit;
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'users' => $this->users,
+ 'userIDs' => $this->userIDs,
+ 'destinationUserID' => $this->destinationUserID
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\package\PackageCache;
+use wcf\data\user\group\UserGroup;
+use wcf\data\user\rank\UserRankAction;
+use wcf\data\user\rank\UserRankEditor;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\Regex;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the user rank add form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class UserRankAddForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.user.rank.add';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.user.rank.canManageRank');
+
+ /**
+ * rank group id
+ * @var integer
+ */
+ public $groupID = 0;
+
+ /**
+ * rank title
+ * @var string
+ */
+ public $rankTitle = '';
+
+ /**
+ * CSS class name
+ * @var string
+ */
+ public $cssClassName = '';
+
+ /**
+ * custom CSS class name
+ * @var string
+ */
+ public $customCssClassName = '';
+
+ /**
+ * required activity points to acquire the rank
+ * @var integer
+ */
+ public $requiredPoints = 0;
+
+ /**
+ * path to user rank image
+ * @var string
+ */
+ public $rankImage = '';
+
+ /**
+ * number of image repeats
+ * @var integer
+ */
+ public $repeatImage = 1;
+
+ /**
+ * required gender setting (1=male; 2=female)
+ * @var integer
+ */
+ public $requiredGender = 0;
+
+ /**
+ * list of pre-defined css class names
+ * @var array<string>
+ */
+ public $availableCssClassNames = array(
+ 'yellow',
+ 'orange',
+ 'brown',
+ 'red',
+ 'pink',
+ 'purple',
+ 'blue',
+ 'green',
+ 'black',
+
+ 'none', /* not a real value */
+ 'custom' /* not a real value */
+ );
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ I18nHandler::getInstance()->register('rankTitle');
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ I18nHandler::getInstance()->readValues();
+
+ if (I18nHandler::getInstance()->isPlainValue('rankTitle')) $this->rankTitle = I18nHandler::getInstance()->getValue('rankTitle');
+ if (isset($_POST['cssClassName'])) $this->cssClassName = StringUtil::trim($_POST['cssClassName']);
+ if (isset($_POST['customCssClassName'])) $this->customCssClassName = StringUtil::trim($_POST['customCssClassName']);
+ if (isset($_POST['groupID'])) $this->groupID = intval($_POST['groupID']);
+ if (isset($_POST['requiredPoints'])) $this->requiredPoints = intval($_POST['requiredPoints']);
+ if (isset($_POST['rankImage'])) $this->rankImage = StringUtil::trim($_POST['rankImage']);
+ if (isset($_POST['repeatImage'])) $this->repeatImage = intval($_POST['repeatImage']);
+ if (isset($_POST['requiredGender'])) $this->requiredGender = intval($_POST['requiredGender']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // validate label
+ if (!I18nHandler::getInstance()->validateValue('rankTitle')) {
+ if (I18nHandler::getInstance()->isPlainValue('rankTitle')) {
+ throw new UserInputException('rankTitle');
+ }
+ else {
+ throw new UserInputException('rankTitle', 'multilingual');
+ }
+ }
+
+ // validate group
+ if (!$this->groupID) {
+ throw new UserInputException('groupID');
+ }
+ $userGroup = UserGroup::getGroupByID($this->groupID);
+ if ($userGroup === null || $userGroup->groupType == UserGroup::GUESTS || $userGroup->groupType == UserGroup::EVERYONE) {
+ throw new UserInputException('groupID', 'invalid');
+ }
+
+ // css class name
+ if (empty($this->cssClassName)) {
+ throw new UserInputException('cssClassName', 'empty');
+ }
+ else if (!in_array($this->cssClassName, $this->availableCssClassNames)) {
+ throw new UserInputException('cssClassName', 'notValid');
+ }
+ else if ($this->cssClassName == 'custom') {
+ if (!empty($this->customCssClassName) && !Regex::compile('^-?[_a-zA-Z]+[_a-zA-Z0-9-]+$')->match($this->customCssClassName)) {
+ throw new UserInputException('cssClassName', 'notValid');
+ }
+ }
+
+ // required gender
+ if ($this->requiredGender < 0 || $this->requiredGender > 2) {
+ $this->requiredGender = 0;
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // save label
+ $this->objectAction = new UserRankAction(array(), 'create', array('data' => array(
+ 'rankTitle' => $this->rankTitle,
+ 'cssClassName' => ($this->cssClassName == 'custom' ? $this->customCssClassName : $this->cssClassName),
+ 'groupID' => $this->groupID,
+ 'requiredPoints' => $this->requiredPoints,
+ 'rankImage' => $this->rankImage,
+ 'repeatImage' => $this->repeatImage,
+ 'requiredGender' => $this->requiredGender
+ )));
+ $this->objectAction->executeAction();
+
+ if (!I18nHandler::getInstance()->isPlainValue('rankTitle')) {
+ $returnValues = $this->objectAction->getReturnValues();
+ $rankID = $returnValues['returnValues']->rankID;
+ I18nHandler::getInstance()->save('rankTitle', 'wcf.user.rank.userRank'.$rankID, 'wcf.user', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.user'));
+
+ // update name
+ $rankEditor = new UserRankEditor($returnValues['returnValues']);
+ $rankEditor->update(array(
+ 'rankTitle' => 'wcf.user.rank.userRank'.$rankID
+ ));
+ }
+ $this->saved();
+
+ // reset values
+ $this->rankTitle = $this->cssClassName = $this->customCssClassName = $this->rankImage = '';
+ $this->groupID = $this->repeatImage = $this->requiredPoints = $this->requiredGender = 0;
+
+ I18nHandler::getInstance()->reset();
+
+ // show success
+ WCF::getTPL()->assign(array(
+ 'success' => true
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ I18nHandler::getInstance()->assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'action' => 'add',
+ 'availableCssClassNames' => $this->availableCssClassNames,
+ 'cssClassName' => $this->cssClassName,
+ 'customCssClassName' => $this->customCssClassName,
+ 'groupID' => $this->groupID,
+ 'rankTitle' => $this->rankTitle,
+ 'availableGroups' => UserGroup::getGroupsByType(array(), array(UserGroup::GUESTS, UserGroup::EVERYONE)),
+ 'requiredPoints' => $this->requiredPoints,
+ 'rankImage' => $this->rankImage,
+ 'repeatImage' => $this->repeatImage,
+ 'requiredGender' => $this->requiredGender
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\acp\form;
+use wcf\data\package\PackageCache;
+use wcf\data\user\rank\UserRank;
+use wcf\data\user\rank\UserRankAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the user rank edit form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class UserRankEditForm extends UserRankAddForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.user.rank.list';
+
+ /**
+ * rank id
+ * @var integer
+ */
+ public $rankID = 0;
+
+ /**
+ * rank object
+ * @var wcf\data\user\rank\UserRank
+ */
+ public $rank = null;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) $this->rankID = intval($_REQUEST['id']);
+ $this->rank = new UserRank($this->rankID);
+ if (!$this->rank->rankID) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ AbstractForm::save();
+
+ $this->rankTitle = 'wcf.user.rank.userRank'.$this->rank->rankID;
+ if (I18nHandler::getInstance()->isPlainValue('rankTitle')) {
+ I18nHandler::getInstance()->remove($this->rankTitle, PackageCache::getInstance()->getPackageID('com.woltlab.wcf.user'));
+ $this->rankTitle = I18nHandler::getInstance()->getValue('rankTitle');
+ }
+ else {
+ I18nHandler::getInstance()->save('rankTitle', $this->rankTitle, 'wcf.user', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.user'));
+ }
+
+ // update label
+ $this->objectAction = new UserRankAction(array($this->rank), 'update', array('data' => array(
+ 'rankTitle' => $this->rankTitle,
+ 'cssClassName' => ($this->cssClassName == 'custom' ? $this->customCssClassName : $this->cssClassName),
+ 'groupID' => $this->groupID,
+ 'requiredPoints' => $this->requiredPoints,
+ 'rankImage' => $this->rankImage,
+ 'repeatImage' => $this->repeatImage,
+ 'requiredGender' => $this->requiredGender
+ )));
+ $this->objectAction->executeAction();
+ $this->saved();
+
+ // reset values if non-custom value was choosen
+ if ($this->cssClassName != 'custom') $this->customCssClassName = '';
+
+ // show success
+ WCF::getTPL()->assign(array(
+ 'success' => true
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST)) {
+ I18nHandler::getInstance()->setOptions('rankTitle', PackageCache::getInstance()->getPackageID('com.woltlab.wcf.user'), $this->rank->rankTitle, 'wcf.user.rank.userRank\d+');
+ $this->rankTitle = $this->rank->rankTitle;
+ $this->cssClassName = $this->rank->cssClassName;
+ if (!in_array($this->cssClassName, $this->availableCssClassNames)) {
+ $this->customCssClassName = $this->cssClassName;
+ $this->cssClassName = 'custom';
+ }
+ $this->groupID = $this->rank->groupID;
+ $this->requiredPoints = $this->rank->requiredPoints;
+ $this->requiredGender = $this->rank->requiredGender;
+ $this->repeatImage = $this->rank->repeatImage;
+ $this->rankImage = $this->rank->rankImage;
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ I18nHandler::getInstance()->assignVariables(!empty($_POST));
+
+ WCF::getTPL()->assign(array(
+ 'rankID' => $this->rankID,
+ 'rank' => $this->rank,
+ 'action' => 'edit'
+ ));
+ }
+}
*/
public $notBanned = 0;
+ /**
+ * last activity start time
+ * @var string
+ */
+ public $lastActivityTimeStart = '';
+
+ /**
+ * last activity end time
+ * @var string
+ */
+ public $lastActivityTimeEnd = '';
+
+ /**
+ * enabled state
+ * @var boolean
+ */
+ public $enabled = 0;
+
+ /**
+ * disabled state
+ * @var boolean
+ */
+ public $disabled = 0;
+
/**
* matches
* @var array<integer>
if (isset($_POST['registrationDateEnd'])) $this->registrationDateEnd = $_POST['registrationDateEnd'];
if (isset($_POST['banned'])) $this->banned = intval($_POST['banned']);
if (isset($_POST['notBanned'])) $this->notBanned = intval($_POST['notBanned']);
+ if (isset($_POST['lastActivityTimeStart'])) $this->lastActivityTimeStart = $_POST['lastActivityTimeStart'];
+ if (isset($_POST['lastActivityTimeEnd'])) $this->lastActivityTimeEnd = $_POST['lastActivityTimeEnd'];
+ if (isset($_POST['enabled'])) $this->enabled = intval($_POST['enabled']);
+ if (isset($_POST['disabled'])) $this->disabled = intval($_POST['disabled']);
if (isset($_POST['itemsPerPage'])) $this->itemsPerPage = intval($_POST['itemsPerPage']);
if (isset($_POST['sortField'])) $this->sortField = $_POST['sortField'];
'sortField' => $this->sortField,
'sortOrder' => $this->sortOrder,
'itemsPerPage' => $this->itemsPerPage,
- 'columns' => $this->columns
+ 'columns' => $this->columns,
+ 'lastActivityTimeStart' => $this->lastActivityTimeStart,
+ 'lastActivityTimeEnd' => $this->lastActivityTimeEnd,
+ 'enabled' => $this->enabled,
+ 'disabled' => $this->disabled
));
}
if ($this->notBanned) {
$this->conditions->add('user_table.banned = ?', array(0));
}
+
+ // last activity time
+ if ($startDate = @strtotime($this->lastActivityTimeStart)) {
+ $this->conditions->add('user_table.lastActivityTime >= ?', array($startDate));
+ }
+ if ($endDate = @strtotime($this->lastActivityTimeEnd)) {
+ $this->conditions->add('user_table.lastActivityTime <= ?', array($endDate));
+ }
+
+ if ($this->enabled) {
+ $this->conditions->add('user_table.activationCode = ?', array(0));
+ }
+ if ($this->disabled) {
+ $this->conditions->add('user_table.activationCode <> ?', array(0));
+ }
}
/**
--- /dev/null
+<?php
+namespace wcf\acp\page;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\page\AbstractPage;
+use wcf\system\WCF;
+
+/**
+ * Provides a list of registered dashboard pages.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage acp.page
+ * @category Community Framework
+ */
+class DashboardListPage extends AbstractPage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.dashboard.list';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.content.dashboard.canEditDashboard');
+
+ /**
+ * list of object types per package id
+ * @var array<array>
+ */
+ public $objectTypes = array();
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // load object types
+ $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.dashboardContainer');
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'objectTypes' => $this->objectTypes
+ ));
+ }
+}
public function assignVariables() {
parent::assignVariables();
+ $usersAwaitingApproval = 0;
+ if (REGISTER_ACTIVATION_METHOD == 2) {
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user
+ WHERE activationCode <> 0";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ $row = $statement->fetchArray();
+ $usersAwaitingApproval = $row['count'];
+ }
+ WCF::getTPL()->assign('usersAwaitingApproval', $usersAwaitingApproval);
+
WCF::getTPL()->assign(array(
'server' => $this->server
));
/**
* @see wcf\page\SortablePage::$validSortFields
*/
- public $validSortFields = array('groupID', 'groupName', 'groupType', 'members');
+ public $validSortFields = array('groupID', 'groupName', 'groupType', 'members', 'priority');
/**
* @see wcf\page\MultipleLinkPage::$objectListClassName
--- /dev/null
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Lists available user ranks.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage acp.form
+ * @category Community Framework
+ */
+class UserRankListPage extends SortablePage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.acp.menu.link.user.rank.list';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('admin.user.rank.canManageRank');
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$objectListClassName
+ */
+ public $objectListClassName = 'wcf\data\user\rank\UserRankList';
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$defaultSortField
+ */
+ public $defaultSortField = 'rankTitle';
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$validSortFields
+ */
+ public $validSortFields = array('rankID', 'groupID', 'requiredPoints', 'rankTitle', 'rankImage', 'requiredGender');
+
+ /**
+ * @see wcf\page\MultipleLinkPage::show()
+ */
+ protected function initObjectList() {
+ parent::initObjectList();
+
+ $this->objectList->sqlSelects = 'user_group.groupName';
+ $this->objectList->sqlJoins = 'LEFT JOIN wcf'.WCF_N.'_user_group user_group ON (user_group.groupID = user_rank.groupID)';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\SystemException;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\authentication\UserAuthenticationFactory;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\HTTPRequest;
+use wcf\util\JSON;
+use wcf\util\StringUtil;
+
+/**
+ * Handles facebook auth.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage action
+ * @category Community Framework
+ */
+class FacebookAuthAction extends AbstractAction {
+ /**
+ * @see wcf\action\AbstractAction::$neededModules
+ */
+ public $neededModules = array('FACEBOOK_PUBLIC_KEY', 'FACEBOOK_PRIVATE_KEY');
+
+ /**
+ * @see wcf\action\IAction::execute()
+ */
+ public function execute() {
+ parent::execute();
+
+ $callbackURL = LinkHandler::getInstance()->getLink('FacebookAuth');
+ // user accepted the connection
+ if (isset($_GET['code'])) {
+ try {
+ // fetch access_token
+ $request = new HTTPRequest('https://graph.facebook.com/oauth/access_token?client_id='.FACEBOOK_PUBLIC_KEY.'&redirect_uri='.rawurlencode($callbackURL).'&client_secret='.FACEBOOK_PRIVATE_KEY.'&code='.rawurlencode($_GET['code']));
+ $request->execute();
+ $reply = $request->getReply();
+
+ $content = $reply['body'];
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+
+ // validate state, validation of state is executed after fetching the access_token to invalidate 'code'
+ if (!isset($_GET['state']) || $_GET['state'] != WCF::getSession()->getVar('__facebookInit')) throw new IllegalLinkException();
+
+ parse_str($content, $data);
+
+ try {
+ // fetch userdata
+ $request = new HTTPRequest('https://graph.facebook.com/me?access_token='.rawurlencode($data['access_token']).'&fields=birthday,bio,email,gender,id,location,name,picture.type(large),website');
+ $request->execute();
+ $reply = $request->getReply();
+
+ $content = $reply['body'];
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+
+ $userData = JSON::decode($content);
+
+ // check whether a user is connected to this facebook account
+ $user = $this->getUser($userData['id']);
+
+ if ($user->userID) {
+ // a user is already connected, but we are logged in, break
+ if (WCF::getUser()->userID) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.3rdparty.facebook.connect.error.inuse'));
+ }
+ // perform login
+ else {
+ if (UserAuthenticationFactory::getInstance()->getUserAuthentication()->supportsPersistentLogins()) {
+ $password = StringUtil::getRandomID();
+ $userEditor = new UserEditor($user);
+ $userEditor->update(array('password' => $password));
+
+ // reload user to retrieve salt
+ $user = new User($user->userID);
+
+ UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($user, $user->username, $password);
+ }
+
+ WCF::getSession()->changeUser($user);
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink());
+ }
+ }
+ else {
+ // save data for connection
+ if (WCF::getUser()->userID) {
+ WCF::getSession()->register('__facebookUsername', $userData['name']);
+ WCF::getSession()->register('__facebookData', $userData);
+
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty');
+ }
+ // save data and redirect to registration
+ else {
+ WCF::getSession()->register('__username', $userData['name']);
+ WCF::getSession()->register('__email', $userData['email']);
+ WCF::getSession()->register('__facebookData', $userData);
+
+ // we assume that bots won't register on facebook first
+ WCF::getSession()->register('recaptchaDone', true);
+
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register'));
+ }
+ }
+
+ $this->executed();
+ exit;
+ }
+ // user declined or any other error that may occur
+ if (isset($_GET['error'])) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.3rdparty.facebook.login.error.'.$_GET['error']));
+ }
+
+ // start auth by redirecting to facebook
+ $token = StringUtil::getRandomID();
+ WCF::getSession()->register('__facebookInit', $token);
+ HeaderUtil::redirect("https://www.facebook.com/dialog/oauth?client_id=".FACEBOOK_PUBLIC_KEY. "&redirect_uri=".rawurlencode($callbackURL)."&state=".$token."&scope=email,user_about_me,user_birthday,user_interests,user_location,user_website");
+ $this->executed();
+ exit;
+ }
+
+ /**
+ * Fetches the User with the given userID.
+ *
+ * @param integer $userID
+ * @return wcf\data\user\User
+ */
+ public function getUser($userID) {
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user
+ WHERE authData = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array('facebook:'.$userID));
+ $row = $statement->fetchArray();
+
+ if ($row === false) {
+ $row = array('userID' => 0);
+ }
+
+ $user = new User($row['userID']);
+ return $user;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\SystemException;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\authentication\UserAuthenticationFactory;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\HTTPRequest;
+use wcf\util\JSON;
+use wcf\util\StringUtil;
+
+/**
+ * Handles github auth.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage action
+ * @category Community Framework
+ */
+class GithubAuthAction extends AbstractAction {
+ /**
+ * @see wcf\action\AbstractAction::$neededModules
+ */
+ public $neededModules = array('GITHUB_PUBLIC_KEY', 'GITHUB_PRIVATE_KEY');
+
+ /**
+ * @see wcf\action\IAction::execute()
+ */
+ public function execute() {
+ parent::execute();
+
+ // user accepted the connection
+ if (isset($_GET['code'])) {
+ try {
+ // fetch access_token
+ $request = new HTTPRequest('https://github.com/login/oauth/access_token', array(), array(
+ 'client_id' => GITHUB_PUBLIC_KEY,
+ 'client_secret' => GITHUB_PRIVATE_KEY,
+ 'code' => $_GET['code']
+ ));
+ $request->execute();
+ $reply = $request->getReply();
+
+ $content = $reply['body'];
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+
+ // validate state, validation of state is executed after fetching the access_token to invalidate 'code'
+ if (!isset($_GET['state']) || $_GET['state'] != WCF::getSession()->getVar('__githubInit')) throw new IllegalLinkException();
+
+ parse_str($content, $data);
+
+ // check whether the token is okay
+ if (isset($data['error'])) throw new IllegalLinkException();
+
+ // check whether a user is connected to this github account
+ $user = $this->getUser($data['access_token']);
+
+ if ($user->userID) {
+ // a user is already connected, but we are logged in, break
+ if (WCF::getUser()->userID) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.3rdparty.github.connect.error.inuse'));
+ }
+ // perform login
+ else {
+ if (UserAuthenticationFactory::getInstance()->getUserAuthentication()->supportsPersistentLogins()) {
+ $password = StringUtil::getRandomID();
+ $userEditor = new UserEditor($user);
+ $userEditor->update(array('password' => $password));
+
+ // reload user to retrieve salt
+ $user = new User($user->userID);
+
+ UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($user, $user->username, $password);
+ }
+
+ WCF::getSession()->changeUser($user);
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink());
+ }
+ }
+ else {
+ try {
+ // fetch userdata
+ $request = new HTTPRequest('https://api.github.com/user?access_token='.$data['access_token']);
+ $request->execute();
+ $reply = $request->getReply();
+ $userData = JSON::decode(StringUtil::trim($reply['body']));
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+
+ // save data for connection
+ if (WCF::getUser()->userID) {
+ WCF::getSession()->register('__githubUsername', $userData['login']);
+ WCF::getSession()->register('__githubToken', $data['access_token']);
+
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty');
+ }
+ // save data and redirect to registration
+ else {
+ WCF::getSession()->register('__githubData', $userData);
+ WCF::getSession()->register('__username', $userData['login']);
+
+ // check whether user has entered a public email
+ if (isset($userData) && isset($userData['email']) && $userData['email'] !== null) {
+ WCF::getSession()->register('__email', $userData['email']);
+ }
+ // fetch emails via api
+ else {
+ try {
+ $request = new HTTPRequest('https://api.github.com/user/emails?access_token='.$data['access_token']);
+ $request->execute();
+ $reply = $request->getReply();
+ $emails = JSON::decode(StringUtil::trim($reply['body']));
+
+ // handle future response as well a current response (see. http://developer.github.com/v3/users/emails/)
+ if (is_string($emails[0])) {
+ $email = $emails[0];
+ }
+ else {
+ $email = $emails[0]['email'];
+ foreach ($emails as $tmp) {
+ if ($tmp['primary']) $email = $tmp['email'];
+ break;
+ }
+ }
+ WCF::getSession()->register('__email', $email);
+ }
+ catch (SystemException $e) { }
+ }
+
+ WCF::getSession()->register('__githubToken', $data['access_token']);
+
+ // we assume that bots won't register on github first
+ WCF::getSession()->register('recaptchaDone', true);
+
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register'));
+ }
+ }
+
+ $this->executed();
+ exit;
+ }
+ // user declined or any other error that may occur
+ if (isset($_GET['error'])) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.3rdparty.github.login.error.'.$_GET['error']));
+ }
+
+ // start auth by redirecting to github
+ $token = StringUtil::getRandomID();
+ WCF::getSession()->register('__githubInit', $token);
+ HeaderUtil::redirect("https://github.com/login/oauth/authorize?client_id=".rawurlencode(GITHUB_PUBLIC_KEY)."&scope=".rawurlencode('user:email')."&state=".$token);
+ $this->executed();
+ exit;
+ }
+
+ /**
+ * Fetches the User with the given access-token.
+ *
+ * @param string $token
+ * @return wcf\data\user\User
+ */
+ public function getUser($token) {
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user
+ WHERE authData = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array('github:'.$token));
+ $row = $statement->fetchArray();
+
+ if ($row === false) {
+ $row = array('userID' => 0);
+ }
+
+ $user = new User($row['userID']);
+ return $user;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\SystemException;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\authentication\UserAuthenticationFactory;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\HTTPRequest;
+use wcf\util\JSON;
+use wcf\util\StringUtil;
+
+/**
+ * Handles google auth.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage action
+ * @category Community Framework
+ */
+class GoogleAuthAction extends AbstractAction {
+ /**
+ * @see wcf\action\AbstractAction::$neededModules
+ */
+ public $neededModules = array('GOOGLE_PUBLIC_KEY', 'GOOGLE_PRIVATE_KEY');
+
+ /**
+ * @see wcf\action\IAction::execute()
+ */
+ public function execute() {
+ parent::execute();
+
+ $callbackURL = LinkHandler::getInstance()->getLink('GoogleAuth', array(
+ 'appendSession' => false
+ ));
+ // user accepted the connection
+ if (isset($_GET['code'])) {
+ try {
+ // fetch access_token
+ $request = new HTTPRequest('https://accounts.google.com/o/oauth2/token', array(), array(
+ 'code' => $_GET['code'],
+ 'client_id' => GOOGLE_PUBLIC_KEY,
+ 'client_secret' => GOOGLE_PRIVATE_KEY,
+ 'redirect_uri' => $callbackURL,
+ 'grant_type' => 'authorization_code'
+ ));
+ $request->execute();
+ $reply = $request->getReply();
+
+ $content = $reply['body'];
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+
+ // validate state, validation of state is executed after fetching the access_token to invalidate 'code'
+ if (!isset($_GET['state']) || $_GET['state'] != WCF::getSession()->getVar('__googleInit')) throw new IllegalLinkException();
+
+ $data = JSON::decode($content);
+
+ try {
+ // fetch userdata
+ $request = new HTTPRequest('https://www.googleapis.com/oauth2/v1/userinfo');
+ $request->addHeader('Authorization', 'Bearer '.$data['access_token']);
+ $request->execute();
+ $reply = $request->getReply();
+
+ $content = $reply['body'];
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+
+ $userData = JSON::decode($content);
+
+ // check whether a user is connected to this google account
+ $user = $this->getUser($userData['id']);
+
+ if ($user->userID) {
+ // a user is already connected, but we are logged in, break
+ if (WCF::getUser()->userID) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.3rdparty.google.connect.error.inuse'));
+ }
+ // perform login
+ else {
+ if (UserAuthenticationFactory::getInstance()->getUserAuthentication()->supportsPersistentLogins()) {
+ $password = StringUtil::getRandomID();
+ $userEditor = new UserEditor($user);
+ $userEditor->update(array('password' => $password));
+
+ // reload user to retrieve salt
+ $user = new User($user->userID);
+
+ UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($user, $user->username, $password);
+ }
+
+ WCF::getSession()->changeUser($user);
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink());
+ }
+ }
+ else {
+ // save data for connection
+ if (WCF::getUser()->userID) {
+ WCF::getSession()->register('__googleUsername', $userData['name']);
+ WCF::getSession()->register('__googleData', $userData);
+
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty');
+ }
+ // save data and redirect to registration
+ else {
+ WCF::getSession()->register('__username', $userData['name']);
+ if (isset($userData['email'])) WCF::getSession()->register('__email', $userData['email']);
+
+ WCF::getSession()->register('__googleData', $userData);
+
+ // we assume that bots won't register on facebook first
+ WCF::getSession()->register('recaptchaDone', true);
+
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register'));
+ }
+ }
+
+ $this->executed();
+ exit;
+ }
+ // user declined or any other error that may occur
+ if (isset($_GET['error'])) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.3rdparty.google.login.error.'.$_GET['error']));
+ }
+
+ // start auth by redirecting to google
+ $token = StringUtil::getRandomID();
+ WCF::getSession()->register('__googleInit', $token);
+ HeaderUtil::redirect("https://accounts.google.com/o/oauth2/auth?client_id=".rawurlencode(GOOGLE_PUBLIC_KEY). "&redirect_uri=".rawurlencode($callbackURL)."&state=".$token."&scope=https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email&response_type=code");
+ $this->executed();
+ exit;
+ }
+
+ /**
+ * Fetches the User with the given userID.
+ *
+ * @param integer $userID
+ * @return wcf\data\user\User
+ */
+ public function getUser($userID) {
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user
+ WHERE authData = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array('google:'.$userID));
+ $row = $statement->fetchArray();
+
+ if ($row === false) {
+ $row = array('userID' => 0);
+ }
+
+ $user = new User($row['userID']);
+ return $user;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\data\user\avatar\Gravatar;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\SystemException;
+use wcf\util\FileUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Downloads and caches gravatars.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage action
+ * @category Community Framework
+ */
+class GravatarDownloadAction extends AbstractAction {
+ /**
+ * user id
+ * @var integer
+ */
+ public $userID = 0;
+
+ /**
+ * user object
+ * @var wcf\data\user\User
+ */
+ public $user = null;
+
+ /**
+ * avatar size
+ * @var integer
+ */
+ public $size = 150;
+
+ /**
+ * @see wcf\action\IAction::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['userID'])) $this->userID = intval($_REQUEST['userID']);
+ $this->user = new User($this->userID);
+ if (!$this->user->userID) {
+ throw new IllegalLinkException();
+ }
+
+ if (!empty($_REQUEST['size'])) $this->size = intval($_REQUEST['size']);
+ }
+
+ /**
+ * @see wcf\action\IAction::execute()
+ */
+ public function execute() {
+ parent::execute();
+
+ if ($this->user->enableGravatar) {
+ // try to use cached gravatar
+ $cachedFilename = sprintf(Gravatar::GRAVATAR_CACHE_LOCATION, md5(StringUtil::toLowerCase($this->user->email)), $this->size);
+ if (file_exists(WCF_DIR.$cachedFilename) && filemtime(WCF_DIR.$cachedFilename) > (TIME_NOW - (Gravatar::GRAVATAR_CACHE_EXPIRE * 86400))) {
+ @header('Content-Type: image/png');
+ @readfile(WCF_DIR.$cachedFilename);
+ exit;
+ }
+
+ // try to download new version
+ $gravatarURL = sprintf(Gravatar::GRAVATAR_BASE, md5(StringUtil::toLowerCase($this->user->email)), $this->size, '404');
+ try {
+ $tmpFile = FileUtil::downloadFileFromHttp($gravatarURL, 'gravatar');
+ copy($tmpFile, WCF_DIR.$cachedFilename);
+ @unlink($tmpFile);
+ @chmod(WCF_DIR.$cachedFilename, 0777);
+
+ @header('Content-Type: image/png');
+ @readfile(WCF_DIR.$cachedFilename);
+ exit;
+ }
+ catch (SystemException $e) {
+ // disable gravatar
+ $editor = new UserEditor($this->user);
+ $editor->update(array(
+ 'enableGravatar' => 0
+ ));
+ }
+ }
+
+ // fallback to default avatar
+ @header('Content-Type: image/svg+xml');
+ @readfile(WCF_DIR.'images/avatars/avatar-default.svg');
+ exit;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+
+/**
+ * Does the user logout.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage action
+ * @category Community Framework
+ */
+class LogoutAction extends \wcf\acp\action\LogoutAction {
+ const AVAILABLE_DURING_OFFLINE_MODE = true;
+
+ /**
+ * @see wcf\action\IAction::execute()
+ */
+ public function execute() {
+ AbstractSecureAction::execute();
+
+ // do logout
+ WCF::getSession()->delete();
+
+ // remove cookies
+ if (isset($_COOKIE[COOKIE_PREFIX.'userID'])) {
+ HeaderUtil::setCookie('userID', 0);
+ }
+ if (isset($_COOKIE[COOKIE_PREFIX.'password'])) {
+ HeaderUtil::setCookie('password', '');
+ }
+
+ $this->executed();
+
+ // forward to index page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->get('wcf.user.logout.redirect'));
+ exit;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\notification\event\UserNotificationEvent;
+use wcf\data\user\User;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Allows a user to disable notifications by a direct link.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage action
+ * @category Community Framework
+ */
+class NotificationDisableAction extends AbstractAction {
+ /**
+ * event id
+ * @var integer
+ */
+ public $eventID = 0;
+
+ /**
+ * notification event
+ * @var wcf\data\user\notification\event\UserNotificationEvent
+ */
+ public $event = null;
+
+ /**
+ * user id
+ * @var integer
+ */
+ public $userID = 0;
+
+ /**
+ * user object
+ * @var wcf\data\user\User
+ */
+ public $user = null;
+
+ /**
+ * security token
+ * @var string
+ */
+ public $token = '';
+
+ /**
+ * @see wcf\action\IAction::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['eventID'])) $this->eventID = intval($_REQUEST['eventID']);
+ $this->event = new UserNotificationEvent($this->eventID);
+ if (!$this->event->eventID) {
+ throw new IllegalLinkException();
+ }
+
+ if (isset($_REQUEST['userID'])) $this->userID = intval($_REQUEST['userID']);
+ $this->user = new User($this->userID);
+ if (!$this->user->userID) {
+ throw new IllegalLinkException();
+ }
+
+ if (isset($_REQUEST['token'])) $this->token = StringUtil::trim($_REQUEST['token']);
+ if (empty($this->token) || $this->token != $this->user->notificationMailToken) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ /**
+ * @see wcf\action\IAction::execute()
+ */
+ public function execute() {
+ parent::execute();
+
+ // get object type
+ $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.notification.notificationType', 'com.woltlab.wcf.notification.notificationType.mail');
+
+ // delete notification setting
+ $sql = "DELETE FROM wcf".WCF_N."_user_notification_event_notification_type
+ WHERE userID = ?
+ AND eventID = ?
+ AND notificationTypeID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->userID,
+ $this->eventID,
+ $objectType->objectTypeID
+ ));
+ $this->executed();
+
+ // redirect to url
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->get('wcf.user.notification.mail.disabled'));
+ exit;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\SystemException;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\authentication\UserAuthenticationFactory;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\HTTPRequest;
+use wcf\util\StringUtil;
+
+/**
+ * Handles twitter auth.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage action
+ * @category Community Framework
+ */
+class TwitterAuthAction extends AbstractAction {
+ /**
+ * @see wcf\action\AbstractAction::$neededModules
+ */
+ public $neededModules = array('TWITTER_PUBLIC_KEY', 'TWITTER_PRIVATE_KEY');
+
+ /**
+ * @see wcf\action\IAction::execute()
+ */
+ public function execute() {
+ parent::execute();
+
+ // user accepted
+ if (isset($_GET['oauth_token']) && isset($_GET['oauth_verifier'])) {
+ // fetch data created in the first step
+ $initData = WCF::getSession()->getVar('__twitterInit');
+ if (!$initData) throw new IllegalLinkException();
+
+ // validate oauth_token
+ if ($_GET['oauth_token'] !== $initData['oauth_token']) throw new IllegalLinkException();
+
+ try {
+ // fetch access_token
+ $oauthHeader = array(
+ 'oauth_consumer_key' => TWITTER_PUBLIC_KEY,
+ 'oauth_nonce' => StringUtil::getRandomID(),
+ 'oauth_signature_method' => 'HMAC-SHA1',
+ 'oauth_timestamp' => TIME_NOW,
+ 'oauth_version' => '1.0',
+ 'oauth_token' => $initData['oauth_token']
+ );
+ $postData = array(
+ 'oauth_verifier' => $_GET['oauth_verifier']
+ );
+
+ $signature = $this->createSignature('https://api.twitter.com/oauth/access_token', array_merge($oauthHeader, $postData));
+ $oauthHeader['oauth_signature'] = $signature;
+
+ $request = new HTTPRequest('https://api.twitter.com/oauth/access_token', array(), $postData);
+ $request->addHeader('Authorization', 'OAuth '.$this->buildOAuthHeader($oauthHeader));
+ $request->execute();
+ $reply = $request->getReply();
+ $content = $reply['body'];
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+
+ parse_str($content, $data);
+
+ // check whether a user is connected to this twitter account
+ $user = $this->getUser($data['user_id']);
+
+ if ($user->userID) {
+ // a user is already connected, but we are logged in, break
+ if (WCF::getUser()->userID) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.3rdparty.twitter.connect.error.inuse'));
+ }
+ // perform login
+ else {
+ if (UserAuthenticationFactory::getInstance()->getUserAuthentication()->supportsPersistentLogins()) {
+ $password = StringUtil::getRandomID();
+ $userEditor = new UserEditor($user);
+ $userEditor->update(array('password' => $password));
+
+ // reload user to retrieve salt
+ $user = new User($user->userID);
+
+ UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($user, $user->username, $password);
+ }
+
+ WCF::getSession()->changeUser($user);
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink());
+ }
+ }
+ else {
+ // save data for connection
+ if (WCF::getUser()->userID) {
+ WCF::getSession()->register('__twitterUsername', $data['screen_name']);
+ WCF::getSession()->register('__twitterData', $data);
+
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('AccountManagement').'#3rdParty');
+ }
+ // save data and redirect to registration
+ else {
+ // fetch user data
+ $twitterData = null;
+ try {
+ $request = new HTTPRequest('https://api.twitter.com/1.1/users/show.json?screen_name=' . $data['screen_name']);
+ $request->execute();
+ $reply = $request->getReply();
+ $twitterData = json_decode($reply['body'], true);
+ }
+ catch (SystemException $e) { /* ignore errors */ }
+
+ WCF::getSession()->register('__username', $data['screen_name']);
+
+ if ($twitterData !== null) $data = $twitterData;
+ WCF::getSession()->register('__twitterData', $data);
+
+ // we assume that bots won't register on twitter first
+ WCF::getSession()->register('recaptchaDone', true);
+
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register'));
+ }
+ }
+
+ $this->executed();
+ exit;
+ }
+
+ // user declined
+ if (isset($_GET['denied'])) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.3rdparty.twitter.login.error.denied'));
+ }
+
+ // start auth by fetching request_token
+ try {
+ $callbackURL = LinkHandler::getInstance()->getLink('TwitterAuth', array(
+ 'appendSession' => false
+ ));
+ $oauthHeader = array(
+ 'oauth_callback' => $callbackURL,
+ 'oauth_consumer_key' => TWITTER_PUBLIC_KEY,
+ 'oauth_nonce' => StringUtil::getRandomID(),
+ 'oauth_signature_method' => 'HMAC-SHA1',
+ 'oauth_timestamp' => TIME_NOW,
+ 'oauth_version' => '1.0'
+ );
+ $signature = $this->createSignature('https://api.twitter.com/oauth/request_token', $oauthHeader);
+ $oauthHeader['oauth_signature'] = $signature;
+
+ // call api
+ $request = new HTTPRequest('https://api.twitter.com/oauth/request_token', array('method' => 'POST'));
+ $request->addHeader('Authorization', 'OAuth '.$this->buildOAuthHeader($oauthHeader));
+ $request->execute();
+ $reply = $request->getReply();
+
+ $content = $reply['body'];
+ }
+ catch (SystemException $e) {
+ throw new IllegalLinkException();
+ }
+
+ parse_str($content, $data);
+ if ($data['oauth_callback_confirmed'] != 'true') throw new IllegalLinkException();
+
+ WCF::getSession()->register('__twitterInit', $data);
+ // redirect to twitter
+ HeaderUtil::redirect('https://api.twitter.com/oauth/authenticate?oauth_token='.rawurlencode($data['oauth_token']));
+
+ $this->executed();
+ exit;
+ }
+
+ /**
+ * Builds the OAuth authorization header.
+ *
+ * @param array $parameters
+ * @return string
+ */
+ public function buildOAuthHeader(array $parameters) {
+ $header = '';
+ foreach ($parameters as $key => $val) {
+ if ($header !== '') $header .= ', ';
+ $header .= rawurlencode($key).'="'.rawurlencode($val).'"';
+ }
+
+ return $header;
+ }
+
+ /**
+ * Creates an OAuth 1 signature.
+ *
+ * @param string $url
+ * @param array $parameters
+ * @param string $tokenSecret
+ * @return string
+ */
+ public function createSignature($url, array $parameters, $tokenSecret = '') {
+ $tmp = array();
+ foreach ($parameters as $key => $val) {
+ $tmp[rawurlencode($key)] = rawurlencode($val);
+ }
+ $parameters = $tmp;
+
+ uksort($parameters, 'strcmp');
+ $parameterString = '';
+ foreach ($parameters as $key => $val) {
+ if ($parameterString !== '') $parameterString .= '&';
+ $parameterString .= $key.'='.$val;
+ }
+
+ $base = "POST&".rawurlencode($url)."&".rawurlencode($parameterString);
+ $key = rawurlencode(TWITTER_PRIVATE_KEY).'&'.rawurlencode($tokenSecret);
+
+ return base64_encode(hash_hmac('sha1', $base, $key, true));
+ }
+
+ /**
+ * Fetches the User with the given userID
+ *
+ * @param integer $userID
+ * @return wcf\data\user\User
+ */
+ public function getUser($userID) {
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user
+ WHERE authData = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array('twitter:'.$userID));
+ $row = $statement->fetchArray();
+
+ if ($row === false) {
+ $row = array('userID' => 0);
+ }
+
+ $user = new User($row['userID']);
+ return $user;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Default interface for action classes providing grouped user lists.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IGroupedUserListAction {
+ /**
+ * Validates parameters to return a parsed list of users.
+ */
+ public function validateGetGroupedUserList();
+
+ /**
+ * Returns a parsed list of users.
+ *
+ * @return array
+ */
+ public function getGroupedUserList();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Default interface for user generated content.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IUserContent extends ILinkableObject, ITitledObject {
+ /**
+ * Returns message creation timestamp.
+ *
+ * @return integer
+ */
+ public function getTime();
+
+ /**
+ * Returns author's user id.
+ *
+ * @return integer
+ */
+ public function getUserID();
+
+ /**
+ * Returns author's username.
+ *
+ * @return string
+ */
+ public function getUsername();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Default interface for objects supporting visit tracking.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IVisitableObjectAction {
+ /**
+ * Marks objects as read.
+ */
+ public function markAsRead();
+
+ /**
+ * Validates parameters to mark objects as read.
+ */
+ public function validateMarkAsRead();
+}
--- /dev/null
+<?php
+namespace wcf\data\dashboard\box;
+use wcf\data\DatabaseObject;
+
+/**
+ * Represents a dashboard box.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.dashboard.box
+ * @category Community Framework
+ */
+class DashboardBox extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'dashboard_box';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'boxID';
+}
--- /dev/null
+<?php
+namespace wcf\data\dashboard\box;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\ISortableAction;
+use wcf\system\dashboard\DashboardHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * Executes dashboard box-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.dashboard.box
+ * @category Community Framework
+ */
+class DashboardBoxAction extends AbstractDatabaseObjectAction implements ISortableAction {
+ /**
+ * list of available dashboard boxes
+ * @var array<wcf\data\dashboard\box\DashboardBox>
+ */
+ public $boxes = array();
+
+ /**
+ * box structure
+ * @var array<integer>
+ */
+ public $boxStructure = array();
+
+ /**
+ * object type object
+ * @var wcf\data\object\type\ObjectType
+ */
+ public $objectType = null;
+
+ /**
+ * @see wcf\data\ISortableAction::validateUpdatePosition()
+ */
+ public function validateUpdatePosition() {
+ // validate permissions
+ WCF::getSession()->checkPermissions(array('admin.content.dashboard.canEditDashboard'));
+
+ $this->readString('boxType');
+ $this->readInteger('objectTypeID');
+
+ // validate box type
+ if (!in_array($this->parameters['boxType'], array('content', 'sidebar'))) {
+ throw new UserInputException('boxType');
+ }
+
+ // validate object type
+ $objectType = ObjectTypeCache::getInstance()->getObjectType($this->parameters['objectTypeID']);
+ if ($objectType !== null) {
+ $objectTypeDefinition = ObjectTypeCache::getInstance()->getDefinitionByName('com.woltlab.wcf.user.dashboardContainer');
+ if ($objectTypeDefinition !== null) {
+ if ($objectType->definitionID == $objectTypeDefinition->definitionID) {
+ $this->objectType = $objectType;
+ }
+ }
+ }
+
+ if ($this->objectType === null) {
+ throw new UserInputException('objectTypeID');
+ }
+
+ // read all dashboard boxes of the relevant box type
+ $boxList = new DashboardBoxList();
+ $boxList->getConditionBuilder()->add("dashboard_box.boxType = ?", array($this->parameters['boxType']));
+ $boxList->readObjects();
+ $this->boxes = $boxList->getObjects();
+
+ // parse structure
+ if (isset($this->parameters['data']) & isset($this->parameters['data']['structure']) && isset($this->parameters['data']['structure'][0])) {
+ $this->boxStructure = ArrayUtil::toIntegerArray($this->parameters['data']['structure'][0]);
+
+ // validate box ids
+ if (!empty($this->boxStructure)) {
+ foreach ($this->boxStructure as $boxID) {
+ if (!isset($this->boxes[$boxID])) {
+ throw new UserInputException('boxID');
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @see wcf\data\ISortableAction::updatePosition()
+ */
+ public function updatePosition() {
+ // remove previous settings
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("objectTypeID = ?", array($this->objectType->objectTypeID));
+ if (!empty($this->boxes)) {
+ $conditions->add("boxID IN (?)", array(array_keys($this->boxes)));
+ }
+
+ $sql = "DELETE FROM wcf".WCF_N."_dashboard_option
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ // update settings
+ if (!empty($this->boxStructure)) {
+ $sql = "INSERT INTO wcf".WCF_N."_dashboard_option
+ (objectTypeID, boxID, showOrder)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ WCF::getDB()->beginTransaction();
+ foreach ($this->boxStructure as $index => $boxID) {
+ $showOrder = $index + 1;
+
+ $statement->execute(array(
+ $this->objectType->objectTypeID,
+ $boxID,
+ $showOrder
+ ));
+ }
+ WCF::getDB()->commitTransaction();
+ }
+
+ // reset cache
+ DashboardHandler::clearCache();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\dashboard\box;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit dashboard boxes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.dashboard.box
+ * @category Community Framework
+ */
+class DashboardBoxEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\dashboard\box\DashboardBox';
+}
--- /dev/null
+<?php
+namespace wcf\data\dashboard\box;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of dashboard boxes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.dashboard.box
+ * @category Community Framework
+ */
+class DashboardBoxList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user;
+use wcf\data\user\group\UserGroup;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\WCF;
+use wcf\util\UserRegistrationUtil;
+
+/**
+ * Executes user-related actions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user
+ * @category Community Framework
+ */
+class ExtendedUserAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$className
+ */
+ public $className = 'wcf\data\user\UserEditor';
+
+ /**
+ * Validates the enable action.
+ */
+ public function validateEnable() {
+ WCF::getSession()->checkPermissions(array('admin.user.canEnableUser'));
+ }
+
+ /**
+ * Validates the disable action.
+ */
+ public function validateDisable() {
+ $this->validateEnable();
+ }
+
+ /**
+ * Enables users.
+ */
+ public function enable() {
+ if (empty($this->objects)) $this->readObjects();
+
+ $action = new UserAction($this->objects, 'update', array(
+ 'data' => array(
+ 'activationCode' => 0
+ ),
+ 'groups' => array(
+ UserGroup::USERS
+ ),
+ 'removeGroups' => array(
+ UserGroup::GUESTS
+ )
+ ));
+ $action->executeAction();
+
+ // update user rank
+ if (MODULE_USER_RANK) {
+ $action = new UserProfileAction($this->objects, 'updateUserRank');
+ $action->executeAction();
+ }
+ // update user online marking
+ $action = new UserProfileAction($this->objects, 'updateUserOnlineMarking');
+ $action->executeAction();
+ }
+
+ /**
+ * Disables users.
+ */
+ public function disable() {
+ if (empty($this->objects)) $this->readObjects();
+
+ $action = new UserAction($this->objects, 'update', array(
+ 'data' => array(
+ 'activationCode' => UserRegistrationUtil::getActivationCode()
+ ),
+ 'removeGroups' => array(
+ UserGroup::USERS
+ ),
+ 'groups' => array(
+ UserGroup::GUESTS
+ )
+ ));
+ $action->executeAction();
+
+ // update user rank
+ if (MODULE_USER_RANK) {
+ $action = new UserProfileAction($this->objects, 'updateUserRank');
+ $action->executeAction();
+ }
+ // update user online marking
+ $action = new UserProfileAction($this->objects, 'updateUserOnlineMarking');
+ $action->executeAction();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user;
+use wcf\data\user\group\Team;
+use wcf\data\user\group\UserGroup;
+use wcf\system\WCF;
+
+/**
+ * Represents a list of team user groups.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user
+ * @category Community Framework
+ */
+class TeamList extends UserProfileList {
+ /**
+ * teams included in the list
+ * @var array<wcf\data\user\group\Team>
+ */
+ protected $teams = array();
+
+ /**
+ * @see wcf\data\DatabaseObjectList::countObjects()
+ */
+ public function countObjects() {
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user_group user_group,
+ wcf".WCF_N."_user_to_group user_to_group
+ WHERE user_to_group.groupID = user_group.groupID
+ AND user_group.showOnTeamPage = 1";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ $row = $statement->fetchArray();
+ return $row['count'];
+ }
+
+ /**
+ * @see wcf\data\DatabaseObjectList::readObjectIDs()
+ */
+ public function readObjectIDs() {
+ $this->objectIDs = array();
+ $sql = "SELECT user_to_group.userID AS objectID
+ FROM wcf".WCF_N."_user_group user_group,
+ wcf".WCF_N."_user_to_group user_to_group
+ LEFT JOIN wcf".WCF_N."_user user_table
+ ON (user_table.userID = user_to_group.userID)
+ WHERE user_to_group.groupID = user_group.groupID
+ AND user_group.showOnTeamPage = 1
+ ORDER BY user_group.priority DESC".(!empty($this->sqlOrderBy) ? ", ".$this->sqlOrderBy : '');
+ $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
+ $statement->execute();
+ while ($row = $statement->fetchArray()) {
+ $this->objectIDs[] = $row['objectID'];
+ }
+ }
+
+ /**
+ * @see wcf\data\DatabaseObjectList::readObjects()
+ */
+ public function readObjects() {
+ parent::readObjects();
+
+ $sql = "SELECT user_to_group.*
+ FROM wcf".WCF_N."_user_group user_group,
+ wcf".WCF_N."_user_to_group user_to_group
+ LEFT JOIN wcf".WCF_N."_user user_table
+ ON (user_table.userID = user_to_group.userID)
+ WHERE user_to_group.groupID = user_group.groupID
+ AND user_group.showOnTeamPage = 1
+ ORDER BY user_group.priority DESC".(!empty($this->sqlOrderBy) ? ", ".$this->sqlOrderBy : '');
+ $statement = WCF::getDB()->prepareStatement($sql, $this->sqlLimit, $this->sqlOffset);
+ $statement->execute();
+ while ($row = $statement->fetchArray()) {
+ if (!isset($this->teams[$row['groupID']])) {
+ $userGroup = UserGroup::getGroupByID($row['groupID']);
+ $this->teams[$row['groupID']] = new Team($userGroup);
+ }
+
+ $this->teams[$row['groupID']]->addMember($this->objects[$row['userID']]);
+ }
+ }
+
+ /**
+ * Returns the teams in the list.
+ *
+ * @return array<wcf\data\user\group\Team>
+ */
+ public function getTeams() {
+ return $this->teams;
+ }
+}
use wcf\data\IClipboardAction;
use wcf\data\ISearchAction;
use wcf\system\clipboard\ClipboardHandler;
+use wcf\system\cache\builder\UserNotificationEventCacheBuilder;
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\exception\PermissionDeniedException;
use wcf\system\exception\UserInputException;
$languageIDs = (isset($this->parameters['languages'])) ? $this->parameters['languages'] : array();
$userEditor->addToLanguages($languageIDs);
+ if (PACKAGE_ID) {
+ // set default notifications
+ $sql = "INSERT INTO wcf".WCF_N."_user_notification_event_to_user
+ (userID, eventID)
+ VALUES (?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ foreach (UserNotificationEventCacheBuilder::getInstance()->getData() as $events) {
+ foreach ($events as $event) {
+ if ($event->preset) {
+ $statement->execute(array($user->userID, $event->eventID));
+ }
+ }
+ }
+ }
+
return $user;
}
foreach ($this->objects as $userEditor) {
$userEditor->addToGroups($groupIDs, $deleteOldGroups, $addDefaultGroups);
}
+
+ if (MODULE_USER_RANK) {
+ $action = new UserProfileAction($this->objects, 'updateUserRank');
+ $action->executeAction();
+ }
+ if (MODULE_USERS_ONLINE) {
+ $action = new UserProfileAction($this->objects, 'updateUserOnlineMarking');
+ $action->executeAction();
+ }
}
/**
--- /dev/null
+<?php
+namespace wcf\data\user;
+use wcf\data\user\avatar\DefaultAvatar;
+use wcf\data\user\avatar\Gravatar;
+use wcf\data\user\avatar\UserAvatar;
+use wcf\data\user\online\UserOnline;
+use wcf\data\user\rank\UserRank;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\breadcrumb\Breadcrumb;
+use wcf\system\breadcrumb\IBreadcrumbProvider;
+use wcf\system\cache\builder\UserGroupPermissionCacheBuilder;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\online\location\UserOnlineLocationHandler;
+use wcf\system\user\signature\SignatureCache;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+use wcf\util\DateUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Decorates the user object and provides functions to retrieve data for user profiles.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user
+ * @category Community Framework
+ */
+class UserProfile extends DatabaseObjectDecorator implements IBreadcrumbProvider {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\User';
+
+ /**
+ * cached list of user profiles
+ * @var array<wcf\data\user\UserProfile>
+ */
+ protected static $userProfiles = array();
+
+ /**
+ * list of ignored user ids
+ * @var array<integer>
+ */
+ protected $ignoredUserIDs = null;
+
+ /**
+ * list of follower user ids
+ * @var array<integer>
+ */
+ protected $followerUserIDs = null;
+
+ /**
+ * list of following user ids
+ * @var array<integer>
+ */
+ protected $followingUserIDs = null;
+
+ /**
+ * user avatar
+ * @var wcf\data\user\avatar\IUserAvatar
+ */
+ protected $avatar = null;
+
+ /**
+ * user rank object
+ * @var wcf\data\user\rank\UserRank
+ */
+ protected $rank = null;
+
+ /**
+ * age of this user
+ * @var integer
+ */
+ protected $__age = null;
+
+ /**
+ * group data and permissions
+ * @var array<array>
+ */
+ protected $groupData = null;
+
+ /**
+ * current location of this user.
+ * @var string
+ */
+ protected $currentLocation = null;
+
+ const GENDER_MALE = 1;
+ const GENDER_FEMALE = 2;
+
+ const ACCESS_EVERYONE = 0;
+ const ACCESS_REGISTERED = 1;
+ const ACCESS_FOLLOWING = 2;
+ const ACCESS_NOBODY = 3;
+
+ /**
+ * @see wcf\data\user\User::__toString()
+ */
+ public function __toString() {
+ return $this->getDecoratedObject()->__toString();
+ }
+
+ /**
+ * Returns a list of all user ids being followed by current user.
+ *
+ * @return array<integer>
+ */
+ public function getFollowingUsers() {
+ if ($this->followingUserIDs === null) {
+ $this->followingUserIDs = array();
+
+ if ($this->userID) {
+ // load storage data
+ UserStorageHandler::getInstance()->loadStorage(array($this->userID));
+
+ // get ids
+ $data = UserStorageHandler::getInstance()->getStorage(array($this->userID), 'followingUserIDs');
+
+ // cache does not exist or is outdated
+ if ($data[$this->userID] === null) {
+ $sql = "SELECT followUserID
+ FROM wcf".WCF_N."_user_follow
+ WHERE userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->userID));
+ while ($row = $statement->fetchArray()) {
+ $this->followingUserIDs[] = $row['followUserID'];
+ }
+
+ // update storage data
+ UserStorageHandler::getInstance()->update($this->userID, 'followingUserIDs', serialize($this->followingUserIDs));
+ }
+ else {
+ $this->followingUserIDs = unserialize($data[$this->userID]);
+ }
+ }
+ }
+
+ return $this->followingUserIDs;
+ }
+
+ /**
+ * Returns a list of user ids following current user.
+ *
+ * @return array<integer>
+ */
+ public function getFollowers() {
+ if ($this->followerUserIDs === null) {
+ $this->followerUserIDs = array();
+
+ if ($this->userID) {
+ // load storage data
+ UserStorageHandler::getInstance()->loadStorage(array($this->userID));
+
+ // get ids
+ $data = UserStorageHandler::getInstance()->getStorage(array($this->userID), 'followerUserIDs');
+
+ // cache does not exist or is outdated
+ if ($data[$this->userID] === null) {
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user_follow
+ WHERE followUserID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->userID));
+ while ($row = $statement->fetchArray()) {
+ $this->followerUserIDs[] = $row['userID'];
+ }
+
+ // update storage data
+ UserStorageHandler::getInstance()->update($this->userID, 'followerUserIDs', serialize($this->followerUserIDs));
+ }
+ else {
+ $this->followerUserIDs = unserialize($data[$this->userID]);
+ }
+ }
+ }
+
+ return $this->followerUserIDs;
+ }
+
+ /**
+ * Returns a list of ignored user ids.
+ *
+ * @return array<integer>
+ */
+ public function getIgnoredUsers() {
+ if ($this->ignoredUserIDs === null) {
+ $this->ignoredUserIDs = array();
+
+ if ($this->userID) {
+ // load storage data
+ UserStorageHandler::getInstance()->loadStorage(array($this->userID));
+
+ // get ids
+ $data = UserStorageHandler::getInstance()->getStorage(array($this->userID), 'ignoredUserIDs');
+
+ // cache does not exist or is outdated
+ if ($data[$this->userID] === null) {
+ $sql = "SELECT ignoreUserID
+ FROM wcf".WCF_N."_user_ignore
+ WHERE userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->userID));
+ while ($row = $statement->fetchArray()) {
+ $this->ignoredUserIDs[] = $row['ignoreUserID'];
+ }
+
+ // update storage data
+ UserStorageHandler::getInstance()->update($this->userID, 'ignoredUserIDs', serialize($this->ignoredUserIDs));
+ }
+ else {
+ $this->ignoredUserIDs = unserialize($data[$this->userID]);
+ }
+ }
+ }
+
+ return $this->ignoredUserIDs;
+ }
+
+ /**
+ * Returns true if current user is following given user id.
+ *
+ * @param integer $userID
+ * @return boolean
+ */
+ public function isFollowing($userID) {
+ return in_array($userID, $this->getFollowingUsers());
+ }
+
+ /**
+ * Returns true if given user ids follows current user.
+ *
+ * @param integer $userID
+ * @return boolean
+ */
+ public function isFollower($userID) {
+ return in_array($userID, $this->getFollowers());
+ }
+
+ /**
+ * Returns true if given user is ignored.
+ *
+ * @param integer $userID
+ * @return boolean
+ */
+ public function isIgnoredUser($userID) {
+ return in_array($userID, $this->getIgnoredUsers());
+ }
+
+ /**
+ * Gets the user's avatar.
+ *
+ * @return wcf\data\user\avatar\IUserAvatar
+ */
+ public function getAvatar() {
+ if ($this->avatar === null) {
+ if (!$this->disableAvatar) {
+ if ($this->avatarID) {
+ if (!$this->fileHash) {
+ // load storage data
+ UserStorageHandler::getInstance()->loadStorage(array($this->userID));
+ $data = UserStorageHandler::getInstance()->getStorage(array($this->userID), 'avatar');
+
+ if ($data[$this->userID] === null) {
+ $this->avatar = new UserAvatar($this->avatarID);
+ UserStorageHandler::getInstance()->update($this->userID, 'avatar', serialize($this->avatar));
+ }
+ else {
+ $this->avatar = unserialize($data[$this->userID]);
+ }
+ }
+ else {
+ $this->avatar = new UserAvatar(null, $this->getDecoratedObject()->data);
+ }
+ }
+ else if (MODULE_GRAVATAR && $this->enableGravatar) {
+ $this->avatar = new Gravatar($this->userID, $this->email);
+ }
+ }
+
+ // use default avatar
+ if ($this->avatar === null) {
+ $this->avatar = new DefaultAvatar();
+ }
+ }
+
+ return $this->avatar;
+ }
+
+ /**
+ * Returns true if this user is currently online.
+ *
+ * @return boolean
+ */
+ public function isOnline() {
+ if ($this->lastActivityTime && $this->lastActivityTime > (TIME_NOW - USER_ONLINE_TIMEOUT) && $this->canViewOnlineStatus()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the active user can view the online status of this user.
+ *
+ * @return boolean
+ */
+ public function canViewOnlineStatus() {
+ return (WCF::getUser()->userID == $this->userID || WCF::getSession()->getPermission('admin.user.canViewInvisible') || $this->isAccessible('canViewOnlineStatus'));
+ }
+
+ /**
+ * Returns the current location of this user.
+ *
+ * @return string
+ */
+ public function getCurrentLocation() {
+ if ($this->currentLocation === null) {
+ $this->currentLocation = '';
+ $this->currentLocation = UserOnlineLocationHandler::getInstance()->getLocation(new UserOnline(new User(null, array(
+ 'controller' => $this->controller,
+ 'objectID' => $this->locationObjectID
+ ))));
+ }
+
+ return $this->currentLocation;
+ }
+
+ /**
+ * Returns the last activity time.
+ *
+ * @return integer
+ */
+ public function getLastActivityTime() {
+ return max($this->lastActivityTime, $this->sessionLastActivityTime);
+ }
+
+ /**
+ * Returns a new user profile object.
+ *
+ * @param integer $userID
+ * @return wcf\data\user\UserProfile
+ */
+ public static function getUserProfile($userID) {
+ $users = self::getUserProfiles(array($userID));
+
+ return (isset($users[$userID]) ? $users[$userID] : null);
+ }
+
+ /**
+ * Returns a list of user profiles.
+ *
+ * @param array $userIDs
+ * @return array<wcf\data\user\UserProfile>
+ */
+ public static function getUserProfiles(array $userIDs) {
+ $users = array();
+
+ // check cache
+ foreach ($userIDs as $index => $userID) {
+ if (isset(self::$userProfiles[$userID])) {
+ $users[$userID] = self::$userProfiles[$userID];
+ unset($userIDs[$index]);
+ }
+ }
+
+ if (!empty($userIDs)) {
+ $userList = new UserProfileList();
+ $userList->getConditionBuilder()->add("user_table.userID IN (?)", array($userIDs));
+ $userList->readObjects();
+
+ foreach ($userList as $user) {
+ $users[$user->userID] = $user;
+ self::$userProfiles[$user->userID] = $user;
+ }
+ }
+
+ return $users;
+ }
+
+ /**
+ * Returns the user profile of the user with the given name.
+ *
+ * @param string $username
+ * @return wcf\data\user\UserProfile
+ */
+ public static function getUserProfileByUsername($username) {
+ $users = self::getUserProfilesByUsername(array($username));
+
+ return $users[$username];
+ }
+
+ /**
+ * Returns the user profiles of the users with the given names.
+ *
+ * @param array<string> $usernames
+ * @return array<wcf\data\user\UserProfile>
+ */
+ public static function getUserProfilesByUsername(array $usernames) {
+ $users = array();
+
+ // save case sensitive usernames
+ $caseSensitiveUsernames = array();
+ foreach ($usernames as &$username) {
+ $tmp = StringUtil::toLowerCase($username);
+ $caseSensitiveUsernames[$tmp] = $username;
+ $username = $tmp;
+ }
+ unset($username);
+
+ // check cache
+ foreach ($usernames as $index => $username) {
+ foreach (self::$userProfiles as $user) {
+ if (StringUtil::toLowerCase($user->username) === $username) {
+ $users[$username] = $user;
+ unset($usernames[$index]);
+ }
+ }
+ }
+
+ if (!empty($usernames)) {
+ $userList = new UserProfileList();
+ $userList->getConditionBuilder()->add("user_table.username IN (?)", array($usernames));
+ $userList->readObjects();
+
+ foreach ($userList as $user) {
+ $users[StringUtil::toLowerCase($user->username)] = $user;
+ self::$userProfiles[$user->userID] = $user;
+ }
+
+ foreach ($usernames as $username) {
+ if (!isset($users[$username])) {
+ $users[$username] = null;
+ }
+ }
+ }
+
+ // revert usernames to original case
+ foreach ($users as $username => $user) {
+ unset($users[$username]);
+ $users[$caseSensitiveUsernames[$username]] = $user;
+ }
+
+ return $users;
+ }
+
+ /**
+ * Returns true if current user fulfills the required permissions.
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function isAccessible($name) {
+ switch ($this->$name) {
+ case self::ACCESS_EVERYONE:
+ return true;
+ break;
+
+ case self::ACCESS_REGISTERED:
+ return (WCF::getUser()->userID ? true : false);
+ break;
+
+ case self::ACCESS_FOLLOWING:
+ return ($this->isFollowing(WCF::getUser()->userID) ? true : false);
+ break;
+
+ case self::ACCESS_NOBODY:
+ return false;
+ break;
+ }
+ }
+
+ /**
+ * Returns true if current user profile is protected.
+ *
+ * @return boolean
+ */
+ public function isProtected() {
+ return (!WCF::getSession()->getPermission('admin.general.canViewPrivateUserOptions') && !$this->isAccessible('canViewProfile') && $this->userID != WCF::getUser()->userID);
+ }
+
+ /**
+ * Returns the age of this user.
+ *
+ * @return integer
+ */
+ public function getAge() {
+ if ($this->__age === null) {
+ if ($this->birthday && $this->birthdayShowYear) {
+ $this->__age = DateUtil::getAge($this->birthday);
+ }
+ else {
+ $this->__age = 0;
+ }
+ }
+
+ return $this->__age;
+ }
+
+ /**
+ * Returns the age of user account in days.
+ *
+ * @return integer
+ */
+ public function getProfileAge() {
+ return (TIME_NOW - $this->registrationDate) / 86400;
+ }
+
+ /**
+ * Returns the value of the permission with the given name.
+ *
+ * @param string $permission
+ * @return mixed permission value
+ */
+ public function getPermission($permission) {
+ if ($this->groupData === null) $this->loadGroupData();
+
+ if (!isset($this->groupData[$permission])) return false;
+ return $this->groupData[$permission];
+ }
+
+ /**
+ * Returns the user title of this user.
+ *
+ * @return string
+ */
+ public function getUserTitle() {
+ if ($this->userTitle) return $this->userTitle;
+ if ($this->getRank()) return WCF::getLanguage()->get($this->getRank()->rankTitle);
+
+ return '';
+ }
+
+ /**
+ * Returns the user rank.
+ *
+ * @return wcf\data\user\rank\UserRank
+ */
+ public function getRank() {
+ if ($this->rank === null) {
+ if (MODULE_USER_RANK && $this->rankID) {
+ if ($this->rankTitle) {
+ $this->rank = new UserRank(null, array(
+ 'rankID' => $this->rankID,
+ 'groupID' => $this->groupID,
+ 'requiredPoints' => $this->requiredPoints,
+ 'rankTitle' => $this->rankTitle,
+ 'cssClassName' => $this->cssClassName,
+ 'rankImage' => $this->rankImage,
+ 'repeatImage' => $this->repeatImage,
+ 'requiredGender' => $this->requiredGender
+ ));
+ }
+ else {
+ // load storage data
+ UserStorageHandler::getInstance()->loadStorage(array($this->userID));
+ $data = UserStorageHandler::getInstance()->getStorage(array($this->userID), 'userRank');
+
+ if ($data[$this->userID] === null) {
+ $this->rank = new UserRank($this->rankID);
+ UserStorageHandler::getInstance()->update($this->userID, 'userRank', serialize($this->rank));
+ }
+ else {
+ $this->rank = unserialize($data[$this->userID]);
+ }
+ }
+ }
+ }
+
+ return $this->rank;
+ }
+
+ /**
+ * Loads group data from cache.
+ */
+ protected function loadGroupData() {
+ // get group data from cache
+ $this->groupData = UserGroupPermissionCacheBuilder::getInstance()->getData($this->getGroupIDs());
+ if (isset($this->groupData['groupIDs']) && $this->groupData['groupIDs'] != $this->getGroupIDs()) {
+ $this->groupData = array();
+ }
+ }
+
+ /**
+ * Returns the old username of this user.
+ *
+ * @return string
+ */
+ public function getOldUsername() {
+ if ($this->oldUsername) {
+ if ($this->lastUsernameChange + PROFILE_SHOW_OLD_USERNAME * 86400 > TIME_NOW) {
+ return $this->oldUsername;
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Returns true if this user can edit his profile.
+ *
+ * @return boolean
+ */
+ public function canEditOwnProfile() {
+ return ($this->activationCode ? false : true);
+ }
+
+ /**
+ * @see wcf\system\breadcrumb\IBreadcrumbProvider::getBreadcrumb()
+ */
+ public function getBreadcrumb() {
+ return new Breadcrumb($this->username, LinkHandler::getInstance()->getLink('User', array(
+ 'object' => $this
+ )));
+ }
+
+ /**
+ * Returns the encoded email address.
+ *
+ * @return string
+ */
+ public function getEncodedEmail() {
+ return StringUtil::encodeAllChars($this->email);
+ }
+
+ /**
+ * Returns true if the current user is connected with Facebook.
+ *
+ * @return boolean
+ */
+ public function isConnectedWithFacebook() {
+ return StringUtil::startsWith($this->authData, 'facebook:');
+ }
+
+ /**
+ * Returns true if the current user is connected with GitHub.
+ *
+ * @return boolean
+ */
+ public function isConnectedWithGithub() {
+ return StringUtil::startsWith($this->authData, 'github:');
+ }
+
+ /**
+ * Returns true if the current user is connected with Google Plus.
+ *
+ * @return boolean
+ */
+ public function isConnectedWithGoogle() {
+ return StringUtil::startsWith($this->authData, 'google:');
+ }
+
+ /**
+ * Returns true if the current user is connected with Twitter.
+ *
+ * @return boolean
+ */
+ public function isConnectedWithTwitter() {
+ return StringUtil::startsWith($this->authData, 'twitter:');
+ }
+
+ /**
+ * Returns 3rd party auth provider name.
+ *
+ * @return string
+ */
+ public function getAuthProvider() {
+ if (!$this->authData) {
+ return '';
+ }
+
+ return StringUtil::substring($this->authData, 0, StringUtil::indexOf($this->authData, ':'));
+ }
+
+ /**
+ * Return true if the user's signature is visible.
+ *
+ * @return boolean
+ */
+ public function showSignature() {
+ if (!$this->signature) return false;
+ if ($this->disabledSignature) return false;
+ if (WCF::getUser()->userID && !WCF::getUser()->showSignature) return false;
+
+ return true;
+ }
+
+ /**
+ * Returns the parsed signature.
+ *
+ * @return string
+ */
+ public function getSignature() {
+ return SignatureCache::getInstance()->getSignature($this->getDecoratedObject());
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\bbcode\BBCodeParser;
+use wcf\system\bbcode\MessageParser;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\option\user\UserOptionHandler;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Executes user profile-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user
+ * @category Community Framework
+ */
+class UserProfileAction extends UserAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ */
+ protected $allowGuestAccess = array('getUserProfile', 'getDetailedActivityPointList');
+
+ /**
+ * user profile object
+ * @var wcf\data\user\UserProfile
+ */
+ public $userProfile = null;
+
+ /**
+ * Validates parameters for signature preview.
+ */
+ public function validateGetMessagePreview() {
+ $this->readString('message', true, 'data');
+
+ if (!isset($this->parameters['options'])) {
+ throw new UserInputException('options');
+ }
+
+ if (isset($this->parameters['options']['enableBBCodes']) && WCF::getSession()->getPermission('user.signature.canUseBBCodes')) {
+ $disallowedBBCodes = BBCodeParser::getInstance()->validateBBCodes($this->parameters['data']['message'], explode(',', WCF::getSession()->getPermission('user.signature.allowedBBCodes')));
+ if (!empty($disallowedBBCodes)) {
+ throw new UserInputException('message', 'disallowedBBCodes', $disallowedBBCodes);
+ }
+ }
+ }
+
+ /**
+ * Returns a rendered signature preview.
+ *
+ * @return array
+ */
+ public function getMessagePreview() {
+ // get options
+ $enableBBCodes = (isset($this->parameters['options']['enableBBCodes'])) ? 1 : 0;
+ $enableHtml = (isset($this->parameters['options']['enableHtml'])) ? 1 : 0;
+ $enableSmilies = (isset($this->parameters['options']['enableSmilies'])) ? 1 : 0;
+
+ // validate permissions for options
+ if ($enableBBCodes && !WCF::getSession()->getPermission('user.signature.canUseBBCodes')) $enableBBCodes = 0;
+ if ($enableHtml && !WCF::getSession()->getPermission('user.signature.canUseHtml')) $enableHtml = 0;
+ if ($enableSmilies && !WCF::getSession()->getPermission('user.signature.canUseSmilies')) $enableSmilies = 0;
+
+ // parse message
+ $message = StringUtil::trim($this->parameters['data']['message']);
+ $preview = MessageParser::getInstance()->parse($message, $enableSmilies, $enableHtml, $enableBBCodes, false);
+
+ return array(
+ 'message' => $preview
+ );
+ }
+
+ /**
+ * Validates user profile preview.
+ */
+ public function validateGetUserProfile() {
+ if (count($this->objectIDs) != 1) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ /**
+ * Returns user profile preview.
+ *
+ * @return array
+ */
+ public function getUserProfile() {
+ $userID = reset($this->objectIDs);
+
+ $userProfileList = new UserProfileList();
+ $userProfileList->getConditionBuilder()->add("user_table.userID = ?", array($userID));
+ $userProfileList->readObjects();
+ $userProfiles = $userProfileList->getObjects();
+
+ WCF::getTPL()->assign(array(
+ 'user' => reset($userProfiles)
+ ));
+
+ return array(
+ 'template' => WCF::getTPL()->fetch('userProfilePreview'),
+ 'userID' => $userID
+ );
+ }
+
+ /**
+ * Validates detailed activity point list
+ */
+ public function validateGetDetailedActivityPointList() {
+ if (count($this->objectIDs) != 1) {
+ throw new UserInputException('objectIDs');
+ }
+ $this->userProfile = UserProfile::getUserProfile(reset($this->objectIDs));
+
+ if ($this->userProfile === null) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ /**
+ * Returns detailed activity point list.
+ *
+ * @return array
+ */
+ public function getDetailedActivityPointList() {
+ $activityPointObjectTypes = array();
+ foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.activityPointEvent') as $objectType) {
+ $activityPointObjectTypes[$objectType->objectTypeID] = $objectType;
+ }
+
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('userID = ?', array($this->userProfile->userID));
+ $conditionBuilder->add('objectTypeID IN (?)', array(array_keys($activityPointObjectTypes)));
+
+ $sql = "SELECT objectTypeID, activityPoints
+ FROM wcf".WCF_N."_user_activity_point
+ ".$conditionBuilder;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditionBuilder->getParameters());
+ while ($row = $statement->fetchArray()) {
+ $activityPointObjectTypes[$row['objectTypeID']]->activityPoints = $row['activityPoints'];
+ }
+
+ WCF::getTPL()->assign(array(
+ 'activityPointObjectTypes' => $activityPointObjectTypes,
+ 'user' => $this->userProfile
+ ));
+
+ return array(
+ 'template' => WCF::getTPL()->fetch('detailedActivityPointList'),
+ 'userID' => $this->userProfile->userID
+ );
+ }
+
+ /**
+ * Validates parameters to begin profile inline editing.
+ */
+ public function validateBeginEdit() {
+ if (!empty($this->objectIDs) && count($this->objectIDs) == 1) {
+ $userID = reset($this->objectIDs);
+ $this->userProfile = UserProfile::getUserProfile($userID);
+ }
+
+ if ($this->userProfile === null || !$this->userProfile->userID) {
+ throw new UserInputException('objectIDs');
+ }
+
+ if (!$this->userProfile->canEdit() && $this->userProfile->userID != WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+ else if (!$this->userProfile->canEditOwnProfile()) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Begins profile inline editing.
+ *
+ * @return array
+ */
+ public function beginEdit() {
+ $optionTree = $this->getOptionHandler($this->userProfile->getDecoratedObject())->getOptionTree();
+ WCF::getTPL()->assign(array(
+ 'errorType' => array(),
+ 'optionTree' => $optionTree,
+ '__userTitle' => $this->userProfile->userTitle
+ ));
+
+ return array(
+ 'template' => WCF::getTPL()->fetch('userProfileAboutEditable')
+ );
+ }
+
+ /**
+ * Validates parameters to save changes to user profile.
+ */
+ public function validateSave() {
+ $this->validateBeginEdit();
+
+ if (!isset($this->parameters['values']) || !is_array($this->parameters['values'])) {
+ throw new UserInputException('values');
+ }
+
+ if (isset($this->parameters['values']['__userTitle']) && !WCF::getSession()->getPermission('user.profile.canEditUserTitle')) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Saves changes to user profile.
+ *
+ * @return array
+ */
+ public function save() {
+ $userTitle = null;
+ if (isset($this->parameters['values']['__userTitle'])) {
+ $userTitle = $this->parameters['values']['__userTitle'];
+ unset($this->parameters['values']['__userTitle']);
+ }
+
+ $optionHandler = $this->getOptionHandler($this->userProfile->getDecoratedObject());
+ $optionHandler->readUserInput($this->parameters);
+
+ $errors = $optionHandler->validate();
+
+ // validate user title
+ if ($userTitle !== null) {
+ try {
+ if (StringUtil::length($userTitle) > USER_TITLE_MAX_LENGTH) {
+ throw new UserInputException('__userTitle', 'tooLong');
+ }
+ if (!StringUtil::executeWordFilter($userTitle, USER_FORBIDDEN_TITLES)) {
+ throw new UserInputException('__userTitle', 'forbidden');
+ }
+ }
+ catch (UserInputException $e) {
+ $errors[$e->getField()] = $e->getType();
+ }
+ }
+
+ // validation was successful
+ if (empty($errors)) {
+ $saveOptions = $optionHandler->save();
+ $data = array(
+ 'options' => $saveOptions
+ );
+
+ // save user title
+ if ($userTitle !== null) {
+ $data['data'] = array(
+ 'userTitle' => $userTitle
+ );
+ }
+
+ $userAction = new UserAction(array($this->userProfile->userID), 'update', $data);
+ $userAction->executeAction();
+
+ // return parsed template
+ $user = new User($this->userProfile->userID);
+
+ // reload option handler
+ $optionHandler = $this->getOptionHandler($user, false);
+
+ $options = $optionHandler->getOptionTree();
+ WCF::getTPL()->assign(array(
+ 'options' => $options,
+ 'userID' => $this->userProfile->userID
+ ));
+
+ return array(
+ 'success' => true,
+ 'template' => WCF::getTPL()->fetch('userProfileAbout')
+ );
+ }
+ else {
+ // validation failed
+ WCF::getTPL()->assign(array(
+ 'errorType' => $errors,
+ 'optionTree' => $optionHandler->getOptionTree(),
+ '__userTitle' => ($userTitle !== null ? $userTitle : $this->userProfile->userTitle)
+ ));
+
+ return array(
+ 'success' => false,
+ 'template' => WCF::getTPL()->fetch('userProfileAboutEditable')
+ );
+ }
+ }
+
+ /**
+ * Updates user ranks.
+ */
+ public function updateUserRank() {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ $resetUserIDs = array();
+ foreach ($this->objects as $user) {
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('user_rank.groupID IN (?)', array($user->getGroupIDs()));
+ $conditionBuilder->add('user_rank.requiredPoints <= ?', array($user->activityPoints));
+ if ($user->gender) $conditionBuilder->add('user_rank.requiredGender IN (?)', array(array(0, $user->gender)));
+ else $conditionBuilder->add('user_rank.requiredGender = ?', array(0));
+
+ $sql = "SELECT user_rank.rankID
+ FROM wcf".WCF_N."_user_rank user_rank
+ LEFT JOIN wcf".WCF_N."_user_group user_group
+ ON (user_group.groupID = user_rank.groupID)
+ ".$conditionBuilder."
+ ORDER BY user_group.priority DESC, user_rank.requiredPoints DESC, user_rank.requiredGender DESC";
+ $statement = WCF::getDB()->prepareStatement($sql, 1);
+ $statement->execute($conditionBuilder->getParameters());
+ $row = $statement->fetchArray();
+ if ($row === false) {
+ if ($user->rankID) {
+ $user->update(array('rankID' => null));
+ $resetUserIDs[] = $user->userID;
+ }
+ }
+ else {
+ if ($row['rankID'] != $user->rankID) {
+ $user->update(array('rankID' => $row['rankID']));
+ $resetUserIDs[] = $user->userID;
+ }
+ }
+ }
+
+ if (!empty($resetUserIDs)) {
+ UserStorageHandler::getInstance()->reset($resetUserIDs, 'userRank');
+ }
+ }
+
+ /**
+ * Updates user online markings.
+ */
+ public function updateUserOnlineMarking() {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ foreach ($this->objects as $user) {
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('groupID IN (?)', array($user->getGroupIDs()));
+
+ $sql = "SELECT groupID
+ FROM wcf".WCF_N."_user_group
+ ".$conditionBuilder."
+ ORDER BY priority DESC";
+ $statement = WCF::getDB()->prepareStatement($sql, 1);
+ $statement->execute($conditionBuilder->getParameters());
+ $row = $statement->fetchArray();
+ if ($row['groupID'] != $user->userOnlineGroupID) {
+ $user->update(array('userOnlineGroupID' => $row['groupID']));
+ }
+ }
+ }
+
+ /**
+ * Returns the user option handler object.
+ *
+ * @param wcf\data\user\User $user
+ * @param boolean $editMode
+ * @return wcf\system\option\user\UserOptionHandler
+ */
+ protected function getOptionHandler(User $user, $editMode = true) {
+ $optionHandler = new UserOptionHandler(false, '', 'profile');
+ if (!$editMode) {
+ $optionHandler->showEmptyOptions(false);
+ $optionHandler->enableEditMode(false);
+ }
+ $optionHandler->setUser($user);
+
+ return $optionHandler;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user;
+
+/**
+ * Represents a list of user profiles.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user
+ * @category Community Framework
+ */
+class UserProfileList extends UserList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$sqlOrderBy
+ */
+ public $sqlOrderBy = 'user_table.username';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$decoratorClassName
+ */
+ public $decoratorClassName = 'wcf\data\user\UserProfile';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::__construct()
+ */
+ public function __construct() {
+ parent::__construct();
+
+ if (!empty($this->sqlSelects)) $this->sqlSelects .= ',';
+ $this->sqlSelects .= "user_avatar.*";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_avatar user_avatar ON (user_avatar.avatarID = user_table.avatarID)";
+
+ if (MODULE_USER_RANK) {
+ $this->sqlSelects .= ",user_rank.*";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_rank user_rank ON (user_rank.rankID = user_table.rankID)";
+ }
+
+ // get current location
+ $this->sqlSelects .= ", session.controller, session.objectID AS locationObjectID, session.lastActivityTime AS sessionLastActivityTime";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_session session ON (session.userID = user_table.userID)";
+ }
+
+ /**
+ * @see wcf\data\DatabaseObjectList::readObjects()
+ */
+ public function readObjects() {
+ if ($this->objectIDs === null) {
+ $this->readObjectIDs();
+ }
+
+ parent::readObjects();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user;
+use wcf\system\exception\UserInputException;
+use wcf\util\StringUtil;
+use wcf\util\UserRegistrationUtil;
+use wcf\util\UserUtil;
+
+/**
+ * Executes user registration-related actions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user
+ * @category Community Framework
+ */
+class UserRegistrationAction extends UserAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ */
+ protected $allowGuestAccess = array('validateEmailAddress', 'validatePassword', 'validateUsername');
+
+ /**
+ * Validates the validate username function.
+ */
+ public function validateValidateUsername() {
+ $this->readString('username');
+ }
+
+ /**
+ * Validates the validate email address function.
+ */
+ public function validateValidateEmailAddress() {
+ $this->readString('email');
+ }
+
+ /**
+ * Validates the validate password function.
+ */
+ public function validateValidatePassword() {
+ $this->readString('password');
+ }
+
+ /**
+ * Validates the given username.
+ *
+ * @return array
+ */
+ public function validateUsername() {
+ if (!UserRegistrationUtil::isValidUsername($this->parameters['username'])) {
+ return array(
+ 'isValid' => false,
+ 'error' => 'notValid'
+ );
+ }
+
+ if (!UserUtil::isAvailableUsername($this->parameters['username'])) {
+ return array(
+ 'isValid' => false,
+ 'error' => 'notUnique'
+ );
+ }
+
+ return array(
+ 'isValid' => true
+ );
+ }
+
+ /**
+ * Validates given email address.
+ *
+ * @return array
+ */
+ public function validateEmailAddress() {
+ if (!UserRegistrationUtil::isValidEmail($this->parameters['email'])) {
+ return array(
+ 'isValid' => false,
+ 'error' => 'notValid'
+ );
+ }
+
+ if (!UserUtil::isAvailableEmail($this->parameters['email'])) {
+ return array(
+ 'isValid' => false,
+ 'error' => 'notUnique'
+ );
+ }
+
+ return array(
+ 'isValid' => true
+ );
+ }
+
+ /**
+ * Validates given password.
+ *
+ * @return array
+ */
+ public function validatePassword() {
+ if (!UserRegistrationUtil::isSecurePassword($this->parameters['password'])) {
+ return array(
+ 'isValid' => false,
+ 'error' => 'notSecure'
+ );
+ }
+
+ return array(
+ 'isValid' => true
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\activity\event;
+use wcf\data\DatabaseObject;
+
+/**
+ * Represents a user's activity.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.event
+ * @category Community Framework
+ */
+class UserActivityEvent extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_activity_event';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'eventID';
+
+ /**
+ * @see wcf\data\IStorableObject::__get()
+ */
+ public function __get($name) {
+ $value = parent::__get($name);
+
+ // treat additional data as data variables if it is an array
+ if ($value === null && isset($this->data['additionalData'][$name])) {
+ $value = $this->data['additionalData'][$name];
+ }
+
+ return $value;
+ }
+
+ /**
+ * @see wcf\data\DatabaseObject::handleData()
+ */
+ protected function handleData($data) {
+ parent::handleData($data);
+
+ $this->data['additionalData'] = @unserialize($this->data['additionalData']);
+ if (!is_array($this->data['additionalData'])) {
+ $this->data['additionalData'] = array();
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\activity\event;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\WCF;
+
+/**
+ * Executes user activity event-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.event
+ * @category Community Framework
+ */
+class UserActivityEventAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ */
+ public $allowGuestAccess = array('load');
+
+ /**
+ * Validates parameters to load recent activity entries.
+ */
+ public function validateLoad() {
+ $this->readBoolean('filteredByFollowedUsers', true);
+ $this->readInteger('lastEventTime');
+ $this->readInteger('userID', true);
+ }
+
+ /**
+ * Loads a list of recent activity entries.
+ *
+ * @return array
+ */
+ public function load() {
+ $eventList = new ViewableUserActivityEventList();
+ $eventList->getConditionBuilder()->add("user_activity_event.time < ?", array($this->parameters['lastEventTime']));
+
+ // profile view
+ if ($this->parameters['userID']) {
+ $eventList->getConditionBuilder()->add("user_activity_event.userID = ?", array($this->parameters['userID']));
+ }
+ else if ($this->parameters['filteredByFollowedUsers'] && count(WCF::getUserProfileHandler()->getFollowingUsers())) {
+ $eventList->getConditionBuilder()->add('user_activity_event.userID IN (?)', array(WCF::getUserProfileHandler()->getFollowingUsers()));
+ }
+
+ $eventList->readObjects();
+ $lastEventTime = $eventList->getLastEventTime();
+
+ if (!$lastEventTime) {
+ return array();
+ }
+
+ // removes orphaned and non-accessable events
+ UserActivityEventHandler::validateEvents($eventList);
+
+ // parse template
+ WCF::getTPL()->assign(array(
+ 'eventList' => $eventList
+ ));
+
+ return array(
+ 'lastEventTime' => $lastEventTime,
+ 'template' => WCF::getTPL()->fetch('recentActivityListItem')
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\activity\event;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit user activity events.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.event
+ * @category Community Framework
+ */
+class UserActivityEventEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\activity\event\UserActivityEvent';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\activity\event;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of user activity events.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.event
+ * @category Community Framework
+ */
+class UserActivityEventList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\activity\event;
+use wcf\data\user\UserProfile;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+
+/**
+ * Provides methods for viewable user activity events.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.event
+ * @category Community Framework
+ */
+class ViewableUserActivityEvent extends DatabaseObjectDecorator {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ public static $baseClass = 'wcf\data\user\activity\event\UserActivityEvent';
+
+ /**
+ * event text
+ * @var string
+ */
+ protected $description = '';
+
+ /**
+ * accessible by current user
+ * @var boolean
+ */
+ protected $isAccessible = false;
+
+ /**
+ * associated object was removed
+ * @var boolean
+ */
+ protected $isOrphaned = false;
+
+ /**
+ * event title
+ * @var string
+ */
+ protected $title = '';
+
+ /**
+ * user profile
+ * @var wcf\data\user\UserProfile
+ */
+ protected $userProfile = null;
+
+ /**
+ * Marks this event as accessible for current user.
+ */
+ public function setIsAccessible() {
+ $this->isAccessible = true;
+ }
+
+ /**
+ * Returns true if event is accessible by current user.
+ *
+ * @return boolean
+ */
+ public function isAccessible() {
+ return $this->isAccessible;
+ }
+
+ /**
+ * Marks this event as orphaned.
+ */
+ public function setIsOrphaned() {
+ $this->isOrphaned = true;
+ }
+
+ /**
+ * Returns true if event is orphaned (associated object removed).
+ *
+ * @return boolean
+ */
+ public function isOrphaned() {
+ return $this->isOrphaned;
+ }
+
+ /**
+ * Sets user profile.
+ *
+ * @param wcf\data\user\UserProfile $userProfile
+ */
+ public function setUserProfile(UserProfile $userProfile) {
+ $this->userProfile = $userProfile;
+ }
+
+ /**
+ * Returns user profile.
+ *
+ * @return wcf\data\user\UserProfile
+ */
+ public function getUserProfile() {
+ return $this->userProfile;
+ }
+
+ /**
+ * Sets event text.
+ *
+ * @param string $description
+ */
+ public function setDescription($description) {
+ $this->description = $description;
+ }
+
+ /**
+ * Returns event text.
+ *
+ * @return string
+ */
+ public function getDescription() {
+ return $this->description;
+ }
+
+ /**
+ * Sets event title.
+ *
+ * @param string $title
+ */
+ public function setTitle($title) {
+ $this->title = $title;
+ }
+
+ /**
+ * Returns event title.
+ *
+ * @return string
+ */
+ public function getTitle() {
+ return $this->title;
+ }
+
+ /**
+ * Returns the object type name.
+ *
+ * @return string
+ */
+ public function getObjectTypeName() {
+ return UserActivityEventHandler::getInstance()->getObjectType($this->objectTypeID)->objectType;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\activity\event;
+use wcf\data\user\UserProfile;
+use wcf\system\language\LanguageFactory;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a list of viewable user activity events.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.event
+ * @category Community Framework
+ */
+class ViewableUserActivityEventList extends UserActivityEventList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$className
+ */
+ public $className = 'wcf\data\user\activity\event\UserActivityEvent';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$sqlLimit
+ */
+ public $sqlLimit = 20;
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$sqlOrderBy
+ */
+ public $sqlOrderBy = 'user_activity_event.time DESC';
+
+ /**
+ * Creates a new ViewableUserActivityEventList object.
+ */
+ public function __construct() {
+ parent::__construct();
+
+ if (LanguageFactory::getInstance()->multilingualismEnabled() && count(WCF::getUser()->getLanguageIDs())) {
+ $this->getConditionBuilder()->add('(user_activity_event.languageID IN (?) OR user_activity_event.languageID IS NULL)', array(WCF::getUser()->getLanguageIDs()));
+ }
+ }
+
+ /**
+ * @see wcf\data\DatabaseObjectList::readObjects()
+ */
+ public function readObjects() {
+ parent::readObjects();
+
+ $userIDs = array();
+ $eventGroups = array();
+ foreach ($this->objects as &$event) {
+ $userIDs[] = $event->userID;
+ $event = new ViewableUserActivityEvent($event);
+
+ if (!isset($eventGroups[$event->objectTypeID])) {
+ $objectType = UserActivityEventHandler::getInstance()->getObjectType($event->objectTypeID);
+ $eventGroups[$event->objectTypeID] = array(
+ 'className' => $objectType->className,
+ 'objects' => array()
+ );
+ }
+
+ $eventGroups[$event->objectTypeID]['objects'][] = $event;
+ }
+ unset($event);
+
+ // set user profiles
+ if (!empty($userIDs)) {
+ $userIDs = array_unique($userIDs);
+
+ $users = UserProfile::getUserProfiles($userIDs);
+ foreach ($this->objects as $event) {
+ $event->setUserProfile($users[$event->userID]);
+ }
+ }
+
+ // parse events
+ foreach ($eventGroups as $eventData) {
+ $eventClass = call_user_func(array($eventData['className'], 'getInstance'));
+ $eventClass->prepare($eventData['objects']);
+ }
+ }
+
+ /**
+ * Returns timestamp of oldest entry fetched.
+ *
+ * @return integer
+ */
+ public function getLastEventTime() {
+ $lastEventTime = 0;
+ foreach ($this->objects as $event) {
+ if (!$lastEventTime) {
+ $lastEventTime = $event->time;
+ }
+
+ $lastEventTime = min($lastEventTime, $event->time);
+ }
+
+ return $lastEventTime;
+ }
+
+ /**
+ * Validates event permissions and returns a list of orphaned event ids.
+ *
+ * @return array<integer>
+ */
+ public function validateEvents() {
+ $orphanedEventIDs = array();
+
+ foreach ($this->objects as $index => $event) {
+ if (!$event->isAccessible()) {
+ unset($this->objects[$index]);
+ }
+ else if ($event->isOrphaned()) {
+ $orphanedEventIDs[] = $event->eventID;
+ unset($this->objects[$index]);
+ }
+ }
+ $this->indexToObject = array_keys($this->objects);
+
+ return $orphanedEventIDs;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\activity\point\event;
+use wcf\data\DatabaseObject;
+
+/**
+ * Represents a user activity point event.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.point.event
+ * @category Community Framework
+ */
+class UserActivityPointEvent extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_activity_point_event';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'eventID';
+
+ /**
+ * @see wcf\data\IStorableObject::__get()
+ */
+ public function __get($name) {
+ $value = parent::__get($name);
+
+ // treat additional data as data variables if it is an array
+ if ($value === null && isset($this->data['additionalData'][$name])) {
+ $value = $this->data['additionalData'][$name];
+ }
+
+ return $value;
+ }
+
+ /**
+ * @see wcf\data\DatabaseObject::handleData()
+ */
+ protected function handleData($data) {
+ parent::handleData($data);
+
+ $this->data['additionalData'] = @unserialize($this->data['additionalData']);
+ if (!is_array($this->data['additionalData'])) {
+ $this->data['additionalData'] = array();
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\activity\point\event;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes user activity point event-related actions.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.point.event
+ * @category Community Framework
+ */
+class UserActivityPointEventAction extends AbstractDatabaseObjectAction { }
--- /dev/null
+<?php
+namespace wcf\data\user\activity\point\event;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit user activity point events.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.point.event
+ * @category Community Framework
+ */
+class UserActivityPointEventEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\activity\point\event\UserActivityPointEvent';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\activity\point\event;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of user activity point events.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.activity.point.event
+ * @category Community Framework
+ */
+class UserActivityPointEventList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\avatar;
+use wcf\system\WCF;
+
+/**
+ * Represents a default avatar.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.avatar
+ * @category Community Framework
+ */
+class DefaultAvatar implements IUserAvatar {
+ /**
+ * image size
+ * @var integer
+ */
+ public $size = 150;
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getURL()
+ */
+ public function getURL($size = null) {
+ if ($size === null) $size = $this->size;
+
+ return WCF::getPath().'images/avatars/avatar-default.svg';
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getImageTag()
+ */
+ public function getImageTag($size = null) {
+ if ($size === null) $size = $this->size;
+
+ return '<img src="'.$this->getURL($size).'" style="width: '.$size.'px; height: '.$size.'px" alt="'.WCF::getLanguage()->get('wcf.user.avatar.alt').'" />';
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getWidth()
+ */
+ public function getWidth() {
+ return $this->size;
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getHeight()
+ */
+ public function getHeight() {
+ return $this->size;
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::canCrop()
+ */
+ public function canCrop() {
+ return false;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\avatar;
+use wcf\system\exception\SystemException;
+use wcf\system\request\LinkHandler;
+use wcf\util\FileUtil;
+use wcf\util\StringUtil;
+use wcf\system\WCF;
+
+/**
+ * Represents a gravatar.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.avatar
+ * @category Community Framework
+ * @see http://www.gravatar.com
+ */
+class Gravatar extends DefaultAvatar {
+ /**
+ * gravatar base url
+ * @var string
+ */
+ const GRAVATAR_BASE = 'http://gravatar.com/avatar/%s?s=%d&r=g&d=%s';
+
+ /**
+ * gravatar local cache location
+ * @var string
+ */
+ const GRAVATAR_CACHE_LOCATION = 'images/avatars/gravatars/%s-%s.png';
+
+ /**
+ * gravatar expire time (days)
+ * @var integer
+ */
+ const GRAVATAR_CACHE_EXPIRE = 7;
+
+ /**
+ * user id
+ * @var integer
+ */
+ public $userID = 0;
+
+ /**
+ * gravatar e-mail address
+ * @var string
+ */
+ public $gravatar = '';
+
+ /**
+ * urls of this gravatar
+ * @var array<string>
+ */
+ protected $url = array();
+
+ /**
+ * Creates a new Gravatar object.
+ *
+ * @param integer $userID
+ * @param string $gravatar
+ */
+ public function __construct($userID, $gravatar) {
+ $this->userID = $userID;
+ $this->gravatar = $gravatar;
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getURL()
+ */
+ public function getURL($size = null) {
+ if ($size === null) $size = $this->size;
+
+ if (!isset($this->url[$size])) {
+ // try to use cached gravatar
+ $cachedFilename = sprintf(self::GRAVATAR_CACHE_LOCATION, md5(StringUtil::toLowerCase($this->gravatar)), $size);
+ if (file_exists(WCF_DIR.$cachedFilename) && filemtime(WCF_DIR.$cachedFilename) > (TIME_NOW - (self::GRAVATAR_CACHE_EXPIRE * 86400))) {
+ $this->url[$size] = WCF::getPath().$cachedFilename;
+ }
+ else {
+ $this->url[$size] = LinkHandler::getInstance()->getLink('GravatarDownload', array(
+ 'forceFrontend' => true
+ ), 'userID='.$this->userID.'&size='.$size);
+ }
+ }
+
+ return $this->url[$size];
+ }
+
+ /**
+ * Checks a given email address for gravatar support.
+ *
+ * @param string $email
+ * @return boolean
+ */
+ public static function test($email) {
+ $gravatarURL = sprintf(self::GRAVATAR_BASE, md5(StringUtil::toLowerCase($email)), 80, '404');
+ try {
+ $tmpFile = FileUtil::downloadFileFromHttp($gravatarURL, 'gravatar');
+ @unlink($tmpFile);
+ return true;
+ }
+ catch (SystemException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::canCrop()
+ */
+ public function canCrop() {
+ return false;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\avatar;
+
+/**
+ * Any displayable avatar type should implement this class.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.avatar
+ * @category Community Framework
+ */
+interface IUserAvatar {
+ /**
+ * Returns true if this avatar can be cropped.
+ *
+ * @return boolean
+ */
+ public function canCrop();
+
+ /**
+ * Returns the url to this avatar.
+ *
+ * @param integer $size
+ * @return string
+ */
+ public function getURL($size = null);
+
+ /**
+ * Returns the html code to display this avatar.
+ *
+ * @param integer $size
+ * @return string
+ */
+ public function getImageTag($size = null);
+
+ /**
+ * Returns the width of this avatar.
+ *
+ * @return integer
+ */
+ public function getWidth();
+
+ /**
+ * Returns the height of this avatar.
+ *
+ * @return integer
+ */
+ public function getHeight();
+}
--- /dev/null
+<?php
+namespace wcf\data\user\avatar;
+use wcf\data\DatabaseObject;
+use wcf\util\StringUtil;
+use wcf\system\WCF;
+
+/**
+ * Represents a user's avatar.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.avatar
+ * @category Community Framework
+ */
+class UserAvatar extends DatabaseObject implements IUserAvatar {
+ /**
+ * needed avatar thumbnail sizes
+ * @var array<integer>
+ */
+ public static $avatarThumbnailSizes = array(16, 24, 32, 48, 96, 128);
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_avatar';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'avatarID';
+
+ /**
+ * maximum thumbnail size
+ * @var integer
+ */
+ public static $maxThumbnailSize = 128;
+
+ /**
+ * Returns the physical location of this avatar.
+ *
+ * @param integer $size
+ * @return string
+ */
+ public function getLocation($size = null) {
+ return WCF_DIR . 'images/avatars/' . $this->getFilename($size);
+ }
+
+ /**
+ * Returns the file name of this avatar.
+ *
+ * @param integer $size
+ * @return string
+ */
+ public function getFilename($size = null) {
+ return substr($this->fileHash, 0, 2) . '/' . ($this->avatarID) . '-' . $this->fileHash . ($size !== null ? ('-' . $size) : '') . '.' . $this->avatarExtension;
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getURL()
+ */
+ public function getURL($size = null) {
+ if ($size !== null && $size !== 'resized') {
+ if ($size >= $this->width || $size >= $this->height) $size = null;
+ }
+
+ return WCF::getPath() . 'images/avatars/' . $this->getFilename($size);
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getImageTag()
+ */
+ public function getImageTag($size = null) {
+ $width = $this->width;
+ $height = $this->height;
+ if ($size !== null) {
+ if ($this->width > $size && $this->height > $size) {
+ $width = $height = $size;
+ }
+ else if ($this->width > $size || $this->height > $size) {
+ $widthFactor = $size / $this->width;
+ $heightFactor = $size / $this->height;
+
+ if ($widthFactor < $heightFactor) {
+ $width = $size;
+ $height = round($this->height * $widthFactor, 0);
+ }
+ else {
+ $width = round($this->width * $heightFactor, 0);
+ $height = $size;
+ }
+ }
+ }
+
+ return '<img src="'.StringUtil::encodeHTML($this->getURL($size)).'" style="width: '.$width.'px; height: '.$height.'px" alt="'.WCF::getLanguage()->get('wcf.user.avatar.alt').'" />';
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getWidth()
+ */
+ public function getWidth() {
+ return $this->width;
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::getHeight()
+ */
+ public function getHeight() {
+ return $this->height;
+ }
+
+ /**
+ * @see wcf\data\user\avatar\IUserAvatar::canCrop()
+ */
+ public function canCrop() {
+ return $this->width != $this->height && $this->width > self::$maxThumbnailSize && $this->height > self::$maxThumbnailSize;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\avatar;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserProfile;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\SystemException;
+use wcf\system\exception\UserInputException;
+use wcf\system\image\ImageHandler;
+use wcf\system\upload\AvatarUploadFileValidationStrategy;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+use wcf\util\FileUtil;
+use wcf\util\HTTPRequest;
+
+/**
+ * Executes avatar-related actions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.avatar
+ * @category Community Framework
+ */
+class UserAvatarAction extends AbstractDatabaseObjectAction {
+ /**
+ * currently edited avatar
+ * @var wcf\data\user\avatar\UserAvatarEditor
+ */
+ public $avatar = null;
+
+ /**
+ * Validates the upload action.
+ */
+ public function validateUpload() {
+ $this->readInteger('userID', true);
+
+ if ($this->parameters['userID']) {
+ if (!WCF::getSession()->getPermission('admin.user.canEditUser')) {
+ throw new PermissionDeniedException();
+ }
+
+ $user = new User($this->parameters['userID']);
+ if (!$user->userID) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ // check upload permissions
+ if (!WCF::getSession()->getPermission('user.profile.avatar.canUploadAvatar') || WCF::getUser()->disableAvatar) {
+ throw new PermissionDeniedException();
+ }
+
+ if (count($this->parameters['__files']->getFiles()) != 1) {
+ throw new UserInputException('files');
+ }
+
+ // check max filesize, allowed file extensions etc.
+ $this->parameters['__files']->validateFiles(new AvatarUploadFileValidationStrategy(PHP_INT_MAX, explode("\n", WCF::getSession()->getPermission('user.profile.avatar.allowedFileExtensions'))));
+ }
+
+ /**
+ * Handles uploaded attachments.
+ */
+ public function upload() {
+ // save files
+ $files = $this->parameters['__files']->getFiles();
+ $userID = (!empty($this->parameters['userID']) ? intval($this->parameters['userID']) : WCF::getUser()->userID);
+ $user = ($userID != WCF::getUser()->userID ? new User($userID) : WCF::getUser());
+ $file = $files[0];
+
+ try {
+ if (!$file->getValidationErrorType()) {
+ // shrink avatar if necessary
+ $fileLocation = $this->enforceDimensions($file->getLocation());
+ $imageData = getimagesize($fileLocation);
+
+ $data = array(
+ 'avatarName' => $file->getFilename(),
+ 'avatarExtension' => $file->getFileExtension(),
+ 'width' => $imageData[0],
+ 'height' => $imageData[1],
+ 'userID' => $userID,
+ 'fileHash' => sha1_file($fileLocation)
+ );
+
+ // create avatar
+ $avatar = UserAvatarEditor::create($data);
+
+ // check avatar directory
+ // and create subdirectory if necessary
+ $dir = dirname($avatar->getLocation());
+ if (!@file_exists($dir)) {
+ FileUtil::makePath($dir, 0777);
+ }
+
+ // move uploaded file
+ if (@copy($fileLocation, $avatar->getLocation())) {
+ @unlink($fileLocation);
+
+ // create thumbnails
+ $action = new UserAvatarAction(array($avatar), 'generateThumbnails');
+ $action->executeAction();
+
+ // delete old avatar
+ if ($user->avatarID) {
+ $action = new UserAvatarAction(array($user->avatarID), 'delete');
+ $action->executeAction();
+ }
+
+ // update user
+ $userEditor = new UserEditor($user);
+ $userEditor->update(array(
+ 'avatarID' => $avatar->avatarID,
+ 'enableGravatar' => 0
+ ));
+
+ // reset user storage
+ UserStorageHandler::getInstance()->reset(array($userID), 'avatar');
+
+ // return result
+ return array(
+ 'avatarID' => $avatar->avatarID,
+ 'canCrop' => $avatar->canCrop(),
+ 'url' => $avatar->getURL(96)
+ );
+ }
+ else {
+ // moving failed; delete avatar
+ $editor = new UserAvatarEditor($avatar);
+ $editor->delete();
+ throw new UserInputException('avatar', 'uploadFailed');
+ }
+ }
+ }
+ catch (UserInputException $e) {
+ $file->setValidationErrorType($e->getType());
+ }
+
+ return array('errorType' => $file->getValidationErrorType());
+ }
+
+ /**
+ * Generates the thumbnails of the avatars in all needed sizes.
+ */
+ public function generateThumbnails() {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ foreach ($this->objects as $avatar) {
+ $adapter = ImageHandler::getInstance()->getAdapter();
+ $adapter->loadFile($avatar->getLocation());
+
+ foreach (UserAvatar::$avatarThumbnailSizes as $size) {
+ if ($avatar->width <= $size && $avatar->height <= $size) break 2;
+
+ $thumbnail = $adapter->createThumbnail($size, $size, false);
+ $adapter->writeImage($thumbnail, $avatar->getLocation($size));
+ }
+ }
+ }
+
+ /**
+ * Fetches an avatar from a remote server and sets it for given user.
+ */
+ public function fetchRemoteAvatar() {
+ $avatarID = 0;
+ $filename = '';
+
+ // fetch avatar from URL
+ try {
+ $request = new HTTPRequest($this->parameters['url']);
+ $request->execute();
+ $reply = $request->getReply();
+ $filename = FileUtil::getTemporaryFilename('avatar_');
+ file_put_contents($filename, $reply['body']);
+ }
+ catch (\Exception $e) {
+ if (!empty($filename)) {
+ @unlink($filename);
+ }
+ }
+
+ // rescale avatar if required
+ try {
+ $filename = $this->enforceDimensions($filename);
+ }
+ catch (\Exception $e) { /* ignore errors */ }
+
+ $imageData = getimagesize($filename);
+ $tmp = parse_url($this->parameters['url']);
+ $tmp = pathinfo($tmp['path']);
+
+ $data = array(
+ 'avatarName' => $tmp['basename'],
+ 'avatarExtension' => $tmp['extension'],
+ 'width' => $imageData[0],
+ 'height' => $imageData[1],
+ 'userID' => $this->parameters['userEditor']->userID,
+ 'fileHash' => sha1_file($filename)
+ );
+
+ // create avatar
+ $avatar = UserAvatarEditor::create($data);
+
+ // check avatar directory
+ // and create subdirectory if necessary
+ $dir = dirname($avatar->getLocation());
+ if (!@file_exists($dir)) {
+ FileUtil::makePath($dir, 0777);
+ }
+
+ // move uploaded file
+ if (@copy($filename, $avatar->getLocation())) {
+ @unlink($filename);
+
+ // create thumbnails
+ $action = new UserAvatarAction(array($avatar), 'generateThumbnails');
+ $action->executeAction();
+
+ $avatarID = $avatar->avatarID;
+ }
+ else {
+ // moving failed; delete avatar
+ $editor = new UserAvatarEditor($avatar);
+ $editor->delete();
+ }
+
+ // update user
+ if ($avatarID) {
+ $this->parameters['userEditor']->update(array(
+ 'avatarID' => $avatarID,
+ 'enableGravatar' => 0
+ ));
+
+ // delete old avatar
+ if ($this->parameters['userEditor']->avatarID) {
+ $action = new UserAvatarAction(array(WCF::getUser()->avatarID), 'delete');
+ $action->executeAction();
+
+ // reset user storage
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'avatar');
+ }
+ }
+ }
+
+ /**
+ * Enforces dimensions for given avatar.
+ *
+ * @param string $filename
+ * @return string
+ */
+ protected function enforceDimensions($filename) {
+ $imageData = getimagesize($filename);
+ if ($imageData[0] > MAX_AVATAR_WIDTH || $imageData[1] > MAX_AVATAR_HEIGHT) {
+ try {
+ $adapter = ImageHandler::getInstance()->getAdapter();
+ $adapter->loadFile($filename);
+ $filename = FileUtil::getTemporaryFilename();
+ $thumbnail = $adapter->createThumbnail(MAX_AVATAR_WIDTH, MAX_AVATAR_HEIGHT, false);
+ $adapter->writeImage($thumbnail, $filename);
+ }
+ catch (SystemException $e) {
+ throw new UserInputException('avatar', 'tooLarge');
+ }
+
+ // check filesize (after shrink)
+ if (@filesize($filename) > WCF::getSession()->getPermission('user.profile.avatar.maxSize')) {
+ throw new UserInputException('avatar', 'tooLarge');
+ }
+ }
+
+ return $filename;
+ }
+
+ public function validateGetCropDialog() {
+ $this->avatar = $this->getSingleObject();
+ }
+
+ public function getCropDialog() {
+ return array(
+ 'cropX' => $this->avatar->cropX,
+ 'cropY' => $this->avatar->cropY,
+ 'template' => WCF::getTPL()->fetch('avatarCropDialog', 'wcf', array(
+ 'avatar' => $this->avatar
+ ))
+ );
+ }
+
+ /**
+ * Validates the 'cropAvatar' action.
+ */
+ public function validateCropAvatar() {
+ $this->avatar = $this->getSingleObject();
+
+ // check if user can edit the given avatar
+ if ($this->avatar->userID != WCF::getUser()->userID && !WCF::getSession()->getPermission('admin.user.canEditUser')) {
+ throw new PermissionDeniedException();
+ }
+
+ if (!WCF::getSession()->getPermission('user.profile.avatar.canUploadAvatar') || UserProfile::getUserProfile($this->avatar->userID)->disableAvatar) {
+ throw new PermissionDeniedException();
+ }
+
+ // check parameters
+ $this->readInteger('cropX', true);
+ $this->readInteger('cropY', true);
+
+ if ($this->parameters['cropX'] < 0 || $this->parameters['cropX'] > $this->avatar->width - UserAvatar::$maxThumbnailSize) {
+ throw new UserInputException('cropX');
+ }
+ if ($this->parameters['cropY'] < 0 || $this->parameters['cropY'] > $this->avatar->height - UserAvatar::$maxThumbnailSize) {
+ throw new UserInputException('cropY');
+ }
+ }
+
+ /**
+ * Craps an avatar.
+ */
+ public function cropAvatar() {
+ // created clipped avatar as base for new thumbnails
+ $adapter = ImageHandler::getInstance()->getAdapter();
+ $adapter->loadFile($this->avatar->getLocation());
+ $adapter->clip($this->parameters['cropX'], $this->parameters['cropY'], UserAvatar::$maxThumbnailSize, UserAvatar::$maxThumbnailSize);
+
+ // update thumbnails
+ foreach (UserAvatar::$avatarThumbnailSizes as $size) {
+ $thumbnail = $adapter->createThumbnail($size, $size);
+ $adapter->writeImage($thumbnail, $this->avatar->getLocation($size));
+ }
+
+ // update database entry
+ $this->avatar->update(array(
+ 'cropX' => $this->parameters['cropX'],
+ 'cropY' => $this->parameters['cropY']
+ ));
+
+ return array(
+ 'url' => $this->avatar->getURL(96)
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\avatar;
+use wcf\data\DatabaseObjectEditor;
+use wcf\system\WCF;
+
+/**
+ * Provides functions to edit avatars.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.avatar
+ * @category Community Framework
+ */
+class UserAvatarEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\avatar\UserAvatar';
+
+ /**
+ * @see wcf\data\IEditableObject::delete()
+ */
+ public function delete() {
+ $sql = "DELETE FROM wcf".WCF_N."_user_avatar
+ WHERE avatarID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->avatarID));
+
+ $this->deleteFiles();
+ }
+
+ /**
+ * @see wcf\data\IEditableObject::deleteAll()
+ */
+ public static function deleteAll(array $objectIDs = array()) {
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_user_avatar
+ WHERE avatarID IN (".str_repeat('?,', count($objectIDs) - 1)."?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($objectIDs);
+ while ($avatar = $statement->fetchObject(self::$baseClass)) {
+ $editor = new UserAvatarEditor($avatar);
+ $editor->deleteFiles();
+ }
+
+ return parent::deleteAll($objectIDs);
+ }
+
+ /**
+ * Deletes avatar files.
+ */
+ public function deleteFiles() {
+ foreach (UserAvatar::$avatarThumbnailSizes as $size) {
+ if ($this->width < $size || $this->height < $size) break;
+
+ @unlink($this->getLocation($size));
+ }
+ @unlink($this->getLocation('resize'));
+
+ // delete original size
+ @unlink($this->getLocation());
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\avatar;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of avatars.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.avatar
+ * @category Community Framework
+ */
+class UserAvatarList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\follow;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a user's follower.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.follow
+ * @category Community Framework
+ */
+class UserFollow extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_follow';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'followID';
+
+ /**
+ * Retrieves a follower.
+ *
+ * @param integer $userID
+ * @param integer $followUserID
+ * @return wcf\data\user\follow\UserFollow
+ */
+ public static function getFollow($userID, $followUserID) {
+ $sql = "SELECT followID
+ FROM wcf".WCF_N."_user_follow
+ WHERE userID = ?
+ AND followUserID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $userID,
+ $followUserID
+ ));
+
+ $row = $statement->fetchArray();
+ if (!$row) $row = array();
+
+ return new UserFollow(null, $row);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\follow;
+use wcf\data\user\UserProfile;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IGroupedUserListAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\user\notification\object\UserFollowUserNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\user\GroupedUserList;
+use wcf\system\WCF;
+
+/**
+ * Executes follower-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.follow
+ * @category Community Framework
+ */
+class UserFollowAction extends AbstractDatabaseObjectAction implements IGroupedUserListAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ */
+ protected $allowGuestAccess = array('getGroupedUserList');
+
+ /**
+ * user profile object
+ * @var wcf\data\user\UserProfile;
+ */
+ public $userProfile = null;
+
+ /**
+ * Validates given parameters.
+ */
+ public function validateFollow() {
+ $this->readInteger('userID', false, 'data');
+
+ // validate if you're retarded
+ if ($this->parameters['data']['userID'] == WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Follows an user.
+ *
+ * @return array
+ */
+ public function follow() {
+ $follow = UserFollow::getFollow(WCF::getUser()->userID, $this->parameters['data']['userID']);
+
+ // not following right now
+ if (!$follow->followID) {
+ $follow = UserFollowEditor::create(array(
+ 'userID' => WCF::getUser()->userID,
+ 'followUserID' => $this->parameters['data']['userID'],
+ 'time' => TIME_NOW
+ ));
+
+ // send notification
+ UserNotificationHandler::getInstance()->fireEvent('following', 'com.woltlab.wcf.user.follow', new UserFollowUserNotificationObject($follow), array($follow->followUserID));
+
+ // fire activity event
+ UserActivityEventHandler::getInstance()->fireEvent('com.woltlab.wcf.user.recentActivityEvent.follow', $this->parameters['data']['userID']);
+
+ // reset storage
+ UserStorageHandler::getInstance()->reset(array($this->parameters['data']['userID']), 'followerUserIDs');
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'followingUserIDs');
+ }
+
+ return array(
+ 'following' => 1
+ );
+ }
+
+ /**
+ * @see wcf\data\user\follow\UserFollowAction::validateFollow()
+ */
+ public function validateUnfollow() {
+ $this->validateFollow();
+ }
+
+ /**
+ * Stops following an user.
+ *
+ * @return array
+ */
+ public function unfollow() {
+ $follow = UserFollow::getFollow(WCF::getUser()->userID, $this->parameters['data']['userID']);
+
+ if ($follow->followID) {
+ $followEditor = new UserFollowEditor($follow);
+ $followEditor->delete();
+
+ // remove activity event
+ UserActivityEventHandler::getInstance()->removeEvents('com.woltlab.wcf.user.recentActivityEvent.follow', array($this->parameters['data']['userID']));
+ }
+
+ // reset storage
+ UserStorageHandler::getInstance()->reset(array($this->parameters['data']['userID']), 'followerUserIDs');
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'followingUserIDs');
+
+ return array(
+ 'following' => 0
+ );
+ }
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::validateDelete()
+ */
+ public function validateDelete() {
+ // read objects
+ if (empty($this->objects)) {
+ $this->readObjects();
+
+ if (empty($this->objects)) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ // validate ownership
+ foreach ($this->objects as $follow) {
+ if ($follow->userID != WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+ }
+ }
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::delete()
+ */
+ public function delete() {
+ $returnValues = parent::delete();
+
+ $followUserIDs = array();
+ foreach ($this->objects as $follow) {
+ $followUserIDs[] = $follow->followUserID;
+ // remove activity event
+ UserActivityEventHandler::getInstance()->removeEvents('com.woltlab.wcf.user.recentActivityEvent.follow', array($follow->followUserID));
+ }
+
+ // reset storage
+ UserStorageHandler::getInstance()->reset($followUserIDs, 'followerUserIDs');
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'followingUserIDs');
+
+ return $returnValues;
+ }
+
+ /**
+ * @see wcf\data\IGroupedUserListAction::validateGetGroupedUserList()
+ */
+ public function validateGetGroupedUserList() {
+ $this->readInteger('pageNo');
+ $this->readInteger('userID');
+
+ $this->userProfile = UserProfile::getUserProfile($this->parameters['userID']);
+ if ($this->userProfile->isProtected()) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * @see wcf\data\IGroupedUserListAction::getGroupedUserList()
+ */
+ public function getGroupedUserList() {
+ // resolve page count
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user_follow
+ WHERE followUserID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->parameters['userID']));
+ $row = $statement->fetchArray();
+ $pageCount = ceil($row['count'] / 20);
+
+ // get user ids
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user_follow
+ WHERE followUserID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql, 20, ($this->parameters['pageNo'] - 1) * 20);
+ $statement->execute(array($this->parameters['userID']));
+ $userIDs = array();
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['userID'];
+ }
+
+ // create group
+ $group = new GroupedUserList();
+ $group->addUserIDs($userIDs);
+
+ // load user profiles
+ GroupedUserList::loadUsers();
+
+ WCF::getTPL()->assign(array(
+ 'groupedUsers' => array($group)
+ ));
+
+ return array(
+ 'pageCount' => $pageCount,
+ 'template' => WCF::getTPL()->fetch('groupedUserList')
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\follow;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit followers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.follow
+ * @category Community Framework
+ */
+class UserFollowEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\follow\UserFollow';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\follow;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of followers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.follow
+ * @category Community Framework
+ */
+class UserFollowList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\follow;
+use wcf\data\user\UserProfile;
+
+/**
+ * Represents a list of followers.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.follow
+ * @category Community Framework
+ */
+class UserFollowerList extends UserFollowList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$className
+ */
+ public $className = 'wcf\data\user\follow\UserFollow';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$objectClassName
+ */
+ public $objectClassName = 'wcf\data\user\User';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$sqlOrderBy
+ */
+ public $sqlOrderBy = 'user_follow.time DESC';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::__construct()
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->sqlSelects .= "user_table.username, user_table.email, user_table.disableAvatar, user_table.enableGravatar";
+ $this->sqlSelects .= ", user_avatar.*";
+
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = user_follow.userID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_avatar user_avatar ON (user_avatar.avatarID = user_table.avatarID)";
+ }
+
+ /**
+ * @see wcf\data\DatabaseObjectList::readObjects()
+ */
+ public function readObjects() {
+ parent::readObjects();
+
+ foreach ($this->objects as &$object) {
+ $object = new UserProfile($object);
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\follow;
+use wcf\data\user\UserProfile;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\user\GroupedUserList;
+use wcf\system\WCF;
+
+/**
+ * Executes following-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.follow
+ * @category Community Framework
+ */
+class UserFollowingAction extends UserFollowAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$className
+ */
+ protected $className = 'wcf\data\user\follow\UserFollowEditor';
+
+ /**
+ * @see wcf\data\IGroupedUserListAction::validateGetGroupedUserList()
+ */
+ public function validateGetGroupedUserList() {
+ $this->readInteger('pageNo');
+ $this->readInteger('userID');
+
+ $this->userProfile = UserProfile::getUserProfile($this->parameters['userID']);
+ if ($this->userProfile->isProtected()) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * @see wcf\data\IGroupedUserListAction::getGroupedUserList()
+ */
+ public function getGroupedUserList() {
+ // resolve page count
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user_follow
+ WHERE userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->parameters['userID']));
+ $row = $statement->fetchArray();
+ $pageCount = ceil($row['count'] / 20);
+
+ // get user ids
+ $sql = "SELECT followUserID
+ FROM wcf".WCF_N."_user_follow
+ WHERE userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql, 20, ($this->parameters['pageNo'] - 1) * 20);
+ $statement->execute(array($this->parameters['userID']));
+ $userIDs = array();
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['followUserID'];
+ }
+
+ // create group
+ $group = new GroupedUserList();
+ $group->addUserIDs($userIDs);
+
+ // load user profiles
+ GroupedUserList::loadUsers();
+
+ WCF::getTPL()->assign(array(
+ 'groupedUsers' => array($group)
+ ));
+
+ return array(
+ 'pageCount' => $pageCount,
+ 'template' => WCF::getTPL()->fetch('groupedUserList')
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\follow;
+
+/**
+ * Represents a list of following users.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.follow
+ * @category Community Framework
+ */
+class UserFollowingList extends UserFollowerList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$useQualifiedShorthand
+ */
+ public $useQualifiedShorthand = false;
+
+ /**
+ * @see wcf\data\DatabaseObjectList::__construct()
+ */
+ public function __construct() {
+ UserFollowList::__construct();
+
+ $this->sqlSelects .= "user_avatar.*, user_follow.followID, user_option_value.*";
+
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = user_follow.followUserID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_option_value user_option_value ON (user_option_value.userID = user_table.userID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_avatar user_avatar ON (user_avatar.avatarID = user_table.avatarID)";
+
+ if (MODULE_USER_RANK) {
+ $this->sqlSelects .= ",user_rank.*";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_rank user_rank ON (user_rank.rankID = user_table.rankID)";
+ }
+
+ $this->sqlSelects .= ", user_table.*";
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\group;
+use wcf\data\user\UserProfile;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Represents a team user group.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.group
+ * @category Community Framework
+ */
+class Team extends DatabaseObjectDecorator {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\group\UserGroup';
+
+ /**
+ * list of user group members
+ * @var array<wcf\data\user\UserProfile>
+ */
+ protected $members = array();
+
+ /**
+ * Adds a new member.
+ *
+ * @param wcf\data\user\UserProfile $user
+ */
+ public function addMember(UserProfile $user) {
+ $this->members[] = $user;
+ }
+
+ /**
+ * Returns the list of user group members
+ *
+ * @return array<wcf\data\user\UserProfile>
+ */
+ public function getMembers() {
+ return $this->members;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\ignore;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents an ignored user.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.ignore
+ * @category Community Framework
+ */
+class UserIgnore extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_ignore';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'ignoreID';
+
+ /**
+ * Returns a UserIgnore object for given ignored user id.
+ *
+ * @param integer $ignoreUserID
+ * @return wcf\data\user\ignore\UserIgnore
+ */
+ public static function getIgnore($ignoreUserID) {
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_user_ignore
+ WHERE userID = ?
+ AND ignoreUserID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ WCF::getUser()->userID,
+ $ignoreUserID
+ ));
+
+ $row = $statement->fetchArray();
+ if (!$row) $row = array();
+
+ return new UserIgnore(null, $row);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\ignore;
+use wcf\data\user\ignore\UserIgnore;
+use wcf\data\user\ignore\UserIgnoreEditor;
+use wcf\data\user\UserProfile;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+
+/**
+ * Executes ignored user-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.ignore
+ * @category Community Framework
+ */
+class UserIgnoreAction extends AbstractDatabaseObjectAction {
+ /**
+ * Validates the 'ignore' action.
+ */
+ public function validateIgnore() {
+ $this->readInteger('ignoreUserID', false, 'data');
+
+ $userProfile = UserProfile::getUserProfile($this->parameters['data']['ignoreUserID']);
+ if ($userProfile === null || $userProfile->userID == WCF::getUser()->userID) {
+ throw new IllegalLinkException();
+ }
+
+ // check permissions
+ if ($userProfile->getPermission('user.profile.cannotBeIgnored')) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Ignores an user.
+ *
+ * @return array
+ */
+ public function ignore() {
+ $ignore = UserIgnore::getIgnore($this->parameters['data']['ignoreUserID']);
+
+ if (!$ignore->ignoreID) {
+ UserIgnoreEditor::create(array(
+ 'ignoreUserID' => $this->parameters['data']['ignoreUserID'],
+ 'time' => TIME_NOW,
+ 'userID' => WCF::getUser()->userID,
+ ));
+
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'ignoredUserIDs');
+ }
+
+ return array('isIgnoredUser' => 1);
+ }
+
+ /**
+ * Validates the 'unignore' action.
+ */
+ public function validateUnignore() {
+ $this->readInteger('ignoreUserID', false, 'data');
+
+ $userProfile = UserProfile::getUserProfile($this->parameters['data']['ignoreUserID']);
+ if ($userProfile === null) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ /**
+ * Unignores an user.
+ *
+ * @return array
+ */
+ public function unignore() {
+ $ignore = UserIgnore::getIgnore($this->parameters['data']['ignoreUserID']);
+
+ if ($ignore->ignoreID) {
+ $ignoreEditor = new UserIgnoreEditor($ignore);
+ $ignoreEditor->delete();
+
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'ignoredUserIDs');
+ }
+
+ return array('isIgnoredUser' => 0);
+ }
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::validateDelete()
+ */
+ public function validateDelete() {
+ // read objects
+ if (empty($this->objects)) {
+ $this->readObjects();
+
+ if (empty($this->objects)) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ // validate ownership
+ foreach ($this->objects as $ignore) {
+ if ($ignore->userID != WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+ }
+ }
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::delete()
+ */
+ public function delete() {
+ $returnValues = parent::delete();
+
+ // reset storage
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'ignoredUserIDs');
+
+ return $returnValues;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\ignore;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit ignored users.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.ignore
+ * @category Community Framework
+ */
+class UserIgnoreEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\ignore\UserIgnore';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\ignore;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of ignored users.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.ignore
+ * @category Community Framework
+ */
+class UserIgnoreList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\ignore;
+
+/**
+ * Represents a list of ignored users.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.ignore
+ * @category Community Framework
+ */
+class ViewableUserIgnoreList extends UserIgnoreList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$className
+ */
+ public $className = 'wcf\data\user\ignore\UserIgnore';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$decoratorClassName
+ */
+ public $decoratorClassName = 'wcf\data\user\UserProfile';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$objectClassName
+ */
+ public $objectClassName = 'wcf\data\user\User';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$useQualifiedShorthand
+ */
+ public $useQualifiedShorthand = false;
+
+ /**
+ * @see wcf\data\DatabaseObjectList::__construct()
+ */
+ public function __construct() {
+ parent::__construct();
+
+ if (!empty($this->sqlSelects)) $this->sqlSelects .= ',';
+ $this->sqlSelects .= "user_ignore.ignoreID";
+ $this->sqlSelects .= ", user_option_value.*";
+ $this->sqlSelects .= ", user_avatar.*";
+
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = user_ignore.ignoreUserID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_option_value user_option_value ON (user_option_value.userID = user_table.userID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_avatar user_avatar ON (user_avatar.avatarID = user_table.avatarID)";
+
+ if (MODULE_USER_RANK) {
+ $this->sqlSelects .= ",user_rank.*";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_rank user_rank ON (user_rank.rankID = user_table.rankID)";
+ }
+
+ $this->sqlSelects .= ", user_table.*";
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\menu\item;
+use wcf\data\ProcessibleDatabaseObject;
+use wcf\system\menu\user\DefaultUserMenuItemProvider;
+use wcf\system\menu\ITreeMenuItem;
+use wcf\system\request\LinkHandler;
+use wcf\system\Regex;
+use wcf\system\WCF;
+
+/**
+ * Represents an user menu item.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.menu.item
+ * @category Community Framework
+ */
+class UserMenuItem extends ProcessibleDatabaseObject implements ITreeMenuItem {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_menu_item';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'menuItemID';
+
+ /**
+ * @see wcf\data\ProcessibleDatabaseObject::$processorInterface
+ */
+ protected static $processorInterface = 'wcf\system\menu\user\IUserMenuItemProvider';
+
+ /**
+ * application abbreviation
+ * @var string
+ */
+ protected $application = '';
+
+ /**
+ * menu item controller
+ * @var string
+ */
+ protected $controller = null;
+
+ /**
+ * @see wcf\data\ProcessibleDatabaseObject::getProcessor()
+ */
+ public function getProcessor() {
+ if (parent::getProcessor() === null) {
+ $this->processor = new DefaultUserMenuItemProvider($this);
+ }
+
+ return $this->processor;
+ }
+
+ /**
+ * @see wcf\system\menu\ITreeMenuItem::getLink()
+ */
+ public function getLink() {
+ // external link
+ if (!$this->menuItemController) {
+ return $this->menuItemLink;
+ }
+
+ $this->parseController();
+ return LinkHandler::getInstance()->getLink($this->controller, array('application' => $this->application), $this->menuItemLink);
+ }
+
+ /**
+ * Returns application abbreviation.
+ *
+ * @return string
+ */
+ public function getApplication() {
+ $this->parseController();
+
+ return $this->application;
+ }
+
+ /**
+ * Returns controller name.
+ *
+ * @return string
+ */
+ public function getController() {
+ $this->parseController();
+
+ return $this->controller;
+ }
+
+ /**
+ * Parses controller name.
+ */
+ protected function parseController() {
+ if ($this->controller === null) {
+ $this->controller = '';
+
+ // resolve application and controller
+ if ($this->menuItemController) {
+ $parts = explode('\\', $this->menuItemController);
+ $this->application = array_shift($parts);
+ $menuItemController = array_pop($parts);
+
+ // drop controller suffix
+ $this->controller = Regex::compile('(Action|Form|Page)$')->replace($menuItemController, '');
+ }
+ }
+ }
+
+ /**
+ * Returns the menu item name.
+ *
+ * @return string
+ */
+ public function __toString() {
+ return WCF::getLanguage()->getDynamicVariable($this->menuItem);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\menu\item;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes user menu item-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.menu.item
+ * @category Community Framework
+ */
+class UserMenuItemAction extends AbstractDatabaseObjectAction { }
--- /dev/null
+<?php
+namespace wcf\data\user\menu\item;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit user menu items.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.menu.item
+ * @category Community Framework
+ */
+class UserMenuItemEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\menu\item\UserMenuItem';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\menu\item;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of user menu items.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.menu.item
+ * @category Community Framework
+ */
+class UserMenuItemList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\notification;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a user notification.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification
+ * @category Community Framework
+ */
+class UserNotification extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_notification';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'notificationID';
+
+ /**
+ * @see wcf\data\IStorableObject::__get()
+ */
+ public function __get($name) {
+ $value = parent::__get($name);
+
+ // treat additional data as data variables if it is an array
+ if ($value === null && isset($this->data['additionalData'][$name])) {
+ $value = $this->data['additionalData'][$name];
+ }
+
+ return $value;
+ }
+
+ /**
+ * @see wcf\data\DatabaseObject::handleData()
+ */
+ protected function handleData($data) {
+ parent::handleData($data);
+
+ $this->data['additionalData'] = @unserialize($this->data['additionalData']);
+ if (!is_array($this->data['additionalData'])) {
+ $this->data['additionalData'] = array();
+ }
+ }
+
+ /**
+ * Returns an existing notification.
+ *
+ * @param integer $packageID
+ * @param integer $eventID
+ * @param integer $objectID
+ * @return wcf\data\user\notification\UserNotification
+ */
+ public static function getNotification($packageID, $eventID, $objectID) {
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_user_notification
+ WHERE packageID = ?
+ AND eventID = ?
+ AND objectID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($packageID, $eventID, $objectID));
+ $row = $statement->fetchArray();
+ if ($row !== false) return new UserNotification(null, $row);
+
+ return null;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\notification;
+use wcf\data\user\notification\UserNotificationEditor;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+
+/**
+ * Executes user notification-related actions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification
+ * @category Community Framework
+ */
+class UserNotificationAction extends AbstractDatabaseObjectAction {
+ /**
+ * Adds notification recipients.
+ */
+ public function addRecipients() {
+ $sql = "INSERT IGNORE INTO wcf".WCF_N."_user_notification_to_user
+ (notificationID, userID, mailNotified)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ foreach ($this->objects as $notification) {
+ foreach ($this->parameters['recipients'] as $recipient) {
+ $statement->execute(array($notification->notificationID, $recipient->userID, ($recipient->mailNotificationType == 'daily' ? 0 : 1)));
+ }
+ }
+ }
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::create()
+ */
+ public function create() {
+ // create notification
+ $notification = parent::create();
+
+ // save recpients
+ if (!empty($this->parameters['recipients'])) {
+ $action = new UserNotificationAction(array($notification), 'addRecipients', array(
+ 'recipients' => $this->parameters['recipients']
+ ));
+ $action->executeAction();
+ }
+
+ return $notification;
+ }
+
+ /**
+ * Validates the 'getOustandingNotifications' action.
+ */
+ public function validateGetOutstandingNotifications() {
+ // does nothing
+ }
+
+ /**
+ * Loads user notifications.
+ *
+ * @return array<array>
+ */
+ public function getOutstandingNotifications() {
+ WCF::getTPL()->assign(array(
+ 'notifications' => UserNotificationHandler::getInstance()->getNotifications()
+ ));
+
+ return array(
+ 'template' => WCF::getTPL()->fetch('notificationListOustanding'),
+ 'totalCount' => UserNotificationHandler::getInstance()->getNotificationCount()
+ );
+ }
+
+ /**
+ * Validates if given notification id is valid for current user.
+ */
+ public function validateMarkAsConfirmed() {
+ $this->readInteger('notificationID');
+
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user_notification_to_user
+ WHERE notificationID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->parameters['notificationID'],
+ WCF::getUser()->userID
+ ));
+ $row = $statement->fetchArray();
+
+ // pretend it was marked as confirmed
+ if (!$row['count']) {
+ $this->parameters['alreadyConfirmed'] = true;
+ }
+ }
+
+ /**
+ * Marks a notification as confirmed.
+ *
+ * @return array
+ */
+ public function markAsConfirmed() {
+ if (!isset($this->parameters['alreadyConfirmed'])) {
+ $sql = "DELETE FROM wcf".WCF_N."_user_notification_to_user
+ WHERE notificationID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $this->parameters['notificationID'],
+ WCF::getUser()->userID
+ ));
+
+ // remove entirely read notifications
+ $sql = "SELECT COUNT(*) as count
+ FROM wcf".WCF_N."_user_notification_to_user
+ WHERE notificationID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->parameters['notificationID']));
+ $row = $statement->fetchArray();
+ if (!$row['count']) {
+ UserNotificationEditor::deleteAll(array($this->parameters['notificationID']));
+ }
+
+ // reset notification count
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'userNotificationCount');
+ }
+
+ return array(
+ 'notificationID' => $this->parameters['notificationID'],
+ 'totalCount' => UserNotificationHandler::getInstance()->getNotificationCount()
+ );
+ }
+
+ /**
+ * Validates parameters to mark all notifications of current user as confirmed.
+ */
+ public function validateMarkAllAsConfirmed() { /* does nothing */ }
+
+ /**
+ * Marks all notifications of current user as confirmed.
+ */
+ public function markAllAsConfirmed() {
+ // remove notifications for this user
+ $sql = "DELETE FROM wcf".WCF_N."_user_notification_to_user
+ WHERE userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(WCF::getUser()->userID));
+
+ // reset notification count
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'userNotificationCount');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\notification;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit user notifications.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification
+ * @category Community Framework
+ */
+class UserNotificationEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\notification\UserNotification';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\notification;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of user notifications.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification
+ * @category Community Framework
+ */
+class UserNotificationList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\notification\event;
+use wcf\data\ProcessibleDatabaseObject;
+
+/**
+ * Represents a user notification event.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification.event
+ * @category Community Framework
+ */
+class UserNotificationEvent extends ProcessibleDatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_notification_event';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'eventID';
+
+ /**
+ * @see wcf\data\ProcessibleDatabaseObject::$processorInterface
+ */
+ protected static $processorInterface = 'wcf\system\user\notification\event\IUserNotificationEvent';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\notification\event;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\WCF;
+
+/**
+ * Executes user notification event-related actions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification.event
+ * @category Community Framework
+ */
+class UserNotificationEventAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::create();
+ */
+ public function create() {
+ $event = parent::create();
+
+ if ($event->preset) {
+ $sql = "INSERT INTO wcf".WCF_N."_user_notification_event_to_user
+ (userID, eventID)
+ SELECT userID, ".$event->eventID."
+ FROM wcf".WCF_N."_user";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ }
+
+ return $event;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\notification\event;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit user notification events.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification.event
+ * @category Community Framework
+ */
+class UserNotificationEventEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\notification\event\UserNotificationEvent';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\notification\event;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of user notification events.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification.event
+ * @category Community Framework
+ */
+class UserNotificationEventList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\notification\event\recipient;
+use wcf\data\user\UserList;
+
+/**
+ * Extends the user list to provide special functions for handling recipients of user notifications.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.notification.event.recipient
+ * @category Community Framework
+ */
+class UserNotificationEventRecipientList extends UserList {
+ /**
+ * @see wcf\data\DatabaseObjectList\DatabaseObjectList::__construct()
+ */
+ public function __construct() {
+ $this->sqlJoins = "LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = event_to_user.userID)";
+ $this->sqlSelects = 'user_table.*';
+
+ parent::__construct();
+ }
+
+ /**
+ * @see wcf\data\DatabaseObjectList::getDatabaseTableName()
+ */
+ public function getDatabaseTableName() {
+ return 'wcf'.WCF_N.'_user_notification_event_to_user';
+ }
+
+ /**
+ * @see wcf\data\DatabaseObjectList::getDatabaseTableAlias()
+ */
+ public function getDatabaseTableAlias() {
+ return 'event_to_user';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\object\watch;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a watched object.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.object.watch
+ * @category Community Framework
+ */
+class UserObjectWatch extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_object_watch';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'watchID';
+
+ /**
+ * Returns the UserObjectWatch with the given data or null if no such object
+ * exists.
+ *
+ * @param integer $objectTypeID
+ * @param integer $userID
+ * @param integer $objectID
+ * @return wcf\data\user\object\watch\UserObjectWatch
+ */
+ public static function getUserObjectWatch($objectTypeID, $userID, $objectID) {
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_user_object_watch
+ WHERE objectTypeID = ?
+ AND userID = ?
+ AND objectID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($objectTypeID, $userID, $objectID));
+ $row = $statement->fetch();
+ if (!$row) return null;
+ return new UserObjectWatch(null, $row);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\object\watch;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Executes watched object-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.object.watch
+ * @category Community Framework
+ */
+class UserObjectWatchAction extends AbstractDatabaseObjectAction {
+ /**
+ * object type object
+ * @var wcf\data\object\type\ObjectType
+ */
+ protected $objectType = null;
+
+ /**
+ * user object watch object
+ * @var wcf\data\user\object\watch\UserObjectWatch
+ */
+ protected $userObjectWatch = null;
+
+ /**
+ * Validates parameters to manage a subscription.
+ */
+ public function validateManageSubscription() {
+ $this->readInteger('objectID');
+ $this->readString('objectType');
+
+ // validate object type
+ $this->objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.objectWatch', $this->parameters['objectType']);
+ if ($this->objectType === null) {
+ throw new UserInputException('objectType');
+ }
+
+ // validate object id
+ $this->objectType->getProcessor()->validateObjectID($this->parameters['objectID']);
+
+ // get existing subscription
+ $this->userObjectWatch = UserObjectWatch::getUserObjectWatch($this->objectType->objectTypeID, WCF::getUser()->userID, $this->parameters['objectID']);
+ }
+
+ /**
+ * Returns a form to manage a subscription.
+ *
+ * @return array
+ */
+ public function manageSubscription() {
+ WCF::getTPL()->assign(array(
+ 'objectType' => $this->objectType,
+ 'userObjectWatch' => $this->userObjectWatch
+ ));
+
+ return array(
+ 'objectID' => $this->parameters['objectID'],
+ 'template' => WCF::getTPL()->fetch('manageSubscription')
+ );
+ }
+
+ /**
+ * Validates parameters to save subscription state.
+ */
+ public function validateSaveSubscription() {
+ $this->readBoolean('enableNotification');
+ $this->readBoolean('subscribe');
+
+ $this->validateManageSubscription();
+ }
+
+ /**
+ * Saves subscription state.
+ */
+ public function saveSubscription() {
+ // subscribe
+ if ($this->parameters['subscribe']) {
+ // newly subscribed
+ if ($this->userObjectWatch === null) {
+ UserObjectWatchEditor::create(array(
+ 'notification' => ($this->parameters['enableNotification'] ? 1 : 0),
+ 'objectID' => $this->parameters['objectID'],
+ 'objectTypeID' => $this->objectType->objectTypeID,
+ 'userID' => WCF::getUser()->userID
+ ));
+ }
+ else if ($this->userObjectWatch->notification != $this->parameters['enableNotification']) {
+ // update notification type
+ $editor = new UserObjectWatchEditor($this->userObjectWatch);
+ $editor->update(array(
+ 'notification' => ($this->parameters['enableNotification'] ? 1 : 0)
+ ));
+ }
+
+ // reset user storage
+ $this->objectType->getProcessor()->resetUserStorage(array(WCF::getUser()->userID));
+ }
+ else if ($this->userObjectWatch !== null) {
+ // unsubscribe
+ $editor = new UserObjectWatchEditor($this->userObjectWatch);
+ $editor->delete();
+
+ // reset user storage
+ $this->objectType->getProcessor()->resetUserStorage(array(WCF::getUser()->userID));
+ }
+ }
+
+ /**
+ * Adds a subscription.
+ */
+ public function subscribe() {
+ $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.objectWatch', $this->parameters['data']['objectType']);
+
+ UserObjectWatchEditor::create(array(
+ 'userID' => WCF::getUser()->userID,
+ 'objectID' => intval($this->parameters['data']['objectID']),
+ 'objectTypeID' => $objectType->objectTypeID,
+ 'notification' => (!empty($this->parameters['enableNotification']) ? 1 : 0)
+ ));
+
+ // reset user storage
+ $objectType->getProcessor()->resetUserStorage(array(WCF::getUser()->userID));
+ }
+
+ /**
+ * Removes a subscription.
+ */
+ public function unsubscribe() {
+ $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.objectWatch', $this->parameters['data']['objectType']);
+
+ if ($this->userObjectWatch !== null) $userObjectWatch = $this->userObjectWatch;
+ else {
+ $userObjectWatch = UserObjectWatch::getUserObjectWatch($objectType->objectTypeID, WCF::getUser()->userID, intval($this->parameters['data']['objectID']));
+ }
+ $editor = new UserObjectWatchEditor($userObjectWatch);
+ $editor->delete();
+
+ // reset user storage
+ $objectType->getProcessor()->resetUserStorage(array(WCF::getUser()->userID));
+ }
+
+ /**
+ * Validates the subscribe action.
+ */
+ protected function __validateSubscribe() {
+ $this->readInteger('objectID', false, 'data');
+ $this->readString('objectType', false, 'data');
+
+ // validate object type
+ $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.objectWatch', $this->parameters['data']['objectType']);
+ if ($objectType === null) {
+ throw new UserInputException('objectType');
+ }
+
+ // validate object id
+ $objectType->getProcessor()->validateObjectID(intval($this->parameters['data']['objectID']));
+
+ // get existing subscription
+ $this->userObjectWatch = UserObjectWatch::getUserObjectWatch($objectType->objectTypeID, WCF::getUser()->userID, intval($this->parameters['data']['objectID']));
+ }
+
+ /**
+ * Validates the subscribe action.
+ */
+ public function validateSubscribe() {
+ $this->__validateSubscribe();
+
+ if ($this->userObjectWatch !== null) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Validates the unsubscribe action.
+ */
+ public function validateUnsubscribe() {
+ $this->__validateSubscribe();
+
+ if ($this->userObjectWatch === null) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\object\watch;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit watched objects.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.object.watch
+ * @category Community Framework
+ */
+class UserObjectWatchEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\object\watch\UserObjectWatch';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\object\watch;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of watched objects.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.object.watch
+ * @category Community Framework
+ */
+class UserObjectWatchList extends DatabaseObjectList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$className
+ */
+ public $className = 'wcf\data\user\object\watch\UserObjectWatch';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\online;
+use wcf\data\user\UserProfile;
+use wcf\system\cache\builder\SpiderCacheBuilder;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+use wcf\util\UserUtil;
+
+/**
+ * Represents a user who is online.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.online
+ * @category Community Framework
+ */
+class UserOnline extends UserProfile {
+ /**
+ * location of the user
+ * @var string
+ */
+ protected $location = '';
+
+ /**
+ * spider object
+ * @var wcf\data\spider\Spider
+ */
+ protected $spider = null;
+
+ /**
+ * Returns the formatted username.
+ *
+ * @return string
+ */
+ public function getFormattedUsername() {
+ $username = StringUtil::encodeHTML($this->username);
+
+ if ($this->userOnlineMarking && $this->userOnlineMarking != '%s') {
+ $username = sprintf($this->userOnlineMarking, $username);
+ }
+
+ if ($this->canViewOnlineStatus == 3) {
+ $username .= WCF::getLanguage()->get('wcf.user.usersOnline.invisible');
+ }
+
+ return $username;
+ }
+
+ /**
+ * Sets the location of the user.
+ *
+ * @param string $location
+ */
+ public function setLocation($location) {
+ $this->location = $location;
+ }
+
+ /**
+ * Returns the location of the user.
+ *
+ * @return string
+ */
+ public function getLocation() {
+ return $this->location;
+ }
+
+ /**
+ * Returns the ip address.
+ *
+ * @return string
+ */
+ public function getFormattedIPAddress() {
+ if ($address = UserUtil::convertIPv6To4($this->ipAddress)) {
+ return $address;
+ }
+
+ return $this->ipAddress;
+ }
+
+ /**
+ * Tries to retrieve browser name and version.
+ *
+ * @return string
+ */
+ public function getBrowser() {
+ // opera
+ if (preg_match('~opera.*version/([\d\.]+)~i', $this->userAgent, $match)) {
+ return 'Opera '.$match[1];
+ }
+
+ // firefox
+ if (preg_match('~firefox/([\d\.]+)~i', $this->userAgent, $match)) {
+ return 'Firefox '.$match[1];
+ }
+
+ // ie
+ if (preg_match('~msie ([\d\.]+)~i', $this->userAgent, $match)) {
+ return 'Internet Explorer '.$match[1];
+ }
+
+ // chrome
+ if (preg_match('~chrome/([\d\.]+)~i', $this->userAgent, $match)) {
+ return 'Chrome '.$match[1];
+ }
+
+ // safari
+ if (preg_match('~([\d\.]+) safari~i', $this->userAgent, $match)) {
+ return 'Safari '.$match[1];
+ }
+
+ return $this->userAgent;
+ }
+
+ /**
+ * Returns the spider object
+ *
+ * @return wcf\data\spider\Spider
+ */
+ public function getSpider() {
+ if (!$this->spiderID) return null;
+
+ if ($this->spider === null) {
+ $spiderList = SpiderCacheBuilder::getInstance()->getData();
+ $this->spider = $spiderList[$this->spiderID];
+ }
+
+ return $this->spider;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\online;
+use wcf\data\session\SessionList;
+use wcf\data\user\group\UserGroup;
+use wcf\data\user\User;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Represents a list of currently online users.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.online
+ * @category Community Framework
+ */
+class UsersOnlineList extends SessionList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$sqlOrderBy
+ */
+ public $sqlOrderBy = 'user_table.username';
+
+ /**
+ * users online stats
+ * @var array
+ */
+ public $stats = array(
+ 'total' => 0,
+ 'invisible' => 0,
+ 'members' => 0,
+ 'guests' => 0
+ );
+
+ /**
+ * users online markings
+ * @var array
+ */
+ public $usersOnlineMarkings = null;
+
+ /**
+ * @see wcf\data\DatabaseObjectList::__construct()
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->sqlSelects .= "user_avatar.*, user_option_value.*, user_group.userOnlineMarking, user_table.*";
+
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = session.userID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_option_value user_option_value ON (user_option_value.userID = user_table.userID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_avatar user_avatar ON (user_avatar.avatarID = user_table.avatarID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_group user_group ON (user_group.groupID = user_table.userOnlineGroupID)";
+
+ $this->getConditionBuilder()->add('session.lastActivityTime > ?', array(TIME_NOW - USER_ONLINE_TIMEOUT));
+ }
+
+ /**
+ * @see wcf\data\DatabaseObjectList::readObjects()
+ */
+ public function readObjects() {
+ parent::readObjects();
+
+ $objects = $this->objects;
+ $this->indexToObject = $this->objects = array();
+
+ foreach ($objects as $object) {
+ $object = new UserOnline(new User(null, null, $object));
+ if (!$object->userID || self::isVisible($object->userID, $object->canViewOnlineStatus)) {
+ $this->objects[$object->sessionID] = $object;
+ $this->indexToObject[] = $object->sessionID;
+ }
+ }
+ $this->objectIDs = $this->indexToObject;
+ $this->rewind();
+ }
+
+ /**
+ * Gets users online stats.
+ */
+ public function readStats() {
+ $sql = "SELECT user_option_value.userOption".User::getUserOptionID('canViewOnlineStatus')." AS canViewOnlineStatus, session.userID
+ FROM wcf".WCF_N."_session session
+ LEFT JOIN wcf".WCF_N."_user_option_value user_option_value
+ ON (user_option_value.userID = session.userID)
+ ".$this->getConditionBuilder();
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($this->getConditionBuilder()->getParameters());
+ while ($row = $statement->fetchArray()) {
+ $this->stats['total']++;
+ if ($row['userID']) {
+ $this->stats['members']++;
+
+ if ($row['canViewOnlineStatus'] && !self::isVisible($row['userID'], $row['canViewOnlineStatus'])) {
+ $this->stats['invisible']++;
+ }
+ }
+ else {
+ $this->stats['guests']++;
+ }
+ }
+ }
+
+ /**
+ * Returns a list of the users online markings.
+ *
+ * @return array
+ */
+ public function getUsersOnlineMarkings() {
+ if ($this->usersOnlineMarkings === null) {
+ $this->usersOnlineMarkings = $priorities = array();
+
+ // get groups
+ foreach (UserGroup::getGroupsByType() as $group) {
+ if ($group->userOnlineMarking != '%s') {
+ $priorities[] = $group->priority;
+ $this->usersOnlineMarkings[] = sprintf($group->userOnlineMarking, StringUtil::encodeHTML(WCF::getLanguage()->get($group->groupName)));
+ }
+ }
+
+ // sort list
+ array_multisort($priorities, SORT_DESC, $this->usersOnlineMarkings);
+ }
+
+ return $this->usersOnlineMarkings;
+ }
+
+ /**
+ * Checks the 'canViewOnlineStatus' setting.
+ *
+ * @param integer $userID
+ * @param integer $canViewOnlineStatus
+ * @return boolean
+ */
+ public static function isVisible($userID, $canViewOnlineStatus) {
+ if (WCF::getSession()->getPermission('admin.user.canViewInvisible') || $userID == WCF::getUser()->userID) return true;
+
+ switch ($canViewOnlineStatus) {
+ case 0: // everyone
+ return true;
+ case 1: // registered
+ if (WCF::getUser()->userID) return true;
+ break;
+ case 2: // following
+ if (WCF::getUserProfileHandler()->isFollower($userID)) return true;
+ break;
+ }
+
+ return false;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\profile\menu\item;
+use wcf\data\DatabaseObject;
+use wcf\system\exception\SystemException;
+use wcf\util\ClassUtil;
+
+/**
+ * Represents an user profile menu item.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.profile.menu.item
+ * @category Community Framework
+ */
+class UserProfileMenuItem extends DatabaseObject {
+ /**
+ * content manager
+ * @var wcf\system\menu\user\profile\content\IUserProfileContent
+ */
+ protected $contentManager = null;
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_profile_menu_item';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'menuItemID';
+
+ /**
+ * Returns the item identifier, dots are replaced by underscores.
+ *
+ * @return string
+ */
+ public function getIdentifier() {
+ return str_replace('.', '_', $this->menuItem);
+ }
+
+ /**
+ * Returns the content manager for this menu item.
+ *
+ * @return wcf\system\menu\user\profile\content\IUserProfileMenuContent
+ */
+ public function getContentManager() {
+ if ($this->contentManager === null) {
+ if (!class_exists($this->className)) {
+ throw new SystemException("Unable to find class '".$this->className."'");
+ }
+
+ if (!ClassUtil::isInstanceOf($this->className, 'wcf\system\SingletonFactory')) {
+ throw new SystemException("'".$this->className."' does not extend 'wcf\system\SingletonFactory'");
+ }
+
+ if (!ClassUtil::isInstanceOf($this->className, 'wcf\system\menu\user\profile\content\IUserProfileMenuContent')) {
+ throw new SystemException("'".$this->className."' does not implement 'wcf\system\menu\user\profile\content\IUserProfileMenuContent'");
+ }
+
+ $this->contentManager = call_user_func(array($this->className, 'getInstance'));
+ }
+
+ return $this->contentManager;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\profile\menu\item;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\menu\user\profile\UserProfileMenu;
+
+/**
+ * Executes user profile menu item-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.profile.menu.item
+ * @category Community Framework
+ */
+class UserProfileMenuItemAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ */
+ protected $allowGuestAccess = array('getContent');
+
+ /**
+ * menu item
+ * @var wcf\data\user\profile\menu\item\UserProfileMenuItem
+ */
+ protected $menuItem = null;
+
+ /**
+ * Validates menu item.
+ */
+ public function validateGetContent() {
+ $this->readString('menuItem', false, 'data');
+ $this->readInteger('userID', false, 'data');
+ $this->readString('containerID', false, 'data');
+
+ $this->menuItem = UserProfileMenu::getInstance()->getMenuItem($this->parameters['data']['menuItem']);
+ if ($this->menuItem === null) {
+ throw new UserInputException('menuItem');
+ }
+ if (!$this->menuItem->getContentManager()->isVisible($this->parameters['data']['userID'])) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * Returns content for given menu item.
+ */
+ public function getContent() {
+ $contentManager = $this->menuItem->getContentManager();
+
+ return array(
+ 'containerID' => $this->parameters['data']['containerID'],
+ 'template' => $contentManager->getContent($this->parameters['data']['userID'])
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\profile\menu\item;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\builder\UserProfileMenuCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Provides functions to edit user profile menu items.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.profile.menu.item
+ * @category Community Framework
+ */
+class UserProfileMenuItemEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\profile\menu\item\UserProfileMenuItem';
+
+ /**
+ * @see wcf\data\IEditableObject::create()
+ */
+ public static function create(array $parameters = array()) {
+ // calculate show order
+ $parameters['showOrder'] = self::getShowOrder($parameters['showOrder']);
+
+ return parent::create($parameters);
+ }
+
+ /**
+ * @see wcf\data\IEditableObject::update()
+ */
+ public function update(array $parameters = array()) {
+ if (isset($parameters['showOrder'])) {
+ $this->updateShowOrder($parameters['showOrder']);
+ }
+
+ parent::update($parameters);
+ }
+
+ /**
+ * @see wcf\data\IEditableObject::delete()
+ */
+ public function delete() {
+ // update show order
+ $sql = "UPDATE wcf".WCF_N."_user_profile_menu_item
+ SET showOrder = showOrder - 1
+ WHERE showOrder >= ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->showOrder));
+
+ parent::delete();
+ }
+
+ /**
+ * Updates show order for current menu item.
+ *
+ * @param integer $showOrder
+ */
+ protected function updateShowOrder($showOrder) {
+ if ($this->showOrder != $showOrder) {
+ if ($showOrder < $this->showOrder) {
+ $sql = "UPDATE wcf".WCF_N."_user_profile_menu_item
+ SET showOrder = showOrder + 1
+ WHERE showOrder >= ?
+ AND showOrder < ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $showOrder,
+ $this->showOrder
+ ));
+ }
+ else if ($showOrder > $this->showOrder) {
+ $sql = "UPDATE wcf".WCF_N."_user_profile_menu_item
+ SET showOrder = showOrder - 1
+ WHERE showOrder <= ?
+ AND showOrder > ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $showOrder,
+ $this->showOrder
+ ));
+ }
+ }
+ }
+
+ /**
+ * Returns show order for a new menu item.
+ *
+ * @param integer $showOrder
+ * @return integer
+ */
+ protected static function getShowOrder($showOrder = 0) {
+ if ($showOrder == 0) {
+ // get next number in row
+ $sql = "SELECT MAX(showOrder) AS showOrder
+ FROM wcf".WCF_N."_user_profile_menu_item";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ $row = $statement->fetchArray();
+ if (!empty($row)) $showOrder = intval($row['showOrder']) + 1;
+ else $showOrder = 1;
+ }
+ else {
+ $sql = "UPDATE wcf".WCF_N."_page_menu_item
+ SET showOrder = showOrder + 1
+ WHERE showOrder >= ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($showOrder));
+ }
+
+ return $showOrder;
+ }
+
+ /**
+ * @see wcf\data\IEditableCachedObject::resetCache()
+ */
+ public static function resetCache() {
+ UserProfileMenuCacheBuilder::getInstance()->reset();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\profile\menu\item;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of user profile menu items.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.profile.menu.item
+ * @category Community Framework
+ */
+class UserProfileMenuItemList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\data\user\profile\visitor;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a user profile visitor.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.profile.visitor
+ * @category Community Framework
+ */
+class UserProfileVisitor extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_profile_visitor';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'visitorID';
+
+ /**
+ * Gets a profile visitor object.
+ *
+ * @param integer $ownerID
+ * @param integer $userID
+ * @return wcf\data\user\profile\visitor\UserProfileVisitor
+ */
+ public static function getObject($ownerID, $userID) {
+ $sql = "SELECT *
+ FROM ".static::getDatabaseTableName()."
+ WHERE ownerID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($ownerID, $userID));
+ if ($row = $statement->fetchArray()) {
+ return new UserProfileVisitor(null, $row);
+ }
+
+ return null;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\profile\visitor;
+use wcf\data\user\UserProfile;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IGroupedUserListAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\user\GroupedUserList;
+use wcf\system\WCF;
+
+/**
+ * Executes profile visitor-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.profile.visitor
+ * @category Community Framework
+ */
+class UserProfileVisitorAction extends AbstractDatabaseObjectAction implements IGroupedUserListAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ */
+ protected $allowGuestAccess = array('getGroupedUserList');
+
+ /**
+ * user profile object
+ * @var wcf\data\user\UserProfile;
+ */
+ public $userProfile = null;
+
+ /**
+ * @see wcf\data\IGroupedUserListAction::validateGetGroupedUserList()
+ */
+ public function validateGetGroupedUserList() {
+ $this->readInteger('pageNo');
+ $this->readInteger('userID');
+
+ $this->userProfile = UserProfile::getUserProfile($this->parameters['userID']);
+ if ($this->userProfile->isProtected()) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * @see wcf\data\IGroupedUserListAction::getGroupedUserList()
+ */
+ public function getGroupedUserList() {
+ // resolve page count
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user_profile_visitor
+ WHERE ownerID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->parameters['userID']));
+ $row = $statement->fetchArray();
+ $pageCount = ceil($row['count'] / 20);
+
+ // get user ids
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user_profile_visitor
+ WHERE ownerID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql, 20, ($this->parameters['pageNo'] - 1) * 20);
+ $statement->execute(array($this->parameters['userID']));
+ $userIDs = array();
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['userID'];
+ }
+
+ // create group
+ $group = new GroupedUserList();
+ $group->addUserIDs($userIDs);
+
+ // load user profiles
+ GroupedUserList::loadUsers();
+
+ WCF::getTPL()->assign(array(
+ 'groupedUsers' => array($group)
+ ));
+
+ return array(
+ 'pageCount' => $pageCount,
+ 'template' => WCF::getTPL()->fetch('groupedUserList')
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\profile\visitor;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit profile visitors.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.profile.visitor
+ * @category Community Framework
+ */
+class UserProfileVisitorEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\profile\visitor\UserProfileVisitor';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\profile\visitor;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of profile visitors.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.profile.visitor
+ * @category Community Framework
+ */
+class UserProfileVisitorList extends DatabaseObjectList {
+ /**
+ * @see wcf\data\DatabaseObjectList::$decoratorClassName
+ */
+ public $decoratorClassName = 'wcf\data\user\UserProfile';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$objectClassName
+ */
+ public $objectClassName = 'wcf\data\user\User';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$sqlOrderBy
+ */
+ public $sqlOrderBy = 'user_profile_visitor.time DESC';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::__construct()
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->sqlSelects .= "user_table.username, user_table.email, user_table.disableAvatar, user_table.enableGravatar";
+ $this->sqlSelects .= ", user_avatar.*";
+
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = user_profile_visitor.userID)";
+ $this->sqlJoins .= " LEFT JOIN wcf".WCF_N."_user_avatar user_avatar ON (user_avatar.avatarID = user_table.avatarID)";
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\rank;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Represents a user rank.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.rank
+ * @category Community Framework
+ */
+class UserRank extends DatabaseObject {
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableName
+ */
+ protected static $databaseTableName = 'user_rank';
+
+ /**
+ * @see wcf\data\DatabaseObject::$databaseTableIndexName
+ */
+ protected static $databaseTableIndexName = 'rankID';
+
+ /**
+ * Returns the image of this user rank.
+ *
+ * @return string html code
+ */
+ public function getImage() {
+ if ($this->rankImage) {
+ $image = '<img src="'.(!preg_match('~^(/|https?://)~i', $this->rankImage) ? WCF::getPath() : '').StringUtil::encodeHTML($this->rankImage).'" alt="" />';
+ if ($this->repeatImage > 1) $image = str_repeat($image, $this->repeatImage);
+ return $image;
+ }
+
+ return '';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\user\rank;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes user rank-related actions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.rank
+ * @category Community Framework
+ */
+class UserRankAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+ */
+ protected $permissionsDelete = array('admin.user.rank.canManageRank');
+}
--- /dev/null
+<?php
+namespace wcf\data\user\rank;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit user ranks.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.rank
+ * @category Community Framework
+ */
+class UserRankEditor extends DatabaseObjectEditor {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\rank\UserRank';
+}
--- /dev/null
+<?php
+namespace wcf\data\user\rank;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of user ranks.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage data.user.rank
+ * @category Community Framework
+ */
+class UserRankList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\UserInputException;
+use wcf\system\mail\Mail;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\PasswordUtil;
+use wcf\util\StringUtil;
+use wcf\util\UserRegistrationUtil;
+use wcf\util\UserUtil;
+
+/**
+ * Shows the account management form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class AccountManagementForm extends AbstractSecureForm {
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$loginRequired
+ */
+ public $loginRequired = true;
+
+ /**
+ * user password
+ * @var string
+ */
+ public $password = '';
+
+ /**
+ * new email address
+ * @var string
+ */
+ public $email = '';
+
+ /**
+ * confirmed new email address
+ * @var string
+ */
+ public $confirmEmail = '';
+
+ /**
+ * new password
+ * @var string
+ */
+ public $newPassword = '';
+
+ /**
+ * confirmed new password
+ * @var string
+ */
+ public $confirmNewPassword = '';
+
+ /**
+ * new user name
+ * @var string
+ */
+ public $username = '';
+
+ /**
+ * indicates if the user quit
+ * @var integer
+ */
+ public $quit = 0;
+
+ /**
+ * indicates if the user canceled their quit
+ * @var integer
+ */
+ public $cancelQuit = 0;
+
+ /**
+ * timestamp at which the user quit
+ * @var integer
+ */
+ public $quitStarted = 0;
+
+ /**
+ * indicates if the user wants to connect github
+ * @var integer
+ */
+ public $githubConnect = 0;
+
+ /**
+ * indicates if the user wants to disconnect github
+ * @var integer
+ */
+ public $githubDisconnect = 0;
+
+ /**
+ * indicates if the user wants to connect twitter
+ * @var integer
+ */
+ public $twitterConnect = 0;
+
+ /**
+ * indicates if the user wants to disconnect twitter
+ * @var integer
+ */
+ public $twitterDisconnect = 0;
+
+ /**
+ * indicates if the user wants to connect facebook
+ * @var integer
+ */
+ public $facebookConnect = 0;
+
+ /**
+ * indicates if the user wants to disconnect facebook
+ * @var integer
+ */
+ public $facebookDisconnect = 0;
+
+ /**
+ * indicates if the user wants to connect google
+ * @var integer
+ */
+ public $googleConnect = 0;
+
+ /**
+ * indicates if the user wants to disconnect google
+ * @var integer
+ */
+ public $googleDisconnect = 0;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ $this->quitStarted = WCF::getUser()->quitStarted;
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['password'])) $this->password = $_POST['password'];
+ if (isset($_POST['email'])) $this->email = $_POST['email'];
+ if (isset($_POST['confirmEmail'])) $this->confirmEmail = $_POST['confirmEmail'];
+ if (isset($_POST['newPassword'])) $this->newPassword = $_POST['newPassword'];
+ if (isset($_POST['confirmNewPassword'])) $this->confirmNewPassword = $_POST['confirmNewPassword'];
+ if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']);
+ if (isset($_POST['quit'])) $this->quit = intval($_POST['quit']);
+ if (isset($_POST['cancelQuit'])) $this->cancelQuit = intval($_POST['cancelQuit']);
+ if (isset($_POST['githubConnect'])) $this->githubConnect = intval($_POST['githubConnect']);
+ if (isset($_POST['githubDisconnect'])) $this->githubDisconnect = intval($_POST['githubDisconnect']);
+ if (isset($_POST['twitterConnect'])) $this->twitterConnect = intval($_POST['twitterConnect']);
+ if (isset($_POST['twitterDisconnect'])) $this->twitterDisconnect = intval($_POST['twitterDisconnect']);
+ if (isset($_POST['facebookConnect'])) $this->facebookConnect = intval($_POST['facebookConnect']);
+ if (isset($_POST['facebookDisconnect'])) $this->facebookDisconnect = intval($_POST['facebookDisconnect']);
+ if (isset($_POST['googleConnect'])) $this->googleConnect = intval($_POST['googleConnect']);
+ if (isset($_POST['googleDisconnect'])) $this->googleDisconnect = intval($_POST['googleDisconnect']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // password
+ if (!WCF::getUser()->authData) {
+ if (empty($this->password)) {
+ throw new UserInputException('password');
+ }
+
+ if (!WCF::getUser()->checkPassword($this->password)) {
+ throw new UserInputException('password', 'false');
+ }
+ }
+
+ // user name
+ if (WCF::getSession()->getPermission('user.profile.canRename') && $this->username != WCF::getUser()->username) {
+ if (StringUtil::toLowerCase($this->username) != StringUtil::toLowerCase(WCF::getUser()->username)) {
+ if (WCF::getUser()->lastUsernameChange + WCF::getSession()->getPermission('user.profile.renamePeriod') * 86400 > TIME_NOW) {
+ throw new UserInputException('username', 'alreadyRenamed');
+ }
+
+ // checks for forbidden chars (e.g. the ",")
+ if (!UserRegistrationUtil::isValidUsername($this->username)) {
+ throw new UserInputException('username', 'notValid');
+ }
+
+ // checks if user name exists already.
+ if (!UserUtil::isAvailableUsername($this->username)) {
+ throw new UserInputException('username', 'notUnique');
+ }
+ }
+ }
+
+ // password
+ if (!WCF::getUser()->authData) {
+ if (!empty($this->newPassword) || !empty($this->confirmNewPassword)) {
+ if (empty($this->newPassword)) {
+ throw new UserInputException('newPassword');
+ }
+
+ if (empty($this->confirmNewPassword)) {
+ throw new UserInputException('confirmNewPassword');
+ }
+
+ if (!UserRegistrationUtil::isSecurePassword($this->newPassword)) {
+ throw new UserInputException('newPassword', 'notSecure');
+ }
+
+ if ($this->newPassword != $this->confirmNewPassword) {
+ throw new UserInputException('confirmNewPassword', 'notEqual');
+ }
+ }
+ }
+
+ // email
+ if (WCF::getSession()->getPermission('user.profile.canChangeEmail') && $this->email != WCF::getUser()->email && $this->email != WCF::getUser()->newEmail) {
+ if (empty($this->email)) {
+ throw new UserInputException('email');
+ }
+
+ // checks if only letter case has changed
+ if (StringUtil::toLowerCase($this->email) != StringUtil::toLowerCase(WCF::getUser()->email)) {
+ // check for valid email (one @ etc.)
+ if (!UserRegistrationUtil::isValidEmail($this->email)) {
+ throw new UserInputException('email', 'notValid');
+ }
+
+ // checks if email already exists.
+ if (!UserUtil::isAvailableEmail($this->email)) {
+ throw new UserInputException('email', 'notUnique');
+ }
+ }
+
+ // checks confirm input
+ if (StringUtil::toLowerCase($this->email) != StringUtil::toLowerCase($this->confirmEmail)) {
+ throw new UserInputException('confirmEmail', 'notEqual');
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // default values
+ if (empty($_POST)) {
+ $this->username = WCF::getUser()->username;
+ $this->email = $this->confirmEmail = WCF::getUser()->email;
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'password' => $this->password,
+ 'email' => $this->email,
+ 'confirmEmail' => $this->confirmEmail,
+ 'newPassword' => $this->newPassword,
+ 'confirmNewPassword' => $this->confirmNewPassword,
+ 'username' => $this->username,
+ 'renamePeriod' => WCF::getSession()->getPermission('user.profile.renamePeriod'),
+ 'quitStarted' => $this->quitStarted,
+ 'quit' => $this->quit,
+ 'cancelQuit' => $this->cancelQuit,
+ 'githubConnect' => $this->githubConnect,
+ 'githubDisconnect' => $this->githubDisconnect,
+ 'twitterConnect' => $this->twitterConnect,
+ 'twitterDisconnect' => $this->twitterDisconnect,
+ 'facebookConnect' => $this->facebookConnect,
+ 'facebookDisconnect' => $this->facebookDisconnect,
+ 'googleConnect' => $this->googleConnect,
+ 'googleDisconnect' => $this->googleDisconnect
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ // set active tab
+ UserMenu::getInstance()->setActiveMenuItem('wcf.user.menu.profile.accountManagement');
+
+ parent::show();
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ $success = array();
+ $updateParameters = array();
+ $updateOptions = array();
+ $userEditor = new UserEditor(WCF::getUser());
+
+ // quit
+ if (WCF::getSession()->getPermission('user.profile.canQuit')) {
+ if (!WCF::getUser()->quitStarted && $this->quit == 1) {
+ $updateParameters['quitStarted'] = TIME_NOW;
+ $this->quitStarted = TIME_NOW;
+ $success[] = 'wcf.user.quit.success';
+ }
+ else if (WCF::getUser()->quitStarted && $this->cancelQuit == 1) {
+ $updateParameters['quitStarted'] = 0;
+ $this->quitStarted = 0;
+ $success[] = 'wcf.user.quit.cancel.success';
+ }
+ }
+
+ // user name
+ if (WCF::getSession()->getPermission('user.profile.canRename') && $this->username != WCF::getUser()->username) {
+ if (StringUtil::toLowerCase($this->username) != StringUtil::toLowerCase(WCF::getUser()->username)) {
+ $updateParameters['lastUsernameChange'] = TIME_NOW;
+ $updateParameters['oldUsername'] = $userEditor->username;
+ }
+ $updateParameters['username'] = $this->username;
+ $success[] = 'wcf.user.changeUsername.success';
+ }
+
+ // email
+ if (WCF::getSession()->getPermission('user.profile.canChangeEmail') && $this->email != WCF::getUser()->email && $this->email != WCF::getUser()->newEmail) {
+ if (REGISTER_ACTIVATION_METHOD == 0 || REGISTER_ACTIVATION_METHOD == 2 || StringUtil::toLowerCase($this->email) == StringUtil::toLowerCase(WCF::getUser()->email)) {
+ // update email
+ $updateParameters['email'] = $this->email;
+ $success[] = 'wcf.user.changeEmail.success';
+ }
+ else if (REGISTER_ACTIVATION_METHOD == 1) {
+ // get reactivation code
+ $activationCode = UserRegistrationUtil::getActivationCode();
+
+ // save as new email
+ $updateParameters['reactivationCode'] = $activationCode;
+ $updateParameters['newEmail'] = $this->email;
+
+ $messageData = array(
+ 'username' => WCF::getUser()->username,
+ 'userID' => WCF::getUser()->userID,
+ 'activationCode' => $activationCode
+ );
+
+ $mail = new Mail(array(WCF::getUser()->username => $this->email), WCF::getLanguage()->getDynamicVariable('wcf.user.changeEmail.needReactivation.mail.subject'), WCF::getLanguage()->getDynamicVariable('wcf.user.changeEmail.needReactivation.mail', $messageData));
+ $mail->send();
+ $success[] = 'wcf.user.changeEmail.needReactivation';
+ }
+ }
+
+ // password
+ if (!WCF::getUser()->authData) {
+ if (!empty($this->newPassword) || !empty($this->confirmNewPassword)) {
+ $userEditor->update(array(
+ 'password' => $this->newPassword
+ ));
+
+ // update cookie
+ if (isset($_COOKIE[COOKIE_PREFIX.'password'])) {
+ // reload user
+ $user = new User($userEditor->userID);
+
+ HeaderUtil::setCookie('password', PasswordUtil::getSaltedHash($this->newPassword, $user->password), TIME_NOW + 365 * 24 * 3600);
+ }
+
+ $success[] = 'wcf.user.changePassword.success';
+ }
+ }
+
+ // 3rdParty
+ if (GITHUB_PUBLIC_KEY !== '' && GITHUB_PRIVATE_KEY !== '') {
+ if ($this->githubConnect && WCF::getSession()->getVar('__githubToken')) {
+ $updateParameters['authData'] = 'github:'.WCF::getSession()->getVar('__githubToken');
+ $success[] = 'wcf.user.3rdparty.github.connect.success';
+
+ WCF::getSession()->unregister('__githubToken');
+ WCF::getSession()->unregister('__githubUsername');
+ }
+ else if ($this->githubDisconnect && StringUtil::startsWith(WCF::getUser()->authData, 'github:')) {
+ $updateParameters['authData'] = '';
+ $success[] = 'wcf.user.3rdparty.github.disconnect.success';
+ }
+ }
+ if (TWITTER_PUBLIC_KEY !== '' && TWITTER_PRIVATE_KEY !== '') {
+ if ($this->twitterConnect && WCF::getSession()->getVar('__twitterData')) {
+ $twitterData = WCF::getSession()->getVar('__twitterData');
+ $updateParameters['authData'] = 'twitter:'.$twitterData['user_id'];
+ $success[] = 'wcf.user.3rdparty.twitter.connect.success';
+
+ WCF::getSession()->unregister('__twitterData');
+ WCF::getSession()->unregister('__twitterUsername');
+ }
+ else if ($this->twitterDisconnect && StringUtil::startsWith(WCF::getUser()->authData, 'twitter:')) {
+ $updateParameters['authData'] = '';
+ $success[] = 'wcf.user.3rdparty.twitter.disconnect.success';
+ }
+ }
+ if (FACEBOOK_PUBLIC_KEY !== '' && FACEBOOK_PRIVATE_KEY !== '') {
+ if ($this->facebookConnect && WCF::getSession()->getVar('__facebookData')) {
+ $facebookData = WCF::getSession()->getVar('__facebookData');
+ $updateParameters['authData'] = 'facebook:'.$facebookData['id'];
+ $success[] = 'wcf.user.3rdparty.facebook.connect.success';
+
+ WCF::getSession()->unregister('__facebookData');
+ WCF::getSession()->unregister('__facebookUsername');
+ }
+ else if ($this->facebookDisconnect && StringUtil::startsWith(WCF::getUser()->authData, 'facebook:')) {
+ $updateParameters['authData'] = '';
+ $success[] = 'wcf.user.3rdparty.facebook.disconnect.success';
+ }
+ }
+ if (GOOGLE_PUBLIC_KEY !== '' && GOOGLE_PRIVATE_KEY !== '') {
+ if ($this->googleConnect && WCF::getSession()->getVar('__googleData')) {
+ $googleData = WCF::getSession()->getVar('__googleData');
+ $updateParameters['authData'] = 'facebook:'.$googleData['id'];
+ $success[] = 'wcf.user.3rdparty.google.connect.success';
+
+ WCF::getSession()->unregister('__googleData');
+ WCF::getSession()->unregister('__googleUsername');
+ }
+ else if ($this->googleDisconnect && StringUtil::startsWith(WCF::getUser()->authData, 'google:')) {
+ $updateParameters['authData'] = '';
+ $success[] = 'wcf.user.3rdparty.google.disconnect.success';
+ }
+ }
+
+ if (!empty($updateParameters)) {
+ $userEditor->update($updateParameters);
+ }
+ if (!empty($updateOptions)) {
+ $userEditor->updateUserOptions($updateOptions);
+ }
+
+ $this->saved();
+
+ $success = array_merge($success, WCF::getTPL()->get('success') ?: array());
+
+ // show success message
+ WCF::getTPL()->assign('success', $success);
+
+ // reset password
+ $this->password = '';
+ $this->newPassword = $this->confirmNewPassword = '';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\avatar\Gravatar;
+use wcf\data\user\avatar\UserAvatarAction;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\WCF;
+
+/**
+ * Shows the avatar edit form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class AvatarEditForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$loginRequired
+ */
+ public $loginRequired = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$templateName
+ */
+ public $templateName = 'avatarEdit';
+
+ /**
+ * avatar type
+ * @var string
+ */
+ public $avatarType = 'none';
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['avatarType'])) $this->avatarType = $_POST['avatarType'];
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ if (WCF::getUser()->disableAvatar) throw new PermissionDeniedException();
+
+ if ($this->avatarType != 'custom' && $this->avatarType != 'gravatar') $this->avatarType = 'none';
+
+ switch ($this->avatarType) {
+ case 'custom':
+ if (!WCF::getUser()->avatarID) {
+ throw new UserInputException('custom');
+ }
+ break;
+
+ case 'gravatar':
+ if (!MODULE_GRAVATAR) {
+ $this->avatarType = 'none';
+ break;
+ }
+
+ // test gravatar
+ if (!Gravatar::test(WCF::getUser()->email)) {
+ throw new UserInputException('gravatar', 'notFound');
+ }
+ break;
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ if ($this->avatarType != 'custom') {
+ // delete custom avatar
+ if (WCF::getUser()->avatarID) {
+ $action = new UserAvatarAction(array(WCF::getUser()->avatarID), 'delete');
+ $action->executeAction();
+ }
+ }
+
+ // update user
+ switch ($this->avatarType) {
+ case 'none':
+ $editor = new UserEditor(WCF::getUser());
+ $editor->update(array(
+ 'avatarID' => null,
+ 'enableGravatar' => 0
+ ));
+ break;
+
+ case 'custom':
+ $editor = new UserEditor(WCF::getUser());
+ $editor->update(array(
+ 'enableGravatar' => 0
+ ));
+ break;
+
+ case 'gravatar':
+ $editor = new UserEditor(WCF::getUser());
+ $editor->update(array(
+ 'avatarID' => null,
+ 'enableGravatar' => 1
+ ));
+ break;
+ }
+
+ $this->saved();
+ WCF::getTPL()->assign('success', true);
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST)) {
+ if (WCF::getUser()->avatarID) $this->avatarType = 'custom';
+ else if (MODULE_GRAVATAR && WCF::getUser()->enableGravatar) $this->avatarType = 'gravatar';
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'avatarType' => $this->avatarType
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ // set active tab
+ UserMenu::getInstance()->setActiveMenuItem('wcf.user.menu.profile.avatar');
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+
+/**
+ * Shows the disclaimer.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class DisclaimerForm extends AbstractForm {
+ /**
+ * true, if the user has accepted the disclaimer
+ * @var boolean
+ */
+ public $accept = false;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ // user is already registered
+ if (WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+
+ // registration disabled
+ if (REGISTER_DISABLED) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.register.error.disabled'));
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['accept'])) $this->accept = true;
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ if (!$this->accept) throw new UserInputException('accept');
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ WCF::getSession()->register('disclaimerAccepted', true);
+ $this->saved();
+ WCF::getSession()->update();
+
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register'));
+ exit;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\UserInputException;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\UserUtil;
+
+/**
+ * Shows the email activation form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class EmailActivationForm extends AbstractForm {
+ /**
+ * user id
+ * @var integer
+ */
+ public $userID = null;
+
+ /**
+ * activation code
+ * @var integer
+ */
+ public $activationCode = '';
+
+ /**
+ * User object
+ * @var wcf\data\user\User
+ */
+ public $user = null;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_GET['u']) && !empty($_GET['u'])) $this->userID = intval($_GET['u']);
+ if (isset($_GET['a']) && !empty($_GET['a'])) $this->activationCode = intval($_GET['a']);
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['u']) && !empty($_POST['u'])) $this->userID = intval($_POST['u']);
+ if (isset($_POST['a']) && !empty($_POST['a'])) $this->activationCode = intval($_POST['a']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // check given user id
+ $this->user = new UserEditor(new User($this->userID));
+ if (!$this->user->userID) {
+ throw new UserInputException('u', 'notValid');
+ }
+
+ // user is already enabled
+ if ($this->user->reactivationCode == 0) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.emailActivation.error.emailAlreadyEnabled'));
+ }
+
+ // check whether the new email isn't unique anymore
+ if (!UserUtil::isAvailableEmail($this->user->newEmail)) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.email.error.notUnique'));
+ }
+
+ // check given activation code
+ if ($this->user->reactivationCode != $this->activationCode) {
+ throw new UserInputException('a', 'notValid');
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // enable new email
+ $this->user->update(array(
+ 'email' => $this->user->newEmail,
+ 'newEmail' => '',
+ 'reactivationCode' => 0
+ ));
+ $this->saved();
+
+ // forward to index page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->get('wcf.user.emailActivation.success'));
+ exit;
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'u' => $this->userID,
+ 'a' => $this->activationCode
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ if (REGISTER_ACTIVATION_METHOD != 1) {
+ throw new IllegalLinkException();
+ }
+
+ if (empty($_POST) && $this->userID !== null && $this->activationCode != 0) {
+ $this->submit();
+ }
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\UserInputException;
+use wcf\system\mail\Mail;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\UserRegistrationUtil;
+
+/**
+ * Shows the new email activation code form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class EmailNewActivationCodeForm extends RegisterNewActivationCodeForm {
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (WCF::getUser()->userID) {
+ $this->username = WCF::getUser()->username;
+ }
+ }
+
+ /**
+ * Validates the username.
+ */
+ public function validateUsername() {
+ if (empty($this->username)) {
+ throw new UserInputException('username');
+ }
+
+ $this->user = User::getUserByUsername($this->username);
+ if (!$this->user->userID) {
+ throw new UserInputException('username', 'notFound');
+ }
+
+ if ($this->user->reactivationCode == 0) {
+ throw new UserInputException('username', 'alreadyEnabled');
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ AbstractForm::save();
+
+ // generate activation code
+ $activationCode = UserRegistrationUtil::getActivationCode();
+
+ // save user
+ $userEditor = new UserEditor($this->user);
+ $userEditor->update(array('reactivationCode' => $activationCode));
+
+ // send activation mail
+ $messageData = array(
+ 'username' => $this->user->username,
+ 'userID' => $this->user->userID,
+ 'activationCode' => $activationCode
+ );
+ $mail = new Mail(array($this->user->username => !empty($this->email) ? $this->email : $this->user->email), WCF::getLanguage()->getDynamicVariable('wcf.user.changeEmail.needReactivation.mail.subject'), WCF::getLanguage()->getDynamicVariable('wcf.user.changeEmail.needReactivation.mail', $messageData));
+ $mail->send();
+ $this->saved();
+
+ // forward to index page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->getDynamicVariable('wcf.user.changeEmail.needReactivation'), 10);
+ exit;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\authentication\UserAuthenticationFactory;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+use wcf\util\UserUtil;
+
+/**
+ * Shows the user login form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class LoginForm extends \wcf\acp\form\LoginForm {
+ const AVAILABLE_DURING_OFFLINE_MODE = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * true enables the usage of cookies
+ * @var boolean
+ */
+ public $useCookies = 1;
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'register') {
+ // if the usernamefield is an email, save it as email for the registration
+ if (UserUtil::isValidEmail($this->username)) {
+ WCF::getSession()->register('__email', $this->username);
+ } else {
+ WCF::getSession()->register('__username', $this->username);
+ }
+ WCF::getSession()->update();
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Register'));
+ exit;
+ }
+
+ $this->useCookies = 0;
+ if (isset($_POST['useCookies'])) $this->useCookies = intval($_POST['useCookies']);
+ if (isset($_POST['url'])) $this->url = StringUtil::trim($_POST['url']);
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ AbstractForm::save();
+
+ // set cookies
+ if ($this->useCookies == 1) {
+ UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($this->user, $this->username, $this->password);
+ }
+
+ // change user
+ WCF::getSession()->changeUser($this->user);
+
+ // get redirect url
+ $this->checkURL();
+ $this->saved();
+
+ // redirect to url
+ WCF::getTPL()->assign('__hideUserMenu', true);
+ HeaderUtil::delayedRedirect($this->url, WCF::getLanguage()->get('wcf.user.login.redirect'));
+ exit;
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'useCookies' => $this->useCookies,
+ 'supportsPersistentLogins' => UserAuthenticationFactory::getInstance()->getUserAuthentication()->supportsPersistentLogins()
+ ));
+ }
+
+ /**
+ * Gets the redirect url.
+ */
+ protected function checkURL() {
+ if (empty($this->url) || StringUtil::indexOf($this->url, 'index.php/Login/') !== false) {
+ $this->url = LinkHandler::getInstance()->getLink();
+ }
+ // append missing session id
+ else if (SID_ARG_1ST != '' && !preg_match('/(?:&|\?)s=[a-z0-9]{40}/', $this->url)) {
+ if (StringUtil::indexOf($this->url, '?') !== false) $this->url .= SID_ARG_2ND_NOT_ENCODED;
+ else $this->url .= SID_ARG_1ST;
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\UserInputException;
+use wcf\system\mail\Mail;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the lost password form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class LostPasswordForm extends RecaptchaForm {
+ const AVAILABLE_DURING_OFFLINE_MODE = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * username
+ * @var string
+ */
+ public $username = '';
+
+ /**
+ * email address
+ * @var string
+ */
+ public $email = '';
+
+ /**
+ * user object
+ * @var wcf\data\user\User
+ */
+ public $user;
+
+ /**
+ * @see wcf\form\RecaptchaForm::$useCaptcha
+ */
+ public $useCaptcha = LOST_PASSWORD_USE_CAPTCHA;
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']);
+ if (isset($_POST['email'])) $this->email = StringUtil::trim($_POST['email']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ if (empty($this->username) && empty($this->email)) {
+ throw new UserInputException('username');
+ }
+
+ if (!empty($this->username)) {
+ $this->user = User::getUserByUsername($this->username);
+ if (!$this->user->userID) {
+ throw new UserInputException('username', 'notFound');
+ }
+ }
+ else {
+ $this->user = User::getUserByEmail($this->email);
+ if (!$this->user->userID) {
+ throw new UserInputException('email', 'notFound');
+ }
+ }
+
+ // check if using 3rd party @author dtdesign
+ if ($this->user->authData) {
+ throw new UserInputException('username', '3rdParty');
+ }
+
+ // check whether a lost password request was sent in the last 24 hours
+ if ($this->user->lastLostPasswordRequestTime && TIME_NOW - 86400 < $this->user->lastLostPasswordRequestTime) {
+ throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.lostPassword.error.tooManyRequests', array('hours' => ceil(($this->user->lastLostPasswordRequestTime - (TIME_NOW - 86400)) / 3600))));
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // generate a new lost password key
+ $lostPasswordKey = StringUtil::getRandomID();
+
+ // save key and request time in database
+ $userEditor = new UserEditor($this->user);
+ $userEditor->update(array(
+ 'lostPasswordKey' => $lostPasswordKey,
+ 'lastLostPasswordRequestTime' => TIME_NOW
+ ));
+
+ // send mail
+ $mail = new Mail(array($this->user->username => $this->user->email), WCF::getLanguage()->getDynamicVariable('wcf.user.lostPassword.mail.subject'), WCF::getLanguage()->getDynamicVariable('wcf.user.lostPassword.mail', array(
+ 'username' => $this->user->username,
+ 'userID' => $this->user->userID,
+ 'key' => $lostPasswordKey
+ )));
+ $mail->send();
+ $this->saved();
+
+ // forward to index page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->get('wcf.user.lostPassword.mail.sent'));
+ exit;
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'username' => $this->username,
+ 'email' => $this->email
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\UserProfile;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\mail\Mail;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+use wcf\util\UserUtil;
+
+/**
+ * Shows the user mail form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class MailForm extends RecaptchaForm {
+ /**
+ * @see wcf\form\RecaptchaForm::$useCaptcha
+ */
+ public $useCaptcha = PROFILE_MAIL_USE_CAPTCHA;
+
+ /**
+ * recipient's user id
+ * @var integer
+ */
+ public $userID = 0;
+
+ /**
+ * recipient's user object
+ * @var wcf\data\user\UserProfile
+ */
+ public $user = 0;
+
+ /**
+ * true to add the reply-to header
+ * @var boolean
+ */
+ public $showAddress = true;
+
+ /**
+ * email subject
+ * @var string
+ */
+ public $subject = '';
+
+ /**
+ * email message
+ * @var string
+ */
+ public $message = '';
+
+ /**
+ * sender's email address
+ * @var string
+ */
+ public $email = '';
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) $this->userID = intval($_REQUEST['id']);
+ $this->user = UserProfile::getUserProfile($this->userID);
+ if ($this->user === null) {
+ throw new IllegalLinkException();
+ }
+ // validate ignore status
+ if (WCF::getUser()->userID && $this->user->isIgnoredUser(WCF::getUser()->userID)) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ $this->showAddress = 0;
+ if (isset($_POST['message'])) $this->message = StringUtil::trim($_POST['message']);
+ if (isset($_POST['subject'])) $this->subject = StringUtil::trim($_POST['subject']);
+ if (isset($_POST['email'])) $this->email = StringUtil::trim($_POST['email']);
+ if (isset($_POST['showAddress'])) $this->showAddress = intval($_POST['showAddress']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ if (!WCF::getUser()->userID) {
+ if (empty($this->email)) {
+ throw new UserInputException('email');
+ }
+
+ if (!UserUtil::isValidEmail($this->email)) {
+ throw new UserInputException('email', 'invalid');
+ }
+ }
+
+ if (empty($this->subject)) {
+ throw new UserInputException('subject');
+ }
+
+ if (empty($this->message)) {
+ throw new UserInputException('message');
+ }
+
+ parent::validate();
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // get recipient's language
+ $userLanguage = $this->user->getLanguage();
+
+ // build message data
+ $subjectData = array(
+ 'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->email,
+ 'subject' => $this->subject
+ );
+ $messageData = array(
+ 'message' => $this->message,
+ 'recipient' => $this->user,
+ 'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->email,
+ );
+
+ // build mail
+ $mail = new Mail(array($this->user->username => $this->user->email), $userLanguage->getDynamicVariable('wcf.user.mail.mail.subject', $subjectData), $userLanguage->getDynamicVariable('wcf.user.mail.mail', $messageData));
+ $mail->setLanguage($userLanguage);
+
+ // add reply-to tag
+ if (WCF::getUser()->userID) {
+ if ($this->showAddress) $mail->setHeader('Reply-To: '.Mail::buildAddress(WCF::getUser()->username, WCF::getUser()->email));
+ }
+ else {
+ $mail->setHeader('Reply-To: '.$this->email);
+ }
+
+ // send mail
+ $mail->send();
+ $this->saved();
+
+ // forward to profile page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink('User', array('object' => $this->user)), WCF::getLanguage()->getDynamicVariable('wcf.user.mail.sent', array('user' => $this->user)));
+ exit;
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ WCF::getBreadcrumbs()->add($this->user->getBreadcrumb());
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'user' => $this->user,
+ 'showAddress' => $this->showAddress,
+ 'message' => $this->message,
+ 'subject' => $this->subject,
+ 'email' => $this->email
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ WCF::getSession()->checkPermissions(array('user.profile.canMail'));
+
+ if (!$this->user->isAccessible('canMail')) {
+ throw new PermissionDeniedException();
+ }
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\page\AbstractPage;
+use wcf\system\exception\UserInputException;
+use wcf\system\mail\Mail;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\PasswordUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the new password form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class NewPasswordForm extends AbstractForm {
+ const AVAILABLE_DURING_OFFLINE_MODE = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * user id
+ * @var integer
+ */
+ public $userID = 0;
+
+ /**
+ * lost password key
+ * @var string
+ */
+ public $lostPasswordKey = '';
+
+ /**
+ * User object
+ * @var wcf\data\user\User
+ */
+ public $user;
+
+ /**
+ * new password
+ * @var string
+ */
+ public $newPassword = '';
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['u'])) $this->userID = intval($_REQUEST['u']);
+ if (isset($_REQUEST['k'])) $this->lostPasswordKey = StringUtil::trim($_REQUEST['k']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // get user
+ $this->user = new User($this->userID);
+
+ if (!$this->user->userID) {
+ throw new UserInputException('userID', 'invalid');
+ }
+ if (!$this->user->lostPasswordKey) {
+ throw new UserInputException('lostPasswordKey');
+ }
+
+ if ($this->user->lostPasswordKey != $this->lostPasswordKey) {
+ throw new UserInputException('lostPasswordKey', 'invalid');
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // generate new password
+ $this->newPassword = PasswordUtil::getRandomPassword((REGISTER_PASSWORD_MIN_LENGTH > 9 ? REGISTER_PASSWORD_MIN_LENGTH : 9));
+
+ // update user
+ $userEditor = new UserEditor($this->user);
+ $userEditor->update(array(
+ 'password' => $this->newPassword,
+ 'lastLostPasswordRequestTime' => 0,
+ 'lostPasswordKey' => ''
+ ));
+
+ // send mail
+ $mail = new Mail(array($this->user->username => $this->user->email), WCF::getLanguage()->getDynamicVariable('wcf.user.newPassword.mail.subject'), WCF::getLanguage()->getDynamicVariable('wcf.user.newPassword.mail', array(
+ 'username' => $this->user->username,
+ 'userID' => $this->user->userID,
+ 'newPassword' => $this->newPassword
+ )));
+ $mail->send();
+ $this->saved();
+
+ // forward to index page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->get('wcf.user.newPassword.success'));
+ exit;
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'userID' => $this->userID,
+ 'lostPasswordKey' => $this->lostPasswordKey
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ AbstractPage::readData();
+
+ if (!empty($_POST) || (!empty($this->userID) && !empty($this->lostPasswordKey))) {
+ $this->submit();
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\exception\UserInputException;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the notification settings form.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class NotificationSettingsForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$loginRequired
+ */
+ public $loginRequired = true;
+
+ /**
+ * list of notification events
+ * @var array<array>
+ */
+ public $events = null;
+
+ /**
+ * list of settings by event
+ * @var array<array>
+ */
+ public $settings = array();
+
+ /**
+ * list of valid options for the mail notification type.
+ * @var array<string>
+ */
+ protected static $validMailNotificationTypes = array('none', 'instant', 'daily');
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ $this->events = UserNotificationHandler::getInstance()->getAvailableEvents();
+
+ // filter events
+ foreach ($this->events as $objectTypeID => $events) {
+ foreach ($events as $eventName => $event) {
+ if (!$event->isVisible()) {
+ unset($this->events[$objectTypeID][$eventName]);
+ }
+ }
+
+ if (empty($this->events[$objectTypeID])) {
+ unset($this->events[$objectTypeID]);
+ }
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['settings'])) $this->settings = $_POST['settings'];
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // valid event ids
+ $validEventIDs = array();
+ foreach ($this->events as $events) {
+ foreach ($events as $event) {
+ $validEventIDs[] = $event->eventID;
+ }
+ }
+
+ foreach ($this->settings as $eventID => &$settings) {
+ // validate event id
+ if (!in_array($eventID, $validEventIDs)) {
+ throw new UserInputException();
+ }
+
+ // ensure 'enabled' exists
+ if (!isset($settings['enabled'])) {
+ $settings['enabled'] = 0;
+ }
+
+ // ensure 'mailNotificationType' exists
+ if (!isset($settings['mailNotificationType']) || !in_array($settings['mailNotificationType'], self::$validMailNotificationTypes)) {
+ $settings['mailNotificationType'] = 'none';
+ }
+ }
+ unset($settings);
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // default values
+ if (empty($_POST)) {
+ // get user settings
+ $eventIDs = array();
+ foreach ($this->events as $events) {
+ foreach ($events as $event) {
+ $eventIDs[] = $event->eventID;
+ $this->settings[$event->eventID] = array(
+ 'enabled' => false,
+ 'mailNotificationType' => 'none'
+ );
+ }
+ }
+
+ // get activation state
+ $sql = "SELECT eventID, mailNotificationType
+ FROM wcf".WCF_N."_user_notification_event_to_user
+ WHERE userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(WCF::getUser()->userID));
+ while ($row = $statement->fetchArray()) {
+ $this->settings[$row['eventID']]['enabled'] = true;
+ $this->settings[$row['eventID']]['mailNotificationType'] = $row['mailNotificationType'];
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ $groupedEvents = array();
+ foreach ($this->events as $objectType => $events) {
+ $objectTypeObj = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.notification.objectType', $objectType);
+ $category = ($objectTypeObj->category ?: $objectType);
+
+ if (!isset($groupedEvents[$category])) {
+ $groupedEvents[$category] = array();
+ }
+
+ foreach ($events as $event) $groupedEvents[$category][] = $event;
+ }
+
+ ksort($groupedEvents);
+
+ WCF::getTPL()->assign(array(
+ 'events' => $groupedEvents,
+ 'settings' => $this->settings
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ // set active tab
+ UserMenu::getInstance()->setActiveMenuItem('wcf.user.menu.settings.notification');
+
+ parent::show();
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ $this->updateActivationStates();
+ $this->saved();
+
+ // show success message
+ WCF::getTPL()->assign('success', true);
+ }
+
+ /**
+ * Updates preferences for notification events.
+ */
+ protected function updateActivationStates() {
+ $sql = "DELETE FROM wcf".WCF_N."_user_notification_event_to_user
+ WHERE eventID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ WCF::getDB()->beginTransaction();
+ $newSettings = array();
+ foreach ($this->settings as $eventID => $setting) {
+ $statement->execute(array(
+ $eventID,
+ WCF::getUser()->userID
+ ));
+
+ if ($setting['enabled']) {
+ $newSettings[] = array(
+ 'eventID' => $eventID,
+ 'mailNotificationType' => $setting['mailNotificationType']
+ );
+ }
+ }
+
+ if (!empty($newSettings)) {
+ $sql = "INSERT INTO wcf".WCF_N."_user_notification_event_to_user
+ (eventID, userID, mailNotificationType)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ foreach ($newSettings as $newSetting) {
+ $statement->execute(array(
+ $newSetting['eventID'],
+ WCF::getUser()->userID,
+ $newSetting['mailNotificationType']
+ ));
+ }
+ }
+ WCF::getDB()->commitTransaction();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\group\UserGroup;
+use wcf\data\user\ExtendedUserAction;
+use wcf\data\user\User;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\UserInputException;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the user activation form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class RegisterActivationForm extends AbstractForm {
+ /**
+ * username
+ * @var string
+ */
+ public $username = null;
+
+ /**
+ * activation code
+ * @var integer
+ */
+ public $activationCode = '';
+
+ /**
+ * User object
+ * @var wcf\data\user\User
+ */
+ public $user = null;
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']);
+ if (isset($_POST['activationCode'])) $this->activationCode = intval($_POST['activationCode']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // check given user id
+ $this->user = User::getUserByUsername($this->username);
+ if (!$this->user->userID) {
+ throw new UserInputException('username', 'notFound');
+ }
+
+ // user is already enabled
+ if ($this->user->activationCode == 0) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.registerActivation.error.userAlreadyEnabled'));
+ }
+
+ // check given activation code
+ if ($this->user->activationCode != $this->activationCode) {
+ throw new UserInputException('activationCode', 'notValid');
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // enable user
+ $this->objectAction = new ExtendedUserAction(array($this->user), 'enable');
+ $this->objectAction->executeAction();
+ $this->saved();
+
+ // forward to index page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->get('wcf.user.registerActivation.success'), 10);
+ exit;
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'username' => $this->username,
+ 'activationCode' => $this->activationCode
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ if (REGISTER_ACTIVATION_METHOD != 1) {
+ throw new IllegalLinkException();
+ }
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\acp\form\UserAddForm;
+use wcf\data\user\avatar\Gravatar;
+use wcf\data\user\avatar\UserAvatarAction;
+use wcf\data\user\group\UserGroup;
+use wcf\data\user\User;
+use wcf\data\user\UserAction;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserProfile;
+use wcf\data\user\UserProfileAction;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\mail\Mail;
+use wcf\system\recaptcha\RecaptchaHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\authentication\UserAuthenticationFactory;
+use wcf\system\Regex;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+use wcf\util\UserRegistrationUtil;
+
+/**
+ * Shows the user registration form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class RegisterForm extends UserAddForm {
+ /**
+ * recaptcha challenge
+ * @var string
+ */
+ public $challenge = '';
+
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * true if external authentication is used
+ * @var boolean
+ */
+ public $isExternalAuthentication = false;
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array();
+
+ /**
+ * holds a language variable with information about the registration process
+ * e.g. if you need to activate your account
+ * @var string
+ */
+ public $message = '';
+
+ /**
+ * recaptcha response
+ * @var string
+ */
+ public $response = '';
+
+ /**
+ * enable recaptcha
+ * @var boolean
+ */
+ public $useCaptcha = true;
+
+ /**
+ * min number of seconds between form request and submit
+ * @var integer
+ */
+ public static $minRegistrationTime = 15;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ // user is already registered
+ if (WCF::getUser()->userID) {
+ throw new PermissionDeniedException();
+ }
+
+ // registration disabled
+ if (REGISTER_DISABLED) {
+ throw new NamedUserException(WCF::getLanguage()->get('wcf.user.register.error.disabled'));
+ }
+
+ // check disclaimer
+ if (REGISTER_ENABLE_DISCLAIMER && !WCF::getSession()->getVar('disclaimerAccepted')) {
+ HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Disclaimer'));
+ exit;
+ }
+
+ if (!REGISTER_USE_CAPTCHA || WCF::getSession()->getVar('recaptchaDone')) {
+ $this->useCaptcha = false;
+ }
+
+ if (WCF::getSession()->getVar('__githubToken') || WCF::getSession()->getVar('__twitterData') || WCF::getSession()->getVar('__facebookData') || WCF::getSession()->getVar('__googleData')) {
+ $this->isExternalAuthentication = true;
+ }
+ }
+
+ /**
+ * wcf\acp\form\AbstractOptionListForm::initOptionHandler()
+ */
+ protected function initOptionHandler() {
+ $this->optionHandler->setInRegistration();
+ parent::initOptionHandler();
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ $this->groupIDs = array();
+ if (isset($_POST['recaptcha_challenge_field'])) $this->challenge = StringUtil::trim($_POST['recaptcha_challenge_field']);
+ if (isset($_POST['recaptcha_response_field'])) $this->response = StringUtil::trim($_POST['recaptcha_response_field']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ // validate captcha first
+ if ($this->useCaptcha) {
+ $this->validateCaptcha();
+ }
+
+ parent::validate();
+
+ // validate registration time
+ if (!$this->isExternalAuthentication && (!WCF::getSession()->getVar('registrationStartTime') || (TIME_NOW - WCF::getSession()->getVar('registrationStartTime')) < self::$minRegistrationTime)) {
+ throw new UserInputException('registrationStartTime', array());
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST)) {
+ $this->languageID = WCF::getLanguage()->languageID;
+
+ if (WCF::getSession()->getVar('__username')) {
+ $this->username = WCF::getSession()->getVar('__username');
+ WCF::getSession()->unregister('__username');
+ }
+ if (WCF::getSession()->getVar('__email')) {
+ $this->email = $this->confirmEmail = WCF::getSession()->getVar('__email');
+ WCF::getSession()->unregister('__email');
+ }
+
+ WCF::getSession()->register('registrationStartTime', TIME_NOW);
+ }
+ }
+
+ /**
+ * Reads option tree on page init.
+ */
+ protected function readOptionTree() {
+ $this->optionTree = $this->optionHandler->getOptionTree('profile');
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ RecaptchaHandler::getInstance()->assignVariables();
+ WCF::getTPL()->assign(array(
+ 'isExternalAuthentication' => $this->isExternalAuthentication,
+ 'useCaptcha' => $this->useCaptcha
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ AbstractForm::show();
+ }
+
+ /**
+ * Validates the captcha.
+ */
+ protected function validateCaptcha() {
+ if ($this->useCaptcha) {
+ try {
+ RecaptchaHandler::getInstance()->validate($this->challenge, $this->response);
+ $this->useCaptcha = false;
+ }
+ catch (UserInputException $e) {
+ $this->errorType[$e->getField()] = $e->getType();
+ }
+ }
+ }
+
+ /**
+ * @see wcf\acp\form\UserAddForm::validateUsername()
+ */
+ protected function validateUsername($username) {
+ parent::validateUsername($username);
+
+ // check for min-max length
+ if (!UserRegistrationUtil::isValidUsername($username)) {
+ throw new UserInputException('username', 'notValid');
+ }
+ }
+
+ /**
+ * @see wcf\acp\form\UserAddForm::validatePassword()
+ */
+ protected function validatePassword($password, $confirmPassword) {
+ if (!$this->isExternalAuthentication) {
+ parent::validatePassword($password, $confirmPassword);
+
+ // check security of the given password
+ if (!UserRegistrationUtil::isSecurePassword($password)) {
+ throw new UserInputException('password', 'notSecure');
+ }
+ }
+ }
+
+ /**
+ * @see wcf\acp\form\UserAddForm::validateEmail()
+ */
+ protected function validateEmail($email, $confirmEmail) {
+ parent::validateEmail($email, $confirmEmail);
+
+ if (!UserRegistrationUtil::isValidEmail($email)) {
+ throw new UserInputException('email', 'notValid');
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ AbstractForm::save();
+
+ // get options
+ $saveOptions = $this->optionHandler->save();
+ $registerVia3rdParty = false;
+
+ $avatarURL = '';
+ if ($this->isExternalAuthentication) {
+ // GitHub
+ if (WCF::getSession()->getVar('__githubData')) {
+ $githubData = WCF::getSession()->getVar('__githubData');
+
+ $this->additionalFields['authData'] = 'github:'.WCF::getSession()->getVar('__githubToken');
+
+ WCF::getSession()->unregister('__githubData');
+ WCF::getSession()->unregister('__githubToken');
+
+ $registerVia3rdParty = true;
+
+ if (isset($githubData['bio'])) $saveOptions[User::getUserOptionID('aboutMe')] = $githubData['bio'];
+ if (isset($githubData['location'])) $saveOptions[User::getUserOptionID('location')] = $githubData['location'];
+ }
+
+ // Twitter
+ if (WCF::getSession()->getVar('__twitterData')) {
+ $twitterData = WCF::getSession()->getVar('__twitterData');
+ $this->additionalFields['authData'] = 'twitter:'.$twitterData['user_id'];
+
+ WCF::getSession()->unregister('__twitterData');
+
+ $registerVia3rdParty = true;
+
+ if (isset($twitterData['description'])) $saveOptions[User::getUserOptionID('aboutMe')] = $twitterData['description'];
+ if (isset($twitterData['location'])) $saveOptions[User::getUserOptionID('location')] = $twitterData['location'];
+ }
+
+ // Facebook
+ if (WCF::getSession()->getVar('__facebookData')) {
+ $facebookData = WCF::getSession()->getVar('__facebookData');
+ $this->additionalFields['authData'] = 'facebook:'.$facebookData['id'];
+
+ WCF::getSession()->unregister('__facebookData');
+
+ $registerVia3rdParty = true;
+
+ $saveOptions[User::getUserOptionID('gender')] = ($facebookData['gender'] == 'male' ? UserProfile::GENDER_MALE : UserProfile::GENDER_FEMALE);
+
+ if (isset($facebookData['birthday'])) {
+ list($month, $day, $year) = explode('/', $facebookData['birthday']);
+ $saveOptions[User::getUserOptionID('birthday')] = $year.'-'.$month.'-'.$day;
+ }
+ if (isset($facebookData['bio'])) $saveOptions[User::getUserOptionID('aboutMe')] = $facebookData['bio'];
+ if (isset($facebookData['location'])) $saveOptions[User::getUserOptionID('location')] = $facebookData['location']['name'];
+ if (isset($facebookData['website'])) {
+ if (!Regex::compile('^https?://')->match($facebookData['website'])) {
+ $facebookData['website'] = 'http://' . $facebookData['website'];
+ }
+
+ $saveOptions[User::getUserOptionID('homepage')] = $facebookData['website'];
+ }
+
+ // avatar
+ if (isset($facebookData['picture']) && !$facebookData['picture']['data']['is_silhouette']) {
+ $avatarURL = $facebookData['picture']['data']['url'];
+ }
+ }
+
+ // Google Plus
+ if (WCF::getSession()->getVar('__googleData')) {
+ $googleData = WCF::getSession()->getVar('__googleData');
+ $this->additionalFields['authData'] = 'google:'.$googleData['id'];
+
+ WCF::getSession()->unregister('__googleData');
+
+ $registerVia3rdParty = true;
+
+ switch ($googleData['gender']) {
+ case 'male':
+ $saveOptions[User::getUserOptionID('gender')] = UserProfile::GENDER_MALE;
+ break;
+ case 'female':
+ $saveOptions[User::getUserOptionID('gender')] = UserProfile::GENDER_FEMALE;
+ break;
+ }
+ if (isset($facebookData['birthday'])) $saveOptions[User::getUserOptionID('birthday')] = $googleData['birthday'];
+ }
+
+ // create fake password
+ $this->password = StringUtil::getRandomID();
+ }
+
+ $this->additionalFields['languageID'] = $this->languageID;
+ $this->additionalFields['registrationIpAddress'] = WCF::getSession()->ipAddress;
+
+ // generate activation code
+ $addDefaultGroups = true;
+ if ((REGISTER_ACTIVATION_METHOD == 1 && !$registerVia3rdParty) || REGISTER_ACTIVATION_METHOD == 2) {
+ $activationCode = UserRegistrationUtil::getActivationCode();
+ $this->additionalFields['activationCode'] = $activationCode;
+ $addDefaultGroups = false;
+ $this->groupIDs = UserGroup::getGroupIDsByType(array(UserGroup::EVERYONE, UserGroup::GUESTS));
+ }
+
+ // check gravatar support
+ if (MODULE_GRAVATAR && Gravatar::test($this->email)) {
+ $this->additionalFields['enableGravatar'] = 1;
+ }
+
+ // create user
+ $data = array(
+ 'data' => array_merge($this->additionalFields, array(
+ 'username' => $this->username,
+ 'email' => $this->email,
+ 'password' => $this->password,
+ )),
+ 'groups' => $this->groupIDs,
+ 'languages' => $this->visibleLanguages,
+ 'options' => $saveOptions,
+ 'addDefaultGroups' => $addDefaultGroups
+ );
+ $this->objectAction = new UserAction(array(), 'create', $data);
+ $result = $this->objectAction->executeAction();
+ $user = $result['returnValues'];
+ $userEditor = new UserEditor($user);
+
+ // update user rank
+ if (MODULE_USER_RANK && !REGISTER_ACTIVATION_METHOD) {
+ $action = new UserProfileAction(array($userEditor), 'updateUserRank');
+ $action->executeAction();
+ }
+ // update user online marking
+ $action = new UserProfileAction(array($userEditor), 'updateUserOnlineMarking');
+ $action->executeAction();
+
+ // set avatar if provided
+ if (!empty($avatarURL)) {
+ $userAvatarAction = new UserAvatarAction(array(), 'fetchRemoteAvatar', array(
+ 'url' => $avatarURL,
+ 'userEditor' => $userEditor
+ ));
+ $userAvatarAction->executeAction();
+ }
+
+ // update session
+ WCF::getSession()->changeUser($user);
+
+ // activation management
+ if (REGISTER_ACTIVATION_METHOD == 0) {
+ $this->message = 'wcf.user.register.success';
+ }
+ else if (REGISTER_ACTIVATION_METHOD == 1) {
+ // registering via 3rdParty leads to instant activation
+ if ($registerVia3rdParty) {
+ $this->message = 'wcf.user.register.success';
+ }
+ else {
+ $mail = new Mail(array($this->username => $this->email),
+ WCF::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail.subject'),
+ WCF::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail', array('user' => $user))
+ );
+ $mail->send();
+ $this->message = 'wcf.user.register.needActivation';
+ }
+ }
+ else if (REGISTER_ACTIVATION_METHOD == 2) {
+ $this->message = 'wcf.user.register.awaitActivation';
+ }
+
+ // notify admin
+ if (REGISTER_ADMIN_NOTIFICATION) {
+ // get default language
+ $language = LanguageFactory::getInstance()->getLanguage(LanguageFactory::getInstance()->getDefaultLanguageID());
+
+ // send mail
+ $mail = new Mail(MAIL_ADMIN_ADDRESS,
+ $language->getDynamicVariable('wcf.user.register.notification.mail.subject'),
+ $language->getDynamicVariable('wcf.user.register.notification.mail', array('user' => $user))
+ );
+ $mail->setLanguage($language);
+ $mail->send();
+ }
+
+ // login user
+ UserAuthenticationFactory::getInstance()->getUserAuthentication()->storeAccessData($user, $this->username, $this->password);
+ WCF::getSession()->unregister('recaptchaDone');
+ $this->saved();
+
+ // forward to index page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->getDynamicVariable($this->message, array('user' => $user)), 15);
+ exit;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\mail\Mail;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+use wcf\util\UserRegistrationUtil;
+use wcf\util\UserUtil;
+
+/**
+ * Shows the new activation code form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class RegisterNewActivationCodeForm extends AbstractForm {
+ /**
+ * username
+ * @var string
+ */
+ public $username = '';
+
+ /**
+ * password
+ * @var string
+ */
+ public $password = '';
+
+ /**
+ * email
+ * @var string
+ */
+ public $email = '';
+
+ /**
+ * user object
+ * @var wcf\data\user\User
+ */
+ public $user = null;
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']);
+ if (isset($_POST['password'])) $this->password = $_POST['password'];
+ if (isset($_POST['email'])) $this->email = StringUtil::trim($_POST['email']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // username
+ $this->validateUsername();
+
+ // password
+ $this->validatePassword();
+
+ // email
+ $this->validateEmail();
+ }
+
+ /**
+ * Validates the username.
+ */
+ public function validateUsername() {
+ if (empty($this->username)) {
+ throw new UserInputException('username');
+ }
+
+ $this->user = User::getUserByUsername($this->username);
+ if (!$this->user->userID) {
+ throw new UserInputException('username', 'notFound');
+ }
+
+ if ($this->user->activationCode == 0) {
+ throw new UserInputException('username', 'alreadyEnabled');
+ }
+ }
+
+ /**
+ * Validates the password.
+ */
+ public function validatePassword() {
+ if (empty($this->password)) {
+ throw new UserInputException('password');
+ }
+
+ // check password
+ if (!$this->user->checkPassword($this->password)) {
+ throw new UserInputException('password', 'false');
+ }
+ }
+
+ /**
+ * Validates the email address.
+ */
+ public function validateEmail() {
+ if (!empty($this->email)) {
+ if (!UserRegistrationUtil::isValidEmail($this->email)) {
+ throw new UserInputException('email', 'notValid');
+ }
+
+ // Check if email exists already.
+ if (!UserUtil::isAvailableEmail($this->email)) {
+ throw new UserInputException('email', 'notUnique');
+ }
+ }
+ }
+
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // generate activation code
+ $activationCode = UserRegistrationUtil::getActivationCode();
+
+ // save user
+ $userEditor = new UserEditor($this->user);
+ $parameters = array('activationCode' => $activationCode);
+ if (!empty($this->email)) $parameters['email'] = $this->email;
+ $userEditor->update($parameters);
+
+ // reload user to reflect changes
+ $this->user = new User($this->user->userID);
+
+ // send activation mail
+ $mail = new Mail(array($this->user->username => (!empty($this->email) ? $this->email : $this->user->email)), WCF::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail.subject'), WCF::getLanguage()->getDynamicVariable('wcf.user.register.needActivation.mail', array('user' => $this->user)));
+ $mail->send();
+ $this->saved();
+
+ // forward to index page
+ HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), WCF::getLanguage()->getDynamicVariable('wcf.user.newActivationCode.success', array('email' => (!empty($this->email) ? $this->email : $this->user->email))), 10);
+ exit;
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ if (empty($_POST) && WCF::getUser()->userID) {
+ $this->username = WCF::getUser()->username;
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'username' => $this->username,
+ 'password' => $this->password,
+ 'email' => $this->email
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ if (REGISTER_ACTIVATION_METHOD != 1) {
+ throw new IllegalLinkException();
+ }
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\option\category\UserOptionCategory;
+use wcf\data\user\UserAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\option\user\UserOptionHandler;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\style\StyleHandler;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * Shows the dynamic options edit form.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class SettingsForm extends AbstractForm {
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$loginRequired
+ */
+ public $loginRequired = true;
+
+ /**
+ * user option handler
+ * @var wcf\system\option\user\UserOptionHandler
+ */
+ public $optionHandler = null;
+
+ /**
+ * @see wcf\form\AbstractForm::$errorType
+ */
+ public $errorType = array();
+
+ /**
+ * option category
+ * @var string
+ */
+ public $category = 'general';
+
+ /**
+ * list of available content languages
+ * @var array<wcf\data\language\Language>
+ */
+ public $availableContentLanguages = array();
+
+ /**
+ * list of available languages
+ * @var array<wcf\data\language\Language>
+ */
+ public $availableLanguages = array();
+
+ /**
+ * list of available styles
+ * @var array<wcf\data\style\Style>
+ */
+ public $availableStyles = array();
+
+ /**
+ * list of content language ids
+ * @var array<integer>
+ */
+ public $contentLanguageIDs = array();
+
+ /**
+ * language id
+ * @var integer
+ */
+ public $languageID = 0;
+
+ /**
+ * style id
+ * @var integer
+ */
+ public $styleID = 0;
+
+ /**
+ * @see wcf\page\AbstractPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (!empty($_REQUEST['category'])) {
+ $this->category = $_REQUEST['category'];
+
+ // validate category
+ if (UserOptionCategory::getCategoryByName('settings.'.$this->category) === null) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ $this->optionHandler = new UserOptionHandler(false, '', 'settings.'.$this->category);
+ $this->optionHandler->setUser(WCF::getUser());
+
+ if ($this->category == 'general') {
+ $this->availableContentLanguages = LanguageFactory::getInstance()->getContentLanguages();
+ $this->availableLanguages = LanguageFactory::getInstance()->getLanguages();
+ $this->availableStyles = StyleHandler::getInstance()->getAvailableStyles();
+ }
+ }
+
+ /**
+ * @see wcf\form\AbstractForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ $this->optionHandler->readUserInput($_POST);
+
+ // static options
+ if ($this->category == 'general') {
+ if (isset($_POST['contentLanguageIDs']) && is_array($_POST['contentLanguageIDs'])) $this->contentLanguageIDs = ArrayUtil::toIntegerArray($_POST['contentLanguageIDs']);
+ if (isset($_POST['languageID'])) $this->languageID = intval($_POST['languageID']);
+ if (isset($_POST['styleID'])) $this->styleID = intval($_POST['styleID']);
+ }
+ }
+
+ /**
+ * @see wcf\form\AbstractForm::validate()
+ */
+ public function validate() {
+ parent::validate();
+
+ // dynamic options
+ $optionErrors = $this->optionHandler->validate();
+ if (!empty($optionErrors)) {
+ $this->errorType = $optionErrors;
+ throw new UserInputException('options', $this->errorType);
+ }
+
+ // static options
+ if ($this->category == 'general') {
+ // validate language id
+ if (!isset($this->availableLanguages[$this->languageID])) {
+ $this->languageID = LanguageFactory::getInstance()->getDefaultLanguageID();
+ }
+
+ // validate content language ids
+ foreach ($this->contentLanguageIDs as $key => $languageID) {
+ if (!isset($this->availableContentLanguages[$languageID])) {
+ unset($this->contentLanguageIDs[$key]);
+ }
+ }
+
+ if (empty($this->contentLanguageIDs) && isset($this->availableContentLanguages[$this->languageID])) {
+ $this->contentLanguageIDs[] = $this->languageID;
+ }
+
+ // validate style id
+ if (!isset($this->availableStyles[$this->styleID])) {
+ $this->styleID = 0;
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // default values
+ if (empty($_POST)) {
+ // static options
+ if ($this->category == 'general') {
+ $this->contentLanguageIDs = WCF::getUser()->getLanguageIDs();
+ $this->languageID = WCF::getUser()->languageID;
+ $this->styleID = WCF::getUser()->styleID;
+ }
+ }
+ }
+
+ /**
+ * @see wcf\form\AbstractForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ $saveOptions = $this->optionHandler->save();
+ $parameters = array('options' => $saveOptions);
+ // static options
+ if ($this->category == 'general') {
+ $parameters['data'] = array(
+ 'languageID' => $this->languageID,
+ 'styleID' => $this->styleID
+ );
+ $parameters['languageIDs'] = $this->contentLanguageIDs;
+ }
+
+ $this->objectAction = new UserAction(array(WCF::getUser()), 'update', $parameters);
+ $this->objectAction->executeAction();
+
+ // static options
+ if ($this->category == 'general') {
+ // reset user language ids cache
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'languageIDs');
+ }
+ $this->saved();
+
+ WCF::getTPL()->assign('success', true);
+ }
+
+ /**
+ * @see wcf\page\Page::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'optionTree' => $this->optionHandler->getOptionTree(),
+ 'category' => $this->category
+ ));
+ // static options
+ if ($this->category == 'general') {
+ WCF::getTPL()->assign(array(
+ 'availableContentLanguages' => $this->availableContentLanguages,
+ 'availableLanguages' => $this->availableLanguages,
+ 'availableStyles' => $this->availableStyles,
+ 'contentLanguageIDs' => $this->contentLanguageIDs,
+ 'languageID' => $this->languageID,
+ 'styleID' => $this->styleID
+ ));
+ }
+ }
+
+ /**
+ * @see wcf\page\Page::show()
+ */
+ public function show() {
+ // set active tab
+ UserMenu::getInstance()->setActiveMenuItem('wcf.user.option.category.settings.'.$this->category);
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\user\User;
+use wcf\data\user\UserAction;
+use wcf\system\bbcode\MessageParser;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\user\signature\SignatureCache;
+use wcf\system\WCF;
+
+/**
+ * Shows the signature edit form.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage form
+ * @category Community Framework
+ */
+class SignatureEditForm extends MessageForm {
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$loginRequired
+ */
+ public $loginRequired = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$neededModules
+ */
+ public $neededModules = array('MODULE_USER_SIGNATURE');
+
+ /**
+ * @see wcf\page\AbstractPage::$templateName
+ */
+ public $templateName = 'signatureEdit';
+
+ /**
+ * parsed signature cache
+ * @var string
+ */
+ public $signatureCache = null;
+
+ /**
+ * @see wcf\form\RecaptchaForm::$useCaptcha
+ */
+ public $useCaptacha = false;
+
+ /**
+ * @see wcf\form\MessageForm::$allowedBBCodesPermission
+ */
+ public $allowedBBCodesPermission = 'user.signature.allowedBBCodes';
+
+ /**
+ * @see wcf\form\MessageForm::$permissionCanUseSmilies
+ */
+ public $permissionCanUseSmilies = 'user.signature.canUseSmilies';
+
+ /**
+ * @see wcf\form\MessageForm::$permissionCanUseHtml
+ */
+ public $permissionCanUseHtml = 'user.signature.canUseHtml';
+
+ /**
+ * @see wcf\form\MessageForm::$permissionCanUseBBCodes
+ */
+ public $permissionCanUseBBCodes = 'user.signature.canUseBBCodes';
+
+ /**
+ * @see wcf\form\MessageForm::$showSignatureSetting
+ */
+ public $showSignatureSetting = false;
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ if (WCF::getUser()->disableSignature) throw new PermissionDeniedException();
+
+ AbstractForm::validate();
+
+ if (!empty($this->text)) {
+ $this->validateText();
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // default values
+ if (empty($_POST)) {
+ $this->enableBBCodes = WCF::getUser()->signatureEnableBBCodes;
+ $this->enableHtml = WCF::getUser()->signatureEnableHtml;
+ $this->enableSmilies = WCF::getUser()->signatureEnableSmilies;
+ $this->text = WCF::getUser()->signature;
+ $this->preParse = true;
+ }
+
+ $this->signatureCache = SignatureCache::getInstance()->getSignature(WCF::getUser());
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'signatureCache' => $this->signatureCache
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ // set active tab
+ UserMenu::getInstance()->setActiveMenuItem('wcf.user.menu.profile.signature');
+
+ parent::show();
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ $this->objectAction = new UserAction(array(WCF::getUser()), 'update', array(
+ 'data' => array(
+ 'signature' => $this->text,
+ 'signatureEnableBBCodes' => $this->enableBBCodes,
+ 'signatureEnableHtml' => $this->enableHtml,
+ 'signatureEnableSmilies' => $this->enableSmilies
+ )
+ ));
+ $this->objectAction->executeAction();
+ SignatureCache::getInstance()->getSignature(new User(WCF::getUser()->userID));
+ $this->saved();
+
+ // show success message
+ WCF::getTPL()->assign('success', true);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\acp\form\UserOptionListForm;
+use wcf\data\search\SearchEditor;
+use wcf\system\breadcrumb\Breadcrumb;
+use wcf\system\dashboard\DashboardHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\UserInputException;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the user search form.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage form
+ * @category Community Framework
+ */
+class UserSearchForm extends UserOptionListForm {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.user.search';
+
+ /**
+ * username
+ * @var string
+ */
+ public $username = '';
+
+ /**
+ * matches
+ * @var array<integer>
+ */
+ public $matches = array();
+
+ /**
+ * condtion builder object
+ * @var wcf\system\database\condition\PreparedStatementConditionBuilder
+ */
+ public $conditions = null;
+
+ /**
+ * search id
+ * @var integer
+ */
+ public $searchID = 0;
+
+ /**
+ * number of results
+ * @var integer
+ */
+ public $maxResults = 1000;
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']);
+ }
+
+ /**
+ * @see wcf\acp\form\AbstractOptionListForm::initOptionHandler()
+ */
+ protected function initOptionHandler() {
+ $this->optionHandler->enableSearchMode();
+ $this->optionHandler->init();
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ $this->readOptionTree();
+
+ // add breadcrumbs
+ WCF::getBreadcrumbs()->add(new Breadcrumb(WCF::getLanguage()->get('wcf.user.members'), LinkHandler::getInstance()->getLink('MembersList')));
+ }
+
+ /**
+ * Reads option tree on page init.
+ */
+ protected function readOptionTree() {
+ $this->optionTree = $this->optionHandler->getOptionTree();
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ DashboardHandler::getInstance()->loadBoxes('com.woltlab.wcf.user.MembersListPage', $this);
+
+ WCF::getTPL()->assign(array(
+ 'username' => $this->username,
+ 'optionTree' => $this->optionTree,
+ 'sidebarCollapsed' => UserCollapsibleContentHandler::getInstance()->isCollapsed('com.woltlab.wcf.collapsibleSidebar', 'com.woltlab.wcf.user.MembersListPage'),
+ 'sidebarName' => 'com.woltlab.wcf.user.MembersListPage'
+ ));
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // store search result in database
+ $search = SearchEditor::create(array(
+ 'userID' => WCF::getUser()->userID ?: null,
+ 'searchData' => serialize(array('matches' => $this->matches)),
+ 'searchTime' => TIME_NOW,
+ 'searchType' => 'users'
+ ));
+
+ // get new search id
+ $this->searchID = $search->searchID;
+ $this->saved();
+
+ // forward to result page
+ $url = LinkHandler::getInstance()->getLink('MembersList', array('id' => $this->searchID));
+ HeaderUtil::redirect($url);
+ exit;
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ AbstractForm::validate();
+
+ // do search
+ $this->search();
+
+ if (empty($this->matches)) {
+ throw new UserInputException('search', 'noMatches');
+ }
+ }
+
+ /**
+ * Search for users which fit to the search values.
+ */
+ protected function search() {
+ $this->matches = array();
+ $sql = "SELECT user_table.userID
+ FROM wcf".WCF_N."_user user_table
+ LEFT JOIN wcf".WCF_N."_user_option_value option_value
+ ON (option_value.userID = user_table.userID)";
+
+ // build search condition
+ $this->conditions = new PreparedStatementConditionBuilder();
+
+ // static fields
+ $this->buildStaticConditions();
+
+ // dynamic fields
+ $this->buildDynamicConditions();
+
+ // do search
+ $statement = WCF::getDB()->prepareStatement($sql.$this->conditions, $this->maxResults);
+ $statement->execute($this->conditions->getParameters());
+ while ($row = $statement->fetchArray()) {
+ $this->matches[] = $row['userID'];
+ }
+ }
+
+ /**
+ * Builds the static conditions.
+ */
+ protected function buildStaticConditions() {
+ if (!empty($this->username)) {
+ $this->conditions->add("user_table.username LIKE ?", array('%'.addcslashes($this->username, '_%').'%'));
+ }
+ }
+
+ /**
+ * Builds the dynamic conditions.
+ */
+ protected function buildDynamicConditions() {
+ foreach ($this->optionHandler->getCategoryOptions('profile') as $option) {
+ $option = $option['object'];
+
+ $value = isset($this->optionHandler->optionValues[$option->optionName]) ? $this->optionHandler->optionValues[$option->optionName] : null;
+ $this->optionHandler->getTypeObject($option->optionType)->getCondition($this->conditions, $option, $value);
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\system\dashboard\DashboardHandler;
+use wcf\system\menu\page\PageMenu;
+use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the dashboard page.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class DashboardPage extends AbstractPage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.user.dashboard';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededModules
+ */
+ public $neededModules = array('MODULE_DASHBOARD_PAGE');
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // remove default breadcrumb entry
+ if (PageMenu::getInstance()->getLandingPage()->menuItem == 'wcf.user.dashboard') {
+ WCF::getBreadcrumbs()->remove(0);
+ }
+ }
+
+ /**
+ * @see wcf\page\AbstractPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ DashboardHandler::getInstance()->loadBoxes('com.woltlab.wcf.user.DashboardPage', $this);
+
+ WCF::getTPL()->assign(array(
+ 'sidebarCollapsed' => UserCollapsibleContentHandler::getInstance()->isCollapsed('com.woltlab.wcf.collapsibleSidebar', 'com.woltlab.wcf.user.DashboardPage'),
+ 'sidebarName' => 'com.woltlab.wcf.user.DashboardPage',
+ 'allowSpidersToIndexThisPage' => true
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\WCF;
+
+/**
+ * Shows a list with all users the active user is following.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class FollowingPage extends MultipleLinkPage {
+ /**
+ * @see wcf\page\AbstractPage::$loginRequired
+ */
+ public $loginRequired = true;
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$objectListClassName
+ */
+ public $objectListClassName = 'wcf\data\user\follow\UserFollowingList';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$sqlOrderBy
+ */
+ public $sqlOrderBy = 'user_follow.time DESC';
+
+ /**
+ * @see wcf\page\MultipleLinkPage::readData()
+ */
+ protected function initObjectList() {
+ parent::initObjectList();
+
+ $this->objectList->getConditionBuilder()->add("user_follow.userID = ?", array(WCF::getUser()->userID));
+ }
+
+ /**
+ * @see wcf\page\Page::show()
+ */
+ public function show() {
+ // set active tab
+ UserMenu::getInstance()->setActiveMenuItem('wcf.user.menu.community.following');
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\WCF;
+
+/**
+ * Shows a list with all users the active user ignores.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class IgnoredUsersPage extends MultipleLinkPage {
+ /**
+ * @see wcf\page\AbstractPage::$loginRequired
+ */
+ public $loginRequired = true;
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$objectListClassName
+ */
+ public $objectListClassName = 'wcf\data\user\ignore\ViewableUserIgnoreList';
+
+ /**
+ * @see wcf\data\DatabaseObjectList::$sqlOrderBy
+ */
+ public $sqlOrderBy = 'user_ignore.time DESC';
+
+ /**
+ * @see wcf\page\MultipleLinkPage::readData()
+ */
+ protected function initObjectList() {
+ parent::initObjectList();
+
+ $this->objectList->getConditionBuilder()->add("user_ignore.userID = ?", array(WCF::getUser()->userID));
+ }
+
+ /**
+ * @see wcf\page\Page::show()
+ */
+ public function show() {
+ // set active tab
+ UserMenu::getInstance()->setActiveMenuItem('wcf.user.menu.community.ignoredUsers');
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\data\search\Search;
+use wcf\system\dashboard\DashboardHandler;
+use wcf\system\database\PostgreSQLDatabase;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Shows members page.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class MembersListPage extends SortablePage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.user.members';
+
+ /**
+ * available letters
+ * @var string
+ */
+ public static $availableLetters = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('user.profile.canViewMembersList');
+
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$itemsPerPage
+ */
+ public $itemsPerPage = MEMBERS_LIST_USERS_PER_PAGE;
+
+ /**
+ * @see wcf\page\SortablePage::$defaultSortField
+ */
+ public $defaultSortField = MEMBERS_LIST_DEFAULT_SORT_FIELD;
+
+ /**
+ * @see wcf\page\SortablePage::$defaultSortOrder
+ */
+ public $defaultSortOrder = MEMBERS_LIST_DEFAULT_SORT_ORDER;
+
+ /**
+ * @see wcf\page\SortablePage::$validSortFields
+ */
+ public $validSortFields = array('username', 'registrationDate', 'activityPoints');
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$objectListClassName
+ */
+ public $objectListClassName = 'wcf\data\user\UserProfileList';
+
+ /**
+ * letter
+ * @var string
+ */
+ public $letter = '';
+
+ /**
+ * id of a user search
+ * @var integer
+ */
+ public $searchID = 0;
+
+ /**
+ * user search
+ * @var wcf\data\search\Search
+ */
+ public $search = null;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ // letter
+ if (isset($_REQUEST['letter']) && StringUtil::length($_REQUEST['letter']) == 1 && StringUtil::indexOf(self::$availableLetters, $_REQUEST['letter']) !== false) {
+ $this->letter = $_REQUEST['letter'];
+ }
+
+ if (!empty($_REQUEST['id'])) {
+ $this->searchID = intval($_REQUEST['id']);
+ $this->search = new Search($this->searchID);
+ if (!$this->search->searchID || $this->search->userID != WCF::getUser()->userID || $this->search->searchType != 'users') {
+ throw new IllegalLinkException();
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\MultipleLinkPage::initObjectList()
+ */
+ protected function initObjectList() {
+ parent::initObjectList();
+
+ if ($this->search !== null) {
+ $searchData = unserialize($this->search->searchData);
+ $this->objectList->getConditionBuilder()->add("user_table.userID IN (?)", array($searchData['matches']));
+ unset($searchData);
+ }
+
+ if (!empty($this->letter)) {
+ if ($this->letter == '#') {
+ // PostgreSQL
+ if (WCF::getDB() instanceof PostgreSQLDatabase) {
+ $this->objectList->getConditionBuilder()->add("SUBSTRING(username FROM 1 for 1) IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')");
+ }
+ else {
+ // MySQL
+ $this->objectList->getConditionBuilder()->add("SUBSTRING(username,1,1) IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')");
+ }
+ }
+ else {
+ $this->objectList->getConditionBuilder()->add("username LIKE ?", array($this->letter.'%'));
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ DashboardHandler::getInstance()->loadBoxes('com.woltlab.wcf.user.MembersListPage', $this);
+
+ WCF::getTPL()->assign(array(
+ 'letters' => str_split(self::$availableLetters),
+ 'letter' => $this->letter,
+ 'searchID' => $this->searchID,
+ 'sidebarCollapsed' => UserCollapsibleContentHandler::getInstance()->isCollapsed('com.woltlab.wcf.collapsibleSidebar', 'com.woltlab.wcf.user.MembersListPage'),
+ 'sidebarName' => 'com.woltlab.wcf.user.MembersListPage',
+ 'allowSpidersToIndexThisPage' => true
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows a list with outstanding notifications of the active user.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class NotificationListPage extends MultipleLinkPage {
+ /**
+ * @see wcf\page\AbstractPage::$loginRequired
+ */
+ public $loginRequired = true;
+
+ /**
+ * list of outstanding notifications
+ * @var array<array>
+ */
+ public $notifications = array();
+
+ /**
+ * @see wcf\page\MultipleLinkPage::countItems()
+ */
+ public function countItems() {
+ return UserNotificationHandler::getInstance()->getNotificationCount();
+ }
+
+ /**
+ * @see wcf\page\MultipleLinkPage::initObjectList()
+ */
+ protected function initObjectList() {}
+
+ /**
+ * @see wcf\page\MultipleLinkPage::readObjects()
+ */
+ protected function readObjects() {}
+
+ /**
+ * @see wcf\page\AbstractPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ $this->notifications = UserNotificationHandler::getInstance()->getNotifications($this->sqlLimit, $this->sqlOffset, true);
+ }
+
+ /**
+ * @see wcf\page\AbstractPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'notifications' => $this->notifications
+ ));
+ }
+
+ /**
+ * @see wcf\page\Page::show()
+ */
+ public function show() {
+ // set active tab
+ UserMenu::getInstance()->setActiveMenuItem('wcf.user.menu.community.notification');
+
+ parent::show();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\data\user\activity\event\ViewableUserActivityEventList;
+use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
+use wcf\system\breadcrumb\Breadcrumb;
+use wcf\system\dashboard\DashboardHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the global recent activity list page.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class RecentActivityListPage extends AbstractPage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.user.recentActivity';
+
+ /**
+ * viewable user activity event list
+ * @var wcf\data\user\activity\event\ViewableUserActivityEventList
+ */
+ public $eventList = null;
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ $this->eventList = new ViewableUserActivityEventList();
+ $this->eventList->readObjects();
+
+ // add breadcrumbs
+ WCF::getBreadcrumbs()->add(new Breadcrumb(WCF::getLanguage()->get('wcf.user.members'), LinkHandler::getInstance()->getLink('MembersList')));
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ $lastEventTime = $this->eventList->getLastEventTime();
+
+ // removes orphaned and non-accessable events
+ UserActivityEventHandler::validateEvents($this->eventList);
+
+ DashboardHandler::getInstance()->loadBoxes('com.woltlab.wcf.user.MembersListPage', $this);
+
+ WCF::getTPL()->assign(array(
+ 'eventList' => $this->eventList,
+ 'lastEventTime' => $lastEventTime,
+ 'sidebarCollapsed' => UserCollapsibleContentHandler::getInstance()->isCollapsed('com.woltlab.wcf.collapsibleSidebar', 'com.woltlab.wcf.user.MembersListPage'),
+ 'sidebarName' => 'com.woltlab.wcf.user.MembersListPage',
+ 'allowSpidersToIndexThisPage' => true
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\system\breadcrumb\Breadcrumb;
+use wcf\system\dashboard\DashboardHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the team members list.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class TeamPage extends MultipleLinkPage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.user.team';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('user.profile.canViewMembersList');
+
+ /**
+ * @see wcf\page\AbstractPage::$neededModules
+ */
+ public $neededModules = array('MODULE_TEAM_PAGE');
+
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$itemsPerPage
+ */
+ public $itemsPerPage = 1000;
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$sortField
+ */
+ public $sortField = MEMBERS_LIST_DEFAULT_SORT_FIELD;
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$sortOrder
+ */
+ public $sortOrder = MEMBERS_LIST_DEFAULT_SORT_ORDER;
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$objectListClassName
+ */
+ public $objectListClassName = 'wcf\data\user\TeamList';
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // add breadcrumbs
+ WCF::getBreadcrumbs()->add(new Breadcrumb(WCF::getLanguage()->get('wcf.user.members'), LinkHandler::getInstance()->getLink('MembersList')));
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ DashboardHandler::getInstance()->loadBoxes('com.woltlab.wcf.user.MembersListPage', $this);
+
+ WCF::getTPL()->assign(array(
+ 'sidebarCollapsed' => UserCollapsibleContentHandler::getInstance()->isCollapsed('com.woltlab.wcf.collapsibleSidebar', 'com.woltlab.wcf.user.MembersListPage'),
+ 'sidebarName' => 'com.woltlab.wcf.user.MembersListPage',
+ 'allowSpidersToIndexThisPage' => true
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\follow\UserFollowerList;
+use wcf\data\user\follow\UserFollowingList;
+use wcf\data\user\profile\visitor\UserProfileVisitor;
+use wcf\data\user\profile\visitor\UserProfileVisitorEditor;
+use wcf\data\user\profile\visitor\UserProfileVisitorList;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserProfile;
+use wcf\system\breadcrumb\Breadcrumb;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\menu\user\profile\UserProfileMenu;
+use wcf\system\request\LinkHandler;
+use wcf\system\MetaTagHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the user profile page.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class UserPage extends AbstractPage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.user.members';
+
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('user.profile.canViewUserProfile');
+
+ /**
+ * edit profile on page load
+ * @var boolean
+ */
+ public $editOnInit = false;
+
+ /**
+ * overview editable content object type
+ * @var wcf\data\object\type\ObjectType
+ */
+ public $objectType = null;
+
+ /**
+ * profile content for active menu item
+ * @var string
+ */
+ public $profileContent = '';
+
+ /**
+ * user id
+ * @var integer
+ */
+ public $userID = 0;
+
+ /**
+ * user object
+ * @var wcf\data\user\UserProfile
+ */
+ public $user = null;
+
+ /**
+ * follower list
+ * @var wcf\data\user\follow\UserFollowerList
+ */
+ public $followerList = null;
+
+ /**
+ * following list
+ * @var wcf\data\user\follow\UserFollowingList
+ */
+ public $followingList = null;
+
+ /**
+ * visitor list
+ * @var wcf\data\user\profile\visitor\UserProfileVisitorList
+ */
+ public $visitorList = null;
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) $this->userID = intval($_REQUEST['id']);
+ $this->user = UserProfile::getUserProfile($this->userID);
+ if ($this->user === null) {
+ throw new IllegalLinkException();
+ }
+
+ // check is Accessible
+ if ($this->user->isProtected()) {
+ throw new PermissionDeniedException();
+ }
+
+ if (isset($_REQUEST['editOnInit'])) $this->editOnInit = true;
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // add breadcrumbs
+ WCF::getBreadcrumbs()->add(new Breadcrumb(WCF::getLanguage()->get('wcf.user.members'), LinkHandler::getInstance()->getLink('MembersList')));
+
+ // get profile content
+ if ($this->editOnInit) {
+ // force 'about' tab as primary if editing profile
+ UserProfileMenu::getInstance()->setActiveMenuItem('about');
+ }
+
+ $activeMenuItem = UserProfileMenu::getInstance()->getActiveMenuItem();
+ $contentManager = $activeMenuItem->getContentManager();
+ $this->profileContent = $contentManager->getContent($this->user->userID);
+ $this->objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.profileEditableContent', 'com.woltlab.wcf.user.profileAbout');
+
+ // get followers
+ $this->followerList = new UserFollowerList();
+ $this->followerList->getConditionBuilder()->add('user_follow.followUserID = ?', array($this->userID));
+ $this->followerList->sqlLimit = 10;
+ $this->followerList->readObjects();
+
+ // get following
+ $this->followingList = new UserFollowingList();
+ $this->followingList->getConditionBuilder()->add('user_follow.userID = ?', array($this->userID));
+ $this->followingList->sqlLimit = 10;
+ $this->followingList->readObjects();
+
+ // get visitors
+ $this->visitorList = new UserProfileVisitorList();
+ $this->visitorList->getConditionBuilder()->add('user_profile_visitor.ownerID = ?', array($this->userID));
+ $this->visitorList->sqlLimit = 10;
+ $this->visitorList->readObjects();
+
+ MetaTagHandler::getInstance()->addTag('og:url', 'og:url', LinkHandler::getInstance()->getLink('User', array('object' => $this->user->getDecoratedObject())), true);
+ MetaTagHandler::getInstance()->addTag('og:type', 'og:type', 'profile', true);
+ MetaTagHandler::getInstance()->addTag('og:profile:username', 'og:profile:username', $this->user->username, true);
+ MetaTagHandler::getInstance()->addTag('og:title', 'og:title', WCF::getLanguage()->getDynamicVariable('wcf.user.profile', array('user' => $this->user)) . ' - ' . WCF::getLanguage()->get(PAGE_TITLE), true);
+ MetaTagHandler::getInstance()->addTag('og:image', 'og:image', $this->user->getAvatar()->getURL(), true);
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'editOnInit' => $this->editOnInit,
+ 'overviewObjectType' => $this->objectType,
+ 'profileContent' => $this->profileContent,
+ 'userID' => $this->userID,
+ 'user' => $this->user,
+ 'followers' => $this->followerList->getObjects(),
+ 'followerCount' => $this->followerList->countObjects(),
+ 'following' => $this->followingList->getObjects(),
+ 'followingCount' => $this->followingList->countObjects(),
+ 'visitors' => $this->visitorList->getObjects(),
+ 'visitorCount' => $this->visitorList->countObjects(),
+ 'allowSpidersToIndexThisPage' => true
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ // update profile hits
+ if ($this->user->userID != WCF::getUser()->userID && !WCF::getSession()->spiderID) {
+ $editor = new UserEditor($this->user->getDecoratedObject());
+ $editor->updateCounters(array('profileHits' => 1));
+
+ // save visitor
+ if (WCF::getUser()->userID && !WCF::getUser()->invisible) {
+ if (($visitor = UserProfileVisitor::getObject($this->user->userID, WCF::getUser()->userID)) !== null) {
+ $editor = new UserProfileVisitorEditor($visitor);
+ $editor->update(array(
+ 'time' => TIME_NOW
+ ));
+ }
+ else {
+ UserProfileVisitorEditor::create(array(
+ 'ownerID' => $this->user->userID,
+ 'userID' => WCF::getUser()->userID,
+ 'time' => TIME_NOW
+ ));
+ }
+ }
+ }
+
+ parent::show();
+ }
+
+ /**
+ * @see wcf\page\ITrackablePage::getObjectType()
+ */
+ public function getObjectType() {
+ return 'com.woltlab.wcf.user';
+ }
+
+ /**
+ * @see wcf\page\ITrackablePage::getObjectID()
+ */
+ public function getObjectID() {
+ return $this->userID;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\breadcrumb\Breadcrumb;
+use wcf\system\dashboard\DashboardHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\collapsible\content\UserCollapsibleContentHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows page which lists all users who are online.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage page
+ * @category Community Framework
+ */
+class UsersOnlineListPage extends SortablePage {
+ /**
+ * @see wcf\page\AbstractPage::$activeMenuItem
+ */
+ public $activeMenuItem = 'wcf.user.usersOnline';
+
+ /**
+ * @see wcf\page\AbstractPage::$neededPermissions
+ */
+ public $neededPermissions = array('user.profile.canViewUsersOnlineList');
+
+ /**
+ * @see wcf\page\AbstractPage::$enableTracking
+ */
+ public $enableTracking = true;
+
+ /**
+ * @see wcf\page\SortablePage::$defaultSortField
+ */
+ public $defaultSortField = USERS_ONLINE_DEFAULT_SORT_FIELD;
+
+ /**
+ * @see wcf\page\SortablePage::$defaultSortOrder
+ */
+ public $defaultSortOrder = USERS_ONLINE_DEFAULT_SORT_ORDER;
+
+ /**
+ * @see wcf\page\SortablePage::$validSortFields
+ */
+ public $validSortFields = array('username', 'lastActivityTime', 'requestURI');
+
+ /**
+ * @see wcf\page\MultipleLinkPage::$objectListClassName
+ */
+ public $objectListClassName = 'wcf\data\user\online\UsersOnlineList';
+
+ /**
+ * page locations
+ * @var array
+ */
+ public $locations = array();
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (WCF::getSession()->getPermission('admin.user.canViewIpAddress')) {
+ $this->validSortFields[] = 'ipAddress';
+ $this->validSortFields[] = 'userAgent';
+ }
+ }
+
+ /**
+ * @see wcf\page\MultipleLinkPage::readParameters()
+ */
+ protected function initObjectList() {
+ parent::initObjectList();
+
+ if (!USERS_ONLINE_SHOW_ROBOTS) $this->objectList->getConditionBuilder()->add('session.spiderID = 0');
+ if (!USERS_ONLINE_SHOW_GUESTS) {
+ if (USERS_ONLINE_SHOW_ROBOTS) $this->objectList->getConditionBuilder()->add('(session.userID IS NOT NULL OR session.spiderID <> 0)');
+ else $this->objectList->getConditionBuilder()->add('session.userID IS NOT NULL');
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ parent::readData();
+
+ // add breadcrumbs
+ WCF::getBreadcrumbs()->add(new Breadcrumb(WCF::getLanguage()->get('wcf.user.members'), LinkHandler::getInstance()->getLink('MembersList')));
+
+ // load locations
+ foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.online.location') as $objectType) {
+ $this->locations[$objectType->controller] = $objectType;
+ }
+
+ // cache data
+ foreach ($this->objectList as $userOnline) {
+ if (isset($this->locations[$userOnline->controller]) && $this->locations[$userOnline->controller]->getProcessor()) {
+ $this->locations[$userOnline->controller]->getProcessor()->cache($userOnline);
+ }
+ }
+
+ // set locations
+ foreach ($this->objectList as $userOnline) {
+ if (isset($this->locations[$userOnline->controller])) {
+ if ($this->locations[$userOnline->controller]->getProcessor()) {
+ $userOnline->setLocation($this->locations[$userOnline->controller]->getProcessor()->get($userOnline, $this->locations[$userOnline->controller]->languagevariable));
+ }
+ else {
+ $userOnline->setLocation(WCF::getLanguage()->get($this->locations[$userOnline->controller]->languagevariable));
+ }
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ DashboardHandler::getInstance()->loadBoxes('com.woltlab.wcf.user.MembersListPage', $this);
+
+ WCF::getTPL()->assign(array(
+ 'sidebarCollapsed' => UserCollapsibleContentHandler::getInstance()->isCollapsed('com.woltlab.wcf.collapsibleSidebar', 'com.woltlab.wcf.user.MembersListPage'),
+ 'sidebarName' => 'com.woltlab.wcf.user.MembersListPage',
+ 'allowSpidersToIndexThisPage' => true
+ ));
+ }
+
+ /**
+ * @see wcf\page\MultipleLinkPage::readObjects()
+ */
+ protected function readObjects() {
+ $this->objectList->sqlLimit = 0;
+ if ($this->sqlOrderBy) $this->objectList->sqlOrderBy = ($this->sortField == 'lastActivityTime' ? 'session.' : '').$this->sqlOrderBy;
+ $this->objectList->readObjects();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\dashboard\box\DashboardBoxList;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * Caches user dashboard boxes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class DashboardBoxCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+ */
+ protected function rebuild(array $parameters) {
+ $data = array(
+ 'boxes' => array(),
+ 'pages' => array()
+ );
+
+ // load boxes
+ $boxList = new DashboardBoxList();
+ $boxList->readObjects();
+
+ foreach ($boxList as $box) {
+ $data['boxes'][$box->boxID] = $box;
+ }
+
+ // load settings
+ $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.dashboardContainer');
+ $objectTypeIDs = array();
+ foreach ($objectTypes as $objectType) {
+ $objectTypeIDs[] = $objectType->objectTypeID;
+ }
+
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("objectTypeID IN (?)", array($objectTypeIDs));
+
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_dashboard_option
+ ".$conditions."
+ ORDER BY showOrder ASC";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ while ($row = $statement->fetchArray()) {
+ if (!isset($data['pages'][$row['objectTypeID']])) {
+ $data['pages'][$row['objectTypeID']] = array();
+ }
+
+ $data['pages'][$row['objectTypeID']][] = $row['boxID'];
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\user\UserList;
+
+/**
+ * Caches a list of the most active members.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class MostActiveMembersCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::$maxLifetime
+ */
+ protected $maxLifetime = 600;
+
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+ */
+ protected function rebuild(array $parameters) {
+ $userProfileList = new UserList();
+ $userProfileList->getConditionBuilder()->add('user_table.activityPoints > 0');
+ $userProfileList->sqlOrderBy = 'user_table.activityPoints DESC';
+ $userProfileList->sqlLimit = 5;
+ $userProfileList->readObjectIDs();
+
+ return $userProfileList->getObjectIDs();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\user\UserList;
+
+/**
+ * Caches a list of the newest members.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class NewestMembersCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::$maxLifetime
+ */
+ protected $maxLifetime = 300;
+
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+ */
+ protected function rebuild(array $parameters) {
+ $userProfileList = new UserList();
+ $userProfileList->sqlOrderBy = 'user_table.registrationDate DESC';
+ $userProfileList->sqlLimit = 5;
+ $userProfileList->readObjectIDs();
+
+ return $userProfileList->getObjectIDs();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\user\menu\item\UserMenuItem;
+use wcf\system\WCF;
+
+/**
+ * Caches the user menu item tree.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class UserMenuCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+ */
+ protected function rebuild(array $parameters) {
+ $data = array();
+
+ // get all option categories
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_user_option_category
+ WHERE parentCategoryName = ?
+ ORDER BY showOrder ASC";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array('settings'));
+ while ($row = $statement->fetchArray()) {
+ if (!isset($data['wcf.user.menu.settings'])) {
+ $data['wcf.user.menu.settings'] = array();
+ }
+
+ $categoryShortName = str_replace('settings.', '', $row['categoryName']);
+
+ $data['wcf.user.menu.settings'][] = new UserMenuItem(null, array(
+ 'packageID' => $row['packageID'],
+ 'menuItem' => 'wcf.user.option.category.'.$row['categoryName'],
+ 'parentMenuItem' => 'wcf.user.menu.settings',
+ 'menuItemController' => 'wcf\form\SettingsForm',
+ 'menuItemLink' => ($categoryShortName != 'general' ? 'category='.$categoryShortName : ''),
+ 'permissions' => $row['permissions'],
+ 'options' => $row['options']
+ ));
+ }
+
+ // get all menu items
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_user_menu_item
+ ORDER BY showOrder ASC";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ while ($row = $statement->fetchArray()) {
+ if (!isset($data[$row['parentMenuItem']])) {
+ $data[$row['parentMenuItem']] = array();
+ }
+
+ $data[$row['parentMenuItem']][] = new UserMenuItem(null, $row);
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\user\notification\event\UserNotificationEvent;
+use wcf\system\WCF;
+
+/**
+ * Caches user notification events.
+ *
+ * @author Marcell Werk, Oliver Kliebisch
+ * @copyright 2001-2013 WoltLab GmbH, Oliver Kliebisch
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class UserNotificationEventCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+ */
+ protected function rebuild(array $parameters) {
+ $data = array();
+
+ // get events
+ $sql = "SELECT event.*, object_type.objectType
+ FROM wcf".WCF_N."_user_notification_event event
+ LEFT JOIN wcf".WCF_N."_object_type object_type
+ ON (object_type.objectTypeID = event.objectTypeID)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ while ($row = $statement->fetchArray()) {
+ if (!isset($data[$row['objectType']])) {
+ $data[$row['objectType']] = array();
+ }
+
+ if (!isset($data[$row['objectType']][$row['eventName']])) {
+ $databaseObject = new UserNotificationEvent(null, $row);
+ $data[$row['objectType']][$row['eventName']] = $databaseObject->getProcessor();
+ }
+ }
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\user\profile\menu\item\UserProfileMenuItemList;
+
+/**
+ * Caches the user profile menu items.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class UserProfileMenuCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+ */
+ protected function rebuild(array $parameters) {
+ $itemList = new UserProfileMenuItemList();
+ $itemList->sqlOrderBy = "user_profile_menu_item.showOrder ASC";
+ $itemList->readObjects();
+
+ return $itemList->getObjects();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\system\WCF;
+
+/**
+ * Caches the number of members and the newest member.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.cache.builder
+ * @category Community Framework
+ */
+class UserStatsCacheBuilder extends AbstractCacheBuilder {
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::$maxLifetime
+ */
+ protected $maxLifetime = 600;
+
+ /**
+ * @see wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+ */
+ protected function rebuild(array $parameters) {
+ $data = array();
+
+ // number of members
+ $sql = "SELECT COUNT(*) AS amount
+ FROM wcf".WCF_N."_user";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ $data['members'] = $statement->fetchColumn();
+
+ // newest member
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_user
+ ORDER BY userID DESC";
+ $statement = WCF::getDB()->prepareStatement($sql, 1);
+ $statement->execute();
+ $data['newestMember'] = $statement->fetchObject('wcf\data\user\User');
+
+ return $data;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\clipboard\action;
+use wcf\data\clipboard\action\ClipboardAction;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Prepares clipboard editor items for user objects.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.clipboard.action
+ * @category Community Framework
+ */
+class UserExtendedClipboardAction extends AbstractClipboardAction {
+ /**
+ * @see wcf\system\clipboard\action\AbstractClipboardAction::$supportedActions
+ */
+ protected $supportedActions = array('merge', 'enable');
+
+ /**
+ * @see wcf\system\clipboard\action\IClipboardAction::execute()
+ */
+ public function execute(array $objects, ClipboardAction $action) {
+ $item = parent::execute($objects, $action);
+
+ if ($item === null) {
+ return null;
+ }
+
+ // handle actions
+ switch ($action->actionName) {
+ case 'merge':
+ $item->setURL(LinkHandler::getInstance()->getLink('UserMerge'));
+ break;
+ }
+
+ return $item;
+ }
+
+ /**
+ * @see wcf\system\clipboard\action\IClipboardAction::getClassName()
+ */
+ public function getClassName() {
+ return 'wcf\data\user\UserAction';
+ }
+
+ /**
+ * @see wcf\system\clipboard\action\IClipboardAction::getTypeName()
+ */
+ public function getTypeName() {
+ return 'com.woltlab.wcf.user';
+ }
+
+ /**
+ * Returns the ids of the users which can be enabled.
+ *
+ * @return array<integer>
+ */
+ protected function validateEnable() {
+ // check permissions
+ if (!WCF::getSession()->getPermission('admin.user.canEnableUser')) {
+ return array();
+ }
+
+ $userIDs = array();
+ foreach ($this->objects as $user) {
+ if ($user->activationCode) $userIDs[] = $user->userID;
+ }
+
+ return $userIDs;
+ }
+
+ /**
+ * Returns the ids of the users which can be merge.
+ *
+ * @return array<integer>
+ */
+ protected function validateMerge() {
+ // check permissions
+ if (!WCF::getSession()->getPermission('admin.user.canEditUser')) {
+ return array();
+ }
+
+ $userIDs = array_keys($this->objects);
+ if (count($userIDs) < 2) return array();
+
+ return $userIDs;
+ }
+}
<?php
namespace wcf\system\cronjob;
use wcf\data\cronjob\Cronjob;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\visitTracker\VisitTracker;
use wcf\system\WCF;
/**
public function execute(Cronjob $cronjob) {
parent::execute($cronjob);
+ // clean up notifications
+ $sql = "DELETE FROM wcf".WCF_N."_user_notification
+ WHERE time < ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ (TIME_NOW - 86400 * USER_CLEANUP_NOTIFICATION_LIFETIME)
+ ));
+
+ // clean up user activity events
+ $sql = "DELETE FROM wcf".WCF_N."_user_activity_event
+ WHERE time < ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ (TIME_NOW - 86400 * USER_CLEANUP_ACTIVITY_EVENT_LIFETIME)
+ ));
+
+ // clean up profile visitors
+ $sql = "DELETE FROM wcf".WCF_N."_user_profile_visitor
+ WHERE time < ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ (TIME_NOW - 86400 * USER_CLEANUP_PROFILE_VISITOR_LIFETIME)
+ ));
+
+ // tracked visits
+ $sql = "DELETE FROM wcf".WCF_N."_tracked_visit
+ WHERE objectTypeID = ?
+ AND visitTime < ?";
+ $statement1 = WCF::getDB()->prepareStatement($sql);
+ $sql = "DELETE FROM wcf".WCF_N."_tracked_visit_type
+ WHERE objectTypeID = ?
+ AND visitTime < ?";
+ $statement2 = WCF::getDB()->prepareStatement($sql);
+ foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.visitTracker.objectType') as $objectType) {
+ // get lifetime
+ $lifetime = ($objectType->lifetime ?: VisitTracker::DEFAULT_LIFETIME);
+
+ // delete data
+ $statement1->execute(array(
+ $objectType->objectTypeID,
+ $lifetime
+ ));
+ $statement2->execute(array(
+ $objectType->objectTypeID,
+ $lifetime
+ ));
+ }
+
// clean up cronjob log
$sql = "DELETE FROM wcf".WCF_N."_cronjob_log
WHERE execTime < ?";
--- /dev/null
+<?php
+namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\data\user\notification\event\UserNotificationEventList;
+use wcf\data\user\notification\UserNotificationList;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserList;
+use wcf\data\user\UserProfile;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\mail\Mail;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Sends daily mail notifications.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.cronjob
+ * @category Community Framework
+ */
+class DailyMailNotificationCronjob extends AbstractCronjob {
+ /**
+ * @see wcf\system\cronjob\ICronjob::execute()
+ */
+ public function execute(Cronjob $cronjob) {
+ parent::execute($cronjob);
+
+ // get user ids
+ $userIDs = array();
+ $sql = "SELECT DISTINCT notification_to_user.userID
+ FROM wcf".WCF_N."_user_notification_to_user notification_to_user,
+ wcf".WCF_N."_user_notification notification
+ WHERE notification.notificationID = notification_to_user.notificationID
+ AND notification_to_user.mailNotified = 0
+ AND notification.time < ".(TIME_NOW - 3600 * 23);
+ $statement = WCF::getDB()->prepareStatement($sql, 250);
+ $statement->execute();
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['userID'];
+ }
+ if (empty($userIDs)) return;
+
+ // get users
+ $userList = new UserList();
+ $userList->setObjectIDs($userIDs);
+ $userList->readObjects();
+ $users = $userList->getObjects();
+
+ // get notifications
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("notification.notificationID = notification_to_user.notificationID");
+ $conditions->add("notification_to_user.userID IN (?)", array($userIDs));
+ $conditions->add("notification_to_user.mailNotified = ?", array(0));
+
+ $sql = "SELECT notification_to_user.notificationID, notification_event.eventID,
+ object_type.objectType, notification.objectID,
+ notification.additionalData, notification.authorID,
+ notification.time, notification_to_user.userID
+ FROM wcf".WCF_N."_user_notification_to_user notification_to_user,
+ wcf".WCF_N."_user_notification notification
+ LEFT JOIN wcf".WCF_N."_user_notification_event notification_event
+ ON (notification_event.eventID = notification.eventID)
+ LEFT JOIN wcf".WCF_N."_object_type object_type
+ ON (object_type.objectTypeID = notification_event.objectTypeID)
+ ".$conditions."
+ ORDER BY notification.time";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ // mark notifications as done
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($userIDs));
+ $conditions->add("mailNotified = ?", array(0));
+ $sql = "UPDATE wcf".WCF_N."_user_notification_to_user
+ SET mailNotified = 1
+ ".$conditions;
+ $statement2 = WCF::getDB()->prepareStatement($sql);
+ $statement2->execute($conditions->getParameters());
+
+ // collect data
+ $authorIDs = $eventsToUser = $objectTypes = $eventIDs = $notificationIDs = array();
+ $availableObjectTypes = UserNotificationHandler::getInstance()->getAvailableObjectTypes();
+ while ($row = $statement->fetchArray()) {
+ if (!isset($eventsToUser[$row['userID']])) $eventsToUser[$row['userID']] = array();
+ $eventsToUser[$row['userID']][] = $row;
+
+ // cache object types
+ if (!isset($objectTypes[$row['objectType']])) {
+ $objectTypes[$row['objectType']] = array(
+ 'objectType' => $availableObjectTypes[$row['objectType']],
+ 'objectIDs' => array(),
+ 'objects' => array()
+ );
+ }
+
+ $objectTypes[$row['objectType']]['objectIDs'][] = $row['objectID'];
+ $eventIDs[] = $row['eventID'];
+ $notificationIDs[] = $row['notificationID'];
+ $authorIDs[] = $row['authorID'];
+ }
+
+ // load authors
+ $authors = UserProfile::getUserProfiles($authorIDs);
+
+ // load objects associated with each object type
+ foreach ($objectTypes as $objectType => $objectData) {
+ $objectTypes[$objectType]['objects'] = $objectData['objectType']->getObjectsByIDs($objectData['objectIDs']);
+ }
+
+ // load required events
+ $eventList = new UserNotificationEventList();
+ $eventList->getConditionBuilder()->add("user_notification_event.eventID IN (?)", array($eventIDs));
+ $eventList->readObjects();
+ $eventObjects = $eventList->getObjects();
+
+ // load notification objects
+ $notificationList = new UserNotificationList();
+ $notificationList->getConditionBuilder()->add("user_notification.notificationID IN (?)", array($notificationIDs));
+ $notificationList->readObjects();
+ $notificationObjects = $notificationList->getObjects();
+
+ foreach ($eventsToUser as $userID => $events) {
+ if (!isset($users[$userID])) continue;
+ $user = $users[$userID];
+
+ // add mail header
+ $message = $user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.header', array(
+ 'user' => $user
+ ))."\n\n";
+
+ foreach ($events as $event) {
+ $className = $eventObjects[$event['eventID']]->className;
+ $class = new $className($eventObjects[$event['eventID']]);
+
+ $class->setObject(
+ $notificationObjects[$event['notificationID']],
+ $objectTypes[$event['objectType']]['objects'][$event['objectID']],
+ $authors[$event['authorID']],
+ unserialize($event['additionalData'])
+ );
+ $class->setLanguage($user->getLanguage());
+
+ if ($message != '') $message .= "\n\n";
+ $message .= $class->getEmailMessage();
+ }
+
+ // append notification mail footer
+ $token = $user->notificationMailToken;
+ if (!$token) {
+ // generate token if not present
+ $token = StringUtil::substring(StringUtil::getHash(serialize(array($user->userID, StringUtil::getRandomID()))), 0, 20);
+ $editor = new UserEditor($user);
+ $editor->update(array('notificationMailToken' => $token));
+ }
+
+ $message .= "\n\n".$user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.daily.footer', array(
+ 'user' => $user,
+ 'token' => $token
+ ));
+
+ // build mail
+ $mail = new Mail(array($user->username => $user->email), $user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.daily.subject', array('count' => count($events))), $message);
+ $mail->setLanguage($user->getLanguage());
+ $mail->send();
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\system\WCF;
+
+/**
+ * Updates the last activity timestamp in the user table.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.cronjob
+ * @category Community Framework
+ */
+class LastActivityCronjob extends AbstractCronjob {
+ /**
+ * @see wcf\system\cronjob\ICronjob::execute()
+ */
+ public function execute(Cronjob $cronjob) {
+ parent::execute($cronjob);
+
+ $sql = "UPDATE wcf".WCF_N."_user user_table,
+ wcf".WCF_N."_session session
+ SET user_table.lastActivityTime = session.lastActivityTime
+ WHERE user_table.userID = session.userID
+ AND session.userID <> 0";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\data\user\UserAction;
+use wcf\system\WCF;
+
+/**
+ * Deletes canceled user accounts.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.cronjob
+ * @category Community Framework
+ */
+class UserQuitCronjob extends AbstractCronjob {
+ /**
+ * @see wcf\system\cronjob\ICronjob::execute()
+ */
+ public function execute(Cronjob $cronjob) {
+ parent::execute($cronjob);
+
+ $userIDs = array();
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user
+ WHERE quitStarted > ?
+ AND quitStarted < ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ 0,
+ (TIME_NOW - 7 * 24 * 3600)
+ ));
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['userID'];
+ }
+
+ if (!empty($userIDs)) {
+ $action = new UserAction($userIDs, 'delete');
+ $action->executeAction();
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard;
+use wcf\data\dashboard\box\DashboardBox;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\page\IPage;
+use wcf\system\cache\builder\DashboardBoxCacheBuilder;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\ClassUtil;
+
+/**
+ * Handles dashboard boxes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard
+ * @category Community Framework
+ */
+class DashboardHandler extends SingletonFactory {
+ /**
+ * list of cached dashboard boxes
+ * @var array<wcf\data\dashboard\box\DashboardBox>
+ */
+ protected $boxCache = null;
+
+ /**
+ * configuration options for pages
+ * @var array<array>
+ */
+ protected $pageCache = null;
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ $this->boxCache = DashboardBoxCacheBuilder::getInstance()->getData(array(), 'boxes');
+ $this->pageCache = DashboardBoxCacheBuilder::getInstance()->getData(array(), 'pages');
+ }
+
+ /**
+ * Returns active dashboard boxes for given object type id.
+ *
+ * @param string $objectType
+ * @param wcf\page\IPage $page
+ */
+ public function loadBoxes($objectType, IPage $page) {
+ $objectTypeObj = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.dashboardContainer', $objectType);
+ if ($objectTypeObj === null) {
+ throw new SystemException("Unable to find object type '".$objectType."' for definition 'com.woltlab.wcf.user.dashboardContainer'");
+ }
+
+ $boxIDs = array();
+ if (isset($this->pageCache[$objectTypeObj->objectTypeID]) && is_array($this->pageCache[$objectTypeObj->objectTypeID])) {
+ foreach ($this->pageCache[$objectTypeObj->objectTypeID] as $boxID) {
+ $boxIDs[] = $boxID;
+ }
+ }
+
+ $contentTemplate = $sidebarTemplate = '';
+ foreach ($boxIDs as $boxID) {
+ $className = $this->boxCache[$boxID]->className;
+ if (!ClassUtil::isInstanceOf($className, 'wcf\system\dashboard\box\IDashboardBox')) {
+ throw new SystemException("'".$className."' does not implement 'wcf\system\dashboard\box\IDashboardbox'");
+ }
+
+ $boxObject = new $className();
+ $boxObject->init($this->boxCache[$boxID], $page);
+
+ if ($this->boxCache[$boxID]->boxType == 'content') {
+ $contentTemplate .= $boxObject->getTemplate();
+ }
+ else {
+ $sidebarTemplate .= $boxObject->getTemplate();
+ }
+ }
+
+ WCF::getTPL()->assign(array(
+ '__boxContent' => $contentTemplate,
+ '__boxSidebar' => $sidebarTemplate
+ ));
+ }
+
+ /**
+ * Sets default values upon installation, you should not call this method
+ * under any other circumstances. If you do not specify a list of box names,
+ * all boxes will be assigned as disabled for given object type.
+ *
+ * @param string $objectType
+ * @param array<names> $enableBoxNames
+ */
+ public static function setDefaultValues($objectType, array $enableBoxNames = array()) {
+ $objectTypeID = 0;
+
+ // no boxes given, aborting
+ if (empty($enableBoxNames)) {
+ return;
+ }
+
+ // get object type id (cache might be outdated)
+ if (PACKAGE_ID && PACKAGE_ID != 1) {
+ // reset object type cache
+ ObjectTypeCache::getInstance()->resetCache();
+
+ // get object type
+ $objectTypeObj = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.dashboardContainer', $objectType);
+ if ($objectTypeObj === null) {
+ throw new SystemException("Object type '".$objectType."' is not valid for definition 'com.woltlab.wcf.user.dashboardContainer'");
+ }
+
+ $objectTypeID = $objectTypeObj->objectTypeID;
+
+ // select available box ids
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("boxName IN (?)", array(array_keys($enableBoxNames)));
+
+ $sql = "SELECT boxID, boxName, boxType
+ FROM wcf".WCF_N."_dashboard_box
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ }
+ else {
+ // work-around during WCFSetup
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("object_type.objectType = ?", array($objectType));
+ $conditions->add("object_type_definition.definitionName = ?", array('com.woltlab.wcf.user.dashboardContainer'));
+
+ $sql = "SELECT object_type.objectTypeID
+ FROM wcf".WCF_N."_object_type object_type
+ LEFT JOIN wcf".WCF_N."_object_type_definition object_type_definition
+ ON (object_type_definition.definitionID = object_type.definitionID)
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ $row = $statement->fetchArray();
+ if ($row) {
+ $objectTypeID = $row['objectTypeID'];
+ }
+
+ if (!$objectTypeID) {
+ throw new SystemException("Object type '".$objectType."' is not valid for definition 'com.woltlab.wcf.user.dashboardContainer'");
+ }
+
+ // select available box ids
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("boxName IN (?)", array(array_keys($enableBoxNames)));
+
+ $sql = "SELECT boxID, boxName, boxType
+ FROM wcf".WCF_N."_dashboard_box
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ }
+
+ $boxes = array();
+ while ($row = $statement->fetchArray()) {
+ $boxes[$row['boxID']] = new DashboardBox(null, $row);
+ }
+
+ if (!empty($boxes)) {
+ $sql = "UPDATE wcf".WCF_N."_dashboard_option
+ SET showOrder = showOrder + 1
+ WHERE objectTypeID = ?
+ AND boxID IN (SELECT boxID FROM wcf".WCF_N."_dashboard_box WHERE boxType = ?)
+ AND showOrder >= ?";
+ $updateStatement = WCF::getDB()->prepareStatement($sql);
+ $sql = "INSERT INTO wcf".WCF_N."_dashboard_option
+ (objectTypeID, boxID, showOrder)
+ VALUES (?, ?, ?)";
+ $insertStatement = WCF::getDB()->prepareStatement($sql);
+
+ WCF::getDB()->beginTransaction();
+ foreach ($boxes as $boxID => $box) {
+ // move other boxes
+ $updateStatement->execute(array(
+ $objectTypeID,
+ $box->boxType,
+ $enableBoxNames[$box->boxName]
+ ));
+
+ // insert associations
+ $insertStatement->execute(array(
+ $objectTypeID,
+ $boxID,
+ $enableBoxNames[$box->boxName]
+ ));
+ }
+ WCF::getDB()->commitTransaction();
+ }
+ }
+
+ /**
+ * Clears dashboard box cache.
+ */
+ public static function clearCache() {
+ DashboardBoxCacheBuilder::getInstance()->reset();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\dashboard\box\DashboardBox;
+use wcf\page\IPage;
+use wcf\system\event\EventHandler;
+use wcf\system\WCF;
+
+/**
+ * Default implementation for dashboard boxes displayed within content container.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+abstract class AbstractContentDashboardBox implements IDashboardBox {
+ /**
+ * dashboard box object
+ * @var wcf\data\dashboard\box\DashboardBox
+ */
+ public $box = null;
+
+ /**
+ * IPage object
+ * @var wcf\page\IPage
+ */
+ public $page = null;
+
+ /**
+ * template name
+ * @var string
+ */
+ public $templateName = 'dashboardBoxContent';
+
+ /**
+ * @see wcf\system\dashboard\box\IDashboardBox::init()
+ */
+ public function init(DashboardBox $box, IPage $page) {
+ $this->box = $box;
+ $this->page = $page;
+
+ // fire event
+ EventHandler::getInstance()->fireAction($this, 'init');
+ }
+
+ /**
+ * @see wcf\system\dashboard\box\IDashboardBox::getTemplate()
+ */
+ public function getTemplate() {
+ $template = $this->render();
+ if (empty($template)) {
+ return '';
+ }
+
+ WCF::getTPL()->assign(array(
+ 'box' => $this->box,
+ 'template' => $template
+ ));
+
+ return WCF::getTPL()->fetch($this->templateName);
+ }
+
+ /**
+ * Renders box view.
+ *
+ * @return string
+ */
+ abstract protected function render();
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+
+/**
+ * Default implementation for dashboard boxes displayed within the sidebar container.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+abstract class AbstractSidebarDashboardBox extends AbstractContentDashboardBox {
+ /**
+ * @see wcf\system\dashboard\box\AbstractDashboardBoxContent::$templateName
+ */
+ public $templateName = 'dashboardBoxSidebar';
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\dashboard\box\DashboardBox;
+use wcf\page\IPage;
+
+/**
+ * Default interface for dashboard boxes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+interface IDashboardBox {
+ /**
+ * Initializes this box.
+ *
+ * @param wcf\data\dashboard\box\DashboardBox $box
+ * @param wcf\page\IPage $page
+ */
+ public function init(DashboardBox $box, IPage $page);
+
+ /**
+ * Returns parsed box template.
+ *
+ * @return string
+ */
+ public function getTemplate();
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\user\UserProfileList;
+use wcf\system\cache\builder\MostActiveMembersCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Shows a list of the most active members.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+class MostActiveMembersDashboardBox extends AbstractSidebarDashboardBox {
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+ */
+ protected function render() {
+ // get ids
+ $mostActiveMemberIDs = MostActiveMembersCacheBuilder::getInstance()->getData();
+ if (empty($mostActiveMemberIDs)) return '';
+
+ // get profile data
+ $userProfileList = new UserProfileList();
+ $userProfileList->sqlOrderBy = 'user_table.activityPoints DESC';
+ $userProfileList->setObjectIDs($mostActiveMemberIDs);
+ $userProfileList->readObjects();
+
+ WCF::getTPL()->assign(array(
+ 'mostActiveMembers' => $userProfileList
+ ));
+ return WCF::getTPL()->fetch('dashboardBoxMostActiveMembers');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\user\UserProfileList;
+use wcf\system\cache\builder\NewestMembersCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Shows a list of the newest members.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+class NewestMembersDashboardBox extends AbstractSidebarDashboardBox {
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+ */
+ protected function render() {
+ // get ids
+ $newestMemberIDs = NewestMembersCacheBuilder::getInstance()->getData();
+ if (empty($newestMemberIDs)) return '';
+
+ // get profile data
+ $userProfileList = new UserProfileList();
+ $userProfileList->sqlOrderBy = 'user_table.registrationDate DESC';
+ $userProfileList->setObjectIDs($newestMemberIDs);
+ $userProfileList->readObjects();
+
+ WCF::getTPL()->assign(array(
+ 'newestMembers' => $userProfileList
+ ));
+ return WCF::getTPL()->fetch('dashboardBoxNewestMembers');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\dashboard\box\DashboardBox;
+use wcf\data\user\activity\event\ViewableUserActivityEventList;
+use wcf\page\IPage;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\WCF;
+
+/**
+ * Dashboard box for recent activity.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+class RecentActivityDashboardBox extends AbstractContentDashboardBox {
+ /**
+ * recent activity list
+ * @var wcf\data\user\activity\event\ViewableUserActivityEventList
+ */
+ public $eventList = null;
+
+ /**
+ * true, if results were filtered by followed users
+ * @var boolean
+ */
+ public $filteredByFollowedUsers = false;
+
+ /**
+ * latest event time
+ * @var integer
+ */
+ public $lastEventTime = 0;
+
+ /**
+ * @see wcf\system\dashboard\box\IDashboardBox::init()
+ */
+ public function init(DashboardBox $box, IPage $page) {
+ parent::init($box, $page);
+
+ $this->eventList = new ViewableUserActivityEventList();
+ if (count(WCF::getUserProfileHandler()->getFollowingUsers())) {
+ $this->filteredByFollowedUsers = true;
+ $this->eventList->getConditionBuilder()->add('user_activity_event.userID IN (?)', array(WCF::getUserProfileHandler()->getFollowingUsers()));
+ }
+ $this->eventList->sqlLimit = RECENT_ACTIVITY_ITEMS;
+ $this->eventList->readObjects();
+ $this->lastEventTime = $this->eventList->getLastEventTime();
+
+ // removes orphaned and non-accessable events
+ UserActivityEventHandler::validateEvents($this->eventList);
+ }
+
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+ */
+ protected function render() {
+ if (count($this->eventList)) {
+ WCF::getTPL()->assign(array(
+ 'eventList' => $this->eventList,
+ 'lastEventTime' => $this->lastEventTime,
+ 'filteredByFollowedUsers' => $this->filteredByFollowedUsers
+ ));
+
+ return WCF::getTPL()->fetch('dashboardBoxRecentActivity');
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\dashboard\box\DashboardBox;
+use wcf\data\user\activity\event\ViewableUserActivityEventList;
+use wcf\page\IPage;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\WCF;
+
+/**
+ * Dashboard box for recent activity in the sidebar.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+class RecentActivitySidebarDashboardBox extends AbstractSidebarDashboardBox {
+ /**
+ * recent activity list
+ * @var wcf\data\user\activity\event\ViewableUserActivityEventList
+ */
+ public $eventList = null;
+
+ /**
+ * @see wcf\system\dashboard\box\IDashboardBox::init()
+ */
+ public function init(DashboardBox $box, IPage $page) {
+ parent::init($box, $page);
+
+ $this->eventList = new ViewableUserActivityEventList();
+ $this->eventList->sqlLimit = RECENT_ACTIVITY_SIDEBAR_ITEMS;
+ $this->eventList->readObjects();
+
+ // removes orphaned and non-accessable events
+ UserActivityEventHandler::validateEvents($this->eventList);
+ }
+
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+ */
+ protected function render() {
+ if (count($this->eventList)) {
+ WCF::getTPL()->assign(array(
+ 'eventList' => $this->eventList
+ ));
+
+ return WCF::getTPL()->fetch('dashboardBoxRecentActivitySidebar');
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\system\WCF;
+
+/**
+ * Dashboard box for registration button.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+class RegisterButtonDashboardBox extends AbstractSidebarDashboardBox {
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::$templateName
+ */
+ public $templateName = 'dashboardBoxRegisterButton';
+
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+ */
+ protected function render() {
+ return ((!WCF::getUser()->userID && !REGISTER_DISABLED) ? true : false);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\system\WCF;
+
+/**
+ * 'Signed in as' dashboard box.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+class SignedInAsDashboardBox extends AbstractSidebarDashboardBox {
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::$templateName
+ */
+ public $templateName = 'dashboardBoxSignedInAs';
+
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+ */
+ protected function render() {
+ return (WCF::getUser()->userID ? true : false);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\system\cache\builder\UserStatsCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Stats dashboard box.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.dashboard.box
+ * @category Community Framework
+ */
+class StatsSidebarDashboardBox extends AbstractSidebarDashboardBox {
+ /**
+ * @see wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+ */
+ protected function render() {
+ WCF::getTPL()->assign(array(
+ 'dashboardStats' => UserStatsCacheBuilder::getInstance()->getData()
+ ));
+
+ return WCF::getTPL()->fetch('dashboardBoxStatsSidebar');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\event\listener;
+use wcf\data\bbcode\BBCode;
+use wcf\data\user\UserList;
+use wcf\system\event\IEventListener;
+use wcf\system\request\LinkHandler;
+use wcf\system\Callback;
+use wcf\system\Regex;
+use wcf\util\StringUtil;
+
+/**
+ * Parses @user mentions.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.event.listener
+ * @category Community Framework
+ */
+class PreParserAtUserListener implements IEventListener {
+ /**
+ * @see wcf\system\event\IEventListener::execute()
+ */
+ public function execute($eventObj, $className, $eventName) {
+ if (!$eventObj->text) return;
+
+ // check if needed url BBCode is allowed
+ if ($eventObj->allowedBBCodes !== null && !BBCode::isAllowedBBCode('url', $eventObj->allowedBBCodes)) {
+ return;
+ }
+
+ static $userRegex = null;
+ if ($userRegex === null) {
+ $userRegex = new Regex("(?<=^|\s)@([^',\s][^,\s]{2,}|'(?:''|[^'])*')");
+ }
+
+ $userRegex->match($eventObj->text, true);
+ $matches = $userRegex->getMatches();
+
+ if (!empty($matches[1])) {
+ $usernames = array();
+ foreach ($matches[1] as $match) {
+ $username = self::getUsername($match);
+ if (!in_array($username, $usernames)) $usernames[] = $username;
+ }
+
+ if (!empty($usernames)) {
+ // fetch users
+ $userList = new UserList();
+ $userList->getConditionBuilder()->add('user_table.username IN (?)', array($usernames));
+ $userList->readObjects();
+ $users = array();
+ foreach ($userList as $user) {
+ $users[StringUtil::toLowerCase($user->username)] = $user;
+ }
+
+ $eventObj->text = $userRegex->replace($eventObj->text, new Callback(function ($matches) use ($users) {
+ $username = PreParserAtUserListener::getUsername($matches[1]);
+
+ if (isset($users[$username])) {
+ $link = LinkHandler::getInstance()->getLink('User', array(
+ 'object' => $users[$username]
+ ));
+ return "[url='".$link."']@".$users[$username]->username.'[/url]';
+ }
+
+ return $matches[0];
+ }));
+ }
+ }
+ }
+
+ /**
+ * Returns the username for the given regular expression match.
+ *
+ * @param string $match
+ * @return string
+ */
+ public static function getUsername($match) {
+ // remove escaped single quotation mark
+ $match = StringUtil::replace("''", "'", $match);
+
+ // remove single quotation marks
+ if ($match{0} == "'") {
+ $match = StringUtil::substring($match, 1, -1);
+ }
+
+ return StringUtil::toLowerCase($match);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\menu\user;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Default implementations of a user menu item provider.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.menu.user
+ * @category Community Framework
+ */
+class DefaultUserMenuItemProvider extends DatabaseObjectDecorator implements IUserMenuItemProvider {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\menu\item\UserMenuItem';
+
+ /**
+ * @see wcf\system\menu\page\IUserMenuItemProvider::isVisible()
+ */
+ public function isVisible() {
+ return true;
+ }
+
+ /**
+ * @see wcf\system\menu\page\IUserMenuItemProvider::getLink()
+ */
+ public function getLink() {
+ // explicit call to satisfy our interface
+ return $this->getDecoratedObject()->getLink();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\menu\user;
+use wcf\data\IDatabaseObjectProcessor;
+
+/**
+ * Any user menu item provider should implement this interface.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.menu.user
+ * @category Community Framework
+ */
+interface IUserMenuItemProvider extends IDatabaseObjectProcessor {
+ /**
+ * Returns true if the associated menu item should be visible for the active user.
+ *
+ * @return boolean
+ */
+ public function isVisible();
+
+ /**
+ * Returns the href of the associated menu item.
+ *
+ * @return string
+ */
+ public function getLink();
+}
--- /dev/null
+<?php
+namespace wcf\system\menu\user;
+use wcf\system\cache\builder\UserMenuCacheBuilder;
+use wcf\system\menu\ITreeMenuItem;
+use wcf\system\menu\TreeMenu;
+
+/**
+ * Builds the user menu.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.menu.user
+ * @category Community Framework
+ */
+class UserMenu extends TreeMenu {
+ /**
+ * @see wcf\system\menu\TreeMenu::loadCache()
+ */
+ protected function loadCache() {
+ parent::loadCache();
+
+ $this->menuItems = UserMenuCacheBuilder::getInstance()->getData();
+ }
+
+ /**
+ * @see wcf\system\menu\TreeMenu::checkMenuItem()
+ */
+ protected function checkMenuItem(ITreeMenuItem $item) {
+ if (!parent::checkMenuItem($item)) return false;
+
+ return $item->getProcessor()->isVisible();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\menu\user\profile;
+use wcf\data\user\profile\menu\item\UserProfileMenuItem;
+use wcf\system\cache\builder\UserProfileMenuCacheBuilder;
+use wcf\system\event\EventHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Builds the user profile menu.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.menu.user.profile
+ * @category Community Framework
+ */
+class UserProfileMenu extends SingletonFactory {
+ /**
+ * list of all menu items
+ * @var array<wcf\data\user\profile\menu\item\UserProfileMenuItem>
+ */
+ public $menuItems = null;
+
+ /**
+ * active menu item
+ * @var wcf\data\user\profile\menu\item\UserProfileMenuItem
+ */
+ public $activeMenuItem = null;
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ // get menu items from cache
+ $this->loadCache();
+
+ // check menu items
+ $this->checkMenuItems();
+
+ // call init event
+ EventHandler::getInstance()->fireAction($this, 'init');
+ }
+
+ /**
+ * Loads cached menu items.
+ */
+ protected function loadCache() {
+ // call loadCache event
+ EventHandler::getInstance()->fireAction($this, 'loadCache');
+
+ $this->menuItems = UserProfileMenuCacheBuilder::getInstance()->getData();
+ }
+
+ /**
+ * Checks the options and permissions of the menu items.
+ */
+ protected function checkMenuItems() {
+ foreach ($this->menuItems as $key => $item) {
+ if (!$this->checkMenuItem($item)) {
+ // remove this item
+ unset($this->menuItems[$key]);
+ }
+ }
+ }
+
+ /**
+ * Checks the options and permissions of given menu item.
+ *
+ * @param wcf\data\user\profile\menu\item\UserProfileMenuItem $item
+ * @return boolean
+ */
+ protected function checkMenuItem(UserProfileMenuItem $item) {
+ // check the options of this item
+ $hasEnabledOption = true;
+ if (!empty($item->options)) {
+ $hasEnabledOption = false;
+ $options = explode(',', strtoupper($item->options));
+ foreach ($options as $option) {
+ if (defined($option) && constant($option)) {
+ $hasEnabledOption = true;
+ break;
+ }
+ }
+ }
+ if (!$hasEnabledOption) return false;
+
+ // check the permission of this item for the active user
+ $hasPermission = true;
+ if (!empty($item->permissions)) {
+ $hasPermission = false;
+ $permissions = explode(',', $item->permissions);
+ foreach ($permissions as $permission) {
+ if (WCF::getSession()->getPermission($permission)) {
+ $hasPermission = true;
+ break;
+ }
+ }
+ }
+ if (!$hasPermission) return false;
+
+ return true;
+ }
+
+ /**
+ * Returns the list of menu items.
+ *
+ * @return array<wcf\data\user\profile\menu\item\UserProfileMenuItem>
+ */
+ public function getMenuItems() {
+ return $this->menuItems;
+ }
+
+ /**
+ * Sets active menu item.
+ *
+ * @param string $menuItem
+ * @return boolean
+ */
+ public function setActiveMenuItem($menuItem) {
+ foreach ($this->menuItems as $item) {
+ if ($item->menuItem == $menuItem) {
+ $this->activeMenuItem = $item;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the first menu item.
+ *
+ * @return wcf\data\user\profile\menu\item\UserProfileMenuItem
+ */
+ public function getActiveMenuItem() {
+ if (empty($this->menuItems)) {
+ return null;
+ }
+
+ if ($this->activeMenuItem === null) {
+ reset($this->menuItems);
+ $this->activeMenuItem = current($this->menuItems);
+ }
+
+ return $this->activeMenuItem;
+ }
+
+ /**
+ * Returns a specific menu item.
+ *
+ * @return wcf\data\user\profile\menu\item\UserProfileMenuItem
+ */
+ public function getMenuItem($menuItem) {
+ foreach ($this->menuItems as $item) {
+ if ($item->menuItem == $menuItem) {
+ return $item;
+ }
+ }
+
+ return null;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\menu\user\profile\content;
+use wcf\data\user\User;
+use wcf\system\option\user\UserOptionHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles user profile information content.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.menu.user.profile.content
+ * @category Community Framework
+ */
+class AboutUserProfileMenuContent extends SingletonFactory implements IUserProfileMenuContent {
+ /**
+ * user option handler object
+ * @var wcf\system\option\user\UserOptionHandler
+ */
+ public $optionHandler = null;
+
+ /**
+ * @see wcf\system\menu\user\profile\content\IUserProfileMenuContent::getContent()
+ */
+ public function getContent($userID) {
+ if ($this->optionHandler === null) {
+ $this->optionHandler = new UserOptionHandler(false, '', 'profile');
+ $this->optionHandler->enableEditMode(false);
+ $this->optionHandler->showEmptyOptions(false);
+ }
+
+ $user = new User($userID);
+ $this->optionHandler->setUser($user);
+
+ WCF::getTPL()->assign(array(
+ 'options' => $this->optionHandler->getOptionTree(),
+ 'userID' => $user->userID,
+ ));
+
+ return WCF::getTPL()->fetch('userProfileAbout');
+ }
+
+ /**
+ * @see wcf\system\menu\user\profile\content\IUserProfileMenuContent::isVisible()
+ */
+ public function isVisible($userID) {
+ return true;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\menu\user\profile\content;
+
+/**
+ * Default interface for user profile menu content.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.menu.user.profile.content
+ * @category Community Framework
+ */
+interface IUserProfileMenuContent {
+ /**
+ * Returns content for this user profile menu item.
+ *
+ * @param integer $userID
+ * @return string
+ */
+ public function getContent($userID);
+
+ /**
+ * Returns true if the associated menu item should be visible for the active user.
+ *
+ * @param integer $userID
+ * @return boolean
+ */
+ public function isVisible($userID);
+}
--- /dev/null
+<?php
+namespace wcf\system\menu\user\profile\content;
+use wcf\data\user\activity\event\ViewableUserActivityEventList;
+use wcf\system\user\activity\event\UserActivityEventHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles user activity events.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.menu.user.profile.content
+ * @category Community Framework
+ */
+class RecentActivityUserProfileMenuContent extends SingletonFactory implements IUserProfileMenuContent {
+ /**
+ * @see wcf\system\menu\user\profile\content\IUserProfileMenuContent::getContent()
+ */
+ public function getContent($userID) {
+ $eventList = new ViewableUserActivityEventList();
+ $eventList->getConditionBuilder()->add("user_activity_event.userID = ?", array($userID));
+ $eventList->readObjects();
+
+ $lastEventTime = $eventList->getLastEventTime();
+ if ($lastEventTime) {
+ UserActivityEventHandler::validateEvents($eventList);
+ }
+
+ WCF::getTPL()->assign(array(
+ 'eventList' => $eventList,
+ 'lastEventTime' => $lastEventTime,
+ 'placeholder' => WCF::getLanguage()->get('wcf.user.profile.content.recentActivity.noEntries'),
+ 'userID' => $userID
+ ));
+
+ return WCF::getTPL()->fetch('recentActivities');
+ }
+
+ /**
+ * @see wcf\system\menu\user\profile\content\IUserProfileMenuContent::isVisible()
+ */
+ public function isVisible($userID) {
+ return true;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option;
+use wcf\data\option\Option;
+use wcf\data\user\User;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\DateUtil;
+
+/**
+ * Option type implementation for birthday input fields.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option
+ * @category Community Framework
+ */
+class BirthdayOptionType extends DateOptionType {
+ /**
+ * input css class
+ * @var string
+ */
+ protected $inputClass = 'birthday';
+
+ /**
+ * @see wcf\system\option\IOptionType::getFormElement()
+ */
+ public function validate(Option $option, $newValue) {
+ parent::validate($option, $newValue);
+
+ if (empty($newValue)) return;
+
+ $timestamp = @strtotime($newValue);
+ if ($timestamp > TIME_NOW) {
+ throw new UserInputException($option->optionName, 'validationFailed');
+ }
+ }
+
+ /**
+ * @see wcf\system\option\IOptionType::getFormElement()
+ */
+ public function getFormElement(Option $option, $value) {
+ if ($value == '0000-00-00') $value = '';
+
+ return parent::getFormElement($option, $value);
+ }
+
+ /**
+ * @see wcf\system\option\ISearchableUserOption::getSearchFormElement()
+ */
+ public function getSearchFormElement(Option $option, $value) {
+ $ageFrom = $ageTo = '';
+ if (!empty($value['ageFrom'])) $ageFrom = intval($value['ageFrom']);
+ if (!empty($value['ageTo'])) $ageTo = intval($value['ageTo']);
+
+ WCF::getTPL()->assign(array(
+ 'option' => $option,
+ 'valueAgeFrom' => $ageFrom,
+ 'valueAgeTo' => $ageTo
+ ));
+ return WCF::getTPL()->fetch('birthdaySearchableOptionType');
+ }
+
+ /**
+ * @see wcf\system\option\ISearchableUserOption::getCondition()
+ */
+ public function getCondition(PreparedStatementConditionBuilder &$conditions, Option $option, $value) {
+ if (empty($value['ageFrom']) || empty($value['ageTo'])) return false;
+
+ $ageFrom = intval($value['ageFrom']);
+ $ageTo = intval($value['ageTo']);
+ if (!$ageFrom || !$ageTo) return false;
+
+ $dateFrom = DateUtil::getDateTimeByTimestamp(TIME_NOW)->sub(new \DateInterval('P'.($ageTo + 1).'Y'))->add(new \DateInterval('P1D'));
+ $dateTo = DateUtil::getDateTimeByTimestamp(TIME_NOW)->sub(new \DateInterval('P'.$ageFrom.'Y'));
+
+ $conditions->add("option_value.userOption".User::getUserOptionID('birthdayShowYear')." = ? AND option_value.userOption".$option->optionID." BETWEEN DATE(?) AND DATE(?)", array(1, $dateFrom->format('Y-m-d'), $dateTo->format('Y-m-d')));
+ return true;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option;
+use wcf\data\bbcode\BBCodeCache;
+use wcf\data\option\Option;
+use wcf\data\smiley\SmileyCache;
+use wcf\system\bbcode\BBCodeParser;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Option type implementation for message.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option
+ * @category Community Framework
+ */
+class MessageOptionType extends TextareaOptionType {
+ /**
+ * @see wcf\system\option\IOptionType::getFormElement()
+ */
+ public function getFormElement(Option $option, $value) {
+ $allowedBBCodes = array();
+ if ($option->allowedbbcodepermission) {
+ $allowedBBCodes = explode(',', WCF::getSession()->getPermission($option->allowedbbcodepermission));
+ }
+ else {
+ $allowedBBCodes = array_keys(BBCodeCache::getInstance()->getBBCodes());
+ }
+
+ WCF::getTPL()->assign(array(
+ 'allowedBBCodes' => $allowedBBCodes,
+ 'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies(),
+ 'option' => $option,
+ 'value' => $value
+ ));
+
+ return WCF::getTPL()->fetch('messageOptionType');
+ }
+
+ /**
+ * @see wcf\system\option\IOptionType::validate()
+ */
+ public function validate(Option $option, $newValue) {
+ parent::validate($option, $newValue);
+
+ if ($option->allowedbbcodepermission) {
+ $disallowedBBCodes = BBCodeParser::getInstance()->validateBBCodes($newValue, explode(',', WCF::getSession()->getPermission($option->allowedbbcodepermission)));
+ if (!empty($disallowedBBCodes)) {
+ WCF::getTPL()->assign('disallowedBBCodes', $disallowedBBCodes);
+ throw new UserInputException($option->optionName, 'disallowedBBCodes');
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option;
+use wcf\data\option\Option;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * Option type implementation for user option selection.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option
+ * @category Community Framework
+ */
+class UseroptionsOptionType extends AbstractOptionType {
+ /**
+ * list of available user options
+ * @var array<string>
+ */
+ protected static $userOptions = null;
+
+ /**
+ * @see wcf\system\option\IOptionType::validate()
+ */
+ public function validate(Option $option, $newValue) {
+ if (!is_array($newValue)) {
+ $newValue = array();
+ }
+
+ foreach ($newValue as $optionName) {
+ if (!in_array($optionName, self::getUserOptions())) {
+ throw new UserInputException($option->optionName, 'validationFailed');
+ }
+ }
+ }
+
+ /**
+ * @see wcf\system\option\IOptionType::getData()
+ */
+ public function getData(Option $option, $newValue) {
+ if (!is_array($newValue)) return '';
+ return implode(',', $newValue);
+ }
+
+ /**
+ * @see wcf\system\option\IOptionType::getFormElement()
+ */
+ public function getFormElement(Option $option, $value) {
+ WCF::getTPL()->assign(array(
+ 'option' => $option,
+ 'value' => explode(',', $value),
+ 'availableOptions' => self::getUserOptions()
+ ));
+ return WCF::getTPL()->fetch('useroptionsOptionType');
+ }
+
+ /**
+ * Returns the list of available user options.
+ *
+ * @return string
+ */
+ protected static function getUserOptions() {
+ if (self::$userOptions === null) {
+ self::$userOptions = array();
+ $sql = "SELECT optionName
+ FROM wcf".WCF_N."_user_option
+ WHERE categoryName LIKE 'profile%'
+ AND optionType <> 'boolean'
+ AND outputClass = ''";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ while ($row = $statement->fetchArray()) {
+ self::$userOptions[] = $row['optionName'];
+ }
+ }
+
+ return self::$userOptions;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\system\WCF;
+use wcf\util\DateUtil;
+
+/**
+ * User option output implementation for the output of a user's birthday.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class BirthdayUserOptionOutput extends DateUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ // set date format
+ $this->dateFormat = ($user->birthdayShowYear ? DateUtil::DATE_FORMAT : str_replace('Y', '', WCF::getLanguage()->get(DateUtil::DATE_FORMAT)));
+
+ // format date
+ $dateString = parent::getOutput($user, $option, $value);
+ if ($dateString && $user->birthdayShowYear) {
+ $age = DateUtil::getAge($value);
+ if ($age > 0) {
+ $dateString .= ' ('.$age.')';
+ }
+ }
+
+ return $dateString;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\util\DateUtil;
+
+/**
+ * User option output implementation for for the output of a date input.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class DateUserOptionOutput implements IUserOptionOutput {
+ /**
+ * date format
+ * @var string
+ */
+ protected $dateFormat = DateUtil::DATE_FORMAT;
+
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ if (empty($value) || $value == '0000-00-00') return '';
+
+ $date = self::splitDate($value);
+ return DateUtil::format(DateUtil::getDateTimeByTimestamp(gmmktime(12, 1, 1, $date['month'], $date['day'], $date['year'])), $this->dateFormat);
+ }
+
+ /**
+ * Splits the given dashed date into its components.
+ *
+ * @param string $value
+ * @return array<integer>
+ */
+ protected static function splitDate($value) {
+ $year = $month = $day = 0;
+ $optionValue = explode('-', $value);
+ if (isset($optionValue[0])) $year = intval($optionValue[0]);
+ if (isset($optionValue[1])) $month = intval($optionValue[1]);
+ if (isset($optionValue[2])) $day = intval($optionValue[2]);
+
+ return array('year' => $year, 'month' => $month, 'day' => $day);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\util\StringUtil;
+
+/**
+ * User option output implementation for the output of a facebook user profile.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class FacebookUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ if (empty($value)) return '';
+
+ $url = StringUtil::encodeHTML('http://www.facebook.com/'.$value);
+ $value = StringUtil::encodeHTML($value);
+
+ return '<a href="'.$url.'" class="externalURL"'.(EXTERNAL_LINK_REL_NOFOLLOW ? ' rel="nofollow"' : '').(EXTERNAL_LINK_TARGET_BLANK ? ' target="_blank"' : '').'>'.$value.'</a>';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\util\StringUtil;
+
+/**
+ * User option output implementation for the output of a float.
+ *
+ * @author Tobias Friebel
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class FloatUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ if (empty($value) || $value == '0') {
+ $value = 0.00;
+ }
+
+ return StringUtil::formatDouble($value, 2);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\util\StringUtil;
+
+/**
+ * User option output implementation for the output of a google+ user profile.
+ *
+ * @author Jeffrey Reichardt
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class GooglePlusUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ if (empty($value)) return '';
+
+ $url = StringUtil::encodeHTML('https://plus.google.com/'.$value.'/posts');
+ $value = StringUtil::encodeHTML($value);
+
+ return '<a href="'.$url.'" class="externalURL"'.(EXTERNAL_LINK_REL_NOFOLLOW ? ' rel="me nofollow"' : ' rel="me"').(EXTERNAL_LINK_TARGET_BLANK ? ' target="_blank"' : '').'>'.$value.'</a>';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\util\StringUtil;
+
+/**
+ * User option output implementation for an image.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class ImageUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ if (empty($value)) return '';
+
+ return '<img src="'.StringUtil::encodeHTML($value).'" alt="" />';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\system\bbcode\MessageParser;
+use wcf\system\WCF;
+
+/**
+ * User option output implementation for a formatted textarea value.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class MessageUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ MessageParser::getInstance()->setOutputType('text/html');
+
+ WCF::getTPL()->assign(array(
+ 'option' => $option,
+ 'value' => MessageParser::getInstance()->parse($value),
+ ));
+ return WCF::getTPL()->fetch('messageUserOptionOutput');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\util\StringUtil;
+
+/**
+ * User option output implementation for a simple textarea value.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class NewlineToBreakUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ return nl2br(StringUtil::encodeHTML($value));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\system\WCF;
+use wcf\util\OptionUtil;
+use wcf\util\StringUtil;
+
+/**
+ * User option output implementation for the output of select options.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class SelectOptionsUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ $result = self::getResult($option, $value);
+ if ($result === null) {
+ return '';
+ }
+ else if (is_array($result)) {
+ $output = '';
+ foreach ($result as $resultValue) {
+ if (!empty($output)) $output .= "<br />\n";
+ $output .= WCF::getLanguage()->get($resultValue);
+ }
+
+ return $output;
+ }
+ else {
+ return WCF::getLanguage()->get($result);
+ }
+ }
+
+ /**
+ * Returns the selected option value(s) for output.
+ *
+ * @param wcf\data\user\option\UserOption $option
+ * @param string $value
+ * @return mixed
+ */
+ protected static function getResult(UserOption $option, $value) {
+ $options = OptionUtil::parseSelectOptions($option->selectOptions);
+
+ // multiselect
+ if (StringUtil::indexOf($value, "\n") !== false) {
+ $values = explode("\n", $value);
+ $result = array();
+ foreach ($values as $value) {
+ if (isset($options[$value])) {
+ $result[] = $options[$value];
+ }
+ }
+
+ return $result;
+ }
+ else {
+ if (!empty($value) && isset($options[$value])) return $options[$value];
+ return null;
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\util\StringUtil;
+
+/**
+ * User option output implementation for the output of a twitter user profile.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class TwitterUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ if (empty($value)) return '';
+
+ $url = StringUtil::encodeHTML('http://twitter.com/'.$value);
+ $value = StringUtil::encodeHTML($value);
+
+ return '<a href="'.$url.'" class="externalURL"'.(EXTERNAL_LINK_REL_NOFOLLOW ? ' rel="nofollow"' : '').(EXTERNAL_LINK_TARGET_BLANK ? ' target="_blank"' : '').'>'.$value.'</a>';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\option\user;
+use wcf\data\user\option\UserOption;
+use wcf\data\user\User;
+use wcf\util\StringUtil;
+
+/**
+ * User option output implementation for the output of an url.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.option.user
+ * @category Community Framework
+ */
+class URLUserOptionOutput implements IUserOptionOutput {
+ /**
+ * @see wcf\system\option\user\IUserOptionOutput::getOutput()
+ */
+ public function getOutput(User $user, UserOption $option, $value) {
+ if (empty($value) || $value == 'http://') return '';
+
+ $value = self::getURL($value);
+ $value = StringUtil::encodeHTML($value);
+ return '<a href="'.$value.'" class="externalURL"'.(EXTERNAL_LINK_REL_NOFOLLOW ? ' rel="nofollow"' : '').(EXTERNAL_LINK_TARGET_BLANK ? ' target="_blank"' : '').'>'.$value.'</a>';
+ }
+
+ /**
+ * Formats the URL.
+ *
+ * @param string $url
+ * @return string
+ */
+ private static function getURL($url) {
+ if (!preg_match('~^https?://~i', $url)) {
+ $url = 'http://'.$url;
+ }
+
+ return $url;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\package\plugin;
+use wcf\system\cache\builder\DashboardBoxCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Installs, updates and deletes dashboard boxes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.package.plugin
+ * @category Community Framework
+ */
+class DashboardBoxPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$className
+ */
+ public $className = 'wcf\data\dashboard\box\DashboardBoxEditor';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName
+ */
+ public $tableName = 'dashboard_box';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$tagName
+ */
+ public $tagName = 'dashboardbox';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::handleDelete()
+ */
+ protected function handleDelete(array $items) {
+ $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
+ WHERE boxName = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ foreach ($items as $item) {
+ $statement->execute(array(
+ $item['attributes']['name'],
+ $this->installation->getPackageID()
+ ));
+ }
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::prepareImport()
+ */
+ protected function prepareImport(array $data) {
+ return array(
+ 'boxName' => $data['attributes']['name'],
+ 'boxType' => ($data['elements']['boxtype'] == 'content') ? 'content' : 'sidebar',
+ 'className' => $data['elements']['classname']
+ );
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::findExistingItem()
+ */
+ protected function findExistingItem(array $data) {
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_".$this->tableName."
+ WHERE boxName = ?
+ AND packageID = ?";
+ $parameters = array(
+ $data['boxName'],
+ $this->installation->getPackageID()
+ );
+
+ return array(
+ 'sql' => $sql,
+ 'parameters' => $parameters
+ );
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::cleanup()
+ */
+ protected function cleanup() {
+ DashboardBoxCacheBuilder::getInstance()->reset();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\package\plugin;
+
+/**
+ * Installs, updates and deletes user menu items.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.package.plugin
+ * @category Community Framework
+ */
+class UserMenuPackageInstallationPlugin extends AbstractMenuPackageInstallationPlugin {
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$className
+ */
+ public $className = 'wcf\data\user\menu\item\UserMenuItemEditor';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName
+ */
+ public $tableName = 'user_menu_item';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$tagName
+ */
+ public $tagName = 'usermenuitem';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::prepareImport()
+ */
+ protected function prepareImport(array $data) {
+ $result = parent::prepareImport($data);
+
+ // class name
+ if (!empty($data['elements']['classname'])) {
+ $result['className'] = $data['elements']['classname'];
+ }
+
+ return $result;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\package\plugin;
+use wcf\system\exception\SystemException;
+use wcf\system\WCF;
+
+/**
+ * Installs, updates and deletes user notification events.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.package.plugin
+ * @category Community Framework
+ */
+class UserNotificationEventPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$className
+ */
+ public $className = 'wcf\data\user\notification\event\UserNotificationEventEditor';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName
+ */
+ public $tableName = 'user_notification_event';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$tagName
+ */
+ public $tagName = 'event';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::handleDelete()
+ */
+ protected function handleDelete(array $items) {
+ $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
+ WHERE packageID = ?
+ AND eventName = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ foreach ($items as $item) {
+ $statement->execute(array(
+ $this->installation->getPackageID(),
+ $item['elements']['name']
+ ));
+ }
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::prepareImport()
+ */
+ protected function prepareImport(array $data) {
+ // get object type id
+ $sql = "SELECT object_type.objectTypeID
+ FROM wcf".WCF_N."_object_type object_type
+ WHERE object_type.objectType = ?
+ AND object_type.definitionID IN (
+ SELECT definitionID
+ FROM wcf".WCF_N."_object_type_definition
+ WHERE definitionName = 'com.woltlab.wcf.notification.objectType'
+ )";
+ $statement = WCF::getDB()->prepareStatement($sql, 1);
+ $statement->execute(array($data['elements']['objecttype']));
+ $row = $statement->fetchArray();
+ if (empty($row['objectTypeID'])) throw new SystemException("unknown notification object type '".$data['elements']['objecttype']."' given");
+ $objectTypeID = $row['objectTypeID'];
+
+ return array(
+ 'eventName' => $data['elements']['name'],
+ 'className' => $data['elements']['classname'],
+ 'objectTypeID' => $objectTypeID,
+ 'permissions' => (isset($data['elements']['permissions']) ? $data['elements']['permissions'] : ''),
+ 'options' => (isset($data['elements']['options']) ? $data['elements']['options'] : ''),
+ 'preset' => (!empty($data['elements']['preset']) ? 1 : 0)
+ );
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::findExistingItem()
+ */
+ protected function findExistingItem(array $data) {
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_".$this->tableName."
+ WHERE objectTypeID = ?
+ AND eventName = ?";
+ $parameters = array(
+ $data['objectTypeID'],
+ $data['eventName']
+ );
+
+ return array(
+ 'sql' => $sql,
+ 'parameters' => $parameters
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\package\plugin;
+use wcf\system\WCF;
+
+/**
+ * Installs, updates and deletes user profile menu items.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2011 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.package.plugin
+ * @category Community Framework
+ */
+class UserProfileMenuPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin {
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$className
+ */
+ public $className = 'wcf\data\user\profile\menu\item\UserProfileMenuItemEditor';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName
+ */
+ public $tableName = 'user_profile_menu_item';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$tagName
+ */
+ public $tagName = 'userprofilemenuitem';
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::handleDelete()
+ */
+ protected function handleDelete(array $items) {
+ $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName."
+ WHERE menuItem = ?
+ AND packageID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ foreach ($items as $item) {
+ $statement->execute(array(
+ $item['attributes']['name'],
+ $this->installation->getPackageID()
+ ));
+ }
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::prepareImport()
+ */
+ protected function prepareImport(array $data) {
+ // adjust show order
+ $showOrder = (isset($data['elements']['showorder'])) ? $data['elements']['showorder'] : null;
+ $showOrder = $this->getShowOrder($showOrder);
+
+ // merge values and default values
+ return array(
+ 'menuItem' => $data['attributes']['name'],
+ 'options' => (isset($data['elements']['options'])) ? $data['elements']['options'] : '',
+ 'permissions' => (isset($data['elements']['permissions'])) ? $data['elements']['permissions'] : '',
+ 'showOrder' => $showOrder,
+ 'className' => $data['elements']['classname']
+ );
+ }
+
+ /**
+ * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::findExistingItem()
+ */
+ protected function findExistingItem(array $data) {
+ $sql = "SELECT *
+ FROM wcf".WCF_N."_".$this->tableName."
+ WHERE menuItem = ?
+ AND packageID = ?";
+ $parameters = array(
+ $data['menuItem'],
+ $this->installation->getPackageID()
+ );
+
+ return array(
+ 'sql' => $sql,
+ 'parameters' => $parameters
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\sitemap;
+use wcf\system\sitemap\ISitemapProvider;
+use wcf\system\WCF;
+
+/**
+ * Provides a sitemap for user account.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.sitemap
+ * @category Community Framework
+ */
+class UserAccountSitemapProvider implements ISitemapProvider {
+ /**
+ * @see wcf\system\sitemap\ISitemapProvider::getTemplate()
+ */
+ public function getTemplate() {
+ return WCF::getTPL()->fetch('sitemapUserAccount');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\upload;
+use wcf\system\exception\SystemException;
+
+/**
+ * Validation strategy for avatar uploads.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.upload
+ * @category Community Framework
+ */
+class AvatarUploadFileValidationStrategy extends DefaultUploadFileValidationStrategy {
+ /**
+ * @see wcf\system\upload\IUploadFileValidationStrategy::validate()
+ */
+ public function validate(UploadFile $uploadFile) {
+ if (!parent::validate($uploadFile)) return false;
+
+ // get image size
+ try {
+ $imageData = getimagesize($uploadFile->getLocation());
+ if ($imageData[0] < 48 || $imageData[1] < 48) {
+ $uploadFile->setValidationErrorType('tooSmall');
+ return false;
+ }
+ }
+ catch (SystemException $e) {
+ $uploadFile->setValidationErrorType('badImage');
+ return false;
+ }
+
+ return true;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user;
+use wcf\data\user\UserProfile;
+use wcf\system\WCF;
+
+/**
+ * Provides a grouped list of users.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user
+ * @category Community Framework
+ */
+class GroupedUserList implements \Countable, \Iterator {
+ /**
+ * list of user profiles shared across all instances of GroupedUserList
+ * @var array<wcf\data\user\UserProfile>
+ */
+ protected static $users = array();
+
+ /**
+ * group name
+ * @var string
+ */
+ protected $groupName = '';
+
+ /**
+ * current iterator index
+ * @var integer
+ */
+ protected $index = 0;
+
+ /**
+ * message displayed if no users are in this group
+ * @var string
+ */
+ protected $noUsersMessage = '';
+
+ /**
+ * list of user ids assigned for this group
+ * @var array<integer>
+ */
+ protected $userIDs = array();
+
+ /**
+ * Creates a new grouped list of users.
+ *
+ * @param string $groupName
+ * @param string $noUsersMessage
+ */
+ public function __construct($groupName = '', $noUsersMessage = '') {
+ $this->groupName = $groupName;
+ $this->noUsersMessage = $noUsersMessage;
+ }
+
+ /**
+ * Returns the group name.
+ *
+ * @return string
+ */
+ public function getGroupName() {
+ return $this->groupName;
+ }
+
+ /**
+ * Returns the message if no users are in this group.
+ *
+ * @return string
+ */
+ public function getNoUsersMessage() {
+ return ($this->noUsersMessage) ? WCF::getLanguage()->get($this->noUsersMessage) : '';
+ }
+
+ /**
+ * @see wcf\system\user\GroupedUserList::getGroupName()
+ */
+ public function __toString() {
+ return $this->getGroupName();
+ }
+
+ /**
+ * Adds a list of user ids to this group.
+ *
+ * @param array<integer> $userIDs
+ */
+ public function addUserIDs(array $userIDs) {
+ foreach ($userIDs as $userID) {
+ // already added, ignore
+ if (in_array($userID, $this->userIDs)) {
+ continue;
+ }
+
+ $this->userIDs[] = $userID;
+
+ // add entry to static cache
+ self::$users[$userID] = null;
+ }
+ }
+
+ /**
+ * Loads user profiles for outstanding user ids.
+ */
+ public static function loadUsers() {
+ $userIDs = array();
+ foreach (self::$users as $userID => $user) {
+ if ($user === null) {
+ $userIDs[] = $userID;
+ }
+ }
+
+ // load user profiles
+ if (!empty($userIDs)) {
+ $userProfiles = UserProfile::getUserProfiles($userIDs);
+ foreach ($userProfiles as $userID => $userProfile) {
+ self::$users[$userID] = $userProfile;
+ }
+ }
+ }
+
+ /**
+ * @see \Countable::count()
+ */
+ public function count() {
+ return count($this->userIDs);
+ }
+
+ /**
+ * @see \Iterator::current()
+ */
+ public function current() {
+ $userID = $this->userIDs[$this->index];
+ return self::$users[$userID];
+ }
+
+ /**
+ * CAUTION: This methods does not return the current iterator index,
+ * rather than the object key which maps to that index.
+ *
+ * @see \Iterator::key()
+ */
+ public function key() {
+ return $this->userIDs[$this->index];
+ }
+
+ /**
+ * @see \Iterator::next()
+ */
+ public function next() {
+ ++$this->index;
+ }
+
+ /**
+ * @see \Iterator::rewind()
+ */
+ public function rewind() {
+ $this->index = 0;
+ }
+
+ /**
+ * @see \Iterator::valid()
+ */
+ public function valid() {
+ return isset($this->userIDs[$this->index]);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user;
+use wcf\data\user\UserProfile;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Wrapper for the profile of the active user to be used as a core object.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.user
+ * @category Community Framework
+ */
+class UserProfileHandler extends SingletonFactory {
+ /**
+ * user profile object
+ * @var wcf\data\user\UserProfile
+ */
+ protected $userProfile = null;
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ $this->userProfile = new UserProfile(WCF::getUser());
+ }
+
+ /**
+ * Delegates method calls to the user profile object.
+ *
+ * @param string $name
+ * @param array $arguments
+ * @return mixed
+ */
+ public function __call($name, $arguments) {
+ return call_user_func_array(array($this->userProfile, $name), $arguments);
+ }
+
+ /**
+ * Delegates property accesses to user profile object.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name) {
+ return $this->userProfile->$name;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\activity\event;
+use wcf\data\user\UserList;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * User activity event implementation for follows.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.activity.event
+ * @category Community Framework
+ */
+class FollowUserActivityEvent extends SingletonFactory implements IUserActivityEvent {
+ /**
+ * @see wcf\system\user\activity\event\IUserActivityEvent::prepare()
+ */
+ public function prepare(array $events) {
+ $objectIDs = array();
+ foreach ($events as $event) {
+ $objectIDs[] = $event->objectID;
+ }
+
+ // fetch user id and username
+ $userList = new UserList();
+ $userList->getConditionBuilder()->add("user_table.userID IN (?)", array($objectIDs));
+ $userList->readObjects();
+ $users = $userList->getObjects();
+
+ // set message
+ foreach ($events as $event) {
+ if (isset($users[$event->objectID])) {
+ $event->setIsAccessible();
+
+ $text = WCF::getLanguage()->getDynamicVariable('wcf.user.profile.recentActivity.follow', array('user' => $users[$event->objectID]));
+ $event->setTitle($text);
+ }
+ else {
+ $event->setIsOrphaned();
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\activity\event;
+
+/**
+ * Default interface for user activity events.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.activity.event
+ * @category Community Framework
+ */
+interface IUserActivityEvent {
+ /**
+ * Prepares a list of events for output.
+ *
+ * @param array<wcf\data\user\activity\event\ViewableUserActivityEvent> $events
+ */
+ public function prepare(array $events);
+}
--- /dev/null
+<?php
+namespace wcf\system\user\activity\event;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\activity\event\UserActivityEventAction;
+use wcf\data\user\activity\event\ViewableUserActivityEventList;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * User activity event handler.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.activity.event
+ * @category Community Framework
+ */
+class UserActivityEventHandler extends SingletonFactory {
+ /**
+ * cached object types
+ * @var array<wcf\data\object\type\ObjectType>
+ */
+ protected $objectTypes = array();
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ // load object types
+ $cache = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.recentActivityEvent');
+ foreach ($cache as $objectType) {
+ $this->objectTypes['names'][$objectType->objectType] = $objectType->objectTypeID;
+ $this->objectTypes['objects'][$objectType->objectTypeID] = $objectType;
+ }
+ }
+
+ /**
+ * Returns an object type by id.
+ *
+ * @param integer $objectTypeID
+ * @return wcf\data\object\type\ObjectType
+ */
+ public function getObjectType($objectTypeID) {
+ if (isset($this->objectTypes['objects'][$objectTypeID])) {
+ return $this->objectTypes['objects'][$objectTypeID];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an object type id by object type name.
+ *
+ * @param string $objectType
+ * @return integer
+ */
+ public function getObjectTypeID($objectType) {
+ if (isset($this->objectTypes['names'][$objectType])) {
+ return $this->objectTypes['names'][$objectType];
+ }
+
+ return null;
+ }
+
+ /**
+ * Fires a new activity event.
+ *
+ * @param string $objectType
+ * @param integer $objectID
+ * @param integer $languageID
+ * @param integer $userID
+ * @param integer $time
+ * @param array $additonalData
+ * @return wcf\data\user\activity\event\UserActivityEvent
+ */
+ public function fireEvent($objectType, $objectID, $languageID = null, $userID = null, $time = TIME_NOW, $additonalData = array()) {
+ $objectTypeID = $this->getObjectTypeID($objectType);
+ if ($userID === null) $userID = WCF::getUser()->userID;
+
+ $eventAction = new UserActivityEventAction(array(), 'create', array(
+ 'data' => array(
+ 'objectTypeID' => $objectTypeID,
+ 'objectID' => $objectID,
+ 'languageID' => $languageID,
+ 'userID' => $userID,
+ 'time' => $time,
+ 'additionalData' => serialize($additonalData)
+ )
+ ));
+ $returnValues = $eventAction->executeAction();
+
+ return $returnValues['returnValues'];
+ }
+
+ /**
+ * Removes activity events.
+ *
+ * @param string $objectType
+ * @param array<integer> $objectIDs
+ */
+ public function removeEvents($objectType, array $objectIDs) {
+ $objectTypeID = $this->getObjectTypeID($objectType);
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("objectTypeID = ?", array($objectTypeID));
+ $conditions->add("objectID IN (?)", array($objectIDs));
+
+ $sql = "DELETE FROM wcf".WCF_N."_user_activity_event
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ }
+
+ /**
+ * Validates an event list and removes orphaned events.
+ *
+ * @param wcf\data\user\activity\event\ViewableUserActivityEventList $eventList
+ */
+ public static function validateEvents(ViewableUserActivityEventList $eventList) {
+ $eventIDs = $eventList->validateEvents();
+
+ // remove orphaned event ids
+ if (!empty($eventIDs)) {
+ $sql = "DELETE FROM wcf".WCF_N."_user_activity_event
+ WHERE eventID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ foreach ($eventIDs as $eventID) {
+ $statement->execute(array($eventID));
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\activity\point;
+
+/**
+ * Default implementation of a user activity point object processor.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.activity.point
+ * @category Community Framework
+ */
+class DefaultUserActivityPointObjectProcessor implements IUserActivityPointObjectProcessor {
+ /**
+ * @see wcf\system\user\activity\point\IUserActivityPointObject::countRequests();
+ */
+ public function countRequests() {
+ return 0;
+ }
+
+ /**
+ * @see wcf\system\user\activity\point\IUserActivityPointObject::updateActivityPointEvents();
+ */
+ public function updateActivityPointEvents($request) {
+ return;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\activity\point;
+
+/**
+ * Every UserActivityPointObjectProcessor has to implement this interface.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.activity.point
+ * @category Community Framework
+ */
+interface IUserActivityPointObjectProcessor {
+ /**
+ * This method has to return the amount of requests needed to completely
+ * process this UserActivityPointObject.
+ *
+ * @return integer
+ */
+ public function countRequests();
+
+ /**
+ * This method updates the activityPointEvents. $request will be an integer
+ * between 0 (first request) and the number returned by countRequests() minus 1.
+ *
+ * @param integer $request
+ */
+ public function updateActivityPointEvents($request);
+}
--- /dev/null
+<?php
+namespace wcf\system\user\activity\point;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\activity\point\event\UserActivityPointEventAction;
+use wcf\data\user\UserProfileAction;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles the user activity point events
+ *
+ * @author Tim Duesterhus, Alexander Ebert, Matthias Schmidt
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.activity.point
+ * @category Community Framework
+ */
+class UserActivityPointHandler extends SingletonFactory {
+ /**
+ * list of user activity point object types
+ * @var array<wcf\data\object\type\ObjectType>
+ */
+ protected $objectTypes = array();
+
+ /**
+ * maps the user activity point object type ids to their object type names
+ * @var array<string>
+ */
+ protected $objectTypeNames = array();
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.activityPointEvent');
+
+ foreach ($this->objectTypes as $objectType) {
+ $this->objectTypeNames[$objectType->objectTypeID] = $objectType->objectType;
+ }
+ }
+
+ /**
+ * Adds a new user activity point event.
+ *
+ * @param string $objectType
+ * @param integer $objectID
+ * @param integer $userID
+ * @param array<mixed> $additionalData
+ */
+ public function fireEvent($objectType, $objectID, $userID = null, array $additionalData = array()) {
+ $objectTypeObj = $this->getObjectTypeByName($objectType);
+ if ($objectTypeObj === null) {
+ throw new SystemException("Object type '".$objectType."' is not valid for object type definition 'com.woltlab.wcf.user.activityPointEvent'");
+ }
+
+ if ($userID === null) $userID = WCF::getUser()->userID;
+ if (!$userID) throw new SystemException("Cannot fire user activity point events for guests");
+
+ $objectAction = new UserActivityPointEventAction(array(), 'create', array(
+ 'data' => array(
+ 'objectTypeID' => $objectTypeObj->objectTypeID,
+ 'objectID' => $objectID,
+ 'userID' => $userID,
+ 'additionalData' => serialize($additionalData)
+ )
+ ));
+ $returnValues = $objectAction->executeAction();
+ $event = $returnValues['returnValues'];
+
+ $this->updateUser($userID, $objectType);
+
+ return $event;
+ }
+
+ /**
+ * Bulk import for user activity point events.
+ *
+ * structure of $data:
+ * array(
+ * objectID => array(
+ * userID => userID,
+ * additionalData => mixed (optional)
+ * )
+ * )
+ *
+ * @param string $objectType
+ * @param array<array> $data
+ */
+ public function fireEvents($objectType, array $data) {
+ $objectTypeObj = $this->getObjectTypeByName($objectType);
+ if ($objectTypeObj === null) {
+ throw new SystemException("Object type '".$objectType."' is not valid for object type definition 'com.woltlab.wcf.user.activityPointEvent'");
+ }
+
+ $sql = "INSERT INTO wcf".WCF_N."_user_activity_point_event
+ (objectTypeID, objectID, userID, additionalData)
+ VALUES (?, ?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+
+ WCF::getDB()->beginTransaction();
+ $userIDs = array();
+ foreach ($data as $objectID => $objectData) {
+ $statement->execute(array(
+ $objectTypeObj->objectTypeID,
+ $objectID,
+ $objectData['userID'],
+ (isset($objectData['additionalData']) ? serialize($objectData['additionalData']) : '')
+ ));
+
+ $userIDs[] = $objectData['userID'];
+ }
+ WCF::getDB()->commitTransaction();
+
+ $userIDs = array_unique($userIDs);
+ $this->updateUsers($userIDs, $objectType);
+ }
+
+ /**
+ * Removes activity point events.
+ *
+ * @param string $objectType
+ * @param array<integer> $objectIDs
+ */
+ public function removeEvents($objectType, array $objectIDs) {
+ if (empty($objectIDs)) return;
+
+ // get and validate object type
+ $objectTypeObj = $this->getObjectTypeByName($objectType);
+ if ($objectTypeObj === null) {
+ throw new SystemException("Object type '".$objectType."' is not valid for object type definition 'com.woltlab.wcf.user.activityPointEvent'");
+ }
+
+ // get user ids
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("objectTypeID = ?", array($objectTypeObj->objectTypeID));
+ $conditions->add("objectID IN (?)", array($objectIDs));
+ $sql = "SELECT DISTINCT userID
+ FROM wcf".WCF_N."_user_activity_point_event
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ $userIDs = array();
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['userID'];
+ }
+
+ // delete events
+ $sql = "DELETE FROM wcf".WCF_N."_user_activity_point_event
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ if (!empty($userIDs)) {
+ $this->updateUsers($userIDs, $objectType);
+ }
+ }
+
+ /**
+ * Returns the user activity point event object type with the given id or
+ * null if no such object tyoe exists.
+ *
+ * @param integer $objectTypeID
+ * @return wcf\data\object\type\ObjectType
+ */
+ public function getObjectType($objectTypeID) {
+ if (isset($this->objectTypeNames[$objectTypeID])) {
+ return $this->getObjectTypeByName($this->objectTypeNames[$objectTypeID]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the user activity point event object type with the given name
+ * or null if no such object type exists.
+ *
+ * @param string $objectType
+ * @return wcf\data\object\type\ObjectType
+ */
+ public function getObjectTypeByName($objectType) {
+ if (isset($this->objectTypes[$objectType])) {
+ return $this->objectTypes[$objectType];
+ }
+
+ return null;
+ }
+
+ /**
+ * Updates the caches for the given user.
+ *
+ * @param integer $userID
+ * @param string $objectType
+ */
+ public function updateUser($userID, $objectType) {
+ $objectType = $this->getObjectTypeByName($objectType);
+
+ // update user_activity_point
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user_activity_point
+ WHERE userID = ?
+ AND objectTypeID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $userID,
+ $objectType->objectTypeID
+ ));
+ $row = $statement->fetchArray();
+
+ // update existing entry
+ if ($row['count']) {
+ $sql = "UPDATE wcf".WCF_N."_user_activity_point
+ SET activityPoints = activityPoints + ?
+ WHERE userID = ?
+ AND objectTypeID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $objectType->points,
+ $userID,
+ $objectType->objectTypeID
+ ));
+ }
+ else {
+ // create new entry
+ $sql = "INSERT INTO wcf".WCF_N."_user_activity_point
+ (userID, objectTypeID, activityPoints)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $userID,
+ $objectType->objectTypeID,
+ $objectType->points
+ ));
+ }
+
+ $sql = "UPDATE wcf".WCF_N."_user
+ SET activityPoints = activityPoints + ?
+ WHERE userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(
+ $objectType->points,
+ $userID
+ ));
+
+ // update user ranks
+ $this->updateUserRanks(array($userID));
+ }
+
+ /**
+ * Updates activity points for given user ids and object type.
+ *
+ * @param array<integer> $userIDs
+ * @param string $objectType
+ */
+ public function updateUsers(array $userIDs, $objectType = null) {
+ $objectTypes = array();
+ if ($objectType === null) {
+ $objectTypes = $this->objectTypes;
+ }
+ else {
+ $objectTypeObj = $this->getObjectTypeByName($objectType);
+ if ($objectTypeObj === null) {
+ throw new SystemException("Object type '".$objectType."' is not valid for object type definition 'com.woltlab.wcf.user.activityPointEvent'");
+ }
+ $objectTypes[] = $objectTypeObj;
+ }
+
+ $objectTypeIDs = array();
+ foreach ($objectTypes as $objectType) {
+ $objectTypeIDs[] = $objectType->objectTypeID;
+ }
+
+ // remove cached values first
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($userIDs));
+ $conditions->add("objectTypeID IN (?)", array($objectTypeIDs));
+ $sql = "DELETE FROM wcf".WCF_N."_user_activity_point
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ // update users for every given object type
+ WCF::getDB()->beginTransaction();
+ foreach ($objectTypes as $objectType) {
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($userIDs));
+ $conditions->add("objectTypeID = ?", array($objectType->objectTypeID));
+
+ $parameters = $conditions->getParameters();
+ array_unshift($parameters, $objectType->points);
+
+ $sql = "INSERT INTO wcf".WCF_N."_user_activity_point
+ (userID, objectTypeID, activityPoints)
+ SELECT userID, objectTypeID, (COUNT(*) * ?) AS activityPoints
+ FROM wcf".WCF_N."_user_activity_point_event
+ ".$conditions."
+ GROUP BY userID, objectTypeID";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($parameters);
+ }
+ WCF::getDB()->commitTransaction();
+
+ // update activity points for given user ids
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("userID IN (?)", array($userIDs));
+
+ $sql = "UPDATE wcf".WCF_N."_user user_table
+ SET activityPoints = COALESCE((
+ SELECT SUM(activityPoints) AS activityPoints
+ FROM wcf".WCF_N."_user_activity_point
+ WHERE userID = user_table.userID
+ GROUP BY userID
+ ), 0)
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ // update user ranks
+ $this->updateUserRanks($userIDs);
+ }
+
+ /**
+ * Updates the user ranks for the given users.
+ *
+ * @param array<integer> $userIDs
+ */
+ protected function updateUserRanks(array $userIDs) {
+ $action = new UserProfileAction($userIDs, 'updateUserRank');
+ $action->executeAction();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\notification\event\recipient\UserNotificationEventRecipientList;
+use wcf\data\user\notification\event\UserNotificationEventList;
+use wcf\data\user\notification\UserNotification;
+use wcf\data\user\notification\UserNotificationAction;
+use wcf\data\user\notification\UserNotificationList;
+use wcf\data\user\User;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserProfile;
+use wcf\system\cache\builder\UserNotificationEventCacheBuilder;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
+use wcf\system\mail\Mail;
+use wcf\system\user\notification\event\IUserNotificationEvent;
+use wcf\system\user\notification\object\IUserNotificationObject;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Handles user notifications.
+ *
+ * @author Marcel Werk, Oliver Kliebisch
+ * @copyright 2001-2013 WoltLab GmbH, Oliver Kliebisch
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification
+ * @category Community Framework
+ */
+class UserNotificationHandler extends SingletonFactory {
+ /**
+ * list of available object types
+ * @var array
+ */
+ protected $availableObjectTypes = array();
+
+ /**
+ * list of available events
+ * @var array
+ */
+ protected $availableEvents = array();
+
+ /**
+ * number of outstanding notifications
+ * @var integer
+ */
+ protected $notificationCount = null;
+
+ /**
+ * list of object types
+ * @var array<wcf\data\object\type\ObjectType>
+ */
+ protected $objectTypes = array();
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ // get available object types
+ $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.notification.objectType');
+ foreach ($this->objectTypes as $typeName => $object) {
+ $this->availableObjectTypes[$typeName] = $object->getProcessor();
+ }
+
+ // get available events
+ $this->availableEvents = UserNotificationEventCacheBuilder::getInstance()->getData();
+ }
+
+ /**
+ * Triggers a notification event.
+ *
+ * @param string $eventName
+ * @param string $objectType
+ * @param wcf\system\user\notification\object\IUserNotificationObject $notificationObject
+ * @param array<integer> $recipientIDs
+ * @param array<mixed> $additionalData
+ */
+ public function fireEvent($eventName, $objectType, IUserNotificationObject $notificationObject, array $recipientIDs, array $additionalData = array()) {
+ // check given object type and event name
+ if (!isset($this->availableEvents[$objectType][$eventName])) {
+ throw new SystemException("Unknown event ".$objectType."-".$eventName." given");
+ }
+
+ // get objects
+ $objectTypeObject = $this->availableObjectTypes[$objectType];
+ $event = $this->availableEvents[$objectType][$eventName];
+ // set object data
+ $event->setObject(new UserNotification(null, array()), $notificationObject, new UserProfile(WCF::getUser()), $additionalData);
+
+ // find existing events
+ $userIDs = array();
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('notification_to_user.notificationID = notification.notificationID');
+ $conditionBuilder->add('notification_to_user.userID IN (?)', array($recipientIDs));
+ $conditionBuilder->add('notification.eventHash = ?', array($event->getEventHash()));
+ $sql = "SELECT notification_to_user.userID
+ FROM wcf".WCF_N."_user_notification notification,
+ wcf".WCF_N."_user_notification_to_user notification_to_user
+ ".$conditionBuilder;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditionBuilder->getParameters());
+ while ($row = $statement->fetchArray()) $userIDs[] = $row['userID'];
+
+ // skip recipients with outstanding notifications
+ if (!empty($userIDs)) {
+ $recipientIDs = array_diff($recipientIDs, $userIDs);
+ if (empty($recipientIDs)) return;
+ }
+
+ // get recipients
+ $recipientList = new UserNotificationEventRecipientList();
+ $recipientList->getConditionBuilder()->add('event_to_user.eventID = ?', array($event->eventID));
+ $recipientList->getConditionBuilder()->add('event_to_user.userID IN (?)', array($recipientIDs));
+ $recipientList->readObjects();
+ if (count($recipientList)) {
+ // find existing notification
+ $notification = UserNotification::getNotification($objectTypeObject->packageID, $event->eventID, $notificationObject->getObjectID());
+ if ($notification !== null) {
+ // only update recipients
+ $action = new UserNotificationAction(array($notification), 'addRecipients', array(
+ 'recipients' => $recipientList->getObjects()
+ ));
+ $action->executeAction();
+ }
+ else {
+ // create new notification
+ $action = new UserNotificationAction(array(), 'create', array(
+ 'data' => array(
+ 'packageID' => $objectTypeObject->packageID,
+ 'eventID' => $event->eventID,
+ 'objectID' => $notificationObject->getObjectID(),
+ 'authorID' => $event->getAuthorID(),
+ 'time' => TIME_NOW,
+ 'eventHash' => $event->getEventHash(),
+ 'additionalData' => serialize($additionalData)
+ ),
+ 'recipients' => $recipientList->getObjects()
+ ));
+ $result = $action->executeAction();
+ $notification = $result['returnValues'];
+ }
+
+ // sends notifications
+ foreach ($recipientList->getObjects() as $recipient) {
+ if ($recipient->mailNotificationType == 'instant') {
+ $this->sendInstantMailNotification($notification, $recipient, $event);
+ }
+ }
+
+ // reset notification count
+ UserStorageHandler::getInstance()->reset($recipientList->getObjectIDs(), 'userNotificationCount');
+ }
+ }
+
+ /**
+ * Returns the number of outstanding notifications for the active user.
+ *
+ * @return integer
+ */
+ public function getNotificationCount() {
+ if ($this->notificationCount === null) {
+ $this->notificationCount = 0;
+
+ if (WCF::getUser()->userID) {
+ // load storage data
+ UserStorageHandler::getInstance()->loadStorage(array(WCF::getUser()->userID));
+
+ // get ids
+ $data = UserStorageHandler::getInstance()->getStorage(array(WCF::getUser()->userID), 'userNotificationCount');
+
+ // cache does not exist or is outdated
+ if ($data[WCF::getUser()->userID] === null) {
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('notification.notificationID = notification_to_user.notificationID');
+ $conditionBuilder->add('notification_to_user.userID = ?', array(WCF::getUser()->userID));
+
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user_notification_to_user notification_to_user,
+ wcf".WCF_N."_user_notification notification
+ ".$conditionBuilder->__toString();
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditionBuilder->getParameters());
+ $row = $statement->fetchArray();
+ $this->notificationCount = $row['count'];
+
+ // update storage data
+ UserStorageHandler::getInstance()->update(WCF::getUser()->userID, 'userNotificationCount', serialize($this->notificationCount));
+ }
+ else {
+ $this->notificationCount = unserialize($data[WCF::getUser()->userID]);
+ }
+ }
+ }
+
+ return $this->notificationCount;
+ }
+
+ /**
+ * Returns a limited list of outstanding notifications.
+ *
+ * @param integer $limit
+ * @param integer $offset
+ * @return array<array>
+ */
+ public function getNotifications($limit = 5, $offset = 0) {
+ // build enormous query
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("notification_to_user.userID = ?", array(WCF::getUser()->userID));
+ $conditions->add("notification.notificationID = notification_to_user.notificationID");
+
+ $sql = "SELECT notification_to_user.notificationID, notification_event.eventID,
+ object_type.objectType, notification.objectID,
+ notification.additionalData, notification.authorID,
+ notification.time
+ FROM wcf".WCF_N."_user_notification_to_user notification_to_user,
+ wcf".WCF_N."_user_notification notification
+ LEFT JOIN wcf".WCF_N."_user_notification_event notification_event
+ ON (notification_event.eventID = notification.eventID)
+ LEFT JOIN wcf".WCF_N."_object_type object_type
+ ON (object_type.objectTypeID = notification_event.objectTypeID)
+ ".$conditions."
+ ORDER BY notification.time DESC";
+ $statement = WCF::getDB()->prepareStatement($sql, $limit, $offset);
+ $statement->execute($conditions->getParameters());
+
+ $authorIDs = $events = $objectTypes = $eventIDs = $notificationIDs = array();
+ while ($row = $statement->fetchArray()) {
+ $events[] = $row;
+
+ // cache object types
+ if (!isset($objectTypes[$row['objectType']])) {
+ $objectTypes[$row['objectType']] = array(
+ 'objectType' => $this->availableObjectTypes[$row['objectType']],
+ 'objectIDs' => array(),
+ 'objects' => array()
+ );
+ }
+
+ $objectTypes[$row['objectType']]['objectIDs'][] = $row['objectID'];
+ $eventIDs[] = $row['eventID'];
+ $notificationIDs[] = $row['notificationID'];
+ $authorIDs[] = $row['authorID'];
+ }
+
+ // return an empty set if no notifications exist
+ if (empty($events)) {
+ return array(
+ 'count' => 0,
+ 'notifications' => array()
+ );
+ }
+
+ // load authors
+ $authors = UserProfile::getUserProfiles($authorIDs);
+
+ // load objects associated with each object type
+ foreach ($objectTypes as $objectType => $objectData) {
+ $objectTypes[$objectType]['objects'] = $objectData['objectType']->getObjectsByIDs($objectData['objectIDs']);
+ }
+
+ // load required events
+ $eventList = new UserNotificationEventList();
+ $eventList->getConditionBuilder()->add("user_notification_event.eventID IN (?)", array($eventIDs));
+ $eventList->readObjects();
+ $eventObjects = $eventList->getObjects();
+
+ // load notification objects
+ $notificationList = new UserNotificationList();
+ $notificationList->getConditionBuilder()->add("user_notification.notificationID IN (?)", array($notificationIDs));
+ $notificationList->readObjects();
+ $notificationObjects = $notificationList->getObjects();
+
+ // build notification data
+ $notifications = array();
+ foreach ($events as $event) {
+ $className = $eventObjects[$event['eventID']]->className;
+ $class = new $className($eventObjects[$event['eventID']]);
+
+ $class->setObject(
+ $notificationObjects[$event['notificationID']],
+ $objectTypes[$event['objectType']]['objects'][$event['objectID']],
+ $authors[$event['authorID']],
+ unserialize($event['additionalData'])
+ );
+
+ $data = array(
+ 'event' => $class,
+ 'notificationID' => $event['notificationID'],
+ 'time' => $event['time']
+ );
+
+ $notifications[] = $data;
+ }
+
+ return array(
+ 'count' => count($notifications),
+ 'notifications' => $notifications
+ );
+ }
+
+ /**
+ * Returns event object for given object type and event, returns NULL on failure.
+ *
+ * @param string $objectType
+ * @param string $eventName
+ * @return wcf\system\user\notification\event\IUserNotificationEvent
+ */
+ public function getEvent($objectType, $eventName) {
+ if (!isset($this->availableEvents[$objectType][$eventName])) return null;
+
+ return $this->availableEvents[$objectType][$eventName];
+ }
+
+ /**
+ * Retrieves a notification id.
+ *
+ * @param integer $eventID
+ * @param integer $objectID
+ * @param integer $authorID
+ * @param integer $time
+ * @return integer
+ */
+ public function getNotificationID($eventID, $objectID, $authorID = null, $time = null) {
+ if ($authorID === null && $time === null) {
+ throw new SystemException("authorID and time cannot be omitted at once.");
+ }
+
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("eventID = ?", array($eventID));
+ $conditions->add("objectID = ?", array($objectID));
+ if ($authorID !== null) $conditions->add("authorID = ?", array($authorID));
+ if ($time !== null) $conditions->add("time = ?", array($time));
+
+ $sql = "SELECT notificationID
+ FROM wcf".WCF_N."_user_notification
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+
+ $row = $statement->fetchArray();
+
+ return ($row === false) ? null : $row['notificationID'];
+ }
+
+ /**
+ * Returns a list of available object types.
+ *
+ * @return array<wcf\system\user\notification\object\type\IUserNotificationObjectType>
+ */
+ public function getAvailableObjectTypes() {
+ return $this->availableObjectTypes;
+ }
+
+ /**
+ * Returns a list of available events.
+ *
+ * @return array<wcf\system\user\notification\event\IUserNotificationEvent>
+ */
+ public function getAvailableEvents() {
+ return $this->availableEvents;
+ }
+
+ /**
+ * Returns object type id by name.
+ *
+ * @param string $objectType
+ * @return integer
+ */
+ public function getObjectTypeID($objectType) {
+ if (isset($this->objectTypes[$objectType])) {
+ return $this->objectTypes[$objectType]->objectTypeID;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns object type by name.
+ *
+ * @param string $objectType
+ * @return object
+ */
+ public function getObjectTypeProcessor($objectType) {
+ if (isset($this->availableObjectTypes[$objectType])) {
+ return $this->availableObjectTypes[$objectType];
+ }
+
+ return null;
+ }
+
+ /**
+ * Sends the mail notification.
+ *
+ * @param wcf\data\user\notification\UserNotification $notification
+ * @param wcf\data\user\User $user
+ * @param wcf\system\user\notification\event\IUserNotificationEvent $event
+ */
+ public function sendInstantMailNotification(UserNotification $notification, User $user, IUserNotificationEvent $event) {
+ // recipient's language
+ $event->setLanguage($user->getLanguage());
+
+ // add mail header
+ $message = $user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.header', array(
+ 'user' => $user
+ ))."\n\n";
+
+ // get message
+ $message .= $event->getEmailMessage();
+
+ // append notification mail footer
+ $token = $user->notificationMailToken;
+ if (!$token) {
+ // generate token if not present
+ $token = StringUtil::substring(StringUtil::getHash(serialize(array($user->userID, StringUtil::getRandomID()))), 0, 20);
+ $editor = new UserEditor($user);
+ $editor->update(array('notificationMailToken' => $token));
+ }
+ $message .= "\n\n".$user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.footer', array(
+ 'user' => $user,
+ 'token' => $token,
+ 'notification' => $notification
+ ));
+
+ // build mail
+ $mail = new Mail(array($user->username => $user->email), $user->getLanguage()->getDynamicVariable('wcf.user.notification.mail.subject', array('title' => $event->getEmailTitle())), $message);
+ $mail->setLanguage($user->getLanguage());
+ $mail->send();
+ }
+
+ /**
+ * Deletes notifications.
+ *
+ * @param string $eventName
+ * @param string $objectType
+ * @param array<integer> $recipientIDs
+ * @param array<integer> $objectIDs
+ */
+ public function deleteNotifications($eventName, $objectType, array $recipientIDs, array $objectIDs = array()) {
+ // check given object type and event name
+ if (!isset($this->availableEvents[$objectType][$eventName])) {
+ throw new SystemException("Unknown event ".$objectType."-".$eventName." given");
+ }
+
+ // get objects
+ $objectTypeObject = $this->availableObjectTypes[$objectType];
+ $event = $this->availableEvents[$objectType][$eventName];
+
+ // delete notifications
+ $sql = "DELETE FROM wcf".WCF_N."_user_notification_to_user
+ WHERE notificationID IN (
+ SELECT notificationID
+ FROM wcf".WCF_N."_user_notification
+ WHERE packageID = ?
+ AND eventID = ?
+ ".(!empty($objectIDs) ? "AND objectID IN (?".(count($objectIDs) > 1 ? str_repeat(',?', count($objectIDs) - 1) : '').")" : '')."
+ )
+ ".(!empty($recipientIDs) ? ("AND userID IN (?".(count($recipientIDs) > 1 ? str_repeat(',?', count($recipientIDs) - 1) : '').")") : '');
+ $parameters = array($objectTypeObject->packageID, $event->eventID);
+ if (!empty($objectIDs)) $parameters = array_merge($parameters, $objectIDs);
+ if (!empty($recipientIDs)) $parameters = array_merge($parameters, $recipientIDs);
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($parameters);
+
+ // reset storage
+ if (!empty($recipientIDs)) {
+ UserStorageHandler::getInstance()->reset($recipientIDs, 'userNotificationCount');
+ }
+ else {
+ UserStorageHandler::getInstance()->resetAll('userNotificationCount');
+ }
+ }
+
+ /**
+ * Returns the user's notification setting for the given event.
+ *
+ * @param string $objectType
+ * @param string $eventName
+ * @return mixed
+ */
+ public function getEventSetting($objectType, $eventName) {
+ // get event
+ $event = $this->getEvent($objectType, $eventName);
+
+ // get setting
+ $sql = "SELECT mailNotificationType
+ FROM wcf".WCF_N."_user_notification_event_to_user
+ WHERE eventID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($event->eventID, WCF::getUser()->userID));
+ $row = $statement->fetchArray();
+ if ($row === false) return false;
+ return $row['mailNotificationType'];
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\language\Language;
+use wcf\data\user\notification\UserNotification;
+use wcf\data\user\UserProfile;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\user\notification\object\IUserNotificationObject;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Provides default a implementation for user notification events.
+ *
+ * @author Marcel Werk, Oliver Kliebisch
+ * @copyright 2001-2013 WoltLab GmbH, Oliver Kliebisch
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification.event
+ * @category Community Framework
+ */
+abstract class AbstractUserNotificationEvent extends DatabaseObjectDecorator implements IUserNotificationEvent {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\notification\event\UserNotificationEvent';
+
+ /**
+ * author object
+ * @var wcf\data\user\UserProfile
+ */
+ protected $author = null;
+
+ /**
+ * user notification
+ * @var wcf\data\user\notification\UserNotification
+ */
+ protected $notification = null;
+
+ /**
+ * user notification object
+ * @var wcf\system\user\notification\object\IUserNotificationObject
+ */
+ protected $userNotificationObject = null;
+
+ /**
+ * additional data for this event
+ * @var array<mixed>
+ */
+ protected $additionalData = array();
+
+ /**
+ * language object
+ * @var wcf\data\language\Language
+ */
+ protected $language = null;
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::setObject()
+ */
+ public function setObject(UserNotification $notification, IUserNotificationObject $object, UserProfile $author, array $additionalData = array()) {
+ $this->notification = $notification;
+ $this->userNotificationObject = $object;
+ $this->author = $author;
+ $this->additionalData = $additionalData;
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getAuthorID()
+ */
+ public function getAuthorID() {
+ return $this->author->userID;
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getAuthor()
+ */
+ public function getAuthor() {
+ return $this->author;
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::isVisible()
+ */
+ public function isVisible() {
+ return true;
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getEmailTitle()
+ */
+ public function getEmailTitle() {
+ return $this->getTitle();
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getEmailMessage()
+ */
+ public function getEmailMessage() {
+ return $this->getMessage();
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getEventHash()
+ */
+ public function getEventHash() {
+ return StringUtil::getHash($this->packageID . '-'. $this->eventID . '-' . $this->userNotificationObject->getObjectID());
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::setLanguage()
+ */
+ public function setLanguage(Language $language) {
+ $this->language = $language;
+ }
+
+ /**
+ * Gets the language of this event.
+ *
+ * @return wcf\data\language\Language
+ */
+ public function getLanguage() {
+ if ($this->language !== null) return $this->language;
+ return WCF::getLanguage();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\language\Language;
+use wcf\data\user\notification\UserNotification;
+use wcf\data\user\UserProfile;
+use wcf\data\IDatabaseObjectProcessor;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * This interface should be implemented by every event which is fired by the notification system.
+ *
+ * @author Marcel Werk, Oliver Kliebisch
+ * @copyright 2001-2013 WoltLab GmbH, Oliver Kliebisch
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification.event
+ * @category Community Framework
+ */
+interface IUserNotificationEvent extends IDatabaseObjectProcessor {
+ /**
+ * Returns a short title used for the notification overlay, e.g. "New follower".
+ *
+ * @return string
+ */
+ public function getTitle();
+
+ /**
+ * Returns the notification event message, e.g. "dtdesign is now following you".
+ *
+ * @return string
+ */
+ public function getMessage();
+
+ /**
+ * Returns object link.
+ *
+ * @return string
+ */
+ public function getLink();
+
+ /**
+ * Returns the full title for this notification, e.g. for use with e-mails.
+ *
+ * @return string
+ */
+ public function getEmailTitle();
+
+ /**
+ * Returns the message for this notification event.
+ *
+ * @return string
+ */
+ public function getEmailMessage();
+
+ /**
+ * Returns the author id for this notification event.
+ *
+ * @return integer
+ */
+ public function getAuthorID();
+
+ /**
+ * Returns the author for this notification event.
+ *
+ * @return wcf\data\user\UserProfile
+ */
+ public function getAuthor();
+
+ /**
+ * Returns true if this notification event is visible for the active user.
+ *
+ * @return boolean
+ */
+ public function isVisible();
+
+ /**
+ * Returns a unique identifier of the event.
+ *
+ * @return string
+ */
+ public function getEventHash();
+
+ /**
+ * Sets the object for the event.
+ *
+ * @param wcf\data\user\notification\UserNotification $notification
+ * @param wcf\system\user\notification\object\IUserNotificationObject $object
+ * @param wcf\data\user\UserProfile $author
+ * @param array<mixed> $additionalData
+ */
+ public function setObject(UserNotification $notification, IUserNotificationObject $object, UserProfile $author, array $additionalData = array());
+
+ /**
+ * Sets the language for the event
+ *
+ * @param wcf\data\language\Language $language
+ */
+ public function setLanguage(Language $language);
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\notification\event\AbstractUserNotificationEvent;
+use wcf\util\StringUtil;
+
+/**
+ * Notification event for followers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification.event
+ * @category Community Framework
+ */
+class UserFollowFollowingUserNotificationEvent extends AbstractUserNotificationEvent {
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getTitle()
+ */
+ public function getTitle() {
+ return $this->getLanguage()->get('wcf.user.notification.follow.title');
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getMessage()
+ */
+ public function getMessage() {
+ return $this->getLanguage()->getDynamicVariable('wcf.user.notification.follow.message', array('author' => $this->author));
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getEmailMessage()
+ */
+ public function getEmailMessage() {
+ return $this->getLanguage()->getDynamicVariable('wcf.user.notification.follow.mail', array('author' => $this->author));
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getEventHash()
+ */
+ public function getEventHash() {
+ return StringUtil::getHash($this->packageID . '-'. $this->eventID . '-' . $this->author->userID);
+ }
+
+ /**
+ * @see wcf\system\user\notification\event\IUserNotificationEvent::getLink()
+ */
+ public function getLink() {
+ return LinkHandler::getInstance()->getLink('User', array('object' => $this->author));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification\object;
+use wcf\data\IDatabaseObjectProcessor;
+use wcf\data\ITitledObject;
+
+/**
+ * This interface should be implemented by every object which is part of a notification.
+ *
+ * @author Marcel Werk, Oliver Kliebisch
+ * @copyright 2001-2012 WoltLab GmbH, Oliver Kliebisch
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification.object
+ * @category Community Framework
+ */
+interface IUserNotificationObject extends IDatabaseObjectProcessor, ITitledObject {
+ /**
+ * Returns the ID of this object.
+ *
+ * @return integer
+ */
+ public function getObjectID();
+
+ /**
+ * Returns the url of this object.
+ *
+ * @return string
+ */
+ public function getURL();
+
+ /**
+ * Returns the user id of the author of this object.
+ *
+ * @return integer
+ */
+ public function getAuthorID();
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification\object;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\system\user\notification\object\IUserNotificationObject;
+
+/**
+ * Represents a following user as a notification object.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification.object
+ * @category Community Framework
+ */
+class UserFollowUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
+ /**
+ * @see wcf\data\DatabaseObjectDecorator::$baseClass
+ */
+ protected static $baseClass = 'wcf\data\user\follow\UserFollow';
+
+ /**
+ * @see wcf\system\user\notification\object\IUserNotificationObject::getTitle()
+ */
+ public function getTitle() {
+ return '';
+ }
+
+ /**
+ * @see wcf\system\user\notification\object\IUserNotificationObject::getURL()
+ */
+ public function getURL() {
+ return '';
+ }
+
+ /**
+ * @see wcf\system\user\notification\object\IUserNotificationObject::getAuthorID()
+ */
+ public function getAuthorID() {
+ return $this->userID;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification\object\type;
+use wcf\data\object\type\AbstractObjectTypeProcessor;
+
+/**
+ * Provides a default implementation of IUserNotificationObjectType.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification.object.type
+ * @category Community Framework
+ */
+class AbstractUserNotificationObjectType extends AbstractObjectTypeProcessor implements IUserNotificationObjectType {
+ /**
+ * class name of the object decorator
+ * @var string
+ */
+ protected static $decoratorClassName = '';
+
+ /**
+ * object class name
+ * @var string
+ */
+ protected static $objectClassName = '';
+
+ /**
+ * class name for DatabaseObjectList
+ * @var string
+ */
+ protected static $objectListClassName = '';
+
+ /**
+ * @see wcf\system\user\notification\object\type\IUserNotificationObjectType::getObjectsByIDs()
+ */
+ public function getObjectsByIDs(array $objectIDs) {
+ $indexName = call_user_func(array(static::$objectClassName, 'getDatabaseTableIndexName'));
+
+ $objectList = new static::$objectListClassName();
+ $objectList->setObjectIDs($objectIDs);
+ $objectList->sqLimit = 0;
+ $objectList->decoratorClassName = static::$decoratorClassName;
+ $objectList->readObjects();
+ $objects = $objectList->getObjects();
+
+ foreach ($objectIDs as $objectID) {
+ // append empty objects for unknown ids
+ if (!isset($objects[$objectID])) {
+ $objects[$objectID] = new static::$decoratorClassName(new static::$objectClassName(null, array($indexName => $objectID)));
+ }
+ }
+
+ return $objects;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification\object\type;
+use wcf\data\IDatabaseObjectProcessor;
+
+/**
+ * This interface defines the basic methods every notification object type should implement.
+ *
+ * @author Marcel Werk, Oliver Kliebisch
+ * @copyright 2001-2012 WoltLab GmbH, Oliver Kliebisch
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification.object.type
+ * @category Community Framework
+ */
+interface IUserNotificationObjectType extends IDatabaseObjectProcessor {
+ /**
+ * Gets notification objects by their IDs.
+ *
+ * @param array<integer> $objectIDs
+ * @return array<wcf\system\user\notification\object\IUserNotificationObject>
+ */
+ public function getObjectsByIDs(array $objectIDs);
+}
--- /dev/null
+<?php
+namespace wcf\system\user\notification\object\type;
+
+/**
+ * Represents a following user as a notification object type.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.notification.object.type
+ * @category Community Framework
+ */
+class UserFollowUserNotificationObjectType extends AbstractUserNotificationObjectType {
+ /**
+ * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$decoratorClassName
+ */
+ protected static $decoratorClassName = 'wcf\system\user\notification\object\UserFollowUserNotificationObject';
+
+ /**
+ * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$objectClassName
+ */
+ protected static $objectClassName = 'wcf\data\user\follow\UserFollow';
+
+ /**
+ * @see wcf\system\user\notification\object\type\AbstractUserNotificationObjectType::$objectListClassName
+ */
+ protected static $objectListClassName = 'wcf\data\user\follow\UserFollowList';
+}
--- /dev/null
+<?php
+namespace wcf\system\user\object\watch;
+
+/**
+ * Any watchable object type should implement this interface.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.object.watch
+ * @category Community Framework
+ */
+interface IUserObjectWatch {
+ /**
+ * Validates the given object id. Throws an exception on error.
+ *
+ * @param integer $objectID
+ * @throws wcf\system\exception\UserException
+ */
+ public function validateObjectID($objectID);
+
+ /**
+ * Resets the user storage for given users.
+ *
+ * @param array<integer> $userIDs
+ */
+ public function resetUserStorage(array $userIDs);
+}
--- /dev/null
+<?php
+namespace wcf\system\user\object\watch;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
+use wcf\system\user\notification\object\IUserNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles watched objects.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.object.watch
+ * @category Community Framework
+ */
+class UserObjectWatchHandler extends SingletonFactory {
+ /**
+ * Returns the id of the given object type.
+ *
+ * @param string $objectTypeName
+ * @return integer
+ */
+ public function getObjectTypeID($objectTypeName) {
+ $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.objectWatch', $objectTypeName);
+ if ($objectType === null) {
+ throw new SystemException("unknown object type '".$objectTypeName."'");
+ }
+
+ return $objectType->objectTypeID;
+ }
+
+ /**
+ * @see wcf\system\user\object\watch\UserObjectWatchHandler::resetObjects();
+ */
+ public function resetObject($objectType, $objectID) {
+ $this->resetObjects($objectType, array($objectID));
+ }
+
+ /**
+ * Resets the object watch cache for all subscriber of the given object.
+ *
+ * @param string $objectType
+ * @param array<integer> $objectIDs
+ */
+ public function resetObjects($objectType, array $objectIDs) {
+ // get object type id
+ $objectTypeObj = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.objectWatch', $objectType);
+
+ // get subscriber
+ $userIDs = array();
+ $conditionsBuilder = new PreparedStatementConditionBuilder();
+ $conditionsBuilder->add('objectTypeID = ?', array($objectTypeObj->objectTypeID));
+ $conditionsBuilder->add('objectID IN (?)', array($objectIDs));
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user_object_watch
+ ".$conditionsBuilder;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditionsBuilder->getParameters());
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['userID'];
+ }
+
+ if (!empty($userIDs)) {
+ // reset user storage
+ $objectTypeObj->getProcessor()->resetUserStorage($userIDs);
+ }
+ }
+
+ /**
+ * Deletes the given objects.
+ *
+ * @param string $objectType
+ * @param array<integer> $objectIDs
+ */
+ public function deleteObjects($objectType, array $objectIDs) {
+ // get object type id
+ $objectTypeObj = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.objectWatch', $objectType);
+
+ // delete objects
+ $conditionsBuilder = new PreparedStatementConditionBuilder();
+ $conditionsBuilder->add('objectTypeID = ?', array($objectTypeObj->objectTypeID));
+ $conditionsBuilder->add('objectID IN (?)', array($objectIDs));
+
+ $sql = "DELETE FROM wcf".WCF_N."_user_object_watch
+ ".$conditionsBuilder;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditionsBuilder->getParameters());
+ }
+
+ /**
+ * Updates a watched object for all subscriber.
+ *
+ * @param string $objectType
+ * @param integer $objectIDs
+ * @param string $notificationEventName
+ * @param string $notificationObjectType
+ * @param wcf\system\user\notification\object\IUserNotificationObject $notificationObject
+ * @param array $additionalData
+ */
+ public function updateObject($objectType, $objectID, $notificationEventName, $notificationObjectType, IUserNotificationObject $notificationObject, array $additionalData = array()) {
+ // get object type id
+ $objectTypeObj = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.user.objectWatch', $objectType);
+
+ // get subscriber
+ $userIDs = $recipientIDs = array();
+ $sql = "SELECT userID, notification
+ FROM wcf".WCF_N."_user_object_watch
+ WHERE objectTypeID = ?
+ AND objectID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($objectTypeObj->objectTypeID, $objectID));
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['userID'];
+ if ($row['notification'] && $notificationObject->getAuthorID() != $row['userID']) $recipientIDs[] = $row['userID'];
+ }
+
+ if (!empty($userIDs)) {
+ // reset user storage
+ $objectTypeObj->getProcessor()->resetUserStorage($userIDs);
+
+ if (!empty($recipientIDs)) {
+ // create notifications
+ UserNotificationHandler::getInstance()->fireEvent($notificationEventName, $notificationObjectType, $notificationObject, $recipientIDs, $additionalData);
+ }
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\online\location;
+use wcf\data\user\online\UserOnline;
+
+/**
+ * Any page location class should implement this interface.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.online.location
+ * @category Community Framework
+ */
+interface IUserOnlineLocation {
+ /**
+ * Caches the information of a page location.
+ *
+ * @param wcf\data\user\online\UserOnline $user
+ */
+ public function cache(UserOnline $user);
+
+ /**
+ * Returns the information of a page location.
+ *
+ * @param wcf\data\user\online\UserOnline $user
+ * @param string $languageVariable
+ * @return string
+ */
+ public function get(UserOnline $user, $languageVariable = '');
+}
--- /dev/null
+<?php
+namespace wcf\system\user\online\location;
+use wcf\data\user\online\UserOnline;
+use wcf\data\user\UserList;
+use wcf\system\WCF;
+
+/**
+ * Implementation of IUserOnlineLocation for the user profile location.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.online.location
+ * @category Community Framework
+ */
+class UserLocation implements IUserOnlineLocation {
+ /**
+ * user ids
+ * @var array<integer>
+ */
+ protected $userIDs = array();
+
+ /**
+ * list of users
+ * @var array<wcf\data\user\User>
+ */
+ protected $users = null;
+
+ /**
+ * @see wcf\system\user\online\location\IUserOnlineLocation::cache()
+ */
+ public function cache(UserOnline $user) {
+ if ($user->objectID) $this->userIDs[] = $user->objectID;
+ }
+
+ /**
+ * @see wcf\system\user\online\location\IUserOnlineLocation::get()
+ */
+ public function get(UserOnline $user, $languageVariable = '') {
+ if ($this->users === null) {
+ $this->readUsers();
+ }
+
+ if (!isset($this->users[$user->objectID])) {
+ return '';
+ }
+
+ return WCF::getLanguage()->getDynamicVariable($languageVariable, array('user' => $this->users[$user->objectID]));
+ }
+
+ /**
+ * Loads the users.
+ */
+ protected function readUsers() {
+ $this->users = array();
+
+ if (empty($this->userIDs)) return;
+ $this->userIDs = array_unique($this->userIDs);
+
+ $userList = new UserList();
+ $userList->getConditionBuilder()->add('user_table.userID IN (?)', array($this->userIDs));
+ $userList->readObjects();
+ $this->users = $userList->getObjects();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\online\location;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\user\online\UserOnline;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles user online locations.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.online.location
+ * @category Community Framework
+ */
+class UserOnlineLocationHandler extends SingletonFactory {
+ /**
+ * page locations
+ * @var array
+ */
+ public $locations = array();
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ // load locations
+ foreach (ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.online.location') as $objectType) {
+ $this->locations[$objectType->controller] = $objectType;
+ }
+ }
+
+ /**
+ * Gets the location of a given user.
+ *
+ * @param wcf\data\user\online\UserOnline $user
+ * @return string
+ */
+ public function getLocation(UserOnline $user) {
+ if (isset($this->locations[$user->controller])) {
+ if ($this->locations[$user->controller]->getProcessor()) {
+ $this->locations[$user->controller]->getProcessor()->cache($user);
+ return $this->locations[$user->controller]->getProcessor()->get($user, $this->locations[$user->controller]->languagevariable);
+ }
+ else {
+ return WCF::getLanguage()->get($this->locations[$user->controller]->languagevariable);
+ }
+ }
+
+ return '';
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\user\signature;
+use wcf\data\user\User;
+use wcf\system\bbcode\MessageParser;
+use wcf\system\SingletonFactory;
+
+/**
+ * Caches parsed user signatures.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.user.signature
+ * @category Community Framework
+ */
+class SignatureCache extends SingletonFactory {
+ /**
+ * cached signatures
+ * @var string
+ */
+ protected $signatures = array();
+
+ /**
+ * Returns a parsed user signature.
+ *
+ * @param wcf\data\user\User $user
+ * @return string
+ */
+ public function getSignature(User $user) {
+ if (!isset($this->signatures[$user->userID])) {
+ $this->signatures[$user->userID] = MessageParser::getInstance()->parse($user->signature, $user->signatureEnableSmilies, $user->signatureEnableHtml, $user->signatureEnableBBCodes, false);
+ }
+
+ return $this->signatures[$user->userID];
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\visitTracker;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\exception\SystemException;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles object visit tracking.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.visitTracker
+ * @category Community Framework
+ */
+class VisitTracker extends SingletonFactory {
+ /**
+ * default tracking lifetime
+ * @var integer
+ */
+ const DEFAULT_LIFETIME = 604800; // = one week
+
+ /**
+ * list of available object types
+ * @var array
+ */
+ protected $availableObjectTypes = array();
+
+ /**
+ * user visits
+ * @var array
+ */
+ protected $userVisits = null;
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ // get available object types
+ $this->availableObjectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.visitTracker.objectType');
+ }
+
+ /**
+ * Gets the object type id.
+ *
+ * @param string $objectType
+ * @return integer
+ */
+ public function getObjectTypeID($objectType) {
+ if (!isset($this->availableObjectTypes[$objectType])) {
+ throw new SystemException("unknown object type '".$objectType."'");
+ }
+
+ return $this->availableObjectTypes[$objectType]->objectTypeID;
+ }
+
+ /**
+ * Gets the last visit time for a whole object type.
+ *
+ * @param string $objectType
+ * @return integer
+ */
+ public function getVisitTime($objectType) {
+ $objectTypeID = $this->getObjectTypeID($objectType);
+
+ if ($this->userVisits === null) {
+ if (WCF::getUser()->userID) {
+ // get data from storage
+ UserStorageHandler::getInstance()->loadStorage(array(WCF::getUser()->userID));
+
+ // get ids
+ $data = UserStorageHandler::getInstance()->getStorage(array(WCF::getUser()->userID), 'trackedUserVisits');
+
+ // cache does not exist or is outdated
+ if ($data[WCF::getUser()->userID] === null) {
+ $this->userVisits = array();
+ $sql = "SELECT objectTypeID, visitTime
+ FROM wcf".WCF_N."_tracked_visit_type
+ WHERE userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array(WCF::getUser()->userID));
+ while ($row = $statement->fetchArray()) {
+ $this->userVisits[$row['objectTypeID']] = $row['visitTime'];
+ }
+
+ // update storage data
+ UserStorageHandler::getInstance()->update(WCF::getUser()->userID, 'trackedUserVisits', serialize($this->userVisits));
+ }
+ else {
+ $this->userVisits = @unserialize($data[WCF::getUser()->userID]);
+ }
+ }
+ else {
+ $this->userVisits = WCF::getSession()->getVar('trackedUserVisits');
+ }
+
+ if (!$this->userVisits) {
+ $this->userVisits = array();
+ }
+ }
+
+ if (isset($this->userVisits[$objectTypeID])) {
+ return $this->userVisits[$objectTypeID];
+ }
+
+ if ($this->availableObjectTypes[$objectType]->lifetime) {
+ return TIME_NOW - $this->availableObjectTypes[$objectType]->lifetime;
+ }
+
+ return TIME_NOW - self::DEFAULT_LIFETIME;
+ }
+
+ /**
+ * Returns the last visit time for a specific object.
+ *
+ * @param string $objectType
+ * @param integer $objectID
+ * @return integer
+ */
+ public function getObjectVisitTime($objectType, $objectID) {
+ if (WCF::getUser()->userID) {
+ $sql = "SELECT visitTime
+ FROM wcf".WCF_N."_tracked_visit
+ WHERE objectTypeID = ?
+ AND objectID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->getObjectTypeID($objectType), $objectID, WCF::getUser()->userID));
+ $row = $statement->fetchArray();
+ if ($row) return $row['visitTime'];
+ }
+ else {
+ if ($visitTime = WCF::getSession()->getVar('trackedUserVisit_'.$this->getObjectTypeID($objectType).'_'.$objectID)) {
+ return $visitTime;
+ }
+ }
+
+ return $this->getVisitTime($objectType);
+ }
+
+ /**
+ * Deletes all tracked visits of a specific object type.
+ *
+ * @param string $objectType
+ */
+ public function deleteObjectVisits($objectType) {
+ if (WCF::getUser()->userID) {
+ $sql = "DELETE FROM wcf".WCF_N."_tracked_visit
+ WHERE objectTypeID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->getObjectTypeID($objectType), WCF::getUser()->userID));
+ }
+ }
+
+ /**
+ * Tracks an object visit.
+ *
+ * @param string $objectType
+ * @param integer $objectID
+ * @param integer $time
+ */
+ public function trackObjectVisit($objectType, $objectID, $time = TIME_NOW) {
+ if (WCF::getUser()->userID) {
+ // delete old visit
+ $sql = "DELETE FROM wcf".WCF_N."_tracked_visit
+ WHERE objectTypeID = ?
+ AND objectID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->getObjectTypeID($objectType), $objectID, WCF::getUser()->userID));
+
+ // save visit
+ $sql = "INSERT INTO wcf".WCF_N."_tracked_visit
+ (objectTypeID, objectID, userID, visitTime)
+ VALUES (?, ?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->getObjectTypeID($objectType), $objectID, WCF::getUser()->userID, $time));
+ }
+ else {
+ WCF::getSession()->register('trackedUserVisit_'.$this->getObjectTypeID($objectType).'_'.$objectID, $time);
+ }
+ }
+
+ /**
+ * Tracks an object type visit.
+ *
+ * @param string $objectType
+ * @param integer $time
+ */
+ public function trackTypeVisit($objectType, $time = TIME_NOW) {
+ if (WCF::getUser()->userID) {
+ // delete old visit
+ $sql = "DELETE FROM wcf".WCF_N."_tracked_visit_type
+ WHERE objectTypeID = ?
+ AND userID = ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->getObjectTypeID($objectType), WCF::getUser()->userID));
+
+ // save visit
+ $sql = "INSERT INTO wcf".WCF_N."_tracked_visit_type
+ (objectTypeID, userID, visitTime)
+ VALUES (?, ?, ?)";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->getObjectTypeID($objectType), WCF::getUser()->userID, $time));
+
+ // delete obsolete object visits
+ $sql = "DELETE FROM wcf".WCF_N."_tracked_visit
+ WHERE objectTypeID = ?
+ AND userID = ?
+ AND visitTime <= ?";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute(array($this->getObjectTypeID($objectType), WCF::getUser()->userID, $time));
+
+ // reset storage
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'trackedUserVisits');
+ }
+ else {
+ $this->getVisitTime($objectType);
+ $this->userVisits[$this->getObjectTypeID($objectType)] = $time;
+ WCF::getSession()->register('trackedUserVisits', $this->userVisits);
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\worker;
+use wcf\system\request\LinkHandler;
+use wcf\system\user\activity\point\UserActivityPointHandler;
+use wcf\system\WCF;
+
+/**
+ * Worker implementation for updating user activity point caches.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.worker
+ * @category Community Framework
+ */
+class UserActivityPointUpdateCacheWorker extends AbstractWorker {
+ /**
+ * @see wcf\system\worker\AbstractWorker::$limit
+ */
+ protected $limit = 50;
+
+ /**
+ * @see wcf\system\worker\IWorker::validate()
+ */
+ public function validate() {
+ WCF::getSession()->checkPermissions(array('admin.user.canEditActivityPoints'));
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::countObjects()
+ */
+ public function countObjects() {
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user user";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ $row = $statement->fetchArray();
+
+ $this->count = $row['count'];
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::execute()
+ */
+ public function execute() {
+ // get users
+ $sql = "SELECT userID
+ FROM wcf".WCF_N."_user user
+ ORDER BY user.userID";
+ $statement = WCF::getDB()->prepareStatement($sql, $this->limit, ($this->limit * $this->loopCount));
+ $statement->execute();
+
+ $userIDs = array();
+ while ($row = $statement->fetchArray()) {
+ $userIDs[] = $row['userID'];
+ }
+
+ UserActivityPointHandler::getInstance()->updateUsers($userIDs);
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::getProceedURL()
+ */
+ public function getProceedURL() {
+ return LinkHandler::getInstance()->getLink('UserActivityPointOption');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\worker;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Worker implementation for updating user activity point events.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.worker
+ * @category Community Framework
+ */
+class UserActivityPointUpdateEventsWorker extends AbstractWorker {
+ /**
+ * Limiting is dependent on the actual processors.
+ * @see wcf\system\worker\AbstractWorker::$limit
+ */
+ protected $limit = 1;
+
+ /**
+ * object types
+ * @var array<wcf\data\object\type\ObjectType>
+ */
+ public $objectTypes = array();
+
+ public function __construct(array $parameters) {
+ parent::__construct($parameters);
+
+ $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.user.activityPointEvent');
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::validate()
+ */
+ public function validate() {
+ WCF::getSession()->checkPermissions(array('admin.user.canEditActivityPoints'));
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::countObjects()
+ */
+ public function countObjects() {
+ $this->count = 0;
+ foreach ($this->objectTypes as $objectType) {
+ $objectType->requests = $objectType->getProcessor()->countRequests();
+ $this->count += $objectType->requests;
+ }
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::execute()
+ */
+ public function execute() {
+ $loopCount = $this->loopCount;
+ foreach ($this->objectTypes as $objectType) {
+ if ($loopCount < $objectType->requests) {
+ $objectType->getProcessor()->updateActivityPointEvents($loopCount);
+ return;
+ }
+ $loopCount -= $objectType->requests;
+ }
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::getProceedURL()
+ */
+ public function getProceedURL() {
+ return LinkHandler::getInstance()->getLink('UserActivityPointOption');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\worker;
+use wcf\data\user\UserEditor;
+use wcf\data\user\UserList;
+use wcf\data\user\UserProfileAction;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Worker implementation for updating user ranks.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage system.worker
+ * @category Community Framework
+ */
+class UserRankUpdateWorker extends AbstractWorker {
+ /**
+ * @see wcf\system\worker\AbstractWorker::$limit
+ */
+ protected $limit = 100;
+
+ /**
+ * @see wcf\system\worker\IWorker::validate()
+ */
+ public function validate() {
+ WCF::getSession()->checkPermissions(array('admin.user.rank.canManageRank'));
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::countObjects()
+ */
+ public function countObjects() {
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_user user";
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute();
+ $row = $statement->fetchArray();
+
+ $this->count = $row['count'];
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::execute()
+ */
+ public function execute() {
+ // get users
+ $userList = new UserList();
+ $userList->sqlLimit = $this->limit;
+ $userList->sqlOffset = $this->limit * $this->loopCount;
+ $userList->sqlOrderBy = 'user_table.userID';
+ $userList->readObjects();
+
+ $users = array();
+ foreach ($userList as $user) {
+ $users[] = new UserEditor($user);
+ }
+
+ if (!empty($users)) {
+ $action = new UserProfileAction($users, 'updateUserRank');
+ $action->executeAction();
+ $action = new UserProfileAction($users, 'updateUserOnlineMarking');
+ $action->executeAction();
+ }
+ }
+
+ /**
+ * @see wcf\system\worker\IWorker::getProceedURL()
+ */
+ public function getProceedURL() {
+ return LinkHandler::getInstance()->getLink('UserRankList');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\util;
+
+/**
+ * Contains user registration related functions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.user
+ * @subpackage util
+ * @category Community Framework
+ */
+final class UserRegistrationUtil {
+ /**
+ * Disables creation of objects of this class.
+ */
+ private function __construct() { }
+
+ /**
+ * Returns true if the given name is a valid username.
+ *
+ * @param string $name username
+ * @return boolean
+ */
+ public static function isValidUsername($name) {
+ if (!UserUtil::isValidUsername($name)) return false;
+
+ $length = StringUtil::length($name);
+ if ($length < REGISTER_USERNAME_MIN_LENGTH || $length > REGISTER_USERNAME_MAX_LENGTH) return false;
+
+ if (!self::checkForbiddenUsernames($name)) return false;
+
+ if (REGISTER_USERNAME_FORCE_ASCII) {
+ if (!preg_match('/^[\x20-\x7E]+$/', $name)) return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if the given e-mail is a valid address.
+ *
+ * @param string $email
+ * @return boolean
+ */
+ public static function isValidEmail($email) {
+ return (UserUtil::isValidEmail($email) && self::checkForbiddenEmails($email));
+ }
+
+ /**
+ * Returns false if the given name is a forbidden username.
+ *
+ * @return boolean
+ */
+ public static function checkForbiddenUsernames($name) {
+ return StringUtil::executeWordFilter($name, REGISTER_FORBIDDEN_USERNAMES);
+ }
+
+ /**
+ * Returns false if the given email is a forbidden email.
+ *
+ * @return boolean
+ */
+ public static function checkForbiddenEmails($email) {
+ return (StringUtil::executeWordFilter($email, REGISTER_FORBIDDEN_EMAILS) && (!REGISTER_ALLOWED_EMAILS || !StringUtil::executeWordFilter($email, REGISTER_ALLOWED_EMAILS)));
+ }
+
+ /**
+ * Returns true if the given password is secure.
+ *
+ * @param string $password
+ * @return boolean
+ */
+ public static function isSecurePassword($password) {
+ if (REGISTER_ENABLE_PASSWORD_SECURITY_CHECK) {
+ if (StringUtil::length($password) < REGISTER_PASSWORD_MIN_LENGTH) return false;
+
+ if (REGISTER_PASSWORD_MUST_CONTAIN_DIGIT && !preg_match('![0-9]+!', $password)) return false;
+ if (REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE && !preg_match('![a-z]+!', $password)) return false;
+ if (REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE && !preg_match('![A-Z]+!', $password)) return false;
+ if (REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR && !preg_match('![^A-Za-z0-9]+!', $password)) return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generates a random activation code with the given length.
+ * Warning: A length greater than 9 is out of integer range.
+ *
+ * @param integer $length
+ * @return integer
+ */
+ public static function getActivationCode($length = 9) {
+ return MathUtil::getRandomValue(pow(10, $length - 1), pow(10, $length) - 1);
+ }
+}
--- /dev/null
+/* user profile sidebar */
+.sidebar {
+ .userAvatar {
+ text-align: center;
+ overflow: hidden;
+
+ > a {
+ display: block;
+ }
+ }
+}
+
+.framedIconList {
+ li {
+ float: left;
+ margin: 0 2px 4px 0;
+
+ .framed {
+ display: inline-block;
+ }
+ }
+
+ &:after {
+ content: "";
+ height: 0;
+ display: block;
+ clear: both;
+ }
+}
+
+/* user information */
+.userInformation {
+ > .inlineDataList,
+ > .dataList {
+ font-size: @wcfSmallFontSize;
+ }
+
+ > .dataList {
+ margin-top: 0;
+ }
+
+ > .inlineDataList {
+ margin-top: @wcfGapSmall;
+
+ ~ .inlineDataList {
+ margin-top: 0;
+ }
+ }
+
+ > .containerHeadline + .inlineDataList {
+ margin-top: 0;
+ }
+
+ > .containerHeadline > p {
+ margin-bottom: 2px;
+ }
+}
+
+.userTitleBadge {
+ font-weight: normal;
+ max-width: 154px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.userProfilePreview {
+ position: relative;
+
+ > .userInformation {
+ padding-bottom: 16px;// + @wcfGapTiny;
+
+ > .userStats,
+ > .userFields {
+ margin-bottom: @wcfGapTiny+1;
+ padding-top: @wcfGapTiny+1;
+ border-top: 1px dotted @wcfContainerBorderColor;
+ }
+
+ > .userFields {
+ padding-bottom: @wcfGapTiny+1;
+ border-bottom: 1px dotted @wcfContainerBorderColor;
+ }
+
+ > .buttonGroupNavigation {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ }
+ }
+}
+
+/* ##### User List #### */
+.userHeadline {
+ position: relative;
+
+ > nav.buttonGroupNavigation {
+ position: absolute;
+ right: @wcfGapTiny;
+ top: @wcfGapTiny;
+ }
+}
+
+.letters {
+ > li {
+ margin-bottom: @wcfGapTiny;
+
+ > .button {
+ min-width: 1.2em;
+ text-align: center;
+ }
+ }
+}
+
+/* recent activities */
+.recentActivityList {
+ > li {
+ &.recentActivitiesMore {
+ text-align: center;
+
+ > button {
+ padding-left: 30px;
+ padding-right: 30px;
+ }
+
+ > small {
+ color: @wcfDimmedColor;
+ }
+ }
+
+ > div.box48 > div {
+ overflow: hidden;
+ }
+ }
+}
+
+/* dashboard boxes */
+.dashboardBoxRegisterButton {
+ padding-bottom: @wcfGapLarge !important;
+
+ > div {
+ text-align: center;
+
+ > .button {
+ font-size: 120%;
+ padding: 5px 13px;
+ }
+ }
+}
+
+.usersOnline {
+ .usersOnlineLegend {
+ margin-top: @wcfGapSmall;
+
+ > p {
+ display: inline-block;
+ }
+
+ > ul {
+ display: inline-block;
+ }
+ }
+}
+
+@media only screen and (max-width: 800px) {
+ #tplUser .userHeadline {
+ overflow: visible;
+
+ > .invisible {
+ display: block;
+ float: left;
+ }
+
+ > *:not(.invisible) {
+ margin-left: 59px;
+ }
+
+ > h1 {
+ margin-right: 30px;
+ }
+ }
+}
+
+/* avatar cropping */
+
+.userAvatarCrop {
+ cursor: pointer;
+}
+
+#userAvatarCropSelection {
+ position: relative;
+ margin: 0 auto;
+}
+
+#userAvatarCropOverlay {
+ background-color: #000000;
+ height: 100%;
+ opacity: 0.5;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ z-index: 10;
+}
+
+#userAvatarCropOverlaySelection {
+ cursor: move;
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 128px;
+ overflow: hidden;
+ width: 128px;
+ z-index: 20;
+}
<item name="wcf.acp.cronjob.log.error.details"><![CDATA[Fehlermeldung]]></item>
</category>
+ <category name="wcf.acp.dashboard">
+ <item name="wcf.acp.dashboard.list"><![CDATA[Dashboard-Konfiguration]]></item>
+ <item name="wcf.acp.dashboard.option"><![CDATA[Dashboard-Boxen konfigurieren]]></item>
+ <item name="wcf.acp.dashboard.box.sort"><![CDATA[Sie können die Boxen durch Ziehen und Loslassen mit der Maus sortieren. Boxen können aktiviert bzw. deaktiviert werden, indem Sie die gewünschte Box in „Aktive Boxen“ bzw. „Deaktivierte Boxen“ verschieben.]]></item>
+ </category>
+
<category name="wcf.acp.exceptionLog">
<item name="wcf.acp.exceptionLog"><![CDATA[Protokollierte Fehler]]></item>
<item name="wcf.acp.exceptionLog.exception.file"><![CDATA[Datei (Zeile)]]></item>
<item name="wcf.acp.group.option.user.message.canUseBBCodes"><![CDATA[Kann BBCodes benutzen]]></item>
<item name="wcf.acp.group.option.user.message.allowedBBCodes"><![CDATA[Erlaubte BBCodes]]></item>
<item name="wcf.acp.group.option.user.message.allowedBBCodes.description"><![CDATA[Die hier ausgewählten BBCodes dürfen von Mitglieder dieser Benutzergruppe verwendet werden.]]></item>
+ <item name="wcf.acp.group.option.admin.content.dashboard.canEditDashboard"><![CDATA[Kann Dashboards konfigurieren]]></item>
+ <item name="wcf.acp.group.option.admin.user.rank.canManageRank"><![CDATA[Kann Benutzerränge verwalten]]></item>
+ <item name="wcf.acp.group.option.admin.user.canEditActivityPoints"><![CDATA[Kann Aktivtitätspunkte bearbeiten]]></item>
+ <item name="wcf.acp.group.option.admin.user.canViewInvisible"><![CDATA[Kann unsichtbare Benutzer sehen]]></item>
+ <item name="wcf.acp.group.option.admin.user.canViewIpAddress"><![CDATA[Kann die IP-Adressen der Benutzer sehen]]></item>
+ <item name="wcf.acp.group.option.category.admin.content.dashboard"><![CDATA[Dashboard]]></item>
+ <item name="wcf.acp.group.option.category.user.profile"><![CDATA[Benutzerprofile]]></item>
+ <item name="wcf.acp.group.option.category.user.profile.avatar"><![CDATA[Avatare]]></item>
+ <item name="wcf.acp.group.option.category.admin.user.rank"><![CDATA[Benutzerränge]]></item>
+ <item name="wcf.acp.group.option.category.user.signature"><![CDATA[Signaturen]]></item>
+ <item name="wcf.acp.group.option.user.profile.avatar.allowedFileExtensions"><![CDATA[Erlaubte Dateiendungen]]></item>
+ <item name="wcf.acp.group.option.user.profile.avatar.canUploadAvatar"><![CDATA[Kann eigenen Avatar hochladen]]></item>
+ <item name="wcf.acp.group.option.user.profile.avatar.maxSize"><![CDATA[Maximale Dateigröße]]></item>
+ <item name="wcf.acp.group.option.user.profile.canChangeEmail"><![CDATA[Kann E-Mail-Adresse ändern]]></item>
+ <item name="wcf.acp.group.option.user.profile.canEditUserTitle"><![CDATA[Kann eigenen Benutzertitel bearbeiten]]></item>
+ <item name="wcf.acp.group.option.user.profile.canMail"><![CDATA[Kann E-Mails an andere Benutzer senden]]></item>
+ <item name="wcf.acp.group.option.user.profile.canQuit"><![CDATA[Kann Benutzerkonto löschen]]></item>
+ <item name="wcf.acp.group.option.user.profile.canRename"><![CDATA[Kann Benutzernamen ändern]]></item>
+ <item name="wcf.acp.group.option.user.profile.canViewMembersList"><![CDATA[Kann Mitglieder-Liste sehen]]></item>
+ <item name="wcf.acp.group.option.user.profile.canViewUserProfile"><![CDATA[Kann Benutzerprofile sehen]]></item>
+ <item name="wcf.acp.group.option.user.profile.canViewUsersOnlineList"><![CDATA[Kann Benutzer-Online-Liste sehen]]></item>
+ <item name="wcf.acp.group.option.user.signature.canUseBBCodes"><![CDATA[Kann BBCodes in der Signatur verwenden]]></item>
+ <item name="wcf.acp.group.option.user.signature.canUseHtml"><![CDATA[Kann HTML in der Signatur verwenden]]></item>
+ <item name="wcf.acp.group.option.user.signature.canUseSmilies"><![CDATA[Kann Smileys in der Signatur verwenden]]></item>
+ <item name="wcf.acp.group.option.user.signature.allowedBBCodes"><![CDATA[Erlaubte BBCodes]]></item>
+ <item name="wcf.acp.group.option.user.signature.allowedBBCodes.description"><![CDATA[Die hier ausgewählten BBCodes dürfen von Mitgliedern dieser Benutzergruppe in ihrer Signatur verwendet werden.]]></item>
+ <item name="wcf.acp.group.priority"><![CDATA[Priorisierung]]></item>
+ <item name="wcf.acp.group.priority.description"><![CDATA[Bestimmt u.a. die Reihenfolge auf der Team-Seite sowie die Auswahl von Benutzerrängen und „Wer ist online“-Darstellungen auf Basis der höchsten Priorität.]]></item>
+ <item name="wcf.acp.group.userOnlineMarking"><![CDATA[„Benutzer online“-Darstellung]]></item>
+ <item name="wcf.acp.group.userOnlineMarking.description"><![CDATA[Sie können die HTML-Formatierung für Mitglieder dieser Benutzergruppe in der „Wer ist online“-Anzeige anpassen. <em><strong>%s</strong></em> stellt Mitglieder dieser Gruppe beispielsweise in Fettdruck dar.]]></item>
+ <item name="wcf.acp.group.showOnTeamPage"><![CDATA[Mitglieder dieser Benutzergruppe auf der Team-Seite anzeigen]]></item>
+ <item name="wcf.acp.group.option.admin.user.canEnableUser"><![CDATA[Kann Benutzer aktivieren]]></item>
+ <item name="wcf.acp.group.option.user.profile.renamePeriod"><![CDATA[Umbenennung]]></item>
+ <item name="wcf.acp.group.option.user.profile.renamePeriod.description"><![CDATA[Zeitraum nach dem Mitglieder dieser Benutzergruppe ihren Benutzernamen ändern können. [Zeit in Tagen]]]></item>
+ <item name="wcf.acp.group.option.user.profile.cannotBeIgnored"><![CDATA[Kann nicht ignoriert werden]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.acp.menu.link.smiley.list"><![CDATA[Smileys auflisten]]></item>
<item name="wcf.acp.menu.link.smiley.category.add"><![CDATA[Smiley-Kategorie hinzufügen]]></item>
<item name="wcf.acp.menu.link.smiley.category.list"><![CDATA[Smiley-Kategorien auflisten]]></item>
+ <item name="wcf.acp.menu.link.dashboard"><![CDATA[Dashboard]]></item>
+ <item name="wcf.acp.menu.link.dashboard.list"><![CDATA[Konfiguration]]></item>
+ <item name="wcf.acp.menu.link.activityPoint"><![CDATA[Aktivitätspunkte]]></item>
+ <item name="wcf.acp.menu.link.user.rank"><![CDATA[Benutzerränge]]></item>
+ <item name="wcf.acp.menu.link.user.rank.list"><![CDATA[Benutzerränge auflisten]]></item>
+ <item name="wcf.acp.menu.link.user.rank.add"><![CDATA[Benutzerrang hinzufügen]]></item>
</category>
<category name="wcf.acp.option">
<item name="wcf.acp.option.show_signature_default_value"><![CDATA[Signatur anzeigen [Vorgabewert]]]></item>
<item name="wcf.acp.option.enable_share_buttons"><![CDATA[Buttons zum Teilen von Inhalten anzeigen]]></item>
<item name="wcf.acp.option.share_buttons_show_count"><![CDATA[Anzahl der Teilungen anzeigen]]></item>
+ <item name="wcf.acp.option.github_public_key"><![CDATA[GitHub Client ID]]></item>
+ <item name="wcf.acp.option.github_public_key.description"><![CDATA[Sie können Ihre Client ID und Ihr Client Secret bei <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://github.com/settings/applications'|rawurlencode}" class="externalURL">GitHub</a> anfordern.]]></item>
+ <item name="wcf.acp.option.github_private_key"><![CDATA[GitHub Client Secret]]></item>
+ <item name="wcf.acp.option.twitter_public_key"><![CDATA[Twitter Consumer key]]></item>
+ <item name="wcf.acp.option.twitter_public_key.description"><![CDATA[Sie können Ihren Consumer key und Ihr Consumer secret bei <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://dev.twitter.com/apps'|rawurlencode}" class="externalURL">Twitter</a> anfordern.]]></item>
+ <item name="wcf.acp.option.twitter_private_key"><![CDATA[Twitter Consumer secret]]></item>
+ <item name="wcf.acp.option.facebook_public_key"><![CDATA[Facebook APP-ID]]></item>
+ <item name="wcf.acp.option.facebook_public_key.description"><![CDATA[Sie können Ihre APP-ID und Ihren Anwendungs-Geheimcode bei <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://developers.facebook.com/apps'|rawurlencode}" class="externalURL">Facebook</a> anfordern.]]></item>
+ <item name="wcf.acp.option.facebook_private_key"><![CDATA[Facebook Anwendungs-Geheimcode]]></item>
+ <item name="wcf.acp.option.google_public_key"><![CDATA[Google Client ID]]></item>
+ <item name="wcf.acp.option.google_public_key.description"><![CDATA[Sie können Ihre Client ID und Ihr Client Secret bei <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://code.google.com/apis/console/'|rawurlencode}" class="externalURL">Google</a> anfordern.]]></item>
+ <item name="wcf.acp.option.google_private_key"><![CDATA[Google Client Secret]]></item>
+ <item name="wcf.acp.option.category.dashboard"><![CDATA[Dashboard]]></item>
+ <item name="wcf.acp.option.category.dashboard.content"><![CDATA[Inhaltsbereich]]></item>
+ <item name="wcf.acp.option.category.dashboard.sidebar"><![CDATA[Seitenleiste]]></item>
+ <item name="wcf.acp.option.category.dashboard.content.recentActivities"><![CDATA[Letzte Aktivität]]></item>
+ <item name="wcf.acp.option.category.dashboard.sidebar.recentActivities"><![CDATA[Letzte Aktivität]]></item>
+ <item name="wcf.acp.option.category.user.profile"><![CDATA[Profil]]></item>
+ <item name="wcf.acp.option.category.user.avatar"><![CDATA[Avatar]]></item>
+ <item name="wcf.acp.option.category.user.signature"><![CDATA[Signatur]]></item>
+ <item name="wcf.acp.option.category.user.title"><![CDATA[Benutzertitel]]></item>
+ <item name="wcf.acp.option.category.user.cleanup"><![CDATA[Aufräumaktionen]]></item>
+ <item name="wcf.acp.option.category.user.list"><![CDATA[Mitgliederlisten]]></item>
+ <item name="wcf.acp.option.category.user.list.members"><![CDATA[Mitgliederliste]]></item>
+ <item name="wcf.acp.option.category.user.list.online"><![CDATA[„Benutzer online“-Liste]]></item>
+ <item name="wcf.acp.option.category.user.register"><![CDATA[Registrierung]]></item>
+ <item name="wcf.acp.option.category.user.password"><![CDATA[Kennwort]]></item>
+ <item name="wcf.acp.option.category.user.ban"><![CDATA[Filter]]></item>
+ <item name="wcf.acp.option.category.user.3rdPartyAuth"><![CDATA[Authentifizierung über Drittanbieter]]></item>
+ <item name="wcf.acp.option.max_avatar_height"><![CDATA[Maximale Avatar-Höhe]]></item>
+ <item name="wcf.acp.option.max_avatar_width"><![CDATA[Maximale Avatar-Breite]]></item>
+ <item name="wcf.acp.option.module_gravatar"><![CDATA[Gravatare]]></item>
+ <item name="wcf.acp.option.module_gravatar.description"><![CDATA[Aktiviert die Unterstützung für Gravatare („Global Recognized Avatar“).]]></item>
+ <item name="wcf.acp.option.module_users_online"><![CDATA[„Benutzer online“-Anzeige]]></item>
+ <item name="wcf.acp.option.module_user_rank"><![CDATA[Benutzerränge]]></item>
+ <item name="wcf.acp.option.module_user_signature"><![CDATA[Signaturen]]></item>
+ <item name="wcf.acp.option.module_team_page"><![CDATA[Team-Seite]]></item>
+ <item name="wcf.acp.option.module_dashboard_page"><![CDATA[Dashboard]]></item>
+ <item name="wcf.acp.option.module_gravatar"><![CDATA[Gravatare]]></item>
+ <item name="wcf.acp.option.register_enable_password_security_check"><![CDATA[Sicherheitsüberprüfung aktivieren]]></item>
+ <item name="wcf.acp.option.register_enable_password_security_check.description"><![CDATA[Kennwörter werden auf ihre Sicherheit geprüft. Unsichere Kennwörter werden abgelehnt.]]></item>
+ <item name="wcf.acp.option.register_password_min_length"><![CDATA[Minimale Kennwortlänge]]></item>
+ <item name="wcf.acp.option.register_password_must_contain_digit"><![CDATA[Kennwort muss Zahlen enthalten]]></item>
+ <item name="wcf.acp.option.register_password_must_contain_lower_case"><![CDATA[Kennwort muss Kleinbuchstaben enthalten]]></item>
+ <item name="wcf.acp.option.register_password_must_contain_special_char"><![CDATA[Kennwort muss Sonderzeichen enthalten]]></item>
+ <item name="wcf.acp.option.register_password_must_contain_upper_case"><![CDATA[Kennwort muss Großbuchstaben enthalten]]></item>
+ <item name="wcf.acp.option.register_forbidden_usernames"><![CDATA[Reservierte Namen]]></item>
+ <item name="wcf.acp.option.register_forbidden_usernames.description"><![CDATA[Namen, die nicht als Benutzername verwendet werden dürfen. Ein Name pro Zeile]]></item>
+ <item name="wcf.acp.option.register_forbidden_emails"><![CDATA[Reservierte E-Mail-Adressen]]></item>
+ <item name="wcf.acp.option.register_forbidden_emails.description"><![CDATA[E-Mail-Adressen, die nicht bei der Registrierung verwendet werden dürfen. Eine Adresse pro Zeile]]></item>
+ <item name="wcf.acp.option.register_allowed_emails"><![CDATA[Erlaubte E-Mail-Adressen]]></item>
+ <item name="wcf.acp.option.register_allowed_emails.description"><![CDATA[E-Mail-Adressen, die ausschließlich bei der Registrierung verwendet werden dürfen. Eine Adresse pro Zeile]]></item>
+ <item name="wcf.acp.option.register_username_min_length"><![CDATA[Minimale Benutzernamenlänge]]></item>
+ <item name="wcf.acp.option.register_username_max_length"><![CDATA[Maximale Benutzernamenlänge]]></item>
+ <item name="wcf.acp.option.register_username_force_ascii"><![CDATA[Benutzernamen auf ASCII-Zeichen beschränken]]></item>
+ <item name="wcf.acp.option.register_disabled"><![CDATA[Registrierung deaktivieren]]></item>
+ <item name="wcf.acp.option.register_disabled.description"><![CDATA[Schaltet die Registrierung für neue Benutzer gänzlich ab. Neue Benutzer können nur noch manuell durch den Administrator angelegt werden.]]></item>
+ <item name="wcf.acp.option.register_enable_disclaimer"><![CDATA[Nutzungsbedingungen aktivieren]]></item>
+ <item name="wcf.acp.option.register_enable_disclaimer.description"><![CDATA[Die Nutzungsbedingungen müssen durch den Benutzer vor der Registrierung akzeptiert werden.]]></item>
+ <item name="wcf.acp.option.register_admin_notification"><![CDATA[Administrator über neue Registrierungen per E-Mail benachrichtigen]]></item>
+ <item name="wcf.acp.option.register_activation_method"><![CDATA[Aktivierungsmethode]]></item>
+ <item name="wcf.acp.option.register_activation_method.byAdmin"><![CDATA[Aktivierung erfolgt durch Administrator]]></item>
+ <item name="wcf.acp.option.register_activation_method.byUser"><![CDATA[Benutzer aktiviert sich durch E-Mail-Bestätigung]]></item>
+ <item name="wcf.acp.option.register_activation_method.disabled"><![CDATA[Keine Aktivierung notwendig]]></item>
+ <item name="wcf.acp.option.register_use_captcha"><![CDATA[reCAPTCHA in Registrierung aktivieren]]></item>
+ <item name="wcf.acp.option.lost_password_use_captcha"><![CDATA[reCAPTCHA in „Kennwort vergessen“ aktivieren]]></item>
+ <item name="wcf.acp.option.profile_mail_use_captcha"><![CDATA[reCAPTCHA in „E-Mail an Benutzer schicken“ aktivieren]]></item>
+ <item name="wcf.acp.option.signature_max_image_height"><![CDATA[Maximale Höhe von Signatur-Bildern]]></item>
+ <item name="wcf.acp.option.user_title_max_length"><![CDATA[Maximale Länge des Benutzertitels]]></item>
+ <item name="wcf.acp.option.user_forbidden_titles"><![CDATA[Reservierte Benutzertitel]]></item>
+ <item name="wcf.acp.option.user_forbidden_titles.description"><![CDATA[Benutzertitel, die nicht verwendet werden dürfen. Ein Titel pro Zeile]]></item>
+ <item name="wcf.acp.option.profile_show_old_username"><![CDATA[Alten Namen anzeigen]]></item>
+ <item name="wcf.acp.option.profile_show_old_username.description"><![CDATA[Zeitraum in dem bei Änderungen des Benutzernamens zusätzlich der alte Benutzername im Profil angezeigt wird. [Zeit in Tagen]]]></item>
+ <item name="wcf.acp.option.members_list_users_per_page"><![CDATA[Mitglieder pro Seite]]></item>
+ <item name="wcf.acp.option.members_list_default_sort_field"><![CDATA[Standard-Sortierung]]></item>
+ <item name="wcf.acp.option.members_list_default_sort_order"><![CDATA[Standard-Reihenfolge]]></item>
+ <item name="wcf.acp.option.users_online_show_guests"><![CDATA[Unregistrierte Benutzer auflisten]]></item>
+ <item name="wcf.acp.option.users_online_show_robots"><![CDATA[Suchmaschinen-Roboter auflisten]]></item>
+ <item name="wcf.acp.option.users_online_default_sort_field"><![CDATA[Standard-Sortierung]]></item>
+ <item name="wcf.acp.option.users_online_default_sort_order"><![CDATA[Standard-Reihenfolge]]></item>
+ <item name="wcf.acp.option.users_online_page_refresh"><![CDATA[Seite automatisch neu laden]]></item>
+ <item name="wcf.acp.option.users_online_page_refresh.description"><![CDATA[Zeitraum nachdem die Seite automatisch neu geladen wird. [Zeit in Sekunden, 0 um Neuladen abzuschalten]]]></item>
+ <item name="wcf.acp.option.user_cleanup_notification_lifetime"><![CDATA[Benachrichtigungen]]></item>
+ <item name="wcf.acp.option.user_cleanup_notification_lifetime.description"><![CDATA[Zeitraum nach dem Benachrichtigungen automatisch verworfen werden. [Zeitraum in Tagen]]]></item>
+ <item name="wcf.acp.option.user_cleanup_activity_event_lifetime"><![CDATA[Letzte Aktivitäten]]></item>
+ <item name="wcf.acp.option.user_cleanup_activity_event_lifetime.description"><![CDATA[Zeitraum nach dem letzte Aktivitäten automatisch verworfen werden. [Zeitraum in Tagen]]]></item>
+ <item name="wcf.acp.option.user_cleanup_profile_visitor_lifetime"><![CDATA[Profil-Besucher]]></item>
+ <item name="wcf.acp.option.user_cleanup_profile_visitor_lifetime.description"><![CDATA[Zeitraum nach dem Profil-Besucher automatisch verworfen werden. [Zeitraum in Tagen]]]></item>
+ <item name="wcf.acp.option.recent_activity_items"><![CDATA[Anzahl Einträge]]></item>
+ <item name="wcf.acp.option.recent_activity_sidebar_items"><![CDATA[Anzahl Einträge]]></item>
</category>
<category name="wcf.acp.package">
<item name="wcf.acp.user.search.conditions.states"><![CDATA[Zustände]]></item>
<item name="wcf.acp.user.search.conditions.state.banned"><![CDATA[Gesperrt]]></item>
<item name="wcf.acp.user.search.conditions.state.notBanned"><![CDATA[Nicht gesperrt]]></item>
+ <item name="wcf.acp.user.activityPoint.option"><![CDATA[Aktivitätspunkte]]></item>
+ <item name="wcf.acp.user.activityPoint.updateCache"><![CDATA[Aktivitätspunkte neu vergeben]]></item>
+ <item name="wcf.acp.user.activityPoint.updateEvents"><![CDATA[Punkte pro Aktivität aktualisieren]]></item>
+ <item name="wcf.acp.user.delete"><![CDATA[Benutzer löschen]]></item>
+ <item name="wcf.acp.user.general"><![CDATA[Benutzer]]></item>
+ <item name="wcf.acp.user.rank.add"><![CDATA[Benutzerrang hinzufügen]]></item>
+ <item name="wcf.acp.user.rank.cssClassName"><![CDATA[CSS-Klassen]]></item>
+ <item name="wcf.acp.user.rank.cssClassName.description"><![CDATA[Sie können aus den vorgegebenen Darstellungen wählen oder eine eigene Darstellung durch Angabe einer <abbr title="Cascading Style Sheets">CSS</abbr>-Klasse nutzen.]]></item>
+ <item name="wcf.acp.user.rank.currentImage"><![CDATA[Aktuelle Ranggrafik]]></item>
+ <item name="wcf.acp.user.rank.delete.sure"><![CDATA[Wollen Sie den Benutzerrang „{$userRank->rankTitle|language}“ wirklich löschen?]]></item>
+ <item name="wcf.acp.user.rank.edit"><![CDATA[Benutzerrang bearbeiten]]></item>
+ <item name="wcf.acp.user.rank.image"><![CDATA[Ranggrafik]]></item>
+ <item name="wcf.acp.user.rank.list"><![CDATA[Benutzerränge]]></item>
+ <item name="wcf.acp.user.rank.rankImage.description"><![CDATA[Der Pfad zur Ranggrafik kann relativ zum WCF-Verzeichnis oder absolut angegeben werden.]]></item>
+ <item name="wcf.acp.user.rank.requiredGender.description"><![CDATA[Optional können Sie diesen Benutzerrang auf Benutzer mit einem bestimmten Geschlecht einschränken.]]></item>
+ <item name="wcf.acp.user.rank.requiredPoints"><![CDATA[Punkte]]></item>
+ <item name="wcf.acp.user.rank.requiredPoints.description"><![CDATA[Benötigte Menge an Aktivitätspunkten, die ein Benutzer erreichen muss, um in diesen Rang aufzusteigen.]]></item>
+ <item name="wcf.acp.user.rank.requirement"><![CDATA[Beschränkungen]]></item>
+ <item name="wcf.acp.user.rank.repeatImage"><![CDATA[Wiederholung der Rankgrafik]]></item>
+ <item name="wcf.acp.user.rank.repeatImage.description"><![CDATA[Bestimmt, wie oft die Grafik wiederholt wird.]]></item>
+ <item name="wcf.acp.user.rank.title"><![CDATA[Rangbezeichnung]]></item>
+ <item name="wcf.acp.user.rank.userGroup.description"><![CDATA[Dieser Benutzerrang steht ausschließlich den Mitgliedern der ausgewählten Benutzergruppe zur Verfügung.]]></item>
+ <item name="wcf.acp.user.rank.updateRanks"><![CDATA[Benutzerränge aktualisieren]]></item>
+ <item name="wcf.acp.user.disableSignature"><![CDATA[Signatur sperren]]></item>
+ <item name="wcf.acp.user.disableSignatureReason"><![CDATA[Begründung]]></item>
+ <item name="wcf.acp.user.disableAvatar"><![CDATA[Avatar sperren]]></item>
+ <item name="wcf.acp.user.disableAvatarReason"><![CDATA[Begründung]]></item>
+ <item name="wcf.acp.user.disable"><![CDATA[Deaktivieren]]></item>
+ <item name="wcf.acp.user.enable"><![CDATA[Aktivieren]]></item>
+ <item name="wcf.acp.user.quickSearch.disabled"><![CDATA[Nicht aktivierte Benutzer]]></item>
+ <item name="wcf.acp.user.quickSearch.disabledAvatars"><![CDATA[Gesperrte Avatare]]></item>
+ <item name="wcf.acp.user.quickSearch.disabledSignatures"><![CDATA[Gesperrte Signaturen]]></item>
+ <item name="wcf.acp.user.usersAwaitingApprovalInfo"><![CDATA[<a href="{link controller='UserQuickSearch'}mode=disabled{/link}">{#$usersAwaitingApproval} Benutzer</a> {if $usersAwaitingApproval == 1}wartet auf seine{else}warten auf ihre{/if} Aktivierung.]]></item>
+ <item name="wcf.acp.user.search.conditions.state.enabled"><![CDATA[Aktiviert]]></item>
+ <item name="wcf.acp.user.search.conditions.state.disabled"><![CDATA[Nicht aktiviert]]></item>
</category>
<category name="wcf.ajax">
<item name="wcf.clipboard.item.com.woltlab.wcf.user.exportMailAddress"><![CDATA[E-Mail-Adressen exportieren]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendMail"><![CDATA[E-Mail senden]]></item>
<item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{if $count == 1}Ein{else}{#$count}{/if} Benutzer markiert]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.merge"><![CDATA[Zusammenfügen]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.enable"><![CDATA[Aktivieren]]></item>
+ </category>
+
+ <category name="wcf.dashboard">
+ <item name="wcf.dashboard.box.availableBoxes"><![CDATA[Deaktivierte Boxen]]></item>
+ <item name="wcf.dashboard.box.enabledBoxes"><![CDATA[Aktive Boxen]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.recentActivitySidebar"><![CDATA[Letzte Aktivität]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.statsSidebar"><![CDATA[Statistiken]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.newestMembers"><![CDATA[Neueste Mitglieder]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.mostActiveMembers"><![CDATA[Aktivste Mitglieder]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.registerButton"><![CDATA[Registrierungs-Button]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.signedInAs"><![CDATA[Angemeldet als]]></item>
+ <item name="wcf.dashboard.boxType.content"><![CDATA[Inhaltsbereich]]></item>
+ <item name="wcf.dashboard.boxType.sidebar"><![CDATA[Seitenleiste]]></item>
+ <item name="wcf.dashboard.objectType"><![CDATA[Seite]]></item>
+ <item name="wcf.dashboard.objectType.com.woltlab.wcf.user.DashboardPage"><![CDATA[Dashboard]]></item>
+ <item name="wcf.dashboard.objectType.com.woltlab.wcf.user.MembersListPage"><![CDATA[Mitgliederliste]]></item>
+ <item name="wcf.dashboard.box.mostActiveMembers.points"><![CDATA[{#$activeMember->activityPoints} Punkt{if $activeMember->activityPoints != 1}e{/if}]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.recentActivity"><![CDATA[Letzte Aktivität]]></item>
</category>
<category name="wcf.date">
<item name="wcf.page.sitemap"><![CDATA[Schnellnavigation]]></item>
<item name="wcf.page.mainMenu"><![CDATA[Navigation]]></item>
<item name="wcf.page.pagePosition"><![CDATA[Seite {#$pageNo} von {#$pages}]]></item>
+ <item name="wcf.page.sitemap.userAccount"><![CDATA[Benutzerkonto]]></item>
</category>
<category name="wcf.recaptcha">
<item name="wcf.user.option.error.tooShort"><![CDATA[Der eingegebene Text ist zu kurz.]]></item>
<item name="wcf.user.option.error.validationFailed"><![CDATA[Sie haben einen ungültigen Inhalt eingegeben.]]></item>
<item name="wcf.user.error.isBanned"><![CDATA[Ihr Benutzeraccount wurde gesperrt{if $__wcf->user->banReason}: {@$__wcf->user->banReason|htmlspecialchars|nl2br}{else}.{/if}]]></item>
+ <item name="wcf.user.access.everyone"><![CDATA[Jeder]]></item>
+ <item name="wcf.user.access.following"><![CDATA[Benutzer, denen ich folge]]></item>
+ <item name="wcf.user.access.nobody"><![CDATA[Keiner]]></item>
+ <item name="wcf.user.access.registered"><![CDATA[Registrierte Benutzer]]></item>
+ <item name="wcf.user.button.login"><![CDATA[Anmelden]]></item>
+ <item name="wcf.user.button.register"><![CDATA[Registrieren]]></item>
+ <item name="wcf.user.button.registerNow"><![CDATA[Jetzt registrieren]]></item>
+ <item name="wcf.user.error.username.3rdParty"><![CDATA[Das Benutzerkonto ist mit einem Drittanbieter-Konto verbunden, ein Passwort kann nicht angefordert werden.]]></item>
+ <item name="wcf.user.login.action"><![CDATA[Sind Sie bereits registriert?]]></item>
+ <item name="wcf.user.login.action.login"><![CDATA[Ja, mein Kennwort lautet:]]></item>
+ <item name="wcf.user.login.action.register"><![CDATA[Nein, ich möchte mich jetzt registrieren.]]></item>
+ <item name="wcf.user.login.redirect"><![CDATA[Sie wurden erfolgreich angemeldet.]]></item>
+ <item name="wcf.user.loginOrRegister"><![CDATA[Anmelden{if !REGISTER_DISABLED} oder registrieren{/if}]]></item>
+ <item name="wcf.user.logout.redirect"><![CDATA[Sie wurden erfolgreich abgemeldet.]]></item>
+ <item name="wcf.user.useCookies"><![CDATA[Dauerhaft angemeldet bleiben]]></item>
+ <item name="wcf.user.usernameOrEmail"><![CDATA[Benutzername oder E-Mail-Adresse]]></item>
+ <item name="wcf.user.gender.male"><![CDATA[Männlich]]></item>
+ <item name="wcf.user.gender.female"><![CDATA[Weiblich]]></item>
+ <item name="wcf.user.members"><![CDATA[Mitglieder]]></item>
+ <item name="wcf.user.members.noMembers"><![CDATA[Es wurden keine Mitglieder gefunden.]]></item>
+ <item name="wcf.user.members.sort"><![CDATA[Sortierung]]></item>
+ <item name="wcf.user.members.sort.letters"><![CDATA[Anfangsbuchstabe]]></item>
+ <item name="wcf.user.members.sort.letters.all"><![CDATA[Alle]]></item>
+ <item name="wcf.user.membersList.registrationDate"><![CDATA[Mitglied seit {@$user->registrationDate|date}]]></item>
+ <item name="wcf.user.membersList.location"><![CDATA[aus {$user->location}]]></item>
+ <item name="wcf.user.myProfile"><![CDATA[Mein Profil]]></item>
+ <item name="wcf.user.editProfile"><![CDATA[Profil bearbeiten]]></item>
+ <item name="wcf.user.profileHits"><![CDATA[Profil-Aufrufe]]></item>
+ <item name="wcf.user.profileHits.hitsPerDay"><![CDATA[{#$user->profileHits/$user->getProfileAge()} Aufrufe pro Tag]]></item>
+ <item name="wcf.user.online"><![CDATA[Online]]></item>
+ <item name="wcf.user.online.title"><![CDATA[„{$username}“ ist online]]></item>
+ <item name="wcf.user.button.follow"><![CDATA[Folgen]]></item>
+ <item name="wcf.user.button.unfollow"><![CDATA[Entfolgen]]></item>
+ <item name="wcf.user.button.ignore"><![CDATA[Blockieren]]></item>
+ <item name="wcf.user.button.unignore"><![CDATA[Nicht mehr blockieren]]></item>
+ <item name="wcf.user.style"><![CDATA[Stile]]></item>
+ <item name="wcf.user.style.description"><![CDATA[Stile der Benutzeroberfläche]]></item>
+ <item name="wcf.user.username.description"><![CDATA[Der Benutzername muss mindestens {REGISTER_USERNAME_MIN_LENGTH} und darf maximal {REGISTER_USERNAME_MAX_LENGTH} Zeichen lang sein.]]></item>
+ <item name="wcf.user.password.description"><![CDATA[{if REGISTER_ENABLE_PASSWORD_SECURITY_CHECK && REGISTER_PASSWORD_MIN_LENGTH}Das Kennwort muss mindestens {REGISTER_PASSWORD_MIN_LENGTH} Zeichen lang sein.{else}Ein sicheres Kennwort sollte mindestens 8 Zeichen lang sein.{/if}]]></item>
+ <item name="wcf.user.lostPassword"><![CDATA[Kennwort vergessen]]></item>
+ <item name="wcf.user.lostPassword.description"><![CDATA[Wenn Sie Ihr Kennwort vergessen haben, müssen Sie entweder den Benutzernamen oder die E-Mail-Adresse angeben, die Sie in Ihrem Profil hinterlegt haben. Sie können dabei nur eines der beiden Felder ausfüllen. Wenn Sie beide Daten nicht mehr wissen, wenden Sie sich bitte an den Administrator.]]></item>
+ <item name="wcf.user.lostPassword.email.error.notFound"><![CDATA[Es wurde kein Benutzer mit der E-Mail-Adresse: „{$email}“ gefunden.]]></item>
+ <item name="wcf.user.lostPassword.error.tooManyRequests"><![CDATA[Das Kennwort für dieses Benutzerkonto wurde in den letzten 24 Stunden bereits einmal angefordert. Aus Sicherheitsgründen kann das Kennwort eines Benutzers nur einmal pro Tag angefordert werden. Sie können das Kennwort für dieses Benutzerkonto in {#$hours} Stunde{if $hours != 1}n{/if} erneut anfordern.]]></item>
+ <item name="wcf.user.lostPassword.mail.subject"><![CDATA[Kennwort vergessen auf der Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.lostPassword.mail"><![CDATA[Hallo {@$username},
+
+wenn Sie Ihr Kennwort vergessen haben, können Sie über folgenden Link ein neues Kennwort anfordern.
+Klicken Sie hier, um ein neues Kennwort anzufordern: {link controller='NewPassword' encode=false}u={@$userID}&k={@$key}{/link}
+
+Falls Sie Ihr Kennwort nicht vergessen haben, können Sie diese E-Mail ignorieren.]]></item>
+ <item name="wcf.user.lostPassword.mail.sent"><![CDATA[Sie erhalten in Kürze eine E-Mail mit weiteren Informationen.]]></item>
+ <item name="wcf.user.newPassword"><![CDATA[Neues Kennwort anfordern]]></item>
+ <item name="wcf.user.lostPasswordKey"><![CDATA[Sicherheitsschlüssel]]></item>
+ <item name="wcf.user.lostPasswordKey.error.invalid"><![CDATA[Sie haben einen ungültigen Sicherheitsschlüssel angegeben.]]></item>
+ <item name="wcf.user.userID.error.invalid"><![CDATA[Sie haben eine ungültige Benutzer-ID angegeben.]]></item>
+ <item name="wcf.user.newPassword.mail"><![CDATA[Hallo {@$username},
+
+Ihr neues Kennwort für die Website "{@PAGE_TITLE|language}" lautet:
+{@$newPassword}
+
+Sie können Ihr Kennwort unter folgender Adresse jederzeit ändern:
+{link controller='AccountManagement' encode=false}{/link} ]]></item>
+ <item name="wcf.user.newPassword.mail.subject"><![CDATA[Neues Kennwort auf der Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.newPassword.success"><![CDATA[Sie erhalten in Kürze eine E-Mail mit Ihrem neuen Kennwort.]]></item>
+ <item name="wcf.user.accountManagement"><![CDATA[Benutzerkonto-Verwaltung]]></item>
+ <item name="wcf.user.accountManagement.warning"><![CDATA[Sie bearbeiten Ihr eigenes Benutzerkonto. Unbedachte Änderungen können dazu führen, dass Sie sich nicht mehr anmelden können. Bitte seien Sie entsprechend vorsichtig!]]></item>
+ <item name="wcf.user.accountManagement.password.description"><![CDATA[Bitte geben Sie zur Bestätigung Ihr Kennwort ein!]]></item>
+ <item name="wcf.user.newPassword"><![CDATA[Neues Kennwort]]></item>
+ <item name="wcf.user.changeUsername"><![CDATA[Benutzernamen ändern]]></item>
+ <item name="wcf.user.changeUsername.description"><![CDATA[Sie können Ihren Benutzernamen nur einmal alle {$renamePeriod} Tage ändern. Änderungen von Groß- auf Kleinschreibung und umgekehrt sind jederzeit möglich.
+ {if $__wcf->getUser()->lastUsernameChange}Die letzte Änderung erfolgte am {@$__wcf->getUser()->lastUsernameChange|date}.{/if}]]></item>
+ <item name="wcf.user.changePassword"><![CDATA[Kennwort ändern]]></item>
+ <item name="wcf.user.changeEmail"><![CDATA[E-Mail-Adresse ändern]]></item>
+ <item name="wcf.user.newEmail"><![CDATA[Neue E-Mail-Adresse]]></item>
+ <item name="wcf.user.newUsername"><![CDATA[Neuer Benutzername]]></item>
+ <item name="wcf.user.quit"><![CDATA[Benutzerkonto kündigen]]></item>
+ <item name="wcf.user.quit.sure"><![CDATA[Wollen Sie Ihr Benutzerkonto mitsamt Ihrer persönlichen Daten dauerhaft löschen?]]></item>
+ <item name="wcf.user.quit.cancel"><![CDATA[Ihr Benutzerkonto wird am {$quitStarted+7*86400|date} gelöscht. Bitte aktivieren Sie diese Option, wenn Sie die Löschung abbrechen möchten.]]></item>
+ <item name="wcf.user.quit.description"><![CDATA[Hinweis: Beiträge, Anhänge und ähnliches werden nicht gelöscht.]]></item>
+ <item name="wcf.user.quit.success"><![CDATA[Ihr Benutzerkonto wird am {TIME_NOW+7*86400|date} gelöscht. Bis dahin können Sie die Löschung auf dieser Seite abbrechen.]]></item>
+ <item name="wcf.user.quit.cancel.success"><![CDATA[Die Löschung Ihres Benutzerkontos wurde erfolgreich abgebrochen.]]></item>
+ <item name="wcf.user.emailActivation"><![CDATA[Neue E-Mail-Adresse aktivieren]]></item>
+ <item name="wcf.user.password.error.notSecure"><![CDATA[Das Kennwort muss aus Sicherheitsgründen mindestens {REGISTER_PASSWORD_MIN_LENGTH} Zeichen lang sein{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_DIGIT || REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR}{*
+ *}und {*
+ *}{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE}kleine Buchstaben{/if}{*
+ *}{if REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE}{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE}{if REGISTER_PASSWORD_MUST_CONTAIN_DIGIT || REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR},{else} und{/if} {/if}große Buchstaben{/if}{*
+ *}{if REGISTER_PASSWORD_MUST_CONTAIN_DIGIT}{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE}{if REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR},{else} und{/if} {/if}Zahlen{/if}{*
+ *}{if REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR}{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_DIGIT} und {/if}Sonderzeichen{/if} {*
+ *}enthalten{/if}.]]></item>
+ <item name="wcf.user.changeUsername.success"><![CDATA[Ihr Benutzername wurde erfolgreich geändert.]]></item>
+ <item name="wcf.user.changeEmail.success"><![CDATA[Ihre E-Mail-Adresse wurde erfolgreich geändert.]]></item>
+ <item name="wcf.user.changeEmail.needReactivation"><![CDATA[Ihre neue E-Mail-Adresse muss noch aktiviert werden. Dazu wurde eine E-Mail mit einem Aktivierungslink an die neue Adresse gesandt. Sie müssen diesen Aktivierungslink aufrufen, um die neue E-Mail-Adresse zu aktivieren.]]></item>
+ <item name="wcf.user.changeEmail.needReactivation.mail"><![CDATA[Hallo {@$username},
+
+Sie haben Ihre E-Mail-Adresse auf der Website: {@PAGE_TITLE|language} geändert. Zum Abschließen dieser Änderung müssen Sie einmalig die Gültigkeit Ihrer neuen E-Mail-Adresse bestätigen.
+
+Bitte bestätigen Sie die Gültigkeit Ihrer neuen E-Mail-Adresse, indem Sie folgenden Link aufrufen:
+{link controller='EmailActivation' encode=false}u={@$userID}&a={@$activationCode}{/link}
+
+**** Funktioniert der Link oben nicht? ****
+Wenn der Link nicht funktioniert, sollten Sie folgende Adresse in Ihrem Browser aufrufen:
+{link controller='EmailActivation' encode=false}{/link}
+
+Bitte achten Sie darauf, dass keine Leerzeichen in der Adresse sind.
+Wenn Sie den Link aufgerufen haben, müssen Sie auf der erscheinenden Seite Ihre Benutzernummer sowie den Aktivierungscode eingeben.
+
+Ihre Benutzernummer lautet: {@$userID}
+Ihr Aktivierungscode lautet: {@$activationCode}
+
+Wenn Sie Probleme mit der Aktivierung haben, wenden Sie sich bitte an den Administrator: {@MAIL_ADMIN_ADDRESS} ]]></item>
+ <item name="wcf.user.changeEmail.needReactivation.mail.subject"><![CDATA[Aktivierung der neuen E-Mail-Adresse auf der Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.changePassword.success"><![CDATA[Ihr Kennwort wurde erfolgreich geändert.]]></item>
+ <item name="wcf.user.activationCode"><![CDATA[9-stelliger Aktivierungscode]]></item>
+ <item name="wcf.user.newActivationCode"><![CDATA[Neuen Aktivierungscode anfordern]]></item>
+ <item name="wcf.user.registerActivation"><![CDATA[Registrierung abschließen]]></item>
+ <item name="wcf.user.registerActivation.error.userAlreadyEnabled"><![CDATA[Dieser Benutzer ist bereits freigeschaltet.]]></item>
+ <item name="wcf.user.registerActivation.success"><![CDATA[Ihr Benutzerkonto wurde erfolgreich freigeschaltet.]]></item>
+ <item name="wcf.user.activationCode.error.notValid"><![CDATA[Sie haben einen ungültigen Aktivierungscode eingegeben. Klicken Sie auf den unten stehenden Button, falls Sie einen neuen Aktivierungscode anfordern möchten.]]></item>
+ <item name="wcf.user.registerNewActivationCode.email.description"><![CDATA[Optional können Sie hier eine neue E-Mail-Adresse eintragen, an die der neue Aktivierungscode gesendet werden soll. Lassen Sie dieses Feld frei, wenn der Aktivierungscode an die bestehende Adresse geschickt werden soll.]]></item>
+ <item name="wcf.user.newActivationCode.success"><![CDATA[Eine E-Mail mit dem neuen Aktivierungscode wurde an {$email} versendet.]]></item>
+ <item name="wcf.user.emailActivation.error.emailAlreadyEnabled"><![CDATA[Die neue E-Mail-Adresse ist bereits aktiviert.]]></item>
+ <item name="wcf.user.emailActivation.success"><![CDATA[Ihre neue E-Mail-Adresse wurde erfolgreich aktiviert.]]></item>
+ <item name="wcf.user.registerActivation.info"><![CDATA[Eine E-Mail mit dem 9-stelligen Aktivierungscode wurde an Ihre E-Mail-Adresse {$__wcf->user->email} verschickt.]]></item>
+ <item name="wcf.user.username.error.alreadyRenamed"><![CDATA[Der Benutzername wurde innerhalb der letzten {#$__wcf->getSession()->getPermission('user.profile.renamePeriod')} Tage bereits einmal verändert.]]></item>
+ <item name="wcf.user.guest"><![CDATA[Gast]]></item>
+ <item name="wcf.user.signature"><![CDATA[Signatur]]></item>
+ <item name="wcf.user.signature.edit"><![CDATA[Signatur bearbeiten]]></item>
+ <item name="wcf.user.signature.current"><![CDATA[Aktuelle Signatur]]></item>
+ <item name="wcf.user.signature.error.disabled"><![CDATA[Ihre Signatur wurde vom Administrator gesperrt{if $__wcf->user->disableSignatureReason}: {$__wcf->user->disableSignatureReason}{else}.{/if}]]></item>
+ <item name="wcf.user.activityPoint"><![CDATA[Punkte]]></item>
+ <item name="wcf.user.activityPoint.showDetails"><![CDATA[Zeige Details]]></item>
+ <item name="wcf.user.activityPoint.objects"><![CDATA[Anzahl]]></item>
+ <item name="wcf.user.activityPoint.objectType"><![CDATA[Art]]></item>
+ <item name="wcf.user.activityPoint.pointsPerObject"><![CDATA[Punkte]]></item>
+ <item name="wcf.user.activityPoint.sum"><![CDATA[Summe]]></item>
+ <item name="wcf.user.stats"><![CDATA[Statistiken]]></item>
+ <item name="wcf.user.usercp"><![CDATA[Benutzerkonto]]></item>
+ <item name="wcf.user.button.mail"><![CDATA[E-Mail senden]]></item>
+ <item name="wcf.user.ignoredUsers"><![CDATA[Sie blockieren folgende Benutzer]]></item>
+ <item name="wcf.user.ignoredUsers.noUsers"><![CDATA[Sie blockieren derzeit keine Benutzer.]]></item>
+ <item name="wcf.user.following"><![CDATA[Sie folgen folgenden Benutzern]]></item>
+ <item name="wcf.user.following.noUsers"><![CDATA[Sie folgen derzeit keinen Benutzern.]]></item>
+ <item name="wcf.user.userTitle"><![CDATA[Individueller Benutzertitel]]></item>
+ <item name="wcf.user.userTitle.description"><![CDATA[Sie können sich einen individuellen Benutzertitel geben.]]></item>
+ <item name="wcf.user.userTitle.error.tooLong"><![CDATA[Der eingegebene Titel ist zu lang. Die maximal zulässige Gesamtlänge beträgt {#USER_TITLE_MAX_LENGTH} Zeichen.]]></item>
+ <item name="wcf.user.userTitle.error.forbidden"><![CDATA[Sie haben einen ungültigen Benutzertitel eingegeben.]]></item>
+ <item name="wcf.user.team"><![CDATA[Team-Mitglieder]]></item>
+ <item name="wcf.user.birthday.age"><![CDATA[Alter]]></item>
+ <item name="wcf.user.birthday.age.from"><![CDATA[von]]></item>
+ <item name="wcf.user.birthday.age.to"><![CDATA[bis]]></item>
+ <item name="wcf.user.search"><![CDATA[Mitgliedersuche]]></item>
+ <item name="wcf.user.search.error.noMatches"><![CDATA[Zu den angegebenen Kriterien wurde kein Mitglied gefunden.]]></item>
+ <item name="wcf.user.dashboard"><![CDATA[Dashboard]]></item>
+ <item name="wcf.user.newestMember"><![CDATA[Neuestes Mitglied]]></item>
+ <item name="wcf.user.login.3rdParty"><![CDATA[Anmeldung über Drittanbieter]]></item>
+ <item name="wcf.user.search.results"><![CDATA[Suchergebnisse]]></item>
+ <item name="wcf.user.lastActivityTime"><![CDATA[Letzte Aktivität]]></item>
+ </category>
+
+ <category name="wcf.user.menu">
+ <item name="wcf.user.menu.community"><![CDATA[Community]]></item>
+ <item name="wcf.user.menu.community.notification"><![CDATA[Benachrichtigungen]]></item>
+ <item name="wcf.user.menu.community.following"><![CDATA[Nutzer, denen Sie folgen]]></item>
+ <item name="wcf.user.menu.community.ignoredUsers"><![CDATA[Blockierte Nutzer]]></item>
+ <item name="wcf.user.menu.profile"><![CDATA[Benutzerkonto]]></item>
+ <item name="wcf.user.menu.profile.accountManagement"><![CDATA[Verwaltung]]></item>
+ <item name="wcf.user.menu.profile.avatar"><![CDATA[Avatar]]></item>
+ <item name="wcf.user.menu.profile.signature"><![CDATA[Signatur]]></item>
+ <item name="wcf.user.menu.settings"><![CDATA[Einstellungen]]></item>
+ <item name="wcf.user.menu.settings.notification"><![CDATA[Benachrichtigungen]]></item>
+ </category>
+
+ <category name="wcf.user.register">
+ <item name="wcf.user.register"><![CDATA[Registrierung]]></item>
+ <item name="wcf.user.register.needActivation.mail.subject"><![CDATA[Aktivierung der Registrierung auf der Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.register.needActivation"><![CDATA[Vielen Dank für die Registrierung, {$user->username}.<br />
+Wir haben Ihnen eine E-Mail an {$user->email} geschickt. Die E-Mail enthält einen Link, den Sie einmalig aufrufen müssen, um die Gültigkeit Ihrer E-Mail-Adresse zu bestätigen und damit Ihre Registrierung abzuschließen.]]></item>
+ <item name="wcf.user.register.awaitActivation"><![CDATA[Vielen Dank für die Registrierung, {$user->username}. Ihre Benutzerdaten werden in Kürze von einem Administrator geprüft.<br />
+Sobald Ihr Benutzerkonto freigeschaltet ist, werden Sie per E-Mail darüber in Kenntnis gesetzt.]]></item>
+ <item name="wcf.user.register.notification.mail"><![CDATA[Hallo Administrator,
+
+auf der Website {@PAGE_TITLE|language} erfolgte eine neue Benutzeranmeldung durch: {@$user->username}
+
+Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
+
+Sie erreichen das Benutzerprofil des neuen Benutzers, indem Sie folgenden Link aufrufen:
+{link controller='User' object=$user encode=false}{/link} ]]></item>
+ <item name="wcf.user.register.notification.mail.subject"><![CDATA[Neue Benutzeranmeldung auf der Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.register.error.disabled"><![CDATA[Die Registrierung ist momentan deaktiviert.]]></item>
+ <item name="wcf.user.register.success"><![CDATA[Vielen Dank für die Registrierung, {$user->username}. Ihre Registrierung ist hiermit vollständig abgeschlossen.]]></item>
+ <item name="wcf.user.register.needActivation.mail"><![CDATA[Hallo {@$user->username},
+
+vielen Dank für Ihre Registrierung auf der Website: {@PAGE_TITLE|language}.
+Bevor wir Ihre Registrierung aktivieren können, müssen Sie einmalig die Gültigkeit Ihrer E-Mail-Adresse bestätigen.
+
+Bitte bestätigen Sie die Gültigkeit Ihrer E-Mail-Adresse, indem Sie folgenden Link aufrufen:
+{link controller='RegisterActivation' encode=false}{/link}
+
+Wenn Sie den Link aufgerufen haben, müssen Sie auf der erscheinenden Seite Ihren Benutzernamen sowie den Aktivierungscode eingeben.
+
+Ihr Benutzername lautet: {@$user->username}
+Ihr Aktivierungscode lautet: {@$user->activationCode}
+
+Wenn Sie Probleme mit der Aktivierung haben, wenden Sie sich bitte an den Administrator: {@MAIL_ADMIN_ADDRESS}
+
+Sollten Sie sich nicht auf der Website: {@PAGE_TITLE|language} angemeldet haben, können Sie diese E-Mail ignorieren. ]]></item>
+<item name="wcf.user.register.needActivation"><![CDATA[Ihr Benutzerkonto ist noch nicht aktiviert. Sie müssen den <a href="{link controller='RegisterActivation'}{/link}">Aktivierungsvorgang abschließen</a>, um den vollen Funktionsumfang dieser Seite nutzen zu können.]]></item>
+
+<item name="wcf.user.register.disclaimer"><![CDATA[Disclaimer]]></item>
+ <item name="wcf.user.register.disclaimer.accept"><![CDATA[Akzeptieren]]></item>
+ <item name="wcf.user.register.disclaimer.decline"><![CDATA[Ablehnen]]></item>
+ <item name="wcf.user.register.disclaimer.text"><![CDATA[<h1>Haftung für Inhalte</h1>
+ <p>Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt.
+ Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte
+ können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für
+ eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich.
+ Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht
+ verpflichtet, übermittelte oder gespeicherte fremde Informationen zu
+ überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige
+ Tätigkeit hinweisen. Verpflichtungen zur Entfernung oder Sperrung der
+ Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon
+ unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem
+ Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei
+ Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte
+ umgehend entfernen.</p>
+ <h1>Haftung für Links</h1>
+ <p>Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren
+ Inhalte wir keinen Einfluss haben. Deshalb können wir für diese
+ fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte
+ der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der
+ Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung
+ auf mögliche Rechtsverstöße überprüft. Rechtswidrige
+ Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente
+ inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte
+ einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen
+ werden wir derartige Links umgehend entfernen.</p>
+ <h1>Urheberrecht</h1>
+ <p>Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten
+ unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und
+ jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen
+ der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads
+ und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen
+ Gebrauch gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden,
+ werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche
+ gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten wir um einen entsprechenden Hinweis.
+ Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Inhalte umgehend entfernen.</p>
+ <h1>Datenschutz</h1>
+ <p>Die Nutzung unserer Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren Seiten personenbezogene Daten (beispielsweise Name,
+ Anschrift oder eMail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Ihre ausdrückliche Zustimmung nicht an Dritte weitergegeben.
+ </p>
+ <p>Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B.
+ bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann.
+ Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht
+ möglich. </p>
+ <p>Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten
+ durch Dritte zur Übersendung von nicht ausdrücklich angeforderter
+ Werbung und Informationsmaterialien wird hiermit ausdrücklich widersprochen.
+ Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte
+ im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-Mails,
+ vor.</p>
+<p><small><em>Quellen: <a href="http://www.e-recht24.de/muster-disclaimer.htm" class="externalURL">eRecht24 Disclaimer</a></small></em></p>]]></item>
+ </category>
+
+ <category name="wcf.user.usersOnline">
+ <item name="wcf.user.usersOnline"><![CDATA[Benutzer online]]></item>
+ <item name="wcf.user.usersOnline.detail"><![CDATA[
+{if $usersOnlineList->stats[members] > 0}
+ {#$usersOnlineList->stats[members]} Mitglied{if $usersOnlineList->stats[members] != 1}er{/if}
+{/if}
+{if $usersOnlineList->stats[invisible] > 0}
+ (davon {#$usersOnlineList->stats[invisible]} unsichtbar)
+{/if}
+{if $usersOnlineList->stats[guests] > 0 && $usersOnlineList->stats[members] > 0}und{/if}
+{if $usersOnlineList->stats[guests] > 0}
+ {#$usersOnlineList->stats[guests]} Besucher
+{/if}]]></item>
+ <item name="wcf.user.usersOnline.invisible"><![CDATA[ (unsichtbar)]]></item>
+ <item name="wcf.user.usersOnline.marking.legend"><![CDATA[Legende]]></item>
+ <item name="wcf.user.usersOnline.location.UserPage"><![CDATA[<a href="{link controller='User' object=$user}{/link}" class="userLink" data-user-id="{@$user->userID}">Benutzerprofil von „{$user->username}“</a>]]></item>
+ <item name="wcf.user.usersOnline.location.UsersOnlineListPage"><![CDATA[Benutzer-Online-Liste]]></item>
+ <item name="wcf.user.usersOnline.location.MembersListPage"><![CDATA[Mitgliederliste]]></item>
+ <item name="wcf.user.usersOnline.location.TeamPage"><![CDATA[Team-Mitglieder]]></item>
+ <item name="wcf.user.usersOnline.location.DashboardPage"><![CDATA[Dashboard]]></item>
+ <item name="wcf.user.usersOnline.location.RegisterForm"><![CDATA[Registrierung]]></item>
+ <item name="wcf.user.usersOnline.location.LostPasswordForm"><![CDATA[Kennwort vergessen-Formular]]></item>
+ <item name="wcf.user.usersOnline.location.LoginForm"><![CDATA[Anmeldung]]></item>
+ <item name="wcf.user.usersOnline.location.AccountManagementForm"><![CDATA[Account-Verwaltung]]></item>
+ <item name="wcf.user.usersOnline.location.AvatarEditForm"><![CDATA[Avatar-Verwaltung]]></item>
+ <item name="wcf.user.usersOnline.location.SettingsForm"><![CDATA[Einstellungen]]></item>
+ <item name="wcf.user.usersOnline.location.SignatureEditForm"><![CDATA[Signatur-Verwaltung]]></item>
+ <item name="wcf.user.usersOnline.guests"><![CDATA[Gäste]]></item>
+ <item name="wcf.user.usersOnline.location"><![CDATA[Ort]]></item>
+ <item name="wcf.user.usersOnline.ipAddress"><![CDATA[IP-Adresse]]></item>
+ <item name="wcf.user.usersOnline.userAgent"><![CDATA[Browser]]></item>
+ <item name="wcf.user.usersOnline.lastActivity"><![CDATA[Letzte Aktivität]]></item>
+ <item name="wcf.user.usersOnline.location.unknown"><![CDATA[Unbekannter Ort]]></item>
+ <item name="wcf.user.usersOnline.robots"><![CDATA[Suchmaschinen-Roboter]]></item>
+ </category>
+
+ <category name="wcf.user.recentActivity">
+ <item name="wcf.user.recentActivity"><![CDATA[Letzte Aktivitäten]]></item>
+ <item name="wcf.user.recentActivity.filteredByFollowedUsers"><![CDATA[Von Benutzern denen Sie folgen]]></item>
+ <item name="wcf.user.recentActivity.more"><![CDATA[Weitere Aktivitäten]]></item>
+ <item name="wcf.user.recentActivity.noMoreEntries"><![CDATA[Keine weiteren Aktivitäten]]></item>
+ <item name="wcf.user.recentActivity.noEntries"><![CDATA[Es sind keine Aktivitäten vorhanden.]]></item>
+ <item name="wcf.user.recentActivity.com.woltlab.wcf.user.recentActivityEvent.follow"><![CDATA[Folgen]]></item>
+ </category>
+
+ <category name="wcf.user.3rdparty">
+ <item name="wcf.user.3rdparty"><![CDATA[Drittanbieter]]></item>
+ <item name="wcf.user.3rdparty.github"><![CDATA[GitHub]]></item>
+ <item name="wcf.user.3rdparty.github.login"><![CDATA[GitHub]]></item>
+ <item name="wcf.user.3rdparty.github.login.error.access_denied"><![CDATA[Da Sie die Berechtigungen verweigert haben, ist die Anmeldung mit GitHub nicht möglich.]]></item>
+ <item name="wcf.user.3rdparty.github.register"><![CDATA[Sie erstellen einen Account über <span class="icon icon16 icon-github"></span> GitHub. Der Benutzername und Ihre E-Mail-Adresse wurden daher bereits ausgefüllt.]]></item>
+ <item name="wcf.user.3rdparty.github.connect"><![CDATA[Mit GitHub-Konto {if $__wcf->session->getVar('__githubUsername')}(„<a href="https://github.com/{$__wcf->session->getVar('__githubUsername')}">{$__wcf->session->getVar('__githubUsername')}</a>“){/if} verknüpfen]]></item>
+ <item name="wcf.user.3rdparty.github.connect.success"><![CDATA[Ihr Benutzerkonto wurde erfolgreich mit Ihrem GitHub-Konto verknüpft.]]></item>
+ <item name="wcf.user.3rdparty.github.connect.error.inuse"><![CDATA[Ihr GitHub-Konto ist bereits mit einem anderen Benutzerkonto verknüpft.]]></item>
+ <item name="wcf.user.3rdparty.github.disconnect"><![CDATA[Verknüpfung mit GitHub trennen]]></item>
+ <item name="wcf.user.3rdparty.github.disconnect.success"><![CDATA[Die Verknüpfung mit Ihrem GitHub-Konto wurde erfolgreich getrennt.]]></item>
+ <item name="wcf.user.3rdparty.twitter"><![CDATA[Twitter]]></item>
+ <item name="wcf.user.3rdparty.twitter.login"><![CDATA[Twitter]]></item>
+ <item name="wcf.user.3rdparty.twitter.login.error.denied"><![CDATA[Da Sie die Berechtigungen verweigert haben, ist die Anmeldung mit Twitter nicht möglich.]]></item>
+ <item name="wcf.user.3rdparty.twitter.register"><![CDATA[Sie erstellen einen Account über <span class="icon icon16 icon-twitter"></span> Twitter. Der Benutzername wurde daher bereits ausgefüllt. Geben Sie nun noch Ihre E-Mail-Adresse an und Sie können sofort loslegen!]]></item>
+ <item name="wcf.user.3rdparty.twitter.connect"><![CDATA[Mit Twitter-Konto {if $__wcf->session->getVar('__twitterUsername')}(„<a href="https://twitter.com/{$__wcf->session->getVar('__twitterUsername')}">{$__wcf->session->getVar('__twitterUsername')}</a>“){/if} verknüpfen]]></item>
+ <item name="wcf.user.3rdparty.twitter.connect.success"><![CDATA[Ihr Benutzerkonto wurde erfolgreich mit Ihrem Twitter-Konto verknüpft.]]></item>
+ <item name="wcf.user.3rdparty.twitter.connect.error.inuse"><![CDATA[Ihr Twitter-Konto ist bereits mit einem anderen Benutzerkonto verknüpft.]]></item>
+ <item name="wcf.user.3rdparty.twitter.disconnect"><![CDATA[Verknüpfung mit Twitter trennen]]></item>
+ <item name="wcf.user.3rdparty.twitter.disconnect.success"><![CDATA[Die Verknüpfung mit Ihrem Twitter-Konto wurde erfolgreich getrennt.]]></item>
+ <item name="wcf.user.3rdparty.facebook"><![CDATA[Facebook]]></item>
+ <item name="wcf.user.3rdparty.facebook.login"><![CDATA[Facebook]]></item>
+ <item name="wcf.user.3rdparty.facebook.login.error.access_denied"><![CDATA[Da Sie die Berechtigungen verweigert haben, ist die Anmeldung mit Facebook nicht möglich.]]></item>
+ <item name="wcf.user.3rdparty.facebook.register"><![CDATA[Sie erstellen einen Account über <span class="icon icon16 icon-facebook"></span> Facebook. Der Benutzername und Ihre E-Mail-Adresse wurden daher bereits ausgefüllt.]]></item>
+ <item name="wcf.user.3rdparty.facebook.connect"><![CDATA[Mit Facebook-Konto {if $__wcf->session->getVar('__facebookUsername')}(„{$__wcf->session->getVar('__facebookUsername')}“){/if} verknüpfen]]></item>
+ <item name="wcf.user.3rdparty.facebook.connect.success"><![CDATA[Ihr Benutzerkonto wurde erfolgreich mit Ihrem Facebook-Konto verknüpft.]]></item>
+ <item name="wcf.user.3rdparty.facebook.connect.error.inuse"><![CDATA[Ihr Facebook-Konto ist bereits mit einem anderen Benutzerkonto verknüpft.]]></item>
+ <item name="wcf.user.3rdparty.facebook.disconnect"><![CDATA[Verknüpfung mit Facebook trennen]]></item>
+ <item name="wcf.user.3rdparty.facebook.disconnect.success"><![CDATA[Die Verknüpfung mit Ihrem Facebook-Konto wurde erfolgreich getrennt.]]></item>
+ <item name="wcf.user.3rdparty.google"><![CDATA[Google]]></item>
+ <item name="wcf.user.3rdparty.google.login"><![CDATA[Google]]></item>
+ <item name="wcf.user.3rdparty.google.login.error.access_denied"><![CDATA[Da Sie die Berechtigungen verweigert haben, ist die Anmeldung mit Google nicht möglich.]]></item>
+ <item name="wcf.user.3rdparty.google.register"><![CDATA[Sie erstellen einen Account über <span class="icon icon16 icon-google-plus"></span> Google. Der Benutzername und Ihre E-Mail-Adresse wurden daher bereits ausgefüllt.]]></item>
+ <item name="wcf.user.3rdparty.google.connect"><![CDATA[Mit Google-Konto {if $__wcf->session->getVar('__googleUsername')}(„{$__wcf->session->getVar('__googleUsername')}“){/if} verknüpfen]]></item>
+ <item name="wcf.user.3rdparty.google.connect.success"><![CDATA[Ihr Benutzerkonto wurde erfolgreich mit Ihrem Google-Konto verknüpft.]]></item>
+ <item name="wcf.user.3rdparty.google.connect.error.inuse"><![CDATA[Ihr Google-Konto ist bereits mit einem anderen Benutzerkonto verknüpft.]]></item>
+ <item name="wcf.user.3rdparty.google.disconnect"><![CDATA[Verknüpfung mit Google trennen]]></item>
+ <item name="wcf.user.3rdparty.google.disconnect.success"><![CDATA[Die Verknüpfung mit Ihrem Google-Konto wurde erfolgreich getrennt.]]></item>
+ </category>
+
+ <category name="wcf.user.avatar">
+ <item name="wcf.user.avatar"><![CDATA[Avatar]]></item>
+ <item name="wcf.user.avatar.alt"><![CDATA[Benutzer-Avatarbild]]></item>
+ <item name="wcf.user.avatar.edit"><![CDATA[Avatar verwalten]]></item>
+ <item name="wcf.user.avatar.error.disabled"><![CDATA[Der Administrator hat{if $__wcf->user->avatarID || $__wcf->user->enableGravatar} Ihren derzeitigen Avatar gesperrt und{/if} Ihnen die weitere Nutzungsberechtigung der Avatar-Funktion {if !$__wcf->user->disableAvatarReason}entzogen.{else} aus folgenden Gründen entzogen: {$__wcf->user->disableAvatarReason}{/if}]]></item>
+ <item name="wcf.user.avatar.type.custom"><![CDATA[Eigenen Avatar hochladen]]></item>
+ <item name="wcf.user.avatar.type.custom.crop"><![CDATA[Avatar zuschneiden]]></item>
+ <item name="wcf.user.avatar.type.custom.crop.description"><![CDATA[TODO]]></item>
+ <item name="wcf.user.avatar.type.custom.description"><![CDATA[Eigene Avatare dürfen die Dateiendungen {"\n"|str_replace:', ':$__wcf->session->getPermission('user.profile.avatar.allowedFileExtensions')} und maximal eine Größe {@$__wcf->session->getPermission('user.profile.avatar.maxSize')|filesize} besitzen. Die Mindestgröße für Avatare liegt bei 48×48 Pixel, die empfohlene Maximalgröße bei {@MAX_AVATAR_WIDTH}×{@MAX_AVATAR_HEIGHT} Pixel, größere Avatare werden - sofern möglich - automatisch auf die Maximalgröße verkleinert.]]></item>
+ <item name="wcf.user.avatar.type.gravatar"><![CDATA[Gravatar verwenden]]></item>
+ <item name="wcf.user.avatar.type.gravatar.description"><![CDATA[Bei einem Gravatar handelt es sich um einen global verfügbaren Avatar (Global Recognized Avatar), welcher mit Ihrer E-Mail-Adresse („{$__wcf->user->email}“) verknüpft ist. Auf der folgenden Website können Sie einen Gravatar anlegen: <a href="http://www.gravatar.com" class="externalURL">www.gravatar.com</a>]]></item>
+ <item name="wcf.user.avatar.type.gravatar.error.notFound"><![CDATA[Zu Ihrer E-Mail-Adresse konnte kein Gravatar gefunden werden.]]></item>
+ <item name="wcf.user.avatar.type.none"><![CDATA[Keinen Avatar verwenden]]></item>
+ <item name="wcf.user.avatar.type.none.description"><![CDATA[Bereits hochgeladene Avatare werden bei Auswahl dieser Option gelöscht.]]></item>
+ <item name="wcf.user.avatar.upload.error.badImage"><![CDATA[Sie haben kein gültiges Bild hochgeladen.]]></item>
+ <item name="wcf.user.avatar.upload.error.invalidExtension"><![CDATA[Die Datei hat eine ungültige Dateiendung.]]></item>
+ <item name="wcf.user.avatar.upload.error.tooLarge"><![CDATA[Die Datei ist zu groß.]]></item>
+ <item name="wcf.user.avatar.upload.error.tooSmall"><![CDATA[Das Bild ist zu klein.]]></item>
+ <item name="wcf.user.avatar.upload.error.uploadFailed"><![CDATA[Beim Hochladen der Datei ist ein unbekannter Fehler aufgetreten.]]></item>
+ <item name="wcf.user.avatar.upload.success"><![CDATA[Der neue Avatar wurde erfolgreich gespeichert.]]></item>
+ </category>
+
+ <category name="wcf.user.notification">
+ <item name="wcf.user.notification.button.confirmed"><![CDATA[OK]]></item>
+ <item name="wcf.user.notification.count"><![CDATA[if (data.returnValues.count == 0) { "Keine Benachrichtigungen" } else if (data.returnValues.count == 1) { "Eine Benachrichtigung" } else { data.returnValues.count + " Benachrichtigungen" }]]></item>
+ <item name="wcf.user.notification.follow.message"><![CDATA[„{$author->username}“ folgt Ihnen.]]></item>
+ <item name="wcf.user.notification.follow.title"><![CDATA[Neuer Follower]]></item>
+ <item name="wcf.user.notification.follow.mail"><![CDATA[{@$author->username} folgt Ihnen.]]></item>
+ <item name="wcf.user.notification.mail.disabled"><![CDATA[Die E-Mail-Benachrichtigung wurde erfolgreich abgeschaltet.]]></item>
+ <item name="wcf.user.notification.mail.footer"><![CDATA[Diese E-Mail ist eine automatische Benachrichtigung. BITTE ANTWORTEN SIE NICHT AUF DIESE E-MAIL.
+
+Sie können die Einstellungen für Ihre Benachrichtigungen auf {@PAGE_TITLE|language} unter folgender URL detailliert konfigurieren:
+{link controller='NotificationSettings' encode=false}{/link}
+
+Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, können Sie folgenden Link aufrufen, um die Benachrichtigung abzuschalten:
+{link controller='NotificationDisable' encode=false}eventID={@$notification->eventID}&userID={@$user->userID}&token={@$token}{/link}]]></item>
+ <item name="wcf.user.notification.mail.header"><![CDATA[Hallo {@$user->username},]]></item>
+ <item name="wcf.user.notification.mail.subject"><![CDATA[Neue Benachrichtigung: {@$title}]]></item>
+ <item name="wcf.user.notification.mail.daily.subject"><![CDATA[{if $count == 1}Neue Benachrichtigung{else}{#$count} neue Benachrichtigungen{/if}]]></item>
+ <item name="wcf.user.notification.mail.daily.footer"><![CDATA[Diese E-Mail ist eine automatische Benachrichtigung. BITTE ANTWORTEN SIE NICHT AUF DIESE E-MAIL.
+
+Sie können die Einstellungen für Ihre Benachrichtigungen auf {@PAGE_TITLE|language} unter folgender URL detailliert konfigurieren:
+{link controller='NotificationSettings' encode=false}{/link}
+
+Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, können Sie folgenden Link aufrufen, um die Benachrichtigung abzuschalten:
+{link controller='NotificationDisable' encode=false}userID={@$user->userID}&token={@$token}{/link}]]></item>
+ <item name="wcf.user.notification.mailNotificationType.none"><![CDATA[Keine E-Mail-Benachrichtigung]]></item>
+ <item name="wcf.user.notification.mailNotificationType.instant"><![CDATA[Sofortige E-Mail-Benachrichtigung]]></item>
+ <item name="wcf.user.notification.mailNotificationType.daily"><![CDATA[Tägliche E-Mail-Benachrichtigung]]></item>
+ <item name="wcf.user.notification.markAllAsConfirmed"><![CDATA[Alle Benachrichtigungen verwerfen]]></item>
+ <item name="wcf.user.notification.markAllAsConfirmed.confirmMessage"><![CDATA[Wollen Sie wirklich alle Benachrichtigungen verwerfen?]]></item>
+ <item name="wcf.user.notification.markAsConfirmed"><![CDATA[Verwerfen]]></item>
+ <item name="wcf.user.notification.noMoreNotifications"><![CDATA[Keine weiteren Benachrichtigungen]]></item>
+ <item name="wcf.user.notification.noNotifications"><![CDATA[Es sind keine neuen Benachrichtigungen vorhanden.]]></item>
+ <item name="wcf.user.notification.notifications"><![CDATA[Benachrichtigungen]]></item>
+ <item name="wcf.user.notification.showAll"><![CDATA[Alle Benachrichtigungen anzeigen]]></item>
+ <item name="wcf.user.notification.com.woltlab.wcf.user"><![CDATA[Benutzer-Profile]]></item>
+ <item name="wcf.user.notification.com.woltlab.wcf.user.follow.following"><![CDATA[Jemand folgt Ihnen]]></item>
+ </category>
+
+ <category name="wcf.user.profile">
+ <item name="wcf.user.profile"><![CDATA[Benutzerprofil von „{$user->username}“]]></item>
+ <item name="wcf.user.profile.content.about.noPublicData"><![CDATA[{if $userID == $__wcf->getUser()->userID}Sie haben noch keine sichtbaren Informationen hinterlegt.{else}Der Benutzer hat noch keine für Sie sichtbaren Informationen hinterlegt.{/if}]]></item>
+ <item name="wcf.user.profile.content.recentActivity.noEntries"><![CDATA[Es sind keine anzeigbaren Aktivitäten vorhanden.]]></item>
+ <item name="wcf.user.profile.followers"><![CDATA[{$user->username} folgen]]></item>
+ <item name="wcf.user.profile.userList.showAll"><![CDATA[Alle anzeigen]]></item>
+ <item name="wcf.user.profile.following"><![CDATA[{$user->username} folgt]]></item>
+ <item name="wcf.user.profile.menu.about"><![CDATA[Über mich]]></item>
+ <item name="wcf.user.profile.menu.recentActivity"><![CDATA[Letzte Aktivitäten]]></item>
+ <item name="wcf.user.profile.oldUsername"><![CDATA[Hieß früher „{$user->getOldUsername()}“]]></item>
+ <item name="wcf.user.profile.recentActivity.follow"><![CDATA[Folgt nun <a href="{link controller='User' object=$user}{/link}">{$user->username}</a>.]]></item>
+ <item name="wcf.user.profile.visitors"><![CDATA[Profil-Besucher]]></item>
+ </category>
+
+ <category name="wcf.user.objectWatch">
+ <item name="wcf.user.objectWatch.manageSubscription"><![CDATA[Abonnement verwalten]]></item>
+ </category>
+
+ <category name="wcf.user.option">
+ <item name="wcf.user.option.aboutMe"><![CDATA[Über mich]]></item>
+ <item name="wcf.user.option.adminCanMail"><![CDATA[E-Mails der Administration akzeptieren]]></item>
+ <item name="wcf.user.option.adminComment"><![CDATA[Kommentar (intern)]]></item>
+ <item name="wcf.user.option.birthday"><![CDATA[Geburtstag]]></item>
+ <item name="wcf.user.option.birthdayShowYear"><![CDATA[Geburtsjahr im Profil anzeigen]]></item>
+ <item name="wcf.user.option.birthdayShowYear.description"><![CDATA[Mitglieder können dadurch Ihr Alter sehen.]]></item>
+ <item name="wcf.user.option.canMail"><![CDATA[Kann E-Mails senden]]></item>
+ <item name="wcf.user.option.canViewEmailAddress"><![CDATA[Kann E-Mail-Adresse sehen]]></item>
+ <item name="wcf.user.option.canViewOnlineStatus"><![CDATA[Kann Online-Status sehen]]></item>
+ <item name="wcf.user.option.canViewProfile"><![CDATA[Kann Benutzerprofil sehen]]></item>
+
+ <item name="wcf.user.option.category.profile"><![CDATA[Persönliche Daten]]></item>
+ <item name="wcf.user.option.category.profile.aboutMe"><![CDATA[Über mich]]></item>
+ <item name="wcf.user.option.category.profile.contact"><![CDATA[Kontaktmöglichkeiten]]></item>
+ <item name="wcf.user.option.category.profile.personal"><![CDATA[Persönliche Informationen]]></item>
+ <item name="wcf.user.option.category.settings"><![CDATA[Einstellungen]]></item>
+ <item name="wcf.user.option.category.settings.general"><![CDATA[Allgemein]]></item>
+ <item name="wcf.user.option.category.settings.general.date"><![CDATA[Datum & Zeit]]></item>
+ <item name="wcf.user.option.category.settings.general.appearance"><![CDATA[Anzeige]]></item>
+ <item name="wcf.user.option.category.settings.general.interface"><![CDATA[Bedienung]]></item>
+ <item name="wcf.user.option.category.settings.privacy"><![CDATA[Privatsphäre]]></item>
+ <item name="wcf.user.option.category.settings.privacy.content"><![CDATA[Inhalt]]></item>
+ <item name="wcf.user.option.category.settings.privacy.messaging"><![CDATA[Kommunikation]]></item>
+
+ <item name="wcf.user.option.gender"><![CDATA[Geschlecht]]></item>
+ <item name="wcf.user.option.hobbies"><![CDATA[Hobbys]]></item>
+ <item name="wcf.user.option.homepage"><![CDATA[Website]]></item>
+ <item name="wcf.user.option.location"><![CDATA[Wohnort]]></item>
+ <item name="wcf.user.option.occupation"><![CDATA[Beruf]]></item>
+ <item name="wcf.user.option.showSignature"><![CDATA[Persönliche Signatur anderer Mitglieder anzeigen]]></item>
+ <item name="wcf.user.option.timezone"><![CDATA[Zeitzone]]></item>
+ <item name="wcf.user.option.icq"><![CDATA[ICQ]]></item>
+ <item name="wcf.user.option.skype"><![CDATA[Skype]]></item>
+ <item name="wcf.user.option.facebook"><![CDATA[Facebook]]></item>
+ <item name="wcf.user.option.twitter"><![CDATA[Twitter]]></item>
+ <item name="wcf.user.option.googlePlus"><![CDATA[Google+]]></item>
+ <item name="wcf.user.option.googlePlus.description"><![CDATA[Geben Sie Ihre 21-stellige Google+-ID an.]]></item>
+ </category>
+
+ <category name="wcf.user.mail">
+ <item name="wcf.user.mail.information"><![CDATA[Informationen]]></item>
+ <item name="wcf.user.mail.mail"><![CDATA[Hallo {@$recipient->username},
+
+„{@$username}“ hat Ihnen auf der Website „{@PAGE_TITLE}“ folgende Nachricht gesandt:
+{@$message}]]></item>
+ <item name="wcf.user.mail.mail.subject"><![CDATA[Nachricht von {@$username}: {@$subject}]]></item>
+ <item name="wcf.user.mail.message"><![CDATA[Nachricht]]></item>
+ <item name="wcf.user.mail.senderEmail"><![CDATA[Ihre E-Mail-Adresse]]></item>
+ <item name="wcf.user.mail.sent"><![CDATA[Ihre Nachricht an „{$user->username}“ wurde erfolgreich versandt.]]></item>
+ <item name="wcf.user.mail.showAddress"><![CDATA[Meine E-Mail-Adresse als Absender-Adresse benutzen. Der Empfänger kann direkt auf die Nachricht antworten.]]></item>
+ <item name="wcf.user.mail.subject"><![CDATA[Betreff]]></item>
+ <item name="wcf.user.mail.title"><![CDATA[E-Mail an „{$user->username}“ senden]]></item>
+ </category>
+
+ <category name="wcf.user.rank">
+ <item name="wcf.user.rank.administrator"><![CDATA[Administrator]]></item>
+ <item name="wcf.user.rank.moderator"><![CDATA[Moderator]]></item>
+ <item name="wcf.user.rank.superModerator"><![CDATA[Super-Moderator]]></item>
+ <item name="wcf.user.rank.user0"><![CDATA[Anfänger]]></item>
+ <item name="wcf.user.rank.user1"><![CDATA[Schüler]]></item>
+ <item name="wcf.user.rank.user2"><![CDATA[Fortgeschrittener]]></item>
+ <item name="wcf.user.rank.user3"><![CDATA[Profi]]></item>
+ <item name="wcf.user.rank.user4"><![CDATA[Meister]]></item>
+ <item name="wcf.user.rank.user5"><![CDATA[Erleuchteter]]></item>
</category>
<!-- i18n categories -->
- <category name="wcf.user.option" />
<category name="wcf.smiley" />
</language>
<item name="wcf.acp.cronjob.log.error.details"><![CDATA[Error Message]]></item>
</category>
+ <category name="wcf.acp.dashboard">
+ <item name="wcf.acp.dashboard.list"><![CDATA[Dashboard Management]]></item>
+ <item name="wcf.acp.dashboard.option"><![CDATA[Configure Dashboard Boxes]]></item>
+ <item name="wcf.acp.dashboard.box.sort"><![CDATA[Sort boxes to determine show order. You can enable or disable boxes by moving them between the boxes “Active Boxes” and “Disabled Boxes”.]]></item>
+ </category>
+
<category name="wcf.acp.exceptionLog">
<item name="wcf.acp.exceptionLog"><![CDATA[Logged errors]]></item>
<item name="wcf.acp.exceptionLog.exception.file"><![CDATA[File (Line)]]></item>
<item name="wcf.acp.group.option.user.message.canUseBBCodes"><![CDATA[Can use BBCodes]]></item>
<item name="wcf.acp.group.option.user.message.allowedBBCodes"><![CDATA[Allowed BBCodes]]></item>
<item name="wcf.acp.group.option.user.message.allowedBBCodes.description"><![CDATA[Selected BBCodes may be used by members of this group.]]></item>
+ <item name="wcf.acp.group.option.admin.content.dashboard.canEditDashboard"><![CDATA[Can manage dashboard boxes]]></item>
+ <item name="wcf.acp.group.option.admin.user.rank.canManageRank"><![CDATA[Can manage user ranks]]></item>
+ <item name="wcf.acp.group.option.admin.user.canEditActivityPoints"><![CDATA[Can manage activity points]]></item>
+ <item name="wcf.acp.group.option.admin.user.canViewInvisible"><![CDATA[Can see invisible users]]></item>
+ <item name="wcf.acp.group.option.admin.user.canViewIpAddress"><![CDATA[Can see IP addresses]]></item>
+ <item name="wcf.acp.group.option.category.admin.content.dashboard"><![CDATA[Dashboard]]></item>
+ <item name="wcf.acp.group.option.category.user.profile"><![CDATA[User Profiles]]></item>
+ <item name="wcf.acp.group.option.category.user.profile.avatar"><![CDATA[Avatars]]></item>
+ <item name="wcf.acp.group.option.category.admin.user.rank"><![CDATA[User Ranks]]></item>
+ <item name="wcf.acp.group.option.category.user.signature"><![CDATA[Signatures]]></item>
+ <item name="wcf.acp.group.option.user.profile.avatar.allowedFileExtensions"><![CDATA[Allowed File Extensions]]></item>
+ <item name="wcf.acp.group.option.user.profile.avatar.canUploadAvatar"><![CDATA[Can upload own avatar]]></item>
+ <item name="wcf.acp.group.option.user.profile.avatar.maxSize"><![CDATA[Maximimum File Size]]></item>
+ <item name="wcf.acp.group.option.user.profile.canChangeEmail"><![CDATA[Can change email address]]></item>
+ <item name="wcf.acp.group.option.user.profile.canEditUserTitle"><![CDATA[Can edit own user title]]></item>
+ <item name="wcf.acp.group.option.user.profile.canMail"><![CDATA[Can see email addresses]]></item>
+ <item name="wcf.acp.group.option.user.profile.canQuit"><![CDATA[Can quit own user account]]></item>
+ <item name="wcf.acp.group.option.user.profile.canRename"><![CDATA[Can rename own user]]></item>
+ <item name="wcf.acp.group.option.user.profile.canViewMembersList"><![CDATA[Can see members list]]></item>
+ <item name="wcf.acp.group.option.user.profile.canViewUserProfile"><![CDATA[Can see user profiles]]></item>
+ <item name="wcf.acp.group.option.user.profile.canViewUsersOnlineList"><![CDATA[Can see users online list]]></item>
+ <item name="wcf.acp.group.option.user.signature.canUseBBCodes"><![CDATA[Can use BBCodes within signature]]></item>
+ <item name="wcf.acp.group.option.user.signature.canUseHtml"><![CDATA[Can use HTML within signature]]></item>
+ <item name="wcf.acp.group.option.user.signature.canUseSmilies"><![CDATA[Can use Smilies within signature]]></item>
+ <item name="wcf.acp.group.option.user.signature.allowedBBCodes"><![CDATA[Allowed BBCodes]]></item>
+ <item name="wcf.acp.group.option.user.signature.allowedBBCodes.description"><![CDATA[All marked BBCodes may be used within the signature.]]></item>
+ <item name="wcf.acp.group.priority"><![CDATA[Priority]]></item>
+ <item name="wcf.acp.group.priority.description"><![CDATA[Determines show order on team page, user rank and “Who is Online” marking based on highest priority.]]></item>
+ <item name="wcf.acp.group.userOnlineMarking"><![CDATA[“Who is Online” Marking]]></item>
+ <item name="wcf.acp.group.userOnlineMarking.description"><![CDATA[Adjust the HTML formatting for members of this group in the “Who is Online” list. <em><strong>%s</strong></em> results in a bolder appearance.]]></item>
+ <item name="wcf.acp.group.showOnTeamPage"><![CDATA[Show members on team page]]></item>
+ <item name="wcf.acp.group.option.admin.user.canEnableUser"><![CDATA[Can approve users]]></item>
+ <item name="wcf.acp.group.option.user.profile.renamePeriod"><![CDATA[Rename]]></item>
+ <item name="wcf.acp.group.option.user.profile.renamePeriod.description"><![CDATA[Minimum period until members may rename themselves. [time in days]]]></item>
+ <item name="wcf.acp.group.option.user.profile.cannotBeIgnored"><![CDATA[Can not be ignored]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.acp.menu.link.smiley.list"><![CDATA[List Smilies]]></item>
<item name="wcf.acp.menu.link.smiley.category.add"><![CDATA[Add Smiley Category]]></item>
<item name="wcf.acp.menu.link.smiley.category.list"><![CDATA[List Smiley Categories]]></item>
+ <item name="wcf.acp.menu.link.dashboard"><![CDATA[Dashboard]]></item>
+ <item name="wcf.acp.menu.link.dashboard.list"><![CDATA[Configuration]]></item>
+ <item name="wcf.acp.menu.link.activityPoint"><![CDATA[Activity Points]]></item>
+ <item name="wcf.acp.menu.link.user.rank"><![CDATA[User Ranks]]></item>
+ <item name="wcf.acp.menu.link.user.rank.list"><![CDATA[List User Ranks]]></item>
+ <item name="wcf.acp.menu.link.user.rank.add"><![CDATA[Add User Rank]]></item>
</category>
<category name="wcf.acp.option">
<item name="wcf.acp.option.show_signature_default_value"><![CDATA[Show signatures [default value]]]></item>
<item name="wcf.acp.option.enable_share_buttons"><![CDATA[Show content share button]]></item>
<item name="wcf.acp.option.share_buttons_show_count"><![CDATA[Show number of shares]]></item>
+ <item name="wcf.acp.option.github_public_key"><![CDATA[GitHub Client ID]]></item>
+ <item name="wcf.acp.option.github_public_key.description"><![CDATA[You can obtain both your Client ID and Client Secret at <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://github.com/settings/applications'|rawurlencode}" class="externalURL">GitHub</a>.]]></item>
+ <item name="wcf.acp.option.github_private_key"><![CDATA[GitHub Client Secret]]></item>
+ <item name="wcf.acp.option.twitter_public_key"><![CDATA[Twitter Consumer key]]></item>
+ <item name="wcf.acp.option.twitter_public_key.description"><![CDATA[You can obtain both your Consumer key and Consumer secret at <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://dev.twitter.com/apps'|rawurlencode}" class="externalURL">Twitter</a>.]]></item>
+ <item name="wcf.acp.option.twitter_private_key"><![CDATA[Twitter Consumer secret]]></item>
+ <item name="wcf.acp.option.facebook_public_key"><![CDATA[Facebook APP-ID]]></item>
+ <item name="wcf.acp.option.facebook_public_key.description"><![CDATA[You can obtain your APP-ID and Application Secret at <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://developers.facebook.com/apps'|rawurlencode}" class="externalURL">Facebook</a>.]]></item>
+ <item name="wcf.acp.option.facebook_private_key"><![CDATA[Facebook Application Secret]]></item>
+ <item name="wcf.acp.option.google_public_key"><![CDATA[Google Client ID]]></item>
+ <item name="wcf.acp.option.google_public_key.description"><![CDATA[You can obtain your Client ID and Client Secret at <a href="{@$__wcf->getPath()}acp/dereferrer.php?url={'https://code.google.com/apis/console/'|rawurlencode}" class="externalURL">Google</a>.]]></item>
+ <item name="wcf.acp.option.google_private_key"><![CDATA[Google Client Secret]]></item>
+ <item name="wcf.acp.option.category.dashboard"><![CDATA[Dashboard]]></item>
+ <item name="wcf.acp.option.category.dashboard.content"><![CDATA[Content Area]]></item>
+ <item name="wcf.acp.option.category.dashboard.sidebar"><![CDATA[Sidebar]]></item>
+ <item name="wcf.acp.option.category.dashboard.content.recentActivities"><![CDATA[Recent Activities]]></item>
+ <item name="wcf.acp.option.category.dashboard.sidebar.recentActivities"><![CDATA[Recent Activities]]></item>
+ <item name="wcf.acp.option.category.user.profile"><![CDATA[Profile]]></item>
+ <item name="wcf.acp.option.category.user.avatar"><![CDATA[Avatar]]></item>
+ <item name="wcf.acp.option.category.user.signature"><![CDATA[Signature]]></item>
+ <item name="wcf.acp.option.category.user.title"><![CDATA[User Title]]></item>
+ <item name="wcf.acp.option.category.user.cleanup"><![CDATA[Cleanup]]></item>
+ <item name="wcf.acp.option.category.user.list"><![CDATA[Member Lists]]></item>
+ <item name="wcf.acp.option.category.user.list.members"><![CDATA[Members List]]></item>
+ <item name="wcf.acp.option.category.user.list.online"><![CDATA[“Users Online” List]]></item>
+ <item name="wcf.acp.option.category.user.register"><![CDATA[Registration]]></item>
+ <item name="wcf.acp.option.category.user.password"><![CDATA[Password]]></item>
+ <item name="wcf.acp.option.category.user.ban"><![CDATA[Filter]]></item>
+ <item name="wcf.acp.option.category.user.3rdPartyAuth"><![CDATA[Third-Party Authentication]]></item>
+ <item name="wcf.acp.option.max_avatar_height"><![CDATA[Maximum Avatar Height]]></item>
+ <item name="wcf.acp.option.max_avatar_width"><![CDATA[Maximum Avatar Width]]></item>
+ <item name="wcf.acp.option.module_gravatar"><![CDATA[Gravatars]]></item>
+ <item name="wcf.acp.option.module_gravatar.description"><![CDATA[Enables support for Gravatars (“Global Recognized Avatar”).]]></item>
+ <item name="wcf.acp.option.module_users_online"><![CDATA[“Users Online” List]]></item>
+ <item name="wcf.acp.option.module_user_rank"><![CDATA[User Ranks]]></item>
+ <item name="wcf.acp.option.module_user_signature"><![CDATA[Signatures]]></item>
+ <item name="wcf.acp.option.module_team_page"><![CDATA[Staff List]]></item>
+ <item name="wcf.acp.option.module_dashboard_page"><![CDATA[Dashboard]]></item>
+ <item name="wcf.acp.option.module_gravatar"><![CDATA[Gravatars]]></item>
+ <item name="wcf.acp.option.register_enable_password_security_check"><![CDATA[Enable password validation]]></item>
+ <item name="wcf.acp.option.register_enable_password_security_check.description"><![CDATA[Password complexity will be validated, unsafe passwords will be rejected.]]></item>
+ <item name="wcf.acp.option.register_password_min_length"><![CDATA[Minimum Password Length]]></item>
+ <item name="wcf.acp.option.register_password_must_contain_digit"><![CDATA[Password must contain digits]]></item>
+ <item name="wcf.acp.option.register_password_must_contain_lower_case"><![CDATA[Password must contain lowercase characters]]></item>
+ <item name="wcf.acp.option.register_password_must_contain_special_char"><![CDATA[Password must contain special characters]]></item>
+ <item name="wcf.acp.option.register_password_must_contain_upper_case"><![CDATA[Password must container uppercase characters]]></item>
+ <item name="wcf.acp.option.register_forbidden_usernames"><![CDATA[Reserved Usernames]]></item>
+ <item name="wcf.acp.option.register_forbidden_usernames.description"><![CDATA[List of names unavailable for registration, one name per line.]]></item>
+ <item name="wcf.acp.option.register_forbidden_emails"><![CDATA[Reserved Email Addresses]]></item>
+ <item name="wcf.acp.option.register_forbidden_emails.description"><![CDATA[List of email addresses unavailable for registration, one address per line.]]></item>
+ <item name="wcf.acp.option.register_allowed_emails"><![CDATA[Allowed Email Addresses]]></item>
+ <item name="wcf.acp.option.register_allowed_emails.description"><![CDATA[List of valid email addresses for registration, one address per line.]]></item>
+ <item name="wcf.acp.option.register_username_min_length"><![CDATA[Minimum Username Length]]></item>
+ <item name="wcf.acp.option.register_username_max_length"><![CDATA[Maximum Username Length]]></item>
+ <item name="wcf.acp.option.register_username_force_ascii"><![CDATA[Require ASCII characters for usernames]]></item>
+ <item name="wcf.acp.option.register_disabled"><![CDATA[Disable registration]]></item>
+ <item name="wcf.acp.option.register_disabled.description"><![CDATA[Disables registration for all users, new users may be created through an administrator.]]></item>
+ <item name="wcf.acp.option.register_enable_disclaimer"><![CDATA[Enable disclaimer]]></item>
+ <item name="wcf.acp.option.register_enable_disclaimer.description"><![CDATA[Disclaimer must be explicitly accepted during registration.]]></item>
+ <item name="wcf.acp.option.register_admin_notification"><![CDATA[Notify administrator of new registrations]]></item>
+ <item name="wcf.acp.option.register_activation_method"><![CDATA[Approval]]></item>
+ <item name="wcf.acp.option.register_activation_method.byAdmin"><![CDATA[Only administrators can approve]]></item>
+ <item name="wcf.acp.option.register_activation_method.byUser"><![CDATA[Approval through email confirmation]]></item>
+ <item name="wcf.acp.option.register_activation_method.disabled"><![CDATA[No approval required]]></item>
+ <item name="wcf.acp.option.register_use_captcha"><![CDATA[Enable reCAPTCHA protection during registration]]></item>
+ <item name="wcf.acp.option.lost_password_use_captcha"><![CDATA[Enable reCAPTCHA protection for “Lost Password”]]></item>
+ <item name="wcf.acp.option.profile_mail_use_captcha"><![CDATA[Enable reCAPTCHA protection for “Send Email to User”]]></item>
+ <item name="wcf.acp.option.signature_max_image_height"><![CDATA[Maximum Height of Images in Signatures]]></item>
+ <item name="wcf.acp.option.user_title_max_length"><![CDATA[Maximum User Title Length]]></item>
+ <item name="wcf.acp.option.user_forbidden_titles"><![CDATA[Reserved User Titles]]></item>
+ <item name="wcf.acp.option.user_forbidden_titles.description"><![CDATA[List of user titles unavailable for members, one title per line.]]></item>
+ <item name="wcf.acp.option.profile_show_old_username"><![CDATA[Show Previous Username]]></item>
+ <item name="wcf.acp.option.profile_show_old_username.description"><![CDATA[Previous username will be additionally displayed for the given period. [time in days]]]></item>
+ <item name="wcf.acp.option.members_list_users_per_page"><![CDATA[Members per Page]]></item>
+ <item name="wcf.acp.option.members_list_default_sort_field"><![CDATA[Default Sort Field]]></item>
+ <item name="wcf.acp.option.members_list_default_sort_order"><![CDATA[Default Sort Order]]></item>
+ <item name="wcf.acp.option.users_online_show_guests"><![CDATA[Show guests]]></item>
+ <item name="wcf.acp.option.users_online_show_robots"><![CDATA[Show search engine spiders]]></item>
+ <item name="wcf.acp.option.users_online_default_sort_field"><![CDATA[Default Sort Field]]></item>
+ <item name="wcf.acp.option.users_online_default_sort_order"><![CDATA[Default Sort Order]]></item>
+ <item name="wcf.acp.option.users_online_page_refresh"><![CDATA[Refresh Page Every]]></item>
+ <item name="wcf.acp.option.users_online_page_refresh.description"><![CDATA[Page will be periodically refreshed. [time in seconds, 0 to disable]]]></item>
+ <item name="wcf.acp.option.user_cleanup_notification_lifetime"><![CDATA[Notification]]></item>
+ <item name="wcf.acp.option.user_cleanup_notification_lifetime.description"><![CDATA[Notifications will be discarded if they are older than the given period. [time in days]]]></item>
+ <item name="wcf.acp.option.user_cleanup_activity_event_lifetime"><![CDATA[Recent Activity]]></item>
+ <item name="wcf.acp.option.user_cleanup_activity_event_lifetime.description"><![CDATA[Activities will be discarded if they are older than the given period. [time in days]]]></item>
+ <item name="wcf.acp.option.user_cleanup_profile_visitor_lifetime"><![CDATA[Profile Visitors]]></item>
+ <item name="wcf.acp.option.user_cleanup_profile_visitor_lifetime.description"><![CDATA[Profile visitors will be discarded if they are older than the given period. [time in days]]]></item>
+ <item name="wcf.acp.option.recent_activity_items"><![CDATA[Number of Entries]]></item>
+ <item name="wcf.acp.option.recent_activity_sidebar_items"><![CDATA[Number of Entries]]></item>
</category>
<category name="wcf.acp.package">
<item name="wcf.acp.user.search.conditions.states"><![CDATA[States]]></item>
<item name="wcf.acp.user.search.conditions.state.banned"><![CDATA[Banned]]></item>
<item name="wcf.acp.user.search.conditions.state.notBanned"><![CDATA[Not banned]]></item>
+ <item name="wcf.acp.user.activityPoint.option"><![CDATA[Activity Points]]></item>
+ <item name="wcf.acp.user.activityPoint.updateCache"><![CDATA[Update Activity Point Cache]]></item>
+ <item name="wcf.acp.user.activityPoint.updateEvents"><![CDATA[Update Points Per Activity]]></item>
+ <item name="wcf.acp.user.delete"><![CDATA[Delete user(s)]]></item>
+ <item name="wcf.acp.user.general"><![CDATA[User]]></item>
+ <item name="wcf.acp.user.rank.add"><![CDATA[Add User Rank]]></item>
+ <item name="wcf.acp.user.rank.cssClassName"><![CDATA[CSS Class Name]]></item>
+ <item name="wcf.acp.user.rank.cssClassName.description"><![CDATA[You can select a predefined appearance or provide an own <abbr title="Cascading Style Sheets">CSS</abbr> class name.]]></item>
+ <item name="wcf.acp.user.rank.currentImage"><![CDATA[Current Rank Image]]></item>
+ <item name="wcf.acp.user.rank.delete.sure"><![CDATA[Do you really want to delete the user rank “{$userRank->rankTitle|language}”?]]></item>
+ <item name="wcf.acp.user.rank.edit"><![CDATA[Edit User Rank]]></item>
+ <item name="wcf.acp.user.rank.image"><![CDATA[Rank Image]]></item>
+ <item name="wcf.acp.user.rank.list"><![CDATA[User Ranks]]></item>
+ <item name="wcf.acp.user.rank.rankImage.description"><![CDATA[Path must be either relative to WCF’s directory or absolute.]]></item>
+ <item name="wcf.acp.user.rank.requiredGender.description"><![CDATA[Rank might be optionally restricted to a specific gender.]]></item>
+ <item name="wcf.acp.user.rank.requiredPoints"><![CDATA[Points]]></item>
+ <item name="wcf.acp.user.rank.requiredPoints.description"><![CDATA[Required amount of activity points to obtain this rank.]]></item>
+ <item name="wcf.acp.user.rank.requirement"><![CDATA[Restrictions]]></item>
+ <item name="wcf.acp.user.rank.repeatImage"><![CDATA[Repeat User Image]]></item>
+ <item name="wcf.acp.user.rank.repeatImage.description"><![CDATA[Number of times the rank image will be repeated.]]></item>
+ <item name="wcf.acp.user.rank.title"><![CDATA[Title]]></item>
+ <item name="wcf.acp.user.rank.userGroup.description"><![CDATA[Rank is restricted to members of the selected user groups.]]></item>
+ <item name="wcf.acp.user.rank.updateRanks"><![CDATA[Update User Ranks]]></item>
+ <item name="wcf.acp.user.disableSignature"><![CDATA[Block signature]]></item>
+ <item name="wcf.acp.user.disableSignatureReason"><![CDATA[Reason]]></item>
+ <item name="wcf.acp.user.disableAvatar"><![CDATA[Block avatar]]></item>
+ <item name="wcf.acp.user.disableAvatarReason"><![CDATA[Reason]]></item>
+ <item name="wcf.acp.user.disable"><![CDATA[Disable]]></item>
+ <item name="wcf.acp.user.enable"><![CDATA[Approve]]></item>
+ <item name="wcf.acp.user.quickSearch.disabled"><![CDATA[Users awaiting approval]]></item>
+ <item name="wcf.acp.user.quickSearch.disabledAvatars"><![CDATA[Blocked avatars]]></item>
+ <item name="wcf.acp.user.quickSearch.disabledSignatures"><![CDATA[Blocked signatures]]></item>
+ <item name="wcf.acp.user.usersAwaitingApprovalInfo"><![CDATA[TODO: <a href="{link controller='UserQuickSearch'}mode=disabled{/link}">{#$usersAwaitingApproval} Benutzer</a> {if $usersAwaitingApproval == 1}wartet auf seine{else}warten auf ihre{/if} Aktivierung.]]></item>
+ <item name="wcf.acp.user.search.conditions.state.enabled"><![CDATA[Approved]]></item>
+ <item name="wcf.acp.user.search.conditions.state.disabled"><![CDATA[Awaiting approval]]></item>
</category>
<category name="wcf.ajax">
<item name="wcf.clipboard.item.com.woltlab.wcf.user.exportMailAddress"><![CDATA[Export Email addresses]]></item>
<item name="wcf.clipboard.item.com.woltlab.wcf.user.sendMail"><![CDATA[Send Email]]></item>
<item name="wcf.clipboard.label.com.woltlab.wcf.user.marked"><![CDATA[{#$count} User{if $count != 1}s{/if} marked]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.merge"><![CDATA[Merge]]></item>
+ <item name="wcf.clipboard.item.com.woltlab.wcf.user.enable"><![CDATA[Approve]]></item>
+ </category>
+
+ <category name="wcf.dashboard">
+ <item name="wcf.dashboard.box.availableBoxes"><![CDATA[Disabled Boxes]]></item>
+ <item name="wcf.dashboard.box.enabledBoxes"><![CDATA[Active Boxes]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.recentActivitySidebar"><![CDATA[Recent Activity]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.statsSidebar"><![CDATA[Statistics]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.newestMembers"><![CDATA[Newest Members]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.mostActiveMembers"><![CDATA[Most Active Members]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.registerButton"><![CDATA[Register Button]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.signedInAs"><![CDATA[Signed In As]]></item>
+ <item name="wcf.dashboard.boxType.content"><![CDATA[Content Area]]></item>
+ <item name="wcf.dashboard.boxType.sidebar"><![CDATA[Sidebar]]></item>
+ <item name="wcf.dashboard.objectType"><![CDATA[Page]]></item>
+ <item name="wcf.dashboard.objectType.com.woltlab.wcf.user.DashboardPage"><![CDATA[Dashboard]]></item>
+ <item name="wcf.dashboard.objectType.com.woltlab.wcf.user.MembersListPage"><![CDATA[Members List]]></item>
+ <item name="wcf.dashboard.box.mostActiveMembers.points"><![CDATA[{#$activeMember->activityPoints} Point{if $activeMember->activityPoints != 1}s{/if}]]></item>
+ <item name="wcf.dashboard.box.com.woltlab.wcf.user.recentActivity"><![CDATA[Recent Activity]]></item>
</category>
<category name="wcf.date">
<item name="wcf.page.sitemap"><![CDATA[Sitemap]]></item>
<item name="wcf.page.mainMenu"><![CDATA[Navigation]]></item>
<item name="wcf.page.pagePosition"><![CDATA[Page {#$pageNo} of {#$pages}]]></item>
+ <item name="wcf.page.sitemap.userAccount"><![CDATA[User Account]]></item>
</category>
<category name="wcf.recaptcha">
<item name="wcf.user.option.error.tooShort"><![CDATA[Entered text is too short.]]></item>
<item name="wcf.user.option.error.validationFailed"><![CDATA[Entered text is invalid.]]></item>
<item name="wcf.user.error.isBanned"><![CDATA[Your user account has been banned{if $__wcf->user->banReason}: {@$__wcf->user->banReason|htmlspecialchars|nl2br}{else}.{/if}]]></item>
+ <item name="wcf.user.access.everyone"><![CDATA[Everyone]]></item>
+ <item name="wcf.user.access.following"><![CDATA[Users I’m following]]></item>
+ <item name="wcf.user.access.nobody"><![CDATA[Nobody]]></item>
+ <item name="wcf.user.access.registered"><![CDATA[Registered Users]]></item>
+ <item name="wcf.user.button.login"><![CDATA[Login]]></item>
+ <item name="wcf.user.button.register"><![CDATA[Register]]></item>
+ <item name="wcf.user.button.registerNow"><![CDATA[Register Now]]></item>
+ <item name="wcf.user.error.username.3rdParty"><![CDATA[Account is connected with a 3rd party website, password cannot be requested.]]></item>
+ <item name="wcf.user.login.action"><![CDATA[Are you already registered?]]></item>
+ <item name="wcf.user.login.action.login"><![CDATA[Yes, my password is:]]></item>
+ <item name="wcf.user.login.action.register"><![CDATA[No, register now.]]></item>
+ <item name="wcf.user.login.redirect"><![CDATA[You have been logged in.]]></item>
+ <item name="wcf.user.loginOrRegister"><![CDATA[Login{if !REGISTER_DISABLED} or register{/if}]]></item>
+ <item name="wcf.user.logout.redirect"><![CDATA[You have been logged off.]]></item>
+ <item name="wcf.user.useCookies"><![CDATA[Remain logged in]]></item>
+ <item name="wcf.user.usernameOrEmail"><![CDATA[Username or Email Address]]></item>
+ <item name="wcf.user.gender.male"><![CDATA[Male]]></item>
+ <item name="wcf.user.gender.female"><![CDATA[Female]]></item>
+ <item name="wcf.user.members"><![CDATA[Members]]></item>
+ <item name="wcf.user.members.noMembers"><![CDATA[No members matched your criteria.]]></item>
+ <item name="wcf.user.members.sort"><![CDATA[Sort Field]]></item>
+ <item name="wcf.user.members.sort.letters"><![CDATA[First Letter]]></item>
+ <item name="wcf.user.members.sort.letters.all"><![CDATA[All]]></item>
+ <item name="wcf.user.membersList.registrationDate"><![CDATA[Member since {@$user->registrationDate|date}]]></item>
+ <item name="wcf.user.membersList.location"><![CDATA[from {$user->location}]]></item>
+ <item name="wcf.user.myProfile"><![CDATA[My Profile]]></item>
+ <item name="wcf.user.editProfile"><![CDATA[Edit Profile]]></item>
+ <item name="wcf.user.profileHits"><![CDATA[Profile Hits]]></item>
+ <item name="wcf.user.profileHits.hitsPerDay"><![CDATA[{#$user->profileHits/$user->getProfileAge()} hits per day]]></item>
+ <item name="wcf.user.online"><![CDATA[Online]]></item>
+ <item name="wcf.user.online.title"><![CDATA[“{$username}” is online]]></item>
+ <item name="wcf.user.button.follow"><![CDATA[Follow]]></item>
+ <item name="wcf.user.button.unfollow"><![CDATA[Unfollow]]></item>
+ <item name="wcf.user.button.ignore"><![CDATA[Block]]></item>
+ <item name="wcf.user.button.unignore"><![CDATA[Unblock]]></item>
+ <item name="wcf.user.style"><![CDATA[Styles]]></item>
+ <item name="wcf.user.style.description"><![CDATA[Forces a specific style instead of the default one.]]></item>
+ <item name="wcf.user.username.description"><![CDATA[Username must be {REGISTER_USERNAME_MIN_LENGTH} up to {REGISTER_USERNAME_MAX_LENGTH} characters long.]]></item>
+ <item name="wcf.user.password.description"><![CDATA[{if REGISTER_ENABLE_PASSWORD_SECURITY_CHECK && REGISTER_PASSWORD_MIN_LENGTH}Password must be at least {REGISTER_PASSWORD_MIN_LENGTH} characters.{else}A secure password should be at least 8 characters long.{/if}]]></item>
+ <item name="wcf.user.lostPassword"><![CDATA[Lost Password]]></item>
+ <item name="wcf.user.lostPassword.description"><![CDATA[You must provide your username or email address to request a new password. Contact the site’s administrator if you need assistance.]]></item>
+ <item name="wcf.user.lostPassword.email.error.notFound"><![CDATA[“{$email}” is not used by any account.]]></item>
+ <item name="wcf.user.lostPassword.error.tooManyRequests"><![CDATA[Password has been requested at least once in the past 24 hours. For security reasons you must wait at least {#$hours} hour{if $hours != 1}s{/if} before requesting it again.]]></item>
+ <item name="wcf.user.lostPassword.mail.subject"><![CDATA[Lost Password for Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.lostPassword.mail"><![CDATA[Dear {@$username},
+
+if you have lost your password you can request a new one using the link below.
+Request a new password: {link controller='NewPassword' encode=false}u={@$userID}&k={@$key}{/link}
+
+If you have not lost your password, you can safely ignore this email.
+
+Have a nice day,
+Your {@PAGE_TITLE|language} team]]></item>
+ <item name="wcf.user.lostPassword.mail.sent"><![CDATA[You should receive an email shortly.]]></item>
+ <item name="wcf.user.newPassword"><![CDATA[Request New Password]]></item>
+ <item name="wcf.user.lostPasswordKey"><![CDATA[Security Key]]></item>
+ <item name="wcf.user.lostPasswordKey.error.invalid"><![CDATA[Security Key is invalid.]]></item>
+ <item name="wcf.user.userID.error.invalid"><![CDATA[User ID is invalid.]]></item>
+ <item name="wcf.user.newPassword.mail"><![CDATA[Dear {@$username},
+
+your new password for "{@PAGE_TITLE|language}" is:
+{@$newPassword}
+
+You can change your password any time at:
+{link controller='AccountManagement' encode=false}{/link}
+
+Have a nice day,
+Your {@PAGE_TITLE|language} team]]></item>
+ <item name="wcf.user.newPassword.mail.subject"><![CDATA[New Password for Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.newPassword.success"><![CDATA[You should receive an email with your password shortly.]]></item>
+ <item name="wcf.user.accountManagement"><![CDATA[Account Management]]></item>
+ <item name="wcf.user.accountManagement.warning"><![CDATA[Heads up! You’re editing your own user account, careless changes might lock you out!]]></item>
+ <item name="wcf.user.accountManagement.password.description"><![CDATA[Please confirm changes with your password!]]></item>
+ <item name="wcf.user.newPassword"><![CDATA[New Password]]></item>
+ <item name="wcf.user.changeUsername"><![CDATA[Change Username]]></item>
+ <item name="wcf.user.changeUsername.description"><![CDATA[You may change your username every {$renamePeriod} days. Changes between upper or lower case are always allowed.
+ {if $__wcf->getUser()->lastUsernameChange}Last change was {@$__wcf->getUser()->lastUsernameChange|date}.{/if}]]></item>
+ <item name="wcf.user.changePassword"><![CDATA[Change Password]]></item>
+ <item name="wcf.user.changeEmail"><![CDATA[Change Email Address]]></item>
+ <item name="wcf.user.newEmail"><![CDATA[New Email Address]]></item>
+ <item name="wcf.user.newUsername"><![CDATA[New Username]]></item>
+ <item name="wcf.user.quit"><![CDATA[Quit User Account]]></item>
+ <item name="wcf.user.quit.sure"><![CDATA[Do you really want to delete your user account and all associated, private data?]]></item>
+ <item name="wcf.user.quit.cancel"><![CDATA[Your user account will be delete on {$quitStarted+7*86400|date}. Please enable this option if you wish to abort deletion.]]></item>
+ <item name="wcf.user.quit.description"><![CDATA[Note: Post, Attachments and similar content will remain even after your account was deleted.]]></item>
+ <item name="wcf.user.quit.success"><![CDATA[Your user account will be deleted on {TIME_NOW+7*86400|date}. During this period you can abort deletion on this page.]]></item>
+ <item name="wcf.user.quit.cancel.success"><![CDATA[Account deletion has been aborted.]]></item>
+ <item name="wcf.user.emailActivation"><![CDATA[Verify New Email Address]]></item>
+ <item name="wcf.user.password.error.notSecure"><![CDATA[Due to security reasons every password must be at least {REGISTER_PASSWORD_MIN_LENGTH} characters long{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_DIGIT || REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR}{*
+ *}and contain {*
+ *}{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE}lower-case letters{/if}{*
+ *}{if REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE}{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE}{if REGISTER_PASSWORD_MUST_CONTAIN_DIGIT || REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR},{else} and{/if} {/if}upper-case letters{/if}{*
+ *}{if REGISTER_PASSWORD_MUST_CONTAIN_DIGIT}{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE}{if REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR},{else} and{/if} {/if}digits{/if}{*
+ *}{if REGISTER_PASSWORD_MUST_CONTAIN_SPECIAL_CHAR}{if REGISTER_PASSWORD_MUST_CONTAIN_LOWER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_UPPER_CASE || REGISTER_PASSWORD_MUST_CONTAIN_DIGIT} and {/if}special chars{/if}{/if}.]]></item>
+ <item name="wcf.user.changeUsername.success"><![CDATA[Username was changed.]]></item>
+ <item name="wcf.user.changeEmail.success"><![CDATA[Email Address was changed.]]></item>
+ <item name="wcf.user.changeEmail.needReactivation"><![CDATA[Your new email address must be verified first. You should’ve received an email sent to your new email address containing an activation link. Open the link to verify your new email address.]]></item>
+ <item name="wcf.user.changeEmail.needReactivation.mail"><![CDATA[Dear {@$username},
+
+you’ve changed your email address on: {@PAGE_TITLE|language}. It is required to open the link below in order to verify this email address.
+
+{link controller='EmailActivation' encode=false}u={@$userID}&a={@$activationCode}{/link}
+
+**** Unable to click or open the link above? ****
+Please open the link below in your browser:
+{link controller='EmailActivation' encode=false}{/link}
+
+Once prompted please provide the following details:
+
+You User ID: {@$userID}
+Activation Code: {@$activationCode}
+
+If you cannot activate your email address or have any troubles following the instructions, please contact the administrator: {@MAIL_ADMIN_ADDRESS}
+
+Have a nice day,
+Your {@PAGE_TITLE|language} team]]></item>
+ <item name="wcf.user.changeEmail.needReactivation.mail.subject"><![CDATA[Email Activation for Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.changePassword.success"><![CDATA[Password has been changed.]]></item>
+ <item name="wcf.user.activationCode"><![CDATA[9-digit Activation Code]]></item>
+ <item name="wcf.user.newActivationCode"><![CDATA[Request New Activation Code]]></item>
+ <item name="wcf.user.registerActivation"><![CDATA[Complete Registration]]></item>
+ <item name="wcf.user.registerActivation.error.userAlreadyEnabled"><![CDATA[User is already activated.]]></item>
+ <item name="wcf.user.registerActivation.success"><![CDATA[User account has been activated.]]></item>
+ <item name="wcf.user.activationCode.error.notValid"><![CDATA[You have provided an invalid activation code. Click the button below if you wish to request a new activation code.]]></item>
+ <item name="wcf.user.registerNewActivationCode.email.description"><![CDATA[You may provide a different email address or leave it empty to send the code to the same email address again.]]></item>
+ <item name="wcf.user.newActivationCode.success"><![CDATA[Activiation code has been sent to “{$email}”.]]></item>
+ <item name="wcf.user.emailActivation.error.emailAlreadyEnabled"><![CDATA[Email address is already activated.]]></item>
+ <item name="wcf.user.emailActivation.success"><![CDATA[Email address has been activated.]]></item>
+ <item name="wcf.user.registerActivation.info"><![CDATA[An email containing the 9-digit activation code was sent to “{$__wcf->user->email}”.]]></item>
+ <item name="wcf.user.username.error.alreadyRenamed"><![CDATA[Username was already changed within the last {#$__wcf->getSession()->getPermission('user.profile.renamePeriod')} day.]]></item>
+ <item name="wcf.user.guest"><![CDATA[Guest]]></item>
+ <item name="wcf.user.signature"><![CDATA[Signature]]></item>
+ <item name="wcf.user.signature.edit"><![CDATA[Edit Signature]]></item>
+ <item name="wcf.user.signature.current"><![CDATA[Current Signature]]></item>
+ <item name="wcf.user.signature.error.disabled"><![CDATA[Your signature has been disabled{if $__wcf->user->disableSignatureReason}: {$__wcf->user->disableSignatureReason}{else}.{/if}]]></item>
+ <item name="wcf.user.activityPoint"><![CDATA[Points]]></item>
+ <item name="wcf.user.activityPoint.showDetails"><![CDATA[Show Details]]></item>
+ <item name="wcf.user.activityPoint.objects"><![CDATA[Count]]></item>
+ <item name="wcf.user.activityPoint.objectType"><![CDATA[Type]]></item>
+ <item name="wcf.user.activityPoint.pointsPerObject"><![CDATA[Points]]></item>
+ <item name="wcf.user.activityPoint.sum"><![CDATA[Sum]]></item>
+ <item name="wcf.user.stats"><![CDATA[Statistics]]></item>
+ <item name="wcf.user.usercp"><![CDATA[User Account]]></item>
+ <item name="wcf.user.button.mail"><![CDATA[Send Email]]></item>
+ <item name="wcf.user.ignoredUsers"><![CDATA[You’re blocking the following users]]></item>
+ <item name="wcf.user.ignoredUsers.noUsers"><![CDATA[You’re not blocking any users.]]></item>
+ <item name="wcf.user.following"><![CDATA[You’re following these users]]></item>
+ <item name="wcf.user.following.noUsers"><![CDATA[You’re not following anyone.]]></item>
+ <item name="wcf.user.userTitle"><![CDATA[Custom User Title]]></item>
+ <item name="wcf.user.userTitle.description"><![CDATA[You may enter a custom user title.]]></item>
+ <item name="wcf.user.userTitle.error.tooLong"><![CDATA[Title exceeded the maximum length of {#USER_TITLE_MAX_LENGTH} characters.]]></item>
+ <item name="wcf.user.userTitle.error.forbidden"><![CDATA[User Title is invalid.]]></item>
+ <item name="wcf.user.team"><![CDATA[Staff Members]]></item>
+ <item name="wcf.user.birthday.age"><![CDATA[Age]]></item>
+ <item name="wcf.user.birthday.age.from"><![CDATA[from]]></item>
+ <item name="wcf.user.birthday.age.to"><![CDATA[to]]></item>
+ <item name="wcf.user.search"><![CDATA[Search Members]]></item>
+ <item name="wcf.user.search.error.noMatches"><![CDATA[Your criteria did not match any member.]]></item>
+ <item name="wcf.user.dashboard"><![CDATA[Dashboard]]></item>
+ <item name="wcf.user.newestMember"><![CDATA[Newest Member]]></item>
+ <item name="wcf.user.login.3rdParty"><![CDATA[Third-Party Login]]></item>
+ <item name="wcf.user.search.results"><![CDATA[Search Results]]></item>
+ <item name="wcf.user.lastActivityTime"><![CDATA[Last Activity]]></item>
+ </category>
+
+ <category name="wcf.user.menu">
+ <item name="wcf.user.menu.community"><![CDATA[Community]]></item>
+ <item name="wcf.user.menu.community.notification"><![CDATA[Notifications]]></item>
+ <item name="wcf.user.menu.community.following"><![CDATA[Followed Users]]></item>
+ <item name="wcf.user.menu.community.ignoredUsers"><![CDATA[Blocked Users]]></item>
+ <item name="wcf.user.menu.profile"><![CDATA[User Account]]></item>
+ <item name="wcf.user.menu.profile.accountManagement"><![CDATA[Account Management]]></item>
+ <item name="wcf.user.menu.profile.avatar"><![CDATA[Avatar]]></item>
+ <item name="wcf.user.menu.profile.signature"><![CDATA[Signature]]></item>
+ <item name="wcf.user.menu.settings"><![CDATA[Settings]]></item>
+ <item name="wcf.user.menu.settings.notification"><![CDATA[Notifications]]></item>
+ </category>
+
+ <category name="wcf.user.register">
+ <item name="wcf.user.register"><![CDATA[Registration]]></item>
+ <item name="wcf.user.register.needActivation.mail.subject"><![CDATA[Activate Your Registration for Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.register.needActivation"><![CDATA[Thank you for registering, {$user->username}.<br />
+An email was sent to “{$user->email}” containing a one-time link to verify your account and ultimately completing your registration.]]></item>
+ <item name="wcf.user.register.awaitActivation"><![CDATA[Thank you for registering, {$user->username}.<br />
+Your account is awating approval from an administrator.<br />
+A notification will be sent once your account has been approved.]]></item>
+ <item name="wcf.user.register.notification.mail"><![CDATA[Dear Administrator,
+
+someone registered with your website {@PAGE_TITLE|language}: {@$user->username}
+
+Email address used: {@$user->email}
+
+Open the link below to access the user profile:
+{link controller='User' object=$user encode=false}{/link} ]]></item>
+ <item name="wcf.user.register.notification.mail.subject"><![CDATA[New Registration with Your Website: {@PAGE_TITLE|language}]]></item>
+ <item name="wcf.user.register.error.disabled"><![CDATA[Registration is currently disabled.]]></item>
+ <item name="wcf.user.register.success"><![CDATA[Thank you for registering, {$user->username}.<br />
+Your registration is now completed.]]></item>
+ <item name="wcf.user.register.needActivation.mail"><![CDATA[Dear {@$user->username},
+
+thank you for registering on our website: {@PAGE_TITLE|language}.
+Please validate your email address to complete your registration.
+
+Open the link below:
+{link controller='RegisterActivation' encode=false}{/link}
+
+Once prompted provide the details as shown below:
+
+Your username: {@$user->username}
+Activation Code: {@$user->activationCode}
+
+If you cannot open the link or have troubles following the instructions, please contact the administrator: {@MAIL_ADMIN_ADDRESS}
+
+You can safely ignore this email if you did not register with the website: {@PAGE_TITLE|language}.]]></item>
+<item name="wcf.user.register.needActivation"><![CDATA[Your user account is not activated yet. You must complete the <a href="{link controller='RegisterActivation'}{/link}">activation process</a> first.]]></item>
+<item name="wcf.user.register.disclaimer"><![CDATA[Disclaimer]]></item>
+ <item name="wcf.user.register.disclaimer.accept"><![CDATA[Accept]]></item>
+ <item name="wcf.user.register.disclaimer.decline"><![CDATA[Decline]]></item>
+ <item name="wcf.user.register.disclaimer.text"><![CDATA[<p>The registration and use of this forum is completely free for you. Press “Accept” if you agree with the rules mentioned below. You will then be able to register.</p>
+
+<p>Even though administrators and moderators of “{@PAGE_TITLE|language}” try to delete or edit all the unwanted posts, it is impossible for them to check all the posts. All the posts express the opinion of the author, the owners of “{@PAGE_TITLE}” and WoltLab GmbH (the developers of Burning Board) cannot be held responsible for the content of those posts.</p>
+
+<p>By registering, you agree not to post messages that are vulgar, impolite, disrespectful or that express (extreme) political views or (verbal) law offences.</p>
+
+<p>Additionally, the Administrators and Moderators of this board can edit or even delete your account for any reason.</p>
+
+<p>Enjoy your stay at {@PAGE_TITLE}!</p>]]></item>
+ </category>
+
+ <category name="wcf.user.usersOnline">
+ <item name="wcf.user.usersOnline"><![CDATA[Users Online]]></item>
+ <item name="wcf.user.usersOnline.detail"><![CDATA[
+{if $usersOnlineList->stats[members] > 0}
+ {#$usersOnlineList->stats[members]} Member{if $usersOnlineList->stats[members] != 1}s{/if}
+{/if}
+{if $usersOnlineList->stats[invisible] > 0}
+ ({#$usersOnlineList->stats[invisible]} invisible)
+{/if}
+{if $usersOnlineList->stats[guests] > 0 && $usersOnlineList->stats[members] > 0}and{/if}
+{if $usersOnlineList->stats[guests] > 0}
+ {#$usersOnlineList->stats[guests]} Guest{if $usersOnlineList->stats[guests] != 1}s{/if}
+{/if}]]></item>
+ <item name="wcf.user.usersOnline.invisible"><![CDATA[ (invisible)]]></item>
+ <item name="wcf.user.usersOnline.marking.legend"><![CDATA[Legend]]></item>
+ <item name="wcf.user.usersOnline.location.UserPage"><![CDATA[<a href="{link controller='User' object=$user}{/link}" class="userLink" data-user-id="{@$user->userID}">User Profile of “{$user->username}”</a>]]></item>
+ <item name="wcf.user.usersOnline.location.UsersOnlineListPage"><![CDATA[Users Online]]></item>
+ <item name="wcf.user.usersOnline.location.MembersListPage"><![CDATA[Members List]]></item>
+ <item name="wcf.user.usersOnline.location.TeamPage"><![CDATA[Staff Members]]></item>
+ <item name="wcf.user.usersOnline.location.DashboardPage"><![CDATA[Dashboard]]></item>
+ <item name="wcf.user.usersOnline.location.RegisterForm"><![CDATA[Registration]]></item>
+ <item name="wcf.user.usersOnline.location.LostPasswordForm"><![CDATA[Lost Password-Form]]></item>
+ <item name="wcf.user.usersOnline.location.LoginForm"><![CDATA[Login]]></item>
+ <item name="wcf.user.usersOnline.location.AccountManagementForm"><![CDATA[Account Management]]></item>
+ <item name="wcf.user.usersOnline.location.AvatarEditForm"><![CDATA[Avatar Management]]></item>
+ <item name="wcf.user.usersOnline.location.SettingsForm"><![CDATA[Settings]]></item>
+ <item name="wcf.user.usersOnline.location.SignatureEditForm"><![CDATA[Signature Management]]></item>
+ <item name="wcf.user.usersOnline.guests"><![CDATA[Guests]]></item>
+ <item name="wcf.user.usersOnline.location"><![CDATA[Location]]></item>
+ <item name="wcf.user.usersOnline.ipAddress"><![CDATA[IP Address]]></item>
+ <item name="wcf.user.usersOnline.userAgent"><![CDATA[Browser]]></item>
+ <item name="wcf.user.usersOnline.lastActivity"><![CDATA[Last Activity]]></item>
+ <item name="wcf.user.usersOnline.location.unknown"><![CDATA[Unknown location]]></item>
+ <item name="wcf.user.usersOnline.robots"><![CDATA[Search Engine Robots]]></item>
+ </category>
+
+ <category name="wcf.user.recentActivity">
+ <item name="wcf.user.recentActivity"><![CDATA[Recent Activities]]></item>
+ <item name="wcf.user.recentActivity.filteredByFollowedUsers"><![CDATA[Of users you follow]]></item>
+ <item name="wcf.user.recentActivity.more"><![CDATA[Show More]]></item>
+ <item name="wcf.user.recentActivity.noMoreEntries"><![CDATA[No more activities]]></item>
+ <item name="wcf.user.recentActivity.noEntries"><![CDATA[There are no activities.]]></item>
+ <item name="wcf.user.recentActivity.com.woltlab.wcf.user.recentActivityEvent.follow"><![CDATA[Follow]]></item>
+ </category>
+
+ <category name="wcf.user.3rdparty">
+ <item name="wcf.user.3rdparty"><![CDATA[Third-Party Login]]></item>
+ <item name="wcf.user.3rdparty.github"><![CDATA[GitHub]]></item>
+ <item name="wcf.user.3rdparty.github.login"><![CDATA[GitHub]]></item>
+ <item name="wcf.user.3rdparty.github.login.error.access_denied"><![CDATA[Access to your GitHub account has been rejected.]]></item>
+ <item name="wcf.user.3rdparty.github.register"><![CDATA[You’re creating an account through <span class="icon icon16 icon-github"></span> GitHub.]]></item>
+ <item name="wcf.user.3rdparty.github.connect"><![CDATA[Connect with your GitHub account{if $__wcf->session->getVar('__githubUsername')} “<a href="https://github.com/{$__wcf->session->getVar('__githubUsername')}">{$__wcf->session->getVar('__githubUsername')}</a>”{/if}]]></item>
+ <item name="wcf.user.3rdparty.github.connect.success"><![CDATA[Your user account has been connected with GitHub.]]></item>
+ <item name="wcf.user.3rdparty.github.connect.error.inuse"><![CDATA[Your GitHub account is already connected to a different user.]]></item>
+ <item name="wcf.user.3rdparty.github.disconnect"><![CDATA[Cancel connection with GitHub]]></item>
+ <item name="wcf.user.3rdparty.github.disconnect.success"><![CDATA[Your account is no longer connected with GitHub.]]></item>
+ <item name="wcf.user.3rdparty.twitter"><![CDATA[Twitter]]></item>
+ <item name="wcf.user.3rdparty.twitter.login"><![CDATA[Twitter]]></item>
+ <item name="wcf.user.3rdparty.twitter.login.error.denied"><![CDATA[Access to your Twitter account has been rejected.]]></item>
+ <item name="wcf.user.3rdparty.twitter.register"><![CDATA[You’re creating an account through <span class="icon icon16 icon-twitter"></span> Twitter.]]></item>
+ <item name="wcf.user.3rdparty.twitter.connect"><![CDATA[Connect with your Twitter account{if $__wcf->session->getVar('__twitterUsername')} “<a href="https://twitter.com/{$__wcf->session->getVar('__twitterUsername')}">{$__wcf->session->getVar('__twitterUsername')}</a>”{/if}]]></item>
+ <item name="wcf.user.3rdparty.twitter.connect.success"><![CDATA[Your user account has been connected with Twitter.]]></item>
+ <item name="wcf.user.3rdparty.twitter.connect.error.inuse"><![CDATA[Your Twitter account is already connected to a different user.]]></item>
+ <item name="wcf.user.3rdparty.twitter.disconnect"><![CDATA[Cancel connection with Twitter]]></item>
+ <item name="wcf.user.3rdparty.twitter.disconnect.success"><![CDATA[Your account is no longer connected with Twitter.]]></item>
+ <item name="wcf.user.3rdparty.facebook"><![CDATA[Facebook]]></item>
+ <item name="wcf.user.3rdparty.facebook.login"><![CDATA[Facebook]]></item>
+ <item name="wcf.user.3rdparty.facebook.login.error.access_denied"><![CDATA[Access to your Facebook account has been rejected.]]></item>
+ <item name="wcf.user.3rdparty.facebook.register"><![CDATA[You’re creating an account through <span class="icon icon16 icon-facebook"></span> Facebook.]]></item>
+ <item name="wcf.user.3rdparty.facebook.connect"><![CDATA[Connect with your Facebook account{if $__wcf->session->getVar('__facebookUsername')} “{$__wcf->session->getVar('__facebookUsername')}”{/if}]]></item>
+ <item name="wcf.user.3rdparty.facebook.connect.success"><![CDATA[Your user account has been connected with Facebook.]]></item>
+ <item name="wcf.user.3rdparty.facebook.connect.error.inuse"><![CDATA[Your Facebook account is already connected to a different user.]]></item>
+ <item name="wcf.user.3rdparty.facebook.disconnect"><![CDATA[Cancel connection with Facebook]]></item>
+ <item name="wcf.user.3rdparty.facebook.disconnect.success"><![CDATA[Your account is no longer connected with Facebook.]]></item>
+ <item name="wcf.user.3rdparty.google"><![CDATA[Google]]></item>
+ <item name="wcf.user.3rdparty.google.login"><![CDATA[Google]]></item>
+ <item name="wcf.user.3rdparty.google.login.error.access_denied"><![CDATA[Access to your Google account has been rejected.]]></item>
+ <item name="wcf.user.3rdparty.google.register"><![CDATA[You’re creating an account through <span class="icon icon16 icon-google-plus"></span> Google.]]></item>
+ <item name="wcf.user.3rdparty.google.connect"><![CDATA[Connect with your Google account{if $__wcf->session->getVar('__googleUsername')} “{$__wcf->session->getVar('__googleUsername')}”{/if}]]></item>
+ <item name="wcf.user.3rdparty.google.connect.success"><![CDATA[Your user account has been connected with Google.]]></item>
+ <item name="wcf.user.3rdparty.google.connect.error.inuse"><![CDATA[Your Google account is already connected to a different user.]]></item>
+ <item name="wcf.user.3rdparty.google.disconnect"><![CDATA[Cancel connection with Google]]></item>
+ <item name="wcf.user.3rdparty.google.disconnect.success"><![CDATA[Your account is no longer connected with Google.]]></item>
+ </category>
+
+ <category name="wcf.user.avatar">
+ <item name="wcf.user.avatar"><![CDATA[Avatar]]></item>
+ <item name="wcf.user.avatar.alt"><![CDATA[User Avatar]]></item>
+ <item name="wcf.user.avatar.edit"><![CDATA[Avatar Management]]></item>
+ <item name="wcf.user.avatar.error.disabled"><![CDATA[The administrators {if $__wcf->user->avatarID || $__wcf->user->enableGravatar}have banned your avatar and {/if}disallowed you from using an avatar{if $__wcf->user->disableAvatarReason}: {$__wcf->user->disableAvatarReason}{/if}.]]></item>
+ <item name="wcf.user.avatar.type.custom"><![CDATA[Upload Own Avatar]]></item>
+ <item name="wcf.user.avatar.type.custom.crop"><![CDATA[Crop Avatar]]></item>
+ <item name="wcf.user.avatar.type.custom.crop.description"><![CDATA[TODO]]></item>
+ <item name="wcf.user.avatar.type.custom.description"><![CDATA[You may use the following file extensions “{"\n"|str_replace:', ':$__wcf->session->getPermission('user.profile.avatar.allowedFileExtensions')}” for your avatar with a maximum file size of {@$__wcf->session->getPermission('user.profile.avatar.maxSize')|filesize}. The minimum dimensions are 48×48 pixel, it is recommend to provide a dimension of {@MAX_AVATAR_WIDTH}×{@MAX_AVATAR_HEIGHT} pixel. Any avatar that exceeds this limit will be scaled down if possible.]]></item>
+ <item name="wcf.user.avatar.type.gravatar"><![CDATA[Use Gravatar]]></item>
+ <item name="wcf.user.avatar.type.gravatar.description"><![CDATA[Gravatar (Global Recognized Avatar) provides a globally reusable avatar connected with your email address “{$__wcf->user->email}”. Visit <a href="http://www.gravatar.com" class="externalURL">www.gravatar.com</a> to set up or change your avatar.]]></item>
+ <item name="wcf.user.avatar.type.gravatar.error.notFound"><![CDATA[Your email address is not connected with a gravatar.]]></item>
+ <item name="wcf.user.avatar.type.none"><![CDATA[No Avatar]]></item>
+ <item name="wcf.user.avatar.type.none.description"><![CDATA[Your currently used avatar will be deleted.]]></item>
+ <item name="wcf.user.avatar.upload.error.badImage"><![CDATA[Uploaded image is invalid.]]></item>
+ <item name="wcf.user.avatar.upload.error.invalidExtension"><![CDATA[Image does not match the allowed file extensions.]]></item>
+ <item name="wcf.user.avatar.upload.error.tooLarge"><![CDATA[Image exceeds maximum file size.]]></item>
+ <item name="wcf.user.avatar.upload.error.tooSmall"><![CDATA[Image dimensions are below the minimum sizes.]]></item>
+ <item name="wcf.user.avatar.upload.error.uploadFailed"><![CDATA[An unknown error occured.]]></item>
+ <item name="wcf.user.avatar.upload.success"><![CDATA[Avatar has been saved.]]></item>
+ </category>
+
+ <category name="wcf.user.notification">
+ <item name="wcf.user.notification.button.confirmed"><![CDATA[OK]]></item>
+ <item name="wcf.user.notification.count"><![CDATA[if (data.returnValues.count == 0) { "No Notifications" } else if (data.returnValues.count == 1) { "1 Notification" } else { data.returnValues.count + " Notifications" }]]></item>
+ <item name="wcf.user.notification.follow.message"><![CDATA[“{$author->username}” follows you.]]></item>
+ <item name="wcf.user.notification.follow.mail"><![CDATA[{@$author->username} follows you.]]></item>
+ <item name="wcf.user.notification.follow.title"><![CDATA[New Follower]]></item>
+ <item name="wcf.user.notification.mail.disabled"><![CDATA[Email notification has been disabled.]]></item>
+ <item name="wcf.user.notification.mail.footer"><![CDATA[This is an automatic notification, PLEASE DO NOT REPLY TO THIS EMAIL!
+
+You can manage your notification settings for {@PAGE_TITLE|language} on the page below:
+{link controller='NotificationSettings' encode=false}{/link}
+
+If you do not want to receive further email notifications for this event, you can quickly disable it by opening the link below:
+{link controller='NotificationDisable' encode=false}eventID={@$notification->eventID}&userID={@$user->userID}&token={@$token}{/link}]]></item>
+ <item name="wcf.user.notification.mail.header"><![CDATA[Dear {@$user->username},]]></item>
+ <item name="wcf.user.notification.mail.subject"><![CDATA[New Notification: {@$title}]]></item>
+ <item name="wcf.user.notification.mail.daily.subject"><![CDATA[{if $count == 1}New Notification{else}{#$count} New Notifications{/if}]]></item>
+ <item name="wcf.user.notification.mail.daily.footer"><![CDATA[This is an automatic notification, PLEASE DO NOT REPLY TO THIS EMAIL!
+
+You can manage your notification settings for {@PAGE_TITLE|language} on the page below:
+{link controller='NotificationSettings' encode=false}{/link}
+
+If you do not want to receive further email notifications for this event, you can quickly disable it by opening the link below:
+{link controller='NotificationDisable' encode=false}userID={@$user->userID}&token={@$token}{/link}]]></item>
+ <item name="wcf.user.notification.mailNotificationType.none"><![CDATA[No Email Notification]]></item>
+ <item name="wcf.user.notification.mailNotificationType.instant"><![CDATA[Instant Email Notification]]></item>
+ <item name="wcf.user.notification.mailNotificationType.daily"><![CDATA[Daily Email Notification]]></item>
+ <item name="wcf.user.notification.markAllAsConfirmed"><![CDATA[Discard All Notifications]]></item>
+ <item name="wcf.user.notification.markAllAsConfirmed.confirmMessage"><![CDATA[Do you really want to discard all notifications?]]></item>
+ <item name="wcf.user.notification.markAsConfirmed"><![CDATA[Discard]]></item>
+ <item name="wcf.user.notification.noMoreNotifications"><![CDATA[No more notifications]]></item>
+ <item name="wcf.user.notification.noNotifications"><![CDATA[There are no more notifications.]]></item>
+ <item name="wcf.user.notification.notifications"><![CDATA[Notifications]]></item>
+ <item name="wcf.user.notification.showAll"><![CDATA[Show All Notifications]]></item>
+ <item name="wcf.user.notification.com.woltlab.wcf.user"><![CDATA[User Profiles]]></item>
+ <item name="wcf.user.notification.com.woltlab.wcf.user.follow.following"><![CDATA[New follower]]></item>
+ </category>
+
+ <category name="wcf.user.profile">
+ <item name="wcf.user.profile"><![CDATA[User Profile of “{$user->username}”]]></item>
+ <item name="wcf.user.profile.content.about.noPublicData"><![CDATA[{if $userID == $__wcf->getUser()->userID}You did not provide any details yet.{else}There are no details visible to you.{/if}]]></item>
+ <item name="wcf.user.profile.content.recentActivity.noEntries"><![CDATA[There are no recent activities available yet.]]></item>
+ <item name="wcf.user.profile.followers"><![CDATA[{$user->username}’s Follower]]></item>
+ <item name="wcf.user.profile.userList.showAll"><![CDATA[Show All]]></item>
+ <item name="wcf.user.profile.following"><![CDATA[{$user->username} Follows]]></item>
+ <item name="wcf.user.profile.menu.about"><![CDATA[About Me]]></item>
+ <item name="wcf.user.profile.menu.recentActivity"><![CDATA[Recent Activity]]></item>
+ <item name="wcf.user.profile.oldUsername"><![CDATA[Used to be called “{$user->getOldUsername()}”]]></item>
+ <item name="wcf.user.profile.recentActivity.follow"><![CDATA[Now follows <a href="{link controller='User' object=$user}{/link}">{$user->username}</a>.]]></item>
+ <item name="wcf.user.profile.visitors"><![CDATA[Profile Visitors]]></item>
+ </category>
+
+ <category name="wcf.user.objectWatch">
+ <item name="wcf.user.objectWatch.manageSubscription"><![CDATA[Manage Subscription]]></item>
+ </category>
+
+ <category name="wcf.user.option">
+ <item name="wcf.user.option.aboutMe"><![CDATA[About Me]]></item>
+ <item name="wcf.user.option.adminCanMail"><![CDATA[Accept emails sent by administrators]]></item>
+ <item name="wcf.user.option.adminComment"><![CDATA[Comment (internal)]]></item>
+ <item name="wcf.user.option.birthday"><![CDATA[Birthday]]></item>
+ <item name="wcf.user.option.birthdayShowYear"><![CDATA[Show year of birth]]></item>
+ <item name="wcf.user.option.birthdayShowYear.description"><![CDATA[Allows members to view your age.]]></item>
+ <item name="wcf.user.option.canMail"><![CDATA[Can Send Me Emails]]></item>
+ <item name="wcf.user.option.canViewEmailAddress"><![CDATA[Can View My Email Address]]></item>
+ <item name="wcf.user.option.canViewOnlineStatus"><![CDATA[Can View My Online Status]]></item>
+ <item name="wcf.user.option.canViewProfile"><![CDATA[Can View My Profile]]></item>
+
+ <item name="wcf.user.option.category.profile"><![CDATA[Personal Data]]></item>
+ <item name="wcf.user.option.category.profile.aboutMe"><![CDATA[About Me]]></item>
+ <item name="wcf.user.option.category.profile.contact"><![CDATA[Contact Options]]></item>
+ <item name="wcf.user.option.category.profile.personal"><![CDATA[Personal Details]]></item>
+ <item name="wcf.user.option.category.settings"><![CDATA[Settings]]></item>
+ <item name="wcf.user.option.category.settings.general"><![CDATA[General]]></item>
+ <item name="wcf.user.option.category.settings.general.date"><![CDATA[Date & Time]]></item>
+ <item name="wcf.user.option.category.settings.general.appearance"><![CDATA[Appearance]]></item>
+ <item name="wcf.user.option.category.settings.general.interface"><![CDATA[Interface]]></item>
+ <item name="wcf.user.option.category.settings.privacy"><![CDATA[Privacy]]></item>
+ <item name="wcf.user.option.category.settings.privacy.content"><![CDATA[Content]]></item>
+ <item name="wcf.user.option.category.settings.privacy.messaging"><![CDATA[Messaging]]></item>
+
+ <item name="wcf.user.option.gender"><![CDATA[Gender]]></item>
+ <item name="wcf.user.option.hobbies"><![CDATA[Hobbies]]></item>
+ <item name="wcf.user.option.homepage"><![CDATA[Website]]></item>
+ <item name="wcf.user.option.location"><![CDATA[Location]]></item>
+ <item name="wcf.user.option.occupation"><![CDATA[Occupation]]></item>
+ <item name="wcf.user.option.showSignature"><![CDATA[Show members signatures]]></item>
+ <item name="wcf.user.option.timezone"><![CDATA[Timezone]]></item>
+ <item name="wcf.user.option.icq"><![CDATA[ICQ]]></item>
+ <item name="wcf.user.option.skype"><![CDATA[Skype]]></item>
+ <item name="wcf.user.option.facebook"><![CDATA[Facebook]]></item>
+ <item name="wcf.user.option.twitter"><![CDATA[Twitter]]></item>
+ <item name="wcf.user.option.googlePlus"><![CDATA[Google+]]></item>
+ <item name="wcf.user.option.googlePlus.description"><![CDATA[Enter your Google Plus user ID with a length of 21 digits.]]></item>
+ </category>
+
+ <category name="wcf.user.mail">
+ <item name="wcf.user.mail.information"><![CDATA[Details]]></item>
+ <item name="wcf.user.mail.mail"><![CDATA[Dear {@$recipient->username},
+
+"{@$username}" sent you a message on "{@PAGE_TITLE|language}":
+{@$message}]]></item>
+ <item name="wcf.user.mail.mail.subject"><![CDATA[Message From {@$username}: {@$subject}]]></item>
+ <item name="wcf.user.mail.message"><![CDATA[Message]]></item>
+ <item name="wcf.user.mail.senderEmail"><![CDATA[Your email address]]></item>
+ <item name="wcf.user.mail.sent"><![CDATA[Message has been sent to “{$user->username}”.]]></item>
+ <item name="wcf.user.mail.showAddress"><![CDATA[Use my email address as sender address, the recipient can directly reply to me.]]></item>
+ <item name="wcf.user.mail.subject"><![CDATA[Subject]]></item>
+ <item name="wcf.user.mail.title"><![CDATA[Send an email to “{$user->username}”]]></item>
+ </category>
+
+ <category name="wcf.user.rank">
+ <item name="wcf.user.rank.administrator"><![CDATA[Administrator]]></item>
+ <item name="wcf.user.rank.moderator"><![CDATA[Moderator]]></item>
+ <item name="wcf.user.rank.superModerator"><![CDATA[Super-Moderator]]></item>
+
+ <!-- TODO: These rank translations are bullshit, find something better -->
+
+ <item name="wcf.user.rank.user0"><![CDATA[Beginner]]></item>
+ <item name="wcf.user.rank.user1"><![CDATA[Student]]></item>
+ <item name="wcf.user.rank.user2"><![CDATA[Intermediate]]></item>
+ <item name="wcf.user.rank.user3"><![CDATA[Professional]]></item>
+ <item name="wcf.user.rank.user4"><![CDATA[Master]]></item>
+ <item name="wcf.user.rank.user5"><![CDATA[Enlightened]]></item>
</category>
<!-- i18n categories -->
- <category name="wcf.user.option" />
<category name="wcf.smiley" />
</language>
error TEXT
);
+DROP TABLE IF EXISTS wcf1_dashboard_box;
+CREATE TABLE wcf1_dashboard_box (
+ boxID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ packageID INT(10) NOT NULL,
+ boxName VARCHAR(255) NOT NULL DEFAULT '',
+ boxType VARCHAR(30) NOT NULL DEFAULT 'sidebar', -- can be 'content' or 'sidebar'
+ className VARCHAR(255) NOT NULL DEFAULT ''
+);
+
+DROP TABLE IF EXISTS wcf1_dashboard_option;
+CREATE TABLE wcf1_dashboard_option (
+ objectTypeID INT(10) NOT NULL,
+ boxID INT(10) NOT NULL,
+ showOrder INT(10) NOT NULL,
+ UNIQUE KEY dashboardOption (objectTypeID, boxID)
+);
+
DROP TABLE IF EXISTS wcf1_event_listener;
CREATE TABLE wcf1_event_listener (
listenerID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
KEY templateName (environment, templateName)
);
+DROP TABLE IF EXISTS wcf1_tracked_visit;
+CREATE TABLE wcf1_tracked_visit (
+ objectTypeID INT(10) NOT NULL,
+ objectID INT(10) NOT NULL,
+ userID INT(10) NOT NULL,
+ visitTime INT(10) NOT NULL DEFAULT 0,
+ UNIQUE KEY (objectTypeID, objectID, userID),
+ KEY (userID, visitTime)
+);
+
+DROP TABLE IF EXISTS wcf1_tracked_visit_type;
+CREATE TABLE wcf1_tracked_visit_type (
+ objectTypeID INT(10) NOT NULL,
+ userID INT(10) NOT NULL,
+ visitTime INT(10) NOT NULL DEFAULT 0,
+ UNIQUE KEY (objectTypeID, userID),
+ KEY (userID, visitTime)
+);
+
DROP TABLE IF EXISTS wcf1_user;
CREATE TABLE wcf1_user (
userID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
styleID INT(10) NOT NULL DEFAULT 0,
banned TINYINT(1) NOT NULL DEFAULT 0,
banReason MEDIUMTEXT NULL,
+ activationCode INT(10) NOT NULL DEFAULT 0,
+ lastLostPasswordRequestTime INT(10) NOT NULL DEFAULT 0,
+ lostPasswordKey VARCHAR(40) NOT NULL DEFAULT '',
+ lastUsernameChange INT(10) NOT NULL DEFAULT 0,
+ newEmail VARCHAR(255) NOT NULL DEFAULT '',
+ oldUsername VARCHAR(255) NOT NULL DEFAULT '',
+ quitStarted INT(10) NOT NULL DEFAULT 0,
+ reactivationCode INT(10) NOT NULL DEFAULT 0,
+ registrationIpAddress VARCHAR(39) NOT NULL DEFAULT '',
+ avatarID INT(10),
+ disableAvatar TINYINT(1) NOT NULL DEFAULT 0,
+ disableAvatarReason TEXT,
+ enableGravatar TINYINT(1) NOT NULL DEFAULT 0,
+ signature TEXT,
+ signatureEnableBBCodes TINYINT(1) NOT NULL DEFAULT 1,
+ signatureEnableHtml TINYINT(1) NOT NULL DEFAULT 0,
+ signatureEnableSmilies TINYINT(1) NOT NULL DEFAULT 1,
+ disableSignature TINYINT(1) NOT NULL DEFAULT 0,
+ disableSignatureReason TEXT,
+ lastActivityTime INT(10) NOT NULL DEFAULT 0,
+ profileHits INT(10) NOT NULL DEFAULT 0,
+ rankID INT(10),
+ userTitle VARCHAR(255) NOT NULL DEFAULT '',
+ userOnlineGroupID INT(10),
+ activityPoints INT(10) NOT NULL DEFAULT 0,
+ notificationMailToken VARCHAR(20) NOT NULL DEFAULT '',
+ authData VARCHAR(255) NOT NULL DEFAULT '',
KEY username (username),
KEY registrationDate (registrationDate),
- KEY styleID (styleID)
+ KEY styleID (styleID),
+ KEY activationCode (activationCode),
+ KEY registrationData (registrationIpAddress, registrationDate),
+ KEY activityPoints (activityPoints)
+);
+
+DROP TABLE IF EXISTS wcf1_user_activity_event;
+CREATE TABLE wcf1_user_activity_event (
+ eventID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectTypeID INT(10) NOT NULL,
+ objectID INT(10) NOT NULL,
+ languageID INT(10),
+ userID INT(10) NOT NULL,
+ time INT(10) NOT NULL,
+ additionalData TEXT,
+
+ KEY (time),
+ KEY (userID, time),
+ KEY (objectTypeID, objectID)
+);
+
+DROP TABLE IF EXISTS wcf1_user_activity_point;
+CREATE TABLE wcf1_user_activity_point (
+ userID INT(10) NOT NULL,
+ objectTypeID INT(10) NOT NULL,
+ activityPoints INT(10) NOT NULL DEFAULT 0,
+ PRIMARY KEY (userID, objectTypeID),
+ KEY (objectTypeID)
+);
+
+DROP TABLE IF EXISTS wcf1_user_activity_point_event;
+CREATE TABLE wcf1_user_activity_point_event (
+ eventID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectTypeID INT(10) NOT NULL,
+ objectID INT(10) NOT NULL,
+ userID INT(10) NOT NULL,
+ additionalData TEXT,
+ UNIQUE KEY (objectTypeID, userID, objectID)
+);
+
+DROP TABLE IF EXISTS wcf1_user_avatar;
+CREATE TABLE wcf1_user_avatar (
+ avatarID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ avatarName VARCHAR(255) NOT NULL DEFAULT '',
+ avatarExtension VARCHAR(7) NOT NULL DEFAULT '',
+ width SMALLINT(5) NOT NULL DEFAULT 0,
+ height SMALLINT(5) NOT NULL DEFAULT 0,
+ userID INT(10),
+ fileHash VARCHAR(40) NOT NULL DEFAULT '',
+ cropX SMALLINT(5) NOT NULL DEFAULT 0,
+ cropY SMALLINT(5) NOT NULL DEFAULT 0
);
DROP TABLE IF EXISTS wcf1_user_collapsible_content;
UNIQUE KEY (objectTypeID, objectID, userID)
);
+DROP TABLE IF EXISTS wcf1_user_follow;
+CREATE TABLE wcf1_user_follow (
+ followID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ userID INT(10) NOT NULL,
+ followUserID INT(10) NOT NULL,
+ time INT(10) NOT NULL DEFAULT 0,
+ UNIQUE KEY (userID, followUserID)
+);
+
DROP TABLE IF EXISTS wcf1_user_group;
CREATE TABLE wcf1_user_group (
groupID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
groupName VARCHAR(255) NOT NULL DEFAULT '',
- groupType TINYINT(1) NOT NULL DEFAULT 4
+ groupType TINYINT(1) NOT NULL DEFAULT 4,
+ priority MEDIUMINT(8) NOT NULL DEFAULT 0,
+ userOnlineMarking VARCHAR(255) NOT NULL DEFAULT '%s',
+ showOnTeamPage TINYINT(1) NOT NULL DEFAULT 0
);
DROP TABLE IF EXISTS wcf1_user_group_option;
UNIQUE KEY groupID (groupID, optionID)
);
+DROP TABLE IF EXISTS wcf1_user_ignore;
+CREATE TABLE wcf1_user_ignore (
+ ignoreID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ userID INT(10) NOT NULL,
+ ignoreUserID INT(10) NOT NULL,
+ time INT(10) NOT NULL DEFAULT 0,
+ UNIQUE KEY (userID, ignoreUserID)
+);
+
+DROP TABLE IF EXISTS wcf1_user_menu_item;
+CREATE TABLE wcf1_user_menu_item (
+ menuItemID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ packageID INT(10) NOT NULL,
+ menuItem VARCHAR(255) NOT NULL DEFAULT '',
+ parentMenuItem VARCHAR(255) NOT NULL DEFAULT '',
+ menuItemController VARCHAR(255) NOT NULL DEFAULT '',
+ menuItemLink VARCHAR(255) NOT NULL DEFAULT '',
+ showOrder INT(10) NOT NULL DEFAULT 0,
+ permissions TEXT,
+ options TEXT,
+ className VARCHAR(255) NOT NULL DEFAULT '',
+ UNIQUE KEY menuItem (menuItem, packageID)
+);
+
+-- notifications
+DROP TABLE IF EXISTS wcf1_user_notification;
+CREATE TABLE wcf1_user_notification (
+ notificationID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ packageID INT(10) NOT NULL,
+ eventID INT(10) NOT NULL,
+ objectID INT(10) NOT NULL DEFAULT 0,
+ eventHash VARCHAR(40) NOT NULL DEFAULT '',
+ authorID INT(10),
+ time INT(10) NOT NULL DEFAULT 0,
+ additionalData TEXT,
+ KEY (eventHash),
+ UNIQUE KEY (packageID, eventID, objectID)
+);
+
+-- notification recipients
+DROP TABLE IF EXISTS wcf1_user_notification_to_user;
+CREATE TABLE wcf1_user_notification_to_user (
+ notificationID INT(10) NOT NULL,
+ userID INT(10) NOT NULL,
+ mailNotified TINYINT(1) NOT NULL DEFAULT 0,
+ UNIQUE KEY notificationID (notificationID, userID)
+);
+
+-- events that create notifications
+DROP TABLE IF EXISTS wcf1_user_notification_event;
+CREATE TABLE wcf1_user_notification_event (
+ eventID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ packageID INT(10) NOT NULL,
+ eventName VARCHAR(255) NOT NULL DEFAULT '',
+ objectTypeID INT(10) NOT NULL,
+ className VARCHAR(255) NOT NULL DEFAULT '',
+ permissions TEXT,
+ options TEXT,
+ preset TINYINT(1) NOT DEFAULT 0,
+ UNIQUE KEY eventName (eventName, objectTypeID)
+);
+
+-- user configuration for events
+DROP TABLE IF EXISTS wcf1_user_notification_event_to_user;
+CREATE TABLE wcf1_user_notification_event_to_user (
+ userID INT(10) NOT NULL,
+ eventID INT(10) NOT NULL,
+ mailNotificationType ENUM('none', 'instant', 'daily') NOT NULL DEFAULT 'none',
+ UNIQUE KEY (eventID, userID)
+);
+
+DROP TABLE IF EXISTS wcf1_user_object_watch;
+CREATE TABLE wcf1_user_object_watch (
+ watchID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectTypeID INT(10) NOT NULL,
+ objectID INT(10) NOT NULL,
+ userID INT(10) NOT NULL,
+ notification TINYINT(1) NOT NULL DEFAULT 0,
+
+ UNIQUE KEY (objectTypeID, userID, objectID),
+ KEY (objectTypeID, objectID)
+);
+
DROP TABLE IF EXISTS wcf1_user_option;
CREATE TABLE wcf1_user_option (
optionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
userID INT(10) NOT NULL PRIMARY KEY
);
+DROP TABLE IF EXISTS wcf1_user_profile_menu_item;
+CREATE TABLE wcf1_user_profile_menu_item (
+ menuItemID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ packageID INT(10) NOT NULL,
+ menuItem VARCHAR(255) NOT NULL,
+ showOrder INT(10) NOT NULL DEFAULT 0,
+ permissions TEXT NULL,
+ options TEXT NULL,
+ className VARCHAR(255) NOT NULL,
+ UNIQUE KEY (packageID, menuItem)
+);
+
+DROP TABLE IF EXISTS wcf1_user_profile_visitor;
+CREATE TABLE wcf1_user_profile_visitor (
+ visitorID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ ownerID INT(10),
+ userID INT(10),
+ time INT(10) NOT NULL DEFAULT 0,
+ UNIQUE KEY (ownerID, userID),
+ KEY (time)
+);
+
+DROP TABLE IF EXISTS wcf1_user_rank;
+CREATE TABLE wcf1_user_rank (
+ rankID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ groupID INT(10),
+ requiredPoints INT(10) NOT NULL DEFAULT 0,
+ rankTitle VARCHAR(255) NOT NULL DEFAULT '',
+ cssClassName VARCHAR(255) NOT NULL DEFAULT '',
+ rankImage VARCHAR(255) NOT NULL DEFAULT '',
+ repeatImage TINYINT(3) NOT NULL DEFAULT 1,
+ requiredGender TINYINT(1) NOT NULL DEFAULT 0
+);
+
DROP TABLE IF EXISTS wcf1_user_storage;
CREATE TABLE wcf1_user_storage (
userID INT(10) NOT NULL,
ALTER TABLE wcf1_user_to_language ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
ALTER TABLE wcf1_user_to_language ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_dashboard_box ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_dashboard_option ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+ALTER TABLE wcf1_dashboard_option ADD FOREIGN KEY (boxID) REFERENCES wcf1_dashboard_box (boxID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_tracked_visit ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+ALTER TABLE wcf1_tracked_visit ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_tracked_visit_type ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+ALTER TABLE wcf1_tracked_visit_type ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user ADD FOREIGN KEY (avatarID) REFERENCES wcf1_user_avatar (avatarID) ON DELETE SET NULL;
+ALTER TABLE wcf1_user ADD FOREIGN KEY (rankID) REFERENCES wcf1_user_rank (rankID) ON DELETE SET NULL;
+ALTER TABLE wcf1_user ADD FOREIGN KEY (userOnlineGroupID) REFERENCES wcf1_user_group (groupID) ON DELETE SET NULL;
+
+ALTER TABLE wcf1_user_avatar ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_follow ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_follow ADD FOREIGN KEY (followUserID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_ignore ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_ignore ADD FOREIGN KEY (ignoreUserID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_menu_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_notification ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_notification ADD FOREIGN KEY (eventID) REFERENCES wcf1_user_notification_event (eventID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_notification ADD FOREIGN KEY (authorID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
+
+ALTER TABLE wcf1_user_notification_to_user ADD FOREIGN KEY (notificationID) REFERENCES wcf1_user_notification (notificationID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_notification_to_user ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_notification_event ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_notification_event ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_notification_event_to_user ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_notification_event_to_user ADD FOREIGN KEY (eventID) REFERENCES wcf1_user_notification_event (eventID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_profile_menu_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_rank ADD FOREIGN KEY (groupID) REFERENCES wcf1_user_group (groupID) ON DELETE SET NULL;
+
+ALTER TABLE wcf1_user_activity_event ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_activity_event ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_activity_event ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE SET NULL;
+
+ALTER TABLE wcf1_user_activity_point ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_activity_point ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_activity_point_event ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_activity_point_event ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_profile_visitor ADD FOREIGN KEY (ownerID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_profile_visitor ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+ALTER TABLE wcf1_user_object_watch ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
+
/* default inserts */
-- default user groups
INSERT INTO wcf1_user_group (groupName, groupType) VALUES ('wcf.acp.group.group1', 1);
-- soundcloud
INSERT INTO wcf1_bbcode_media_provider (title, regex, html) VALUES ('Soundcloud', 'https?://soundcloud.com/(?<artist>[a-zA-Z0-9_-]+)/(?<song>[a-zA-Z0-9_-]+)', '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=http%3A%2F%2Fsoundcloud.com%2F{$artist}%2F{$song}"></iframe>');
+-- default priorities
+UPDATE wcf1_user_group SET priority = 10 WHERE groupID = 3;
+UPDATE wcf1_user_group SET priority = 1000 WHERE groupID = 4;
+UPDATE wcf1_user_group SET priority = 50 WHERE groupID = 5;
+UPDATE wcf1_user_group SET priority = 100 WHERE groupID = 6;
+
+-- default 'showOnTeamPage' setting
+UPDATE wcf1_user_group SET showOnTeamPage = 1 WHERE groupID IN (4, 5, 6);
+
+-- default ranks
+INSERT INTO wcf1_user_rank (groupID, requiredPoints, rankTitle, cssClassName) VALUES
+ (4, 0, 'wcf.user.rank.administrator', 'blue'),
+ (5, 0, 'wcf.user.rank.moderator', 'blue'),
+ (6, 0, 'wcf.user.rank.superModerator', 'blue'),
+ (3, 0, 'wcf.user.rank.user0', ''),
+ (3, 300, 'wcf.user.rank.user1', ''),
+ (3, 900, 'wcf.user.rank.user2', ''),
+ (3, 3000, 'wcf.user.rank.user3', ''),
+ (3, 9000, 'wcf.user.rank.user4', ''),
+ (3, 15000, 'wcf.user.rank.user5', '');
\ No newline at end of file