Added support for paid subscriptions
authorMarcel Werk <burntime@woltlab.com>
Wed, 17 Sep 2014 15:06:01 +0000 (17:06 +0200)
committerMarcel Werk <burntime@woltlab.com>
Wed, 17 Sep 2014 15:06:01 +0000 (17:06 +0200)
56 files changed:
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/dashboardBox.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
com.woltlab.wcf/option.xml
com.woltlab.wcf/templates/dashboardBoxPaidSubscriptions.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/dashboardBoxPaidSubscriptionsSidebar.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/paidSubscriptionList.tpl [new file with mode: 0644]
com.woltlab.wcf/userGroupOption.xml
com.woltlab.wcf/userMenu.xml
wcfsetup/install/files/acp/templates/paidSubscriptionAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/paidSubscriptionList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/paidSubscriptionTransactionLog.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/paidSubscriptionTransactionLogList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/paidSubscriptionUserAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/paidSubscriptionUserList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/paymentMethodSelectOptionType.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/PaidSubscriptionAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/PaidSubscriptionEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/PaidSubscriptionUserAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/PaidSubscriptionListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/PaidSubscriptionTransactionLogListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/PaidSubscriptionTransactionLogPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/PaidSubscriptionUserListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/action/PaypalCallbackAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscription.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLog.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUser.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/page/PaidSubscriptionListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/page/PaidSubscriptionReturnPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cache/builder/PaidSubscriptionCacheBuilder.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/cronjob/HourlyCleanUpCronjob.class.php
wcfsetup/install/files/lib/system/dashboard/box/PaidSubscriptionsDashboardBox.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/dashboard/box/PaidSubscriptionsSidebarDashboardBox.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/option/PaymentMethodSelectOptionType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/payment/method/AbstractPaymentMethod.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/payment/method/IPaymentMethod.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/payment/method/PaymentMethodHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/payment/method/PaypalPaymentMethod.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/payment/method/SofortUeberweisungPaymentMethod.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/payment/type/AbstractPaymentType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/payment/type/IPaymentType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/payment/type/PaidSubscriptionPaymentType.class.php [new file with mode: 0644]
wcfsetup/install/files/style/user.less
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index 69229f1cbeee03ab12c17c359f63ee0cde1e58b2..b46620fc29bc0d7b10f0ac9edb5ff07fe4a84fc4 100644 (file)
                        <permissions>admin.notice.canManageNotice</permissions>
                </acpmenuitem>
                
+               <acpmenuitem name="wcf.acp.menu.link.paidSubscription">
+                       <parent>wcf.acp.menu.link.user</parent>
+                       <showorder>6</showorder>
+               </acpmenuitem>
+               <acpmenuitem name="wcf.acp.menu.link.paidSubscription.list">
+                       <controller><![CDATA[wcf\acp\page\PaidSubscriptionListPage]]></controller>
+                       <parent>wcf.acp.menu.link.paidSubscription</parent>
+                       <permissions>admin.paidSubscription.canManageSubscription</permissions>
+                       <options>module_paid_subscription</options>
+               </acpmenuitem>
+               <acpmenuitem name="wcf.acp.menu.link.paidSubscription.user.list">
+                       <controller><![CDATA[wcf\acp\page\PaidSubscriptionUserListPage]]></controller>
+                       <parent>wcf.acp.menu.link.paidSubscription</parent>
+                       <permissions>admin.paidSubscription.canManageSubscription</permissions>
+                       <options>module_paid_subscription</options>
+               </acpmenuitem>
+               <acpmenuitem name="wcf.acp.menu.link.paidSubscription.transactionLog.list">
+                       <controller><![CDATA[wcf\acp\page\PaidSubscriptionTransactionLogListPage]]></controller>
+                       <parent>wcf.acp.menu.link.paidSubscription</parent>
+                       <permissions>admin.paidSubscription.canManageSubscription</permissions>
+                       <options>module_paid_subscription</options>
+               </acpmenuitem>
+               
                <acpmenuitem name="wcf.acp.menu.link.display">
                        <showorder>3</showorder>
                </acpmenuitem>
index fede92f78613a49d9da57cb3aa3dba9edf4d95a7..f4ce0c6f46dc6209c6473d129b556c2d3e856139 100644 (file)
@@ -6,6 +6,11 @@
                        <boxtype>content</boxtype>
                </dashboardbox>
                
+               <dashboardbox name="com.woltlab.wcf.paidSubscriptions">
+                       <classname><![CDATA[wcf\system\dashboard\box\PaidSubscriptionsDashboardBox]]></classname>
+                       <boxtype>content</boxtype>
+               </dashboardbox>
+               
                <dashboardbox name="com.woltlab.wcf.user.recentActivitySidebar">
                        <classname><![CDATA[wcf\system\dashboard\box\RecentActivitySidebarDashboardBox]]></classname>
                        <boxtype>sidebar</boxtype>
                        <classname><![CDATA[wcf\system\dashboard\box\StaffOnlineSidebarDashboardBox]]></classname>
                        <boxtype>sidebar</boxtype>
                </dashboardbox>
+               
+               <dashboardbox name="com.woltlab.wcf.paidSubscriptionsSidebar">
+                       <classname><![CDATA[wcf\system\dashboard\box\PaidSubscriptionsSidebarDashboardBox]]></classname>
+                       <boxtype>sidebar</boxtype>
+               </dashboardbox>
        </import>
 </data>
index 59665ac26dc8b3e62ebada56409b51de606983e0..0d875b4723fb4485a330639f711baca741f8ab26 100644 (file)
                        <classname><![CDATA[wcf\system\message\embedded\object\AttachmentMessageEmbeddedObjectHandler]]></classname>
                </type>
                <!-- embedded object handlers -->
+               
+               <type>
+                       <name>com.woltlab.wcf.payment.method.paypal</name>
+                       <definitionname>com.woltlab.wcf.payment.method</definitionname>
+                       <classname><![CDATA[wcf\system\payment\method\PaypalPaymentMethod]]></classname>
+               </type>
+               
+               <!-- <type>
+                       <name>com.woltlab.wcf.payment.method.sofortUeberweisung</name>
+                       <definitionname>com.woltlab.wcf.payment.method</definitionname>
+                       <classname><![CDATA[wcf\system\payment\method\SofortUeberweisungPaymentMethod]]></classname>
+               </type>-->
+               
+               <type>
+                       <name>com.woltlab.wcf.payment.type.paidSubscription</name>
+                       <definitionname>com.woltlab.wcf.payment.type</definitionname>
+                       <classname><![CDATA[wcf\system\payment\type\PaidSubscriptionPaymentType]]></classname>
+               </type>
        </import>
 </data>
index 256f3c9856c6a49521629552ff2f348db6fddb7e..4f9ddc2ba01e9f18400c572f52b6e40d85243914 100644 (file)
                <definition>
                        <name>com.woltlab.wcf.message</name>
                </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.payment.method</name>
+                       <interfacename><![CDATA[wcf\system\payment\method\IPaymentMethod]]></interfacename>
+               </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.payment.type</name>
+                       <interfacename><![CDATA[wcf\system\payment\type\IPaymentType]]></interfacename>
+               </definition>
        </import>
 </data>
index a119536fff2192245b89516c8a64ee0eaa4dc61a..9e839e62b1e0cf14d04b911866b797232b511e8c 100644 (file)
@@ -81,6 +81,9 @@
                                        <category name="general.cache.memcached">
                                                <parent>general.cache</parent>
                                        </category>
+                               <category name="general.payment">
+                                       <parent>general</parent>
+                               </category>
                        <!-- /general -->
                        
                        <!-- user -->
@@ -816,6 +819,23 @@ no:!cache_source_memcached_host]]></enableoptions>
                        </option>
                        <!-- /general.cache -->
                        
+                       <!-- general.payment -->
+                       <option name="available_payment_methods">
+                               <categoryname>general.payment</categoryname>
+                               <optiontype>paymentMethodSelect</optiontype>
+                       </option>
+                       
+                       <option name="paypal_email_address">
+                               <categoryname>general.payment</categoryname>
+                               <optiontype>text</optiontype>
+                       </option>
+                       
+                       <option name="module_paid_subscription">
+                               <categoryname>general.payment</categoryname>
+                               <optiontype>boolean</optiontype>
+                       </option>
+                       <!-- /general.payment -->
+                       
                        <option name="attachment_storage">
                                <categoryname>message.attachment</categoryname>
                                <optiontype>text</optiontype>
diff --git a/com.woltlab.wcf/templates/dashboardBoxPaidSubscriptions.tpl b/com.woltlab.wcf/templates/dashboardBoxPaidSubscriptions.tpl
new file mode 100644 (file)
index 0000000..d8153d1
--- /dev/null
@@ -0,0 +1,22 @@
+<header class="boxHeadline boxSubHeadline">
+       <h2>{lang}wcf.dashboard.box.com.woltlab.wcf.paidSubscriptions{/lang}</h2>
+</header>
+
+<div class="container marginTop containerPadding">
+       <ul class="paidSubscriptionTeaserList">
+               {foreach from=$subscriptions item=subscription}
+                       <li>
+                               <div class="containerHeadline" title="{$subscription->description|language}">
+                                       <h3>{$subscription->title|language}</h3>
+                                       <small>{lang}wcf.paidSubscription.formattedCost{/lang}</small> 
+                               </div>
+                               
+                               <ul class="buttonList marginTopTiny">
+                                       {foreach from=$subscription->getPurchaseButtons() item=button}
+                                               <li>{@$button}</li>
+                                       {/foreach}
+                               </ul>
+                       </li>
+               {/foreach}
+       </ul>
+</div>
diff --git a/com.woltlab.wcf/templates/dashboardBoxPaidSubscriptionsSidebar.tpl b/com.woltlab.wcf/templates/dashboardBoxPaidSubscriptionsSidebar.tpl
new file mode 100644 (file)
index 0000000..35a716e
--- /dev/null
@@ -0,0 +1,16 @@
+<ul class="sidebarBoxList">
+       {foreach from=$subscriptions item=subscription}
+               <li>
+                       <div class="sidebarBoxHeadline" title="{$subscription->description|language}">
+                               <h3>{$subscription->title|language}</h3>
+                               <small>{lang}wcf.paidSubscription.formattedCost{/lang}</small> 
+                       </div>
+                       
+                       <ul class="buttonList marginTopTiny">
+                               {foreach from=$subscription->getPurchaseButtons() item=button}
+                                       <li>{@$button}</li>
+                               {/foreach}
+                       </ul>
+               </li>
+       {/foreach}
+</ul>
diff --git a/com.woltlab.wcf/templates/paidSubscriptionList.tpl b/com.woltlab.wcf/templates/paidSubscriptionList.tpl
new file mode 100644 (file)
index 0000000..4e09f5f
--- /dev/null
@@ -0,0 +1,102 @@
+{include file='documentHeader'}
+
+<head>
+       <title>{lang}wcf.user.menu.settings.paidSubscription{/lang} - {lang}wcf.user.menu.settings{/lang} - {PAGE_TITLE|language}</title>
+       
+       {include file='headInclude'}
+</head>
+
+<body id="tpl{$templateName|ucfirst}" data-template="{$templateName}" data-application="{$templateNameApplication}">
+
+{include file='userMenuSidebar'}
+
+{include file='header' sidebarOrientation='left'}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.user.menu.settings.paidSubscription{/lang}</h1>
+</header>
+
+{include file='userNotice'}
+
+<div class="contentNavigation">
+       {hascontent}
+               <nav>
+                       <ul>
+                               {content}
+                                       {event name='contentNavigationButtonsTop'}
+                               {/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</div>
+
+{if $subscriptions|count}
+       <header class="boxHeadline boxSubHeadline">
+               <h2>{lang}wcf.paidSubscription.availableSubscriptions{/lang}</h2>
+       </header>
+       
+       <div class="container marginTop">
+               <ul class="containerList">
+                       {foreach from=$subscriptions item=subscription}
+                               <li>
+                                       <div class="containerHeadline">
+                                               <h3>{$subscription->title|language}</h3>
+                                               <p>{$subscription->description|language}</p>
+                                               
+                                               <p class="marginTopTiny">{lang}wcf.paidSubscription.formattedCost{/lang}</p>
+                                               
+                                               <ul class="buttonList marginTopTiny">
+                                                       {foreach from=$subscription->getPurchaseButtons() item=button}
+                                                               <li>{@$button}</li>
+                                                       {/foreach}
+                                               </ul>
+                                       </div>
+                               </li>
+                       {/foreach}
+               </ul>
+       </div>
+{/if}
+       
+{if $userSubscriptions|count}
+       <header class="boxHeadline boxSubHeadline">
+               <h2>{lang}wcf.paidSubscription.purchasedSubscriptions{/lang}</h2>
+       </header>
+       
+       <div class="container marginTop">
+               <ul class="containerList">
+                       {foreach from=$userSubscriptions item=userSubscription}
+                               <li>
+                                       <div class="containerHeadline">
+                                               <h3>{$userSubscription->getSubscription()->title|language}</h3>
+                                               <p>{$userSubscription->getSubscription()->description|language}</p>
+                                               
+                                               {if $userSubscription->endDate}
+                                                       <p>{lang}wcf.paidSubscription.expires{/lang}: {@$userSubscription->endDate|time}</p>
+                                               {/if}
+                                       </div>
+                               </li>
+                       {/foreach}
+               </ul>
+       </div>
+{/if}
+
+{if !$subscriptions|count && !$userSubscriptions|count}
+       <p class="warning">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+       {hascontent}
+               <nav>
+                       <ul>
+                               {content}
+                                       {event name='contentNavigationButtonsBottom'}
+                               {/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</div>
+
+{include file='footer'}
+
+</body>
+</html>
index a547ae68bacfae0f84f68c632d2c8376360e762a..d62b5d392cfa29acfa3d85fe431f2247ef21c101 100644 (file)
                                <usersonly>1</usersonly>
                        </option>
                        
+                       <option name="admin.paidSubscription.canManageSubscription">
+                               <categoryname>admin.user.user</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                               <usersonly>1</usersonly>
+                               <options>module_paid_subscription</options>
+                       </option>
+                       
                        <option name="admin.content.dashboard.canEditDashboard">
                                <categoryname>admin.display</categoryname>
                                <optiontype>boolean</optiontype>
index 32876aadafc40c204e387fb17932b75c46d6852a..9e2c10f48e256de521f2032e8c9845a9541b93a5 100644 (file)
                        <controller><![CDATA[wcf\form\NotificationSettingsForm]]></controller>
                        <parent>wcf.user.menu.settings</parent>
                </usermenuitem>
+               
+               <usermenuitem name="wcf.user.menu.settings.paidSubscription">
+                       <controller><![CDATA[wcf\page\PaidSubscriptionListPage]]></controller>
+                       <parent>wcf.user.menu.settings</parent>
+                       <options>module_paid_subscription</options>
+               </usermenuitem>
                <!-- /settings -->
                
                <!-- community -->
diff --git a/wcfsetup/install/files/acp/templates/paidSubscriptionAdd.tpl b/wcfsetup/install/files/acp/templates/paidSubscriptionAdd.tpl
new file mode 100644 (file)
index 0000000..babc66b
--- /dev/null
@@ -0,0 +1,200 @@
+{include file='header' pageTitle='wcf.acp.paidSubscription.'|concat:$action}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               $('#subscriptionLengthPermanent').change(function() {
+                       if ($('#subscriptionLengthPermanent').is(':checked')) {
+                               $('#subscriptionLengthDL, #isRecurringDL').hide();
+                       }
+                       else {
+                               $('#subscriptionLengthDL, #isRecurringDL').show();
+                       }
+               });
+               $('#subscriptionLengthPermanent').change();
+       });
+       //]]>
+</script>
+
+{include file='multipleLanguageInputJavascript' elementIdentifier='description' forceSelection=false}
+{include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.paidSubscription.{$action}{/lang}</h1>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+       <nav>
+               <ul>
+                       <li><a href="{link controller='PaidSubscriptionList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.paidSubscription.list{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtons'}
+               </ul>
+       </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='PaidSubscriptionAdd'}{/link}{else}{link controller='PaidSubscriptionEdit' id=$subscriptionID}{/link}{/if}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.global.form.data{/lang}</legend>
+                       
+                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                               <dt><label for="title">{lang}wcf.global.title{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="title" name="title" value="{$i18nPlainValues['title']}" autofocus="autofocus" class="medium" />
+                                       {if $errorField == 'title'}
+                                               <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.paidSubscription.title.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'description'} class="formError"{/if}>
+                               <dt><label for="description">{lang}wcf.global.description{/lang}</label></dt>
+                               <dd>
+                                       <textarea id="description" name="description" cols="40" rows="10">{$i18nPlainValues[description]}</textarea>
+                                       {if $errorField == 'description'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.paidSubscription.description.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt><label for="showOrder">{lang}wcf.acp.paidSubscription.showOrder{/lang}</label></dt>
+                               <dd>
+                                       <input type="number" id="showOrder" name="showOrder" value="{if $showOrder}{@$showOrder}{/if}" class="tiny" min="0" />
+                                       <small>{lang}wcf.acp.paidSubscription.showOrder.description{/lang}</small>
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="isDisabled" value="1" {if $isDisabled}checked="checked" {/if}/> {lang}wcf.acp.paidSubscription.isDisabled{/lang}</label>
+                                       <small>{lang}wcf.acp.paidSubscription.isDisabled.description{/lang}</small>
+                               </dd>
+                       </dl>
+                       
+                       {if $availableSubscriptions|count}
+                               <dl>
+                                       <dt>{lang}wcf.acp.paidSubscription.excludedSubscriptions{/lang}</dt>
+                                       <dd>
+                                               {foreach from=$availableSubscriptions item=availableSubscription}
+                                                       <label><input type="checkbox" name="excludedSubscriptionIDs[]" value="{@$availableSubscription->subscriptionID}" {if $availableSubscription->subscriptionID|in_array:$excludedSubscriptionIDs}checked="checked" {/if}/> {$availableSubscription->title|language}</label>
+                                               {/foreach}
+                                               <small>{lang}wcf.acp.paidSubscription.excludedSubscriptions.description{/lang}</small>
+                                       </dd>
+                               </dl>
+                       {/if}
+                       
+                       {event name='dataFields'}
+               </fieldset>
+               
+               <fieldset>
+                       <legend>{lang}wcf.acp.paidSubscription.paymentOptions{/lang}</legend>
+               
+                       <dl{if $errorField == 'cost'} class="formError"{/if}>
+                               <dt><label for="cost">{lang}wcf.acp.paidSubscription.cost{/lang}</label></dt>
+                               <dd>
+                                       <input type="number" id="cost" name="cost" value="{$cost}" class="tiny" step="0.01" min="0" />
+                                       <select name="currency" id="currency">
+                                               {htmlOptions values=$availableCurrencies output=$availableCurrencies selected=$currency}
+                                       </select>
+                                       {if $errorField == 'cost'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.paidSubscription.cost.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" id="subscriptionLengthPermanent" name="subscriptionLengthPermanent" value="1" {if !$subscriptionLength}checked="checked" {/if}/> {lang}wcf.acp.paidSubscription.subscriptionLength.permanent{/lang}</label>
+                               </dd>
+                       </dl>
+                       
+                       <dl id="subscriptionLengthDL"{if $errorField == 'subscriptionLength'} class="formError"{/if}>
+                               <dt><label for="subscriptionLength">{lang}wcf.acp.paidSubscription.subscriptionLength{/lang}</label></dt>
+                               <dd>
+                                       <input type="number" id="subscriptionLength" name="subscriptionLength" value="{@$subscriptionLength}" class="tiny" />
+                                       <select name="subscriptionLengthUnit" id="subscriptionLengthUnit">
+                                               <option value="D"{if $subscriptionLengthUnit == 'D'} selected="selected"{/if}>{lang}wcf.acp.paidSubscription.subscriptionLengthUnit.D{/lang}</option>
+                                               <option value="M"{if $subscriptionLengthUnit == 'M'} selected="selected"{/if}>{lang}wcf.acp.paidSubscription.subscriptionLengthUnit.M{/lang}</option>
+                                               <option value="Y"{if $subscriptionLengthUnit == 'Y'} selected="selected"{/if}>{lang}wcf.acp.paidSubscription.subscriptionLengthUnit.Y{/lang}</option>
+                                       </select>
+                                       {if $errorField == 'subscriptionLength'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.paidSubscription.subscriptionLength.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl id="isRecurringDL">
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" name="isRecurring" value="1" {if $isRecurring}checked="checked" {/if}/> {lang}wcf.acp.paidSubscription.isRecurring{/lang}</label>
+                                       <small>{lang}wcf.acp.paidSubscription.isRecurring.description{/lang}</small>
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'groupIDs'} class="formError"{/if}>
+                               <dt><label>{lang}wcf.acp.paidSubscription.userGroups{/lang}</label></dt>
+                               <dd>
+                                       {foreach from=$availableUserGroups item=userGroup}
+                                               <label><input type="checkbox" name="groupIDs[]" value="{@$userGroup->groupID}" {if $userGroup->groupID|in_array:$groupIDs}checked="checked" {/if}/> {$userGroup->groupName|language}</label>
+                                       {/foreach}
+                                       {if $errorField == 'groupIDs'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.paidSubscription.userGroups.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                                       <small>{lang}wcf.acp.paidSubscription.userGroups.description{/lang}</small>
+                                       
+                               </dd>
+                       </dl>
+               </fieldset>
+               
+               {event name='fieldsets'}
+       </div>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
\ No newline at end of file
diff --git a/wcfsetup/install/files/acp/templates/paidSubscriptionList.tpl b/wcfsetup/install/files/acp/templates/paidSubscriptionList.tpl
new file mode 100644 (file)
index 0000000..6cc2dbd
--- /dev/null
@@ -0,0 +1,86 @@
+{include file='header' pageTitle='wcf.acp.paidSubscription.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\paid\\subscription\\PaidSubscriptionAction', '.jsPaidSubscriptionRow');
+               new WCF.Action.Toggle('wcf\\data\\paid\\subscription\\PaidSubscriptionAction', '.jsPaidSubscriptionRow');
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.paidSubscription.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+       {pages print=true assign=pagesLinks controller='PaidSubscriptionList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+       
+       <nav>
+               <ul>
+                       <li><a href="{link controller='PaidSubscriptionAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.paidSubscription.add{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+
+{if $objects|count}
+       <div class="tabularBox tabularBoxTitle marginTop">
+               <header>
+                       <h2>{lang}wcf.acp.paidSubscription.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+               </header>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID columnSubscriptionID{if $sortField == 'subscriptionID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='PaidSubscriptionList'}pageNo={@$pageNo}&sortField=subscriptionID&sortOrder={if $sortField == 'subscriptionID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle{if $sortField == 'title'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionList'}pageNo={@$pageNo}&sortField=title&sortOrder={if $sortField == 'title' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.title{/lang}</a></th>
+                                       <th class="columnDigits columnCost{if $sortField == 'cost'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionList'}pageNo={@$pageNo}&sortField=cost&sortOrder={if $sortField == 'cost' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.paidSubscription.cost{/lang}</a></th>
+                                       <th class="columnDigits columnSubscriptionLength{if $sortField == 'subscriptionLength'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionList'}pageNo={@$pageNo}&sortField=subscriptionLength&sortOrder={if $sortField == 'subscriptionLength' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.paidSubscription.subscriptionLength{/lang}</a></th>
+                                       <th class="columnDigits columnShowOrder{if $sortField == 'showOrder'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionList'}pageNo={@$pageNo}&sortField=showOrder&sortOrder={if $sortField == 'showOrder' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.paidSubscription.showOrder{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item=subscription}
+                                       <tr class="jsPaidSubscriptionRow">
+                                               <td class="columnIcon">
+                                                       <span class="icon icon16 icon-check{if $subscription->isDisabled}-empty{/if} jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if !$subscription->isDisabled}disable{else}enable{/if}{/lang}" data-object-id="{@$subscription->subscriptionID}"></span>
+                                                       <a href="{link controller='PaidSubscriptionEdit' id=$subscription->subscriptionID}{/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="{@$subscription->subscriptionID}" data-confirm-message="{lang}wcf.acp.paidSubscription.delete.confirmMessage{/lang}"></span>
+                                                       <a href="{link controller='PaidSubscriptionUserAdd' id=$subscription->subscriptionID}{/link}" title="{lang}wcf.acp.paidSubscription.user.add{/lang}" class="jsTooltip"><span class="icon icon16 icon-plus"></span></a>
+                                                       
+                                                       {event name='itemButtons'}
+                                               </td>
+                                               <td class="columnID columnSubscriptionID">{@$subscription->subscriptionID}</td>
+                                               <td class="columnTitle"><a href="{link controller='PaidSubscriptionEdit' id=$subscription->subscriptionID}{/link}" title="{lang}wcf.acp.paidSubscription.edit{/lang}">{$subscription->title|language}</a></td>
+                                               <td class="columnDigits columnCost">{@$subscription->currency} {$subscription->cost|currency}</td>
+                                               <td class="columnDigits columnSubscriptionLength">{if $subscription->subscriptionLength}{@$subscription->subscriptionLength} {lang}wcf.acp.paidSubscription.subscriptionLengthUnit.{@$subscription->subscriptionLengthUnit}{/lang}{else}&infin;{/if}</td>
+                                               <td class="columnDigits columnShowOrder">{@$subscription->showOrder}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+       </div>
+       
+       <div class="contentNavigation">
+               {@$pagesLinks}
+               
+               <nav>
+                       <ul>
+                               <li><a href="{link controller='PaidSubscriptionAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.paidSubscription.add{/lang}</span></a></li>
+                       
+                               {event name='contentNavigationButtonsBottom'}
+                       </ul>
+               </nav>
+       </div>
+{else}
+       <p class="warning">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/paidSubscriptionTransactionLog.tpl b/wcfsetup/install/files/acp/templates/paidSubscriptionTransactionLog.tpl
new file mode 100644 (file)
index 0000000..9cc4142
--- /dev/null
@@ -0,0 +1,69 @@
+{capture assign='pageTitle'}{lang}wcf.acp.paidSubscription.transactionLog{/lang}: {@$log->logID}{/capture}
+{include file='header'}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.paidSubscription.transactionLog{/lang}: {@$log->logID}</h1>
+</header>
+
+<div class="contentNavigation">
+       <nav>
+               <ul>
+                       <li><a href="{link controller='PaidSubscriptionTransactionLogList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.paidSubscription.transactionLog.list{/lang}</span></a></li>
+               
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+
+<div class="container containerPadding marginTop">
+       <fieldset>
+               <legend>{lang}wcf.acp.paidSubscription.transactionLog{/lang}: {@$log->logID}</legend>
+               
+               <dl>
+                       <dt>{lang}wcf.acp.paidSubscription.transactionLog.logMessage{/lang}</dt>
+                       <dd>{$log->logMessage}</dd>
+                       
+                       {if $log->userID}
+                               <dt>{lang}wcf.user.username{/lang}</dt>
+                               <dd><a href="{link controller='UserEdit' id=$log->userID}{/link}" title="{lang}wcf.acp.user.edit{/lang}">{$log->getUser()->username}</a></dd>
+                       {/if}
+                       
+                       {if $log->subscriptionID}
+                               <dt>{lang}wcf.acp.paidSubscription.subscription{/lang}</dt>
+                               <dd>{$log->getSubscription()->title|language}</dd>
+                       {/if}
+                       
+                       <dt>{lang}wcf.acp.paidSubscription.transactionLog.paymentMethod{/lang}</dt>
+                       <dd>{lang}wcf.payment.{@$log->getPaymentMethodName()}{/lang}</dd>
+                       
+                       <dt>{lang}wcf.acp.paidSubscription.transactionLog.transactionID{/lang}</dt>
+                       <dd>{$log->transactionID}</dd>
+                       
+                       <dt>{lang}wcf.acp.paidSubscription.transactionLog.logTime{/lang}</dt>
+                       <dd>{@$log->logTime|time}</dd>
+               </dl>
+       </fieldset>
+       
+       <fieldset>
+               <legend>{lang}wcf.acp.paidSubscription.transactionLog.transactionDetails{/lang}</legend>
+       
+               <dl>
+                       {foreach from=$log->getTransactionDetails() key=key item=value}
+                               <dt>{$key}</dt>
+                               <dd>{$value}</dd>
+                       {/foreach}
+               </dl>
+       </fieldset>
+</div>
+
+<div class="contentNavigation">
+       <nav>
+               <ul>
+                       <li><a href="{link controller='PaidSubscriptionTransactionLogList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.paidSubscription.transactionLog.list{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtonsBottom'}
+               </ul>
+       </nav>
+</div>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/paidSubscriptionTransactionLogList.tpl b/wcfsetup/install/files/acp/templates/paidSubscriptionTransactionLogList.tpl
new file mode 100644 (file)
index 0000000..666f5b5
--- /dev/null
@@ -0,0 +1,133 @@
+{include file='header' pageTitle='wcf.acp.paidSubscription.transactionLog.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               WCF.TabMenu.init();
+               new WCF.Search.User('#username');
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.paidSubscription.transactionLog.list{/lang}</h1>
+</header>
+
+<form method="post" action="{link controller='PaidSubscriptionTransactionLogList'}{/link}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.global.filter{/lang}</legend>
+                       
+                       <dl>
+                               <dt><label for="transactionID">{lang}wcf.acp.paidSubscription.transactionLog.transactionID{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="transactionID" name="transactionID" value="{$transactionID}" class="long" />
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="username" name="username" value="{$username}" class="long" />
+                               </dd>
+                       </dl>
+                       
+                       {if $availableSubscriptions|count}
+                               <dl>
+                                       <dt><label for="subscriptionID">{lang}wcf.acp.paidSubscription.subscription{/lang}</label></dt>
+                                       <dd>
+                                               <select name="subscriptionID" id="subscriptionID">
+                                                       <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                                       {foreach from=$availableSubscriptions item=availableSubscription}
+                                                               <option value="{@$availableSubscription->subscriptionID}"{if $availableSubscription->subscriptionID == $subscriptionID} selected="selected"{/if}>{$availableSubscription->title|language}</option>
+                                                       {/foreach}
+                                               </select>
+                                       </dd>
+                               </dl>
+                       {/if}
+               </fieldset>
+       </div>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+<div class="contentNavigation">
+       {assign var='linkParameters' value=''}
+       {if $transactionID}{capture append=linkParameters}&transactionID={@$transactionID|rawurlencode}{/capture}{/if}
+       {if $username}{capture append=linkParameters}&username={@$username|rawurlencode}{/capture}{/if}
+       {if $subscriptionID}{capture append=linkParameters}&subscriptionID={@$subscriptionID}{/capture}{/if}
+       
+       {pages print=true assign=pagesLinks controller='PaidSubscriptionTransactionLogList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}
+       
+       {hascontent}
+               <nav>
+                       <ul>
+                               {content}
+                                       {event name='contentNavigationButtonsTop'}
+                               {/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</div>
+
+{if $objects|count}
+       <div class="tabularBox tabularBoxTitle marginTop">
+               <header>
+                       <h2>{lang}wcf.acp.paidSubscription.transactionLog.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+               </header>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID columnLogID{if $sortField == 'logID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionTransactionLogList'}pageNo={@$pageNo}&sortField=logID&sortOrder={if $sortField == 'logID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle columnLogMessage{if $sortField == 'logMessage'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionTransactionLogList'}pageNo={@$pageNo}&sortField=logMessage&sortOrder={if $sortField == 'logMessage' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.paidSubscription.transactionLog.logMessage{/lang}</a></th>
+                                       <th class="columnText columnUsername{if $sortField == 'userID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionTransactionLogList'}pageNo={@$pageNo}&sortField=userID&sortOrder={if $sortField == 'userID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.user.username{/lang}</a></th>
+                                       <th class="columnText columnSubscriptionTitle{if $sortField == 'subscriptionID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionTransactionLogList'}pageNo={@$pageNo}&sortField=subscriptionID&sortOrder={if $sortField == 'subscriptionID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.paidSubscription.subscription{/lang}</a></th>
+                                       <th class="columnText columnPaymentMethod{if $sortField == 'paymentMethodObjectTypeID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionTransactionLogList'}pageNo={@$pageNo}&sortField=paymentMethodObjectTypeID&sortOrder={if $sortField == 'paymentMethodObjectTypeID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.paidSubscription.transactionLog.paymentMethod{/lang}</a></th>
+                                       <th class="columnText columnTransactionID{if $sortField == 'transactionID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionTransactionLogList'}pageNo={@$pageNo}&sortField=transactionID&sortOrder={if $sortField == 'transactionID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.paidSubscription.transactionLog.transactionID{/lang}</a></th>
+                                       <th class="columnDate columnLogTime{if $sortField == 'logTime'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionTransactionLogList'}pageNo={@$pageNo}&sortField=logTime&sortOrder={if $sortField == 'logTime' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.paidSubscription.transactionLog.logTime{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item=log}
+                                       <tr>
+                                               <td class="columnID columnLogID">{@$log->logID}</td>
+                                               <td class="columnTitle columnLogMessage"><a href="{link controller='PaidSubscriptionTransactionLog' id=$log->logID}{/link}">{$log->logMessage}</a></td>
+                                               <td class="columnText columnUsername"><a href="{link controller='UserEdit' id=$log->userID}{/link}" title="{lang}wcf.acp.user.edit{/lang}">{$log->username}</a></td>
+                                               <td class="columnText columnSubscriptionTitle">{$log->title|language}</td>
+                                               <td class="columnText columnPaymentMethod">{lang}wcf.payment.{@$log->getPaymentMethodName()}{/lang}</td>
+                                               <td class="columnText columnTransactionID">{$log->transactionID}</td>
+                                               <td class="columnDate columnLogTime">{@$log->logTime|time}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+               
+       </div>
+       
+       <div class="contentNavigation">
+               {@$pagesLinks}
+               
+               {hascontent}
+                       <nav>
+                               <ul>
+                                       {content}
+                                               {event name='contentNavigationButtonsBottom'}
+                                       {/content}
+                               </ul>
+                       </nav>
+               {/hascontent}
+       </div>
+{else}
+       <p class="warning">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/paidSubscriptionUserAdd.tpl b/wcfsetup/install/files/acp/templates/paidSubscriptionUserAdd.tpl
new file mode 100644 (file)
index 0000000..1513b6b
--- /dev/null
@@ -0,0 +1,82 @@
+{include file='header' pageTitle='wcf.acp.paidSubscription.user.add'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               WCF.TabMenu.init();
+               new WCF.Search.User('#username');
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.paidSubscription.user.add{/lang}</h1>
+       <p>{$subscription->title|language}</p>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.add{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+       <nav>
+               <ul>
+                       <li><a href="{link controller='PaidSubscriptionList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.paidSubscription.list{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtons'}
+               </ul>
+       </nav>
+</div>
+
+<form method="post" action="{link controller='PaidSubscriptionUserAdd' id=$subscriptionID}{/link}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.global.form.data{/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}" autofocus="autofocus" class="medium" />
+                                       {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 $subscription->subscriptionLength}
+                               <dl{if $errorField == 'endDate'} class="formError"{/if}>
+                                       <dt><label for="endDate">{lang}wcf.acp.paidSubscription.user.endDate{/lang}</label></dt>
+                                       <dd>
+                                               <input type="date" id="endDate" name="endDate" value="{$endDate}" class="medium" data-ignore-timezone="true" />
+                                               {if $errorField == 'endDate'}
+                                                       <small class="innerError">
+                                                               {if $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {else}
+                                                                       {lang}wcf.acp.paidSubscription.user.endDate.error.{@$errorType}{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                       </dd>
+                               </dl>
+                       {/if}
+               </fieldset>
+               
+               {event name='fieldsets'}
+       </div>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/paidSubscriptionUserList.tpl b/wcfsetup/install/files/acp/templates/paidSubscriptionUserList.tpl
new file mode 100644 (file)
index 0000000..0165bdb
--- /dev/null
@@ -0,0 +1,85 @@
+{include file='header' pageTitle='wcf.acp.paidSubscription.user.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\paid\\subscription\\user\\PaidSubscriptionUserAction', '.jsPaidSubscriptionUserRow');
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.paidSubscription.user.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+       {pages print=true assign=pagesLinks controller='PaidSubscriptionUserList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+       
+       {hascontent}
+               <nav>
+                       <ul>
+                               {content}
+                                       {event name='contentNavigationButtonsTop'}
+                               {/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</div>
+
+{if $objects|count}
+       <div class="tabularBox tabularBoxTitle marginTop">
+               <header>
+                       <h2>{lang}wcf.acp.paidSubscription.user.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+               </header>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnID columnSubscriptionUserID{if $sortField == 'subscriptionUserID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=subscriptionUserID&sortOrder={if $sortField == 'subscriptionUserID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnText columnUsername{if $sortField == 'userID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=userID&sortOrder={if $sortField == 'userID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.user.username{/lang}</a></th>
+                                       <th class="columnText columnSubscriptionTitle{if $sortField == 'subscriptionID'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=subscriptionID&sortOrder={if $sortField == 'subscriptionID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.paidSubscription.subscription{/lang}</a></th>
+                                       <th class="columnDate columnEndDate{if $sortField == 'endDate'} active {@$sortOrder}{/if}"><a href="{link controller='PaidSubscriptionUserList'}pageNo={@$pageNo}&sortField=endDate&sortOrder={if $sortField == 'endDate' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.paidSubscription.user.endDate{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item=subscriptionUser}
+                                       <tr class="jsPaidSubscriptionUserRow">
+                                               <td class="columnIcon">
+                                                       <span class="icon icon16 icon-remove jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$subscriptionUser->subscriptionUserID}" data-confirm-message="{lang}wcf.acp.paidSubscription.user.delete.confirmMessage{/lang}"></span>
+                                                       
+                                                       {event name='itemButtons'}
+                                               </td>
+                                               <td class="columnID columnSubscriptionUserID">{@$subscriptionUser->subscriptionUserID}</td>
+                                               <td class="columnText columnUsername"><a href="{link controller='UserEdit' id=$subscriptionUser->userID}{/link}" title="{lang}wcf.acp.user.edit{/lang}">{$subscriptionUser->username}</a></td>
+                                               <td class="columnText columnSubscriptionTitle">{$subscriptionUser->title|language}</td>
+                                               <td class="columnDate columnEndDate">{if $subscriptionUser->endDate}{@$subscriptionUser->endDate|time}{/if}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+               
+       </div>
+       
+       <div class="contentNavigation">
+               {@$pagesLinks}
+               
+               {hascontent}
+                       <nav>
+                               <ul>
+                                       {content}
+                                               {event name='contentNavigationButtonsBottom'}
+                                       {/content}
+                               </ul>
+                       </nav>
+               {/hascontent}
+       </div>
+{else}
+       <p class="warning">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/paymentMethodSelectOptionType.tpl b/wcfsetup/install/files/acp/templates/paymentMethodSelectOptionType.tpl
new file mode 100644 (file)
index 0000000..e972b22
--- /dev/null
@@ -0,0 +1,3 @@
+{foreach from=$selectOptions key=key item=selectOption}
+       <label><input type="checkbox" name="values[{$option->optionName}][]" value="{$key}"{if $key|in_array:$value} checked="checked"{/if} /> {lang}{@$selectOption}{/lang}</label>
+{/foreach}
diff --git a/wcfsetup/install/files/lib/acp/form/PaidSubscriptionAddForm.class.php b/wcfsetup/install/files/lib/acp/form/PaidSubscriptionAddForm.class.php
new file mode 100644 (file)
index 0000000..a48d7c5
--- /dev/null
@@ -0,0 +1,323 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\paid\subscription\PaidSubscriptionAction;
+use wcf\data\paid\subscription\PaidSubscription;
+use wcf\data\paid\subscription\PaidSubscriptionEditor;
+use wcf\data\user\group\UserGroup;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\payment\method\PaymentMethodHandler;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\data\paid\subscription\PaidSubscriptionList;
+
+/**
+ * Shows the paid subscription add form.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class PaidSubscriptionAddForm extends AbstractForm {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.paidSubscription';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\page\AbstractPage::$templateName
+       */
+       public $templateName = 'paidSubscriptionAdd';
+       
+       /**
+        * subscription title
+        * @var string
+        */
+       public $title = '';
+       
+       /**
+        * subscription description
+        * @var string
+        */
+       public $description = '';
+       
+       /**
+        * indicates if the subscription is disabled
+        * @var boolean
+        */
+       public $isDisabled = 0;
+       
+       /**
+        * subscription show order
+        * @var integer
+        */
+       public $showOrder = 0;
+       
+       /**
+        * subscription cost
+        * @var double
+        */
+       public $cost = 0.0;
+       
+       /**
+        * subscription currency
+        * @var string
+        */
+       public $currency = 'USD';
+       
+       /**
+        * indicates if the subscription is permanent
+        * @var boolean
+        */
+       public $subscriptionLengthPermanent = 0;
+       
+       /**
+        * subscription length
+        * @var integer
+        */
+       public $subscriptionLength = 0;
+       
+       /**
+        * subscription length unit
+        * @var string
+        */
+       public $subscriptionLengthUnit = '';
+       
+       /**
+        * indicates if the subscription is a recurring payment
+        * @var boolean
+        */
+       public $isRecurring = 0;
+       
+       /**
+        * list of group ids
+        * @var array<intewer>
+        */
+       public $groupIDs = array();
+       
+       /**
+        * list of excluded subscriptions
+        * @var array<integer>
+        */
+       public $excludedSubscriptionIDs = array();
+       
+       /**
+        * available user groups
+        * @var array
+        */
+       public $availableUserGroups = array();
+       
+       /**
+        * list of available currencies
+        * @var array<string>
+        */
+       public $availableCurrencies = array();
+       
+       /**
+        * list of available subscriptions
+        * @var array
+        */
+       public $availableSubscriptions = array();
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               I18nHandler::getInstance()->register('description');
+               I18nHandler::getInstance()->register('title');
+               
+               // get available user groups
+               $this->availableUserGroups = UserGroup::getAccessibleGroups(array(), array(UserGroup::GUESTS, UserGroup::EVERYONE, UserGroup::USERS));
+               
+               // get available currencies
+               foreach (PaymentMethodHandler::getInstance()->getPaymentMethods() as $paymentMethod) {
+                       $this->availableCurrencies = array_merge($this->availableCurrencies, $paymentMethod->getSupportedCurrencies());
+               }
+               $this->availableCurrencies = array_unique($this->availableCurrencies);
+               sort($this->availableCurrencies);
+               
+               // get available subscriptions
+               $this->getAvailableSubscriptions();
+       }
+       
+       protected function getAvailableSubscriptions() {
+               $subscriptionList = new PaidSubscriptionList();
+               $subscriptionList->sqlOrderBy = 'title';
+               $subscriptionList->readObjects();
+               $this->availableSubscriptions = $subscriptionList->getObjects();
+       }
+       
+       /**
+        * @see \wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               // read i18n values
+               I18nHandler::getInstance()->readValues();
+               
+               // handle i18n plain input
+               if (I18nHandler::getInstance()->isPlainValue('description')) $this->description = I18nHandler::getInstance()->getValue('description');
+               if (I18nHandler::getInstance()->isPlainValue('title')) $this->title = I18nHandler::getInstance()->getValue('title');
+               
+               if (!empty($_POST['isDisabled'])) $this->isDisabled = 1;
+               if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
+               if (isset($_POST['cost'])) $this->cost = floatval($_POST['cost']);
+               if (isset($_POST['currency'])) $this->currency = $_POST['currency'];
+               if (!empty($_POST['subscriptionLengthPermanent'])) $this->subscriptionLengthPermanent = 1;
+               if (isset($_POST['subscriptionLength'])) $this->subscriptionLength = intval($_POST['subscriptionLength']);
+               if (isset($_POST['subscriptionLengthUnit'])) $this->subscriptionLengthUnit = $_POST['subscriptionLengthUnit'];
+               if (!empty($_POST['isRecurring'])) $this->isRecurring = 1;
+               if (isset($_POST['groupIDs']) && is_array($_POST['groupIDs'])) $this->groupIDs = ArrayUtil::toIntegerArray($_POST['groupIDs']);
+               if (isset($_POST['excludedSubscriptionIDs']) && is_array($_POST['excludedSubscriptionIDs'])) $this->excludedSubscriptionIDs = ArrayUtil::toIntegerArray($_POST['excludedSubscriptionIDs']);
+       }
+       
+       /**
+        * @see \wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate title
+               if (!I18nHandler::getInstance()->validateValue('title')) {
+                       if (I18nHandler::getInstance()->isPlainValue('title')) {
+                               throw new UserInputException('title');
+                       }
+                       else {
+                               throw new UserInputException('title', 'multilingual');
+                       }
+               }
+               
+               // validate description
+               if (!I18nHandler::getInstance()->validateValue('description', false, true)) {
+                       throw new UserInputException('description');
+               }
+               
+               // validate cost
+               if ($this->cost < 0.01) {
+                       throw new UserInputException('cost');
+               }
+               // validate currency
+               if (!in_array($this->currency, $this->availableCurrencies)) {
+                       throw new UserInputException('cost');
+               }
+               
+               if (!$this->subscriptionLengthPermanent) {
+                       if ($this->subscriptionLength < 1) {
+                               throw new UserInputException('subscriptionLength');
+                       }
+                       if ($this->subscriptionLengthUnit != 'D' && $this->subscriptionLengthUnit != 'M' && $this->subscriptionLengthUnit != 'Y') {
+                               throw new UserInputException('subscriptionLength');
+                       }
+                       if (($this->subscriptionLengthUnit == 'D' && $this->subscriptionLength > 90) || ($this->subscriptionLengthUnit == 'M' && $this->subscriptionLength > 24) || ($this->subscriptionLengthUnit == 'Y' && $this->subscriptionLength > 5)) {
+                               throw new UserInputException('subscriptionLength', 'invalid');
+                       }
+               }
+               
+               // validate group ids
+               if (empty($this->groupIDs)) {
+                       throw new UserInputException('groupIDs');
+               }
+               foreach ($this->groupIDs as $groupID) {
+                       if (!isset($this->availableUserGroups[$groupID])) throw new UserInputException('groupIDs');
+               }
+               // validate excluded subscriptions
+               foreach ($this->excludedSubscriptionIDs as $key => $subscriptionID) {
+                       if (!isset($this->availableSubscriptions[$subscriptionID])) unset($this->excludedSubscriptionIDs[$key]);
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               parent::save();
+               
+               // save subscription
+               $this->objectAction = new PaidSubscriptionAction(array(), 'create', array('data' => array_merge($this->additionalFields, array(
+                       'title' => $this->title,
+                       'description' => $this->description,
+                       'isDisabled' => $this->isDisabled,      
+                       'showOrder' => $this->showOrder,
+                       'cost' => $this->cost,
+                       'currency' => $this->currency,
+                       'subscriptionLength' => $this->subscriptionLength,
+                       'subscriptionLengthUnit' => $this->subscriptionLengthUnit,
+                       'isRecurring' => $this->isRecurring,
+                       'groupIDs' => implode(',', $this->groupIDs),
+                       'excludedSubscriptionIDs' => implode(',', $this->excludedSubscriptionIDs)
+               ))));
+               $returnValues = $this->objectAction->executeAction();
+               
+               // save i18n values
+               $this->saveI18nValue($returnValues['returnValues'], 'description');
+               $this->saveI18nValue($returnValues['returnValues'], 'title');
+               $this->saved();
+               
+               // reset values
+               $this->title = $this->description = '';
+               $this->isDisabled = $this->showOrder = $this->cost = $this->subscriptionLength = $this->isRecurring = 0;
+               $this->currency = 'EUR';
+               $this->groupIDs = array();
+               I18nHandler::getInstance()->reset();
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * Saves i18n values.
+        *
+        * @param       \wcf\data\paid\subscription\PaidSubscription            $subscription
+        * @param       string                                                  $columnName
+        */
+       public function saveI18nValue(PaidSubscription $subscription, $columnName) {
+               if (!I18nHandler::getInstance()->isPlainValue($columnName)) {
+                       I18nHandler::getInstance()->save($columnName, 'wcf.paidSubscription.subscription'.$subscription->subscriptionID.($columnName == 'description' ? '.description' : ''), 'wcf.paidSubscription', 1);
+                               
+                       // update database
+                       $editor = new PaidSubscriptionEditor($subscription);
+                       $editor->update(array(
+                               $columnName => 'wcf.paidSubscription.subscription'.$subscription->subscriptionID.($columnName == 'description' ? '.description' : '')
+                       ));
+               }
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'add',
+                       'isDisabled' => $this->isDisabled,
+                       'showOrder' => $this->showOrder,
+                       'cost' => $this->cost,
+                       'currency' => $this->currency,
+                       'subscriptionLength' => $this->subscriptionLength,
+                       'subscriptionLengthUnit' => $this->subscriptionLengthUnit,
+                       'isRecurring' => $this->isRecurring,
+                       'groupIDs' => $this->groupIDs,
+                       'excludedSubscriptionIDs' => $this->excludedSubscriptionIDs,
+                       'availableCurrencies' => $this->availableCurrencies,
+                       'availableUserGroups' => $this->availableUserGroups,
+                       'availableSubscriptions' => $this->availableSubscriptions
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/PaidSubscriptionEditForm.class.php b/wcfsetup/install/files/lib/acp/form/PaidSubscriptionEditForm.class.php
new file mode 100644 (file)
index 0000000..d2a04aa
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+namespace wcf\acp\form;
+use wcf\form\AbstractForm;
+use wcf\data\paid\subscription\PaidSubscriptionAction;
+use wcf\data\paid\subscription\PaidSubscription;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\data\paid\subscription\PaidSubscriptionList;
+
+/**
+ * Shows the paid subscription edit form.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class PaidSubscriptionEditForm extends PaidSubscriptionAddForm {
+       /**
+        * id of the edited subscription
+        * @var integer
+        */
+       public $subscriptionID = 0;
+       
+       /**
+        * edited subscription object
+        * @var \wcf\data\paid\subscription\PaidSubscription
+        */
+       public $subscription = null;
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               if (isset($_REQUEST['id'])) $this->subscriptionID = intval($_REQUEST['id']);
+               $this->subscription = new PaidSubscription($this->subscriptionID);
+               if (!$this->subscription->subscriptionID) {
+                       throw new PermissionDeniedException();
+               }
+               
+               parent::readParameters();
+       }
+       
+       protected function getAvailableSubscriptions() {
+               $subscriptionList = new PaidSubscriptionList();
+               $subscriptionList->getConditionBuilder()->add('subscriptionID <> ?', array($this->subscriptionID));
+               $subscriptionList->sqlOrderBy = 'title';
+               $subscriptionList->readObjects();
+               $this->availableSubscriptions = $subscriptionList->getObjects();
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+       
+               if (empty($_POST)) {
+                       I18nHandler::getInstance()->setOptions('description', 1, $this->subscription->description, 'wcf.paidSubscription.subscription\d+.description');
+                       I18nHandler::getInstance()->setOptions('title', 1, $this->subscription->title, 'wcf.paidSubscription.subscription\d+');
+                               
+                       $this->isDisabled = $this->subscription->isDisabled;
+                       $this->showOrder = $this->subscription->showOrder;
+                       $this->cost = $this->subscription->cost;
+                       $this->currency = $this->subscription->currency;
+                       $this->subscriptionLength = $this->subscription->subscriptionLength;
+                       $this->subscriptionLengthUnit = $this->subscription->subscriptionLengthUnit;
+                       $this->isRecurring = $this->subscription->isRecurring;
+                       $this->groupIDs = explode(',', $this->subscription->groupIDs);
+                       $this->excludedSubscriptionIDs = explode(',', $this->subscription->excludedSubscriptionIDs);
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               // read i18n values
+               I18nHandler::getInstance()->readValues();
+               
+               // handle i18n plain input
+               if (I18nHandler::getInstance()->isPlainValue('description')) $this->description = I18nHandler::getInstance()->getValue('description');
+               if (I18nHandler::getInstance()->isPlainValue('title')) $this->title = I18nHandler::getInstance()->getValue('title');
+               
+               if (!empty($_POST['isDisabled'])) $this->isDisabled = 1;
+               if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
+               if (isset($_POST['cost'])) $this->cost = floatval($_POST['cost']);
+               if (isset($_POST['currency'])) $this->currency = $_POST['currency'];
+               if (!empty($_POST['subscriptionLengthPermanent'])) $this->subscriptionLengthPermanent = 1;
+               if (isset($_POST['subscriptionLength'])) $this->subscriptionLength = intval($_POST['subscriptionLength']);
+               if (isset($_POST['subscriptionLengthUnit'])) $this->subscriptionLengthUnit = $_POST['subscriptionLengthUnit'];
+               if (!empty($_POST['isRecurring'])) $this->isRecurring = 1;
+               if (isset($_POST['groupIDs']) && is_array($_POST['groupIDs'])) $this->groupIDs = ArrayUtil::toIntegerArray($_POST['groupIDs']);
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               // update description
+               $this->description = 'wcf.paidSubscription.subscription'.$this->subscription->subscriptionID.'.description';
+               if (I18nHandler::getInstance()->isPlainValue('description')) {
+                       I18nHandler::getInstance()->remove($this->description);
+                       $this->description = I18nHandler::getInstance()->getValue('description');
+               }
+               else {
+                       I18nHandler::getInstance()->save('description', $this->description, 'wcf.paidSubscription', 1);
+               }
+               
+               // update title
+               $this->title = 'wcf.paidSubscription.subscription'.$this->subscription->subscriptionID;
+               if (I18nHandler::getInstance()->isPlainValue('title')) {
+                       I18nHandler::getInstance()->remove($this->title);
+                       $this->title = I18nHandler::getInstance()->getValue('title');
+               }
+               else {
+                       I18nHandler::getInstance()->save('title', $this->title, 'wcf.paidSubscription', 1);
+               }
+               
+               // save subscription
+               $this->objectAction = new PaidSubscriptionAction(array($this->subscription), 'update', array('data' => array_merge($this->additionalFields, array(
+                       'title' => $this->title,
+                       'description' => $this->description,
+                       'isDisabled' => $this->isDisabled,      
+                       'showOrder' => $this->showOrder,
+                       'cost' => $this->cost,
+                       'currency' => $this->currency,
+                       'subscriptionLength' => $this->subscriptionLength,
+                       'subscriptionLengthUnit' => $this->subscriptionLengthUnit,
+                       'isRecurring' => $this->isRecurring,
+                       'groupIDs' => implode(',', $this->groupIDs),
+                       'excludedSubscriptionIDs' => implode(',', $this->excludedSubscriptionIDs)
+               ))));
+               $this->objectAction->executeAction();
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+       
+               $useRequestData = (empty($_POST)) ? false : true;
+               I18nHandler::getInstance()->assignVariables($useRequestData);
+       
+               WCF::getTPL()->assign(array(
+                       'action' => 'edit',
+                       'subscriptionID' => $this->subscriptionID,
+                       'subscription' => $this->subscription
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/PaidSubscriptionUserAddForm.class.php b/wcfsetup/install/files/lib/acp/form/PaidSubscriptionUserAddForm.class.php
new file mode 100644 (file)
index 0000000..f567111
--- /dev/null
@@ -0,0 +1,181 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\paid\subscription\PaidSubscription;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\util\StringUtil;
+use wcf\data\user\User;
+use wcf\data\paid\subscription\user\PaidSubscriptionUser;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserAction;
+use wcf\util\DateUtil;
+
+/**
+ * Shows the user subscription add form.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class PaidSubscriptionUserAddForm extends AbstractForm {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.paidSubscription';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * subscription id
+        * @var integer
+        */
+       public $subscriptionID = 0;
+       
+       /**
+        * subscription object
+        * @var \wcf\data\paid\subscription\PaidSubscription
+        */
+       public $subscription = null;
+       
+       /**
+        * username
+        * @var string
+        */
+       public $username = '';
+       
+       /**
+        * user object
+        * @var \wcf\data\user\User
+        */
+       public $user = null;
+       
+       /**
+        * subscription end date
+        * @var string
+        */
+       public $endDate = '';
+       
+       /**
+        * subscription end date
+        * @var \DateTime
+        */
+       public $endDateTime = null;
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->subscriptionID = intval($_REQUEST['id']);
+               $this->subscription = new PaidSubscription($this->subscriptionID);
+               if (!$this->subscription->subscriptionID) {
+                       throw new PermissionDeniedException();
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']);
+               if (isset($_POST['endDate'])) $this->endDate = $_POST['endDate'];
+       }
+       
+       /**
+        * @see \wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               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->subscription->subscrtionLength) {
+                       $this->endDateTime = \DateTime::createFromFormat('Y-m-d', $this->endDate, new \DateTimeZone('UTC'));
+                       if ($this->endDateTime === false || $this->endDateTime->getTimestamp() < TIME_NOW) {
+                               throw new UserInputException('endDate');
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               parent::save();
+               
+               $userSubscription = PaidSubscriptionUser::getSubscriptionUser($this->subscriptionID, $this->user->userID);
+               $data = array();
+               if ($this->subscription->subscrtionLength) {
+                       $data['endDate'] = $this->endDateTime->getTimestamp();
+               }
+               if ($userSubscription === null) {
+                       // create new subscription
+                       $action = new PaidSubscriptionUserAction(array(), 'create', array(
+                               'user' => $this->user,
+                               'subscription' => $this->subscription,
+                               'data' => $data
+                       ));
+                       $returnValues = $action->executeAction();
+                       $userSubscription = $returnValues['returnValues'];
+               }
+               else {
+                       // extend existing subscription
+                       $action = new PaidSubscriptionUserAction(array($userSubscription), 'extend', array('data' => $data));
+                       $action->executeAction();
+               }
+               $this->saved();
+               
+               // reset values
+               $this->username = $this->endDate = '';
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+       
+               if (empty($_POST)) {
+                       if ($this->subscription->subscriptionLength) {
+                               $d = DateUtil::getDateTimeByTimestamp(TIME_NOW);
+                               $d->add($this->subscription->getDateInterval());
+                               $this->endDate = $d->format('Y-m-d');
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'subscriptionID' => $this->subscriptionID,
+                       'subscription' => $this->subscription,
+                       'username' => $this->username,
+                       'endDate' => $this->endDate
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/PaidSubscriptionListPage.class.php b/wcfsetup/install/files/lib/acp/page/PaidSubscriptionListPage.class.php
new file mode 100644 (file)
index 0000000..5df9804
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Shows the list of paid subscriptions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.page
+ * @category   Community Framework
+ */
+class PaidSubscriptionListPage extends SortablePage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.paidSubscription.list';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\page\SortablePage::$defaultSortField
+        */
+       public $defaultSortField = 'showOrder';
+       
+       /**
+        * @see \wcf\page\SortablePage::$validSortFields
+        */
+       public $validSortFields = array('subscriptionID', 'title', 'showOrder', 'cost', 'subscriptionLength');
+       
+       /**
+        * @see \wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\paid\subscription\PaidSubscriptionList';
+}
diff --git a/wcfsetup/install/files/lib/acp/page/PaidSubscriptionTransactionLogListPage.class.php b/wcfsetup/install/files/lib/acp/page/PaidSubscriptionTransactionLogListPage.class.php
new file mode 100644 (file)
index 0000000..dc80fdd
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+use wcf\util\StringUtil;
+use wcf\system\WCF;
+use wcf\system\cache\builder\PaidSubscriptionCacheBuilder;
+
+/**
+ * Shows the list of paid subscription transactions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.page
+ * @category   Community Framework
+ */
+class PaidSubscriptionTransactionLogListPage extends SortablePage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.paidSubscription.transactionLog.list';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\page\SortablePage::$defaultSortField
+        */
+       public $defaultSortField = 'logTime';
+       
+       /**
+        * @see \wcf\page\SortablePage::$defaultSortOrder
+        */
+       public $defaultSortOrder = 'DESC';
+       
+       /**
+        * @see \wcf\page\SortablePage::$validSortFields
+        */
+       public $validSortFields = array('logID', 'subscriptionUserID', 'userID', 'subscriptionID', 'paymentMethodObjectTypeID', 'logTime', 'transactionID', 'logMessage');
+       
+       /**
+        * @see \wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\paid\subscription\transaction\log\PaidSubscriptionTransactionLogList';
+       
+       /**
+        * transaction id
+        * @var string
+        */
+       public $transactionID = '';
+       
+       /**
+        * username
+        * @var string
+        */
+       public $username = '';
+       
+       /**
+        * subscription id
+        * @var integer
+        */
+       public $subscriptionID = 0;
+       
+       /**
+        * @see \wcf\page\AbstractPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['transactionID'])) $this->transactionID = StringUtil::trim($_REQUEST['transactionID']);
+               if (isset($_REQUEST['username'])) $this->username = StringUtil::trim($_REQUEST['username']);
+               if (isset($_REQUEST['subscriptionID'])) $this->subscriptionID = intval($_REQUEST['subscriptionID']);
+       }
+       
+       /**
+        * Initializes DatabaseObjectList instance.
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+       
+               if ($this->transactionID) {
+                       $this->objectList->getConditionBuilder()->add('paid_subscription_transaction_log.transactionID LIKE ?', array('%' . $this->transactionID . '%'));
+               }
+               if ($this->username) {
+                       $this->objectList->getConditionBuilder()->add('paid_subscription_transaction_log.userID IN (SELECT userID FROM wcf'.WCF_N.'_user WHERE username LIKE ?)', array('%' . $this->username . '%'));
+               }
+               if ($this->subscriptionID) {
+                       $this->objectList->getConditionBuilder()->add('paid_subscription_transaction_log.subscriptionID = ?', array($this->subscriptionID));
+               }
+               
+               $this->objectList->sqlSelects = 'user_table.username, paid_subscription.title';
+               $this->objectList->sqlJoins = "LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = paid_subscription_transaction_log.userID)";
+               $this->objectList->sqlJoins .= " LEFT JOIN wcf".WCF_N."_paid_subscription paid_subscription ON (paid_subscription.subscriptionID = paid_subscription_transaction_log.subscriptionID)";
+       }
+       
+       /**
+        * @see \wcf\page\AbstractPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+       
+               WCF::getTPL()->assign(array(
+                       'transactionID' => $this->transactionID,
+                       'username' => $this->username,
+                       'subscriptionID' => $this->subscriptionID,
+                       'availableSubscriptions' => PaidSubscriptionCacheBuilder::getInstance()->getData()
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/PaidSubscriptionTransactionLogPage.class.php b/wcfsetup/install/files/lib/acp/page/PaidSubscriptionTransactionLogPage.class.php
new file mode 100644 (file)
index 0000000..64901db
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\paid\subscription\transaction\log\PaidSubscriptionTransactionLog;
+use wcf\page\AbstractPage;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Shows transaction details.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.page
+ * @category   Community Framework
+ */
+class PaidSubscriptionTransactionLogPage extends AbstractPage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.paidSubscription';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+       
+               if (isset($_REQUEST['id'])) $this->logID = intval($_REQUEST['id']);
+               $this->log = new PaidSubscriptionTransactionLog($this->logID);
+               if (!$this->log->logID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'logID' => $this->logID,
+                       'log' => $this->log
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/PaidSubscriptionUserListPage.class.php b/wcfsetup/install/files/lib/acp/page/PaidSubscriptionUserListPage.class.php
new file mode 100644 (file)
index 0000000..1eed34c
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Shows the list of paid subscription users.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.page
+ * @category   Community Framework
+ */
+class PaidSubscriptionUserListPage extends SortablePage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.paidSubscription.user.list';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\page\SortablePage::$defaultSortField
+        */
+       public $defaultSortField = 'userID';
+       
+       /**
+        * @see \wcf\page\SortablePage::$validSortFields
+        */
+       public $validSortFields = array('subscriptionUserID', 'userID', 'subscriptionID', 'startDate', 'endDate');
+       
+       /**
+        * @see \wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\paid\subscription\user\PaidSubscriptionUserList';
+       
+       /**
+        * Initializes DatabaseObjectList instance.
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               $this->objectList->getConditionBuilder()->add('isActive = ?', array(1));
+               $this->objectList->sqlSelects = 'user_table.username, paid_subscription.title';
+               $this->objectList->sqlJoins = "LEFT JOIN wcf".WCF_N."_user user_table ON (user_table.userID = paid_subscription_user.userID)";
+               $this->objectList->sqlJoins .= " LEFT JOIN wcf".WCF_N."_paid_subscription paid_subscription ON (paid_subscription.subscriptionID = paid_subscription_user.subscriptionID)";
+       }
+}
diff --git a/wcfsetup/install/files/lib/action/PaypalCallbackAction.class.php b/wcfsetup/install/files/lib/action/PaypalCallbackAction.class.php
new file mode 100644 (file)
index 0000000..93e1bbf
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+namespace wcf\action;
+use wcf\action\AbstractAction;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\exception\SystemException;
+use wcf\system\payment\type\IPaymentType;
+use wcf\util\HTTPRequest;
+
+/**
+ * Handles Paypal callbacks.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 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 PaypalCallbackAction extends AbstractAction {
+       /**
+        * @see \wcf\action\IAction::execute()
+        */
+       public function execute() {
+               parent::execute();
+               
+               // check response
+               $processor = null;
+               try {
+                       // post back to paypal to validate 
+                       $content = '';
+                       try {
+                               $request = new HTTPRequest('http://www.paypal.com', array(), array_merge(array('cmd' => '_notify-validate'), $_POST));
+                               $request->execute();
+                               $reply = $request->getReply();
+                               $content = $reply['body'];
+                       }
+                       catch (SystemException $e) {
+                               throw new SystemException('connection to paypal.com failed');
+                       }
+               
+                       if (strstr($content, "VERIFIED") === false) {
+                               throw new SystemException('request not validated');
+                       }
+                       
+                       // Check that receiver_email is your Primary PayPal email
+                       if (!isset($_POST['receiver_email']) || (strtolower($_POST['receiver_email']) != strtolower(PAYPAL_EMAIL_ADDRESS))) {
+                               throw new SystemException('invalid receiver_email');
+                       }
+                               
+                       // get token
+                       if (!isset($_POST['custom'])) {
+                               throw new SystemException('invalid custom item');
+                       }
+                       $tokenParts = explode(':', $_POST['custom'], 2);
+                       if (count($tokenParts) != 2) {
+                               throw new SystemException('invalid custom item');
+                       }
+                       // get payment type object type
+                       $objectType = ObjectTypeCache::getInstance()->getObjectType(intval($tokenParts[0]));
+                       if ($objectType === null || !($objectType->getProcessor() instanceof IPaymentType)) {
+                               throw new SystemException('invalid payment type id');
+                       }
+                       $processor = $objectType->getProcessor();
+                       
+                       // get status
+                       $status = '';
+                       if ($_POST['txn_type'] == 'web_accept' || $_POST['txn_type'] == 'subscr_payment') {
+                               if ($_POST['payment_status'] == 'Completed') {
+                                       $status = 'completed';
+                               }  
+                       }
+                       if ($_POST['payment_status'] == 'Refunded' || $_POST['payment_status'] == 'Reversed') {
+                               $status = 'reversed';
+                       }
+                       if ($_POST['payment_status'] == 'Canceled_Reversal') {
+                               $status = 'canceled_reversal';
+                       }
+                       
+                       if ($status) {
+                               $processor->processTransaction(ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.payment.method', 'com.woltlab.wcf.payment.method.paypal'), $tokenParts[1], $_POST['mc_gross'], $_POST['mc_currency'], $_POST['txn_id'], $status, $_POST);
+                       }
+               }
+               catch (SystemException $e) {
+                       @header('HTTP/1.1 500 Internal Server Error');
+                       echo $e->getMessage();
+                       exit;
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/PaidSubscription.class.php b/wcfsetup/install/files/lib/data/paid/subscription/PaidSubscription.class.php
new file mode 100644 (file)
index 0000000..ca0883a
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+namespace wcf\data\paid\subscription;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\DatabaseObject;
+use wcf\system\payment\method\PaymentMethodHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * Represents a paid subscription.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription
+ * @category   Community Framework
+ */
+class PaidSubscription extends DatabaseObject {
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'paid_subscription';
+       
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseIndexName
+        */
+       protected static $databaseTableIndexName = 'subscriptionID';
+       
+       /**
+        * Returns list of purchase buttons.
+        * 
+        * @return array<string>
+        */
+       public function getPurchaseButtons() {
+               $objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.payment.type', 'com.woltlab.wcf.payment.type.paidSubscription');
+               $buttons = array();
+               foreach (PaymentMethodHandler::getInstance()->getPaymentMethods() as $paymentMethod) {
+                       // check if payment method supports recurring payments
+                       if ($this->isRecurring && !$paymentMethod->supportsRecurringPayments()) continue;
+                       
+                       // check supported currencies
+                       if (!in_array($this->currency, $paymentMethod->getSupportedCurrencies())) continue;
+                       
+                       $buttons[] = $paymentMethod->getPurchaseButton($this->cost, $this->currency, WCF::getLanguage()->get($this->title), $objectTypeID . ':' . WCF::getUser()->userID . ':' . $this->subscriptionID , LinkHandler::getInstance()->getLink('PaidSubscriptionReturn'), LinkHandler::getInstance()->getLink(), $this->isRecurring, $this->subscriptionLength, $this->subscriptionLengthUnit);
+               }
+               
+               return $buttons;
+       }
+       
+       /**
+        * Returns a DateInterval object based on subscription length.
+        * 
+        * @return \DateInterval
+        */
+       public function getDateInterval() {
+               return new \DateInterval('P' . $this->subscriptionLength . $this->subscriptionLengthUnit);
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionAction.class.php b/wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionAction.class.php
new file mode 100644 (file)
index 0000000..6c6be26
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+namespace wcf\data\paid\subscription;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IToggleAction;
+
+/**
+ * Executes paid subscription-related actions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription
+ * @category   Community Framework
+ */
+class PaidSubscriptionAction extends AbstractDatabaseObjectAction implements IToggleAction {
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+        */
+       protected $permissionsDelete = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+        */
+       protected $permissionsUpdate = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
+        */
+       protected $requireACP = array('create', 'delete', 'toggle', 'update');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::create()
+        */
+       public function create() {
+               $showOrder = 0;
+               if (isset($this->parameters['data']['showOrder'])) {
+                       $showOrder = $this->parameters['data']['showOrder'];
+                       unset($this->parameters['data']['showOrder']);
+               }
+       
+               $subscription = parent::create();
+               $editor = new PaidSubscriptionEditor($subscription);
+               $editor->setShowOrder($showOrder);
+       
+               return new PaidSubscription($subscription->subscriptionID);
+       }
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::update()
+        */
+       public function update() {
+               parent::update();
+       
+               if (count($this->objects) == 1 && isset($this->parameters['data']['showOrder']) && $this->parameters['data']['showOrder'] != reset($this->objects)->showOrder) {
+                       reset($this->objects)->setShowOrder($this->parameters['data']['showOrder']);
+               }
+       }
+       
+       /**
+        * @see \wcf\data\IToggleAction::toggle()
+        */
+       public function toggle() {
+               foreach ($this->objects as $object) {
+                       $object->update(array(
+                               'isDisabled' => $object->isDisabled ? 0 : 1
+                       ));
+               }
+       }
+       
+       /**
+        * @see \wcf\data\IToggleAction::validateToggle()
+        */
+       public function validateToggle() {
+               parent::validateUpdate();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionEditor.class.php b/wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionEditor.class.php
new file mode 100644 (file)
index 0000000..02e9330
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+namespace wcf\data\paid\subscription;
+use wcf\data\DatabaseObjectEditor;
+use wcf\data\IEditableCachedObject;
+use wcf\system\cache\builder\PaidSubscriptionCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Provides functions to edit paid subscriptions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription
+ * @category   Community Framework
+ */
+class PaidSubscriptionEditor extends DatabaseObjectEditor implements IEditableCachedObject {
+       /**
+        * @see \wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\paid\subscription\PaidSubscription';
+       
+       /**
+        * Sets the show order of the subscription.
+        *
+        * @param       integer         $showOrder
+        */
+       public function setShowOrder($showOrder = 0) {
+               $newShowOrder = 1;
+       
+               $sql = "SELECT  MAX(showOrder)
+                       FROM    wcf".WCF_N."_paid_subscription";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+               $maxShowOrder = $statement->fetchColumn();
+               if (!$maxShowOrder) $maxShowOrder = 0;
+       
+               if (!$showOrder || $showOrder > $maxShowOrder) {
+                       $newShowOrder = $maxShowOrder + 1;
+               }
+               else {
+                       // shift other subscriptions
+                       $sql = "UPDATE  wcf".WCF_N."_paid_subscription
+                               SET     showOrder = showOrder + 1
+                               WHERE   showOrder >= ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute(array(
+                               $showOrder
+                       ));
+                               
+                       $newShowOrder = $showOrder;
+               }
+       
+               $this->update(array(
+                       'showOrder' => $newShowOrder
+               ));
+       }
+       
+       /**
+        * @see \wcf\data\IEditableCachedObject::resetCache()
+        */
+       public static function resetCache() {
+               PaidSubscriptionCacheBuilder::getInstance()->reset();
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionList.class.php b/wcfsetup/install/files/lib/data/paid/subscription/PaidSubscriptionList.class.php
new file mode 100644 (file)
index 0000000..05710aa
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+namespace wcf\data\paid\subscription;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of paid subscriptions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription
+ * @category   Community Framework
+ */
+class PaidSubscriptionList extends DatabaseObjectList { }
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLog.class.php b/wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLog.class.php
new file mode 100644 (file)
index 0000000..c9503b9
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+namespace wcf\data\paid\subscription\transaction\log;
+use wcf\data\DatabaseObject;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\paid\subscription\PaidSubscription;
+use wcf\data\user\User;
+use wcf\system\WCF;
+
+/**
+ * Represents a paid subscription transaction log entry.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription.transaction.log
+ * @category   Community Framework
+ */
+class PaidSubscriptionTransactionLog extends DatabaseObject {
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'paid_subscription_transaction_log';
+       
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseIndexName
+        */
+       protected static $databaseTableIndexName = 'logID';
+       
+       /**
+        * user object
+        * @var \wcf\data\user\User
+        */
+       protected $user = null;
+       
+       /**
+        * paid subscription object
+        * @var \wcf\data\paid\subscription\PaidSubscription
+        */
+       protected $subscription = null;
+       
+       /**
+        * Returns the payment method of this transaction.
+        * 
+        * @return      string
+        */
+       public function getPaymentMethodName() {
+               $objectType = ObjectTypeCache::getInstance()->getObjectType($this->paymentMethodObjectTypeID);
+               return $objectType->objectType;
+       }
+       
+       /**
+        * Returns transaction details.
+        * 
+        * @return      array
+        */
+       public function getTransactionDetails() {
+               return unserialize($this->transactionDetails);
+       }
+       
+       /**
+        * Returns the user of this transaction.
+        * 
+        * @return      \wcf\data\user\User
+        */
+       public function getUser() {
+               if ($this->user === null) {
+                       $this->user = new User($this->userID);
+               }
+               
+               return $this->user;
+       }
+       
+       /**
+        * Returns the paid subscription of this transaction.
+        *
+        * @return      \wcf\data\paid\subscription\PaidSubscription
+        */
+       public function getSubscription() {
+               if ($this->subscription === null) {
+                       $this->subscription = new PaidSubscription($this->subscriptionID);
+               }
+       
+               return $this->subscription;
+       }
+       
+       /**
+        * Gets a transaction log entry by transaction id.
+        *
+        * @param       integer         $paymentMethodObjectTypeID
+        * @param       string          $transactionID
+        * @return      \wcf\data\paid\subscription\transaction\log\PaidSubscriptionTransactionLog
+        */
+       public static function getLogByTransactionID($paymentMethodObjectTypeID, $transactionID) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_paid_subscription_transaction_log
+                       WHERE   paymentMethodObjectTypeID = ?
+                               AND transactionID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($paymentMethodObjectTypeID, $transactionID));
+               $row = $statement->fetchArray();
+               if ($row !== false) {
+                       return new PaidSubscriptionTransactionLog(null, $row);
+               }
+               
+               return null;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogAction.class.php b/wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogAction.class.php
new file mode 100644 (file)
index 0000000..17a8b01
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+namespace wcf\data\paid\subscription\transaction\log;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes paid subscription transaction log-related actions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription.transaction.log
+ * @category   Community Framework
+ */
+class PaidSubscriptionTransactionLogAction extends AbstractDatabaseObjectAction {}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogEditor.class.php b/wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogEditor.class.php
new file mode 100644 (file)
index 0000000..0ddac68
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\paid\subscription\transaction\log;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit paid subscription transaction log entries.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription.transaction.log
+ * @category   Community Framework
+ */
+class PaidSubscriptionTransactionLogEditor extends DatabaseObjectEditor {
+       /**
+        * @see \wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\paid\subscription\transaction\log\PaidSubscriptionTransactionLog';
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogList.class.php b/wcfsetup/install/files/lib/data/paid/subscription/transaction/log/PaidSubscriptionTransactionLogList.class.php
new file mode 100644 (file)
index 0000000..6e6647f
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+namespace wcf\data\paid\subscription\transaction\log;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of paid subscription transaction log entries.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription.transaction.log
+ * @category   Community Framework
+ */
+class PaidSubscriptionTransactionLogList extends DatabaseObjectList { }
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUser.class.php b/wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUser.class.php
new file mode 100644 (file)
index 0000000..d1acb2f
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+namespace wcf\data\paid\subscription\user;
+use wcf\data\paid\subscription\PaidSubscription;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a paid subscription user.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription.user
+ * @category   Community Framework
+ */
+class PaidSubscriptionUser extends DatabaseObject {
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'paid_subscription_user';
+       
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseIndexName
+        */
+       protected static $databaseTableIndexName = 'subscriptionUserID';
+       
+       /**
+        * paid subscription object
+        * @var \wcf\data\paid\subscription\PaidSubscription
+        */
+       protected $subscription = null;
+       
+       /**
+        * Gets the paid subscription object.
+        * 
+        * @return \wcf\data\paid\subscription\PaidSubscription
+        */
+       public function getSubscription() {
+               if ($this->subscription === null) {
+                       $this->subscription = new PaidSubscription($this->subscriptionID);
+               }
+               
+               return $this->subscription;
+       }
+       
+       /**
+        * Sets the paid subscription object.
+        *
+        * @param \wcf\data\paid\subscription\PaidSubscription          $subscription
+        */
+       public function setSubscription(PaidSubscription $subscription) {
+               $this->subscription = $subscription;
+       }
+       
+       /**
+        * Gets a specific subscription user.
+        * 
+        * @param       integer         $subscriptionID
+        * @param       integer         $userID
+        * @return      \wcf\data\paid\subscription\user\PaidSubscriptionUser
+        */
+       public static function getSubscriptionUser($subscriptionID, $userID) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_paid_subscription_user
+                       WHERE   subscriptionID = ?
+                               AND userID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($subscriptionID, $userID));
+               $row = $statement->fetchArray();
+               if ($row !== false) {
+                       return new PaidSubscriptionUser(null, $row);
+               }
+               
+               return null;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserAction.class.php b/wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserAction.class.php
new file mode 100644 (file)
index 0000000..cfc5c4d
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+namespace wcf\data\paid\subscription\user;
+use wcf\data\paid\subscription\PaidSubscription;
+use wcf\data\user\group\UserGroup;
+use wcf\data\user\UserAction;
+use wcf\data\user\User;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\UserInputException;
+use wcf\util\DateUtil;
+
+/**
+ * Executes paid subscription user-related actions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription.user
+ * @category   Community Framework
+ */
+class PaidSubscriptionUserAction extends AbstractDatabaseObjectAction {
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+        */
+       protected $permissionsDelete = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+        */
+       protected $permissionsUpdate = array('admin.paidSubscription.canManageSubscription');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
+        */
+       protected $requireACP = array('create', 'delete', 'update');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::create()
+        */
+       public function create() {
+               $this->parameters['data']['subscriptionID'] = $this->parameters['subscription']->subscriptionID;
+               $this->parameters['data']['userID'] = $this->parameters['user']->userID;
+               if (!isset($this->parameters['data']['startDate'])) $this->parameters['data']['startDate'] = TIME_NOW;
+               if (!isset($this->parameters['data']['endDate'])) {
+                       if (!$this->parameters['subscription']->subscriptionLength) {
+                               $this->parameters['data']['endDate'] = 0;
+                       }
+                       else {
+                               $d = DateUtil::getDateTimeByTimestamp($this->parameters['data']['startDate']);
+                               $d->add($this->parameters['subscription']->getDateInterval());
+                               $this->parameters['data']['endDate'] = $d->getTimestamp();
+                       }
+               }
+               if (!isset($this->parameters['data']['isActive'])) $this->parameters['data']['isActive'] = 1;
+               
+               $subscriptionUser = parent::create();
+               
+               // update group memberships
+               $action = new PaidSubscriptionUserAction(array($subscriptionUser), 'addGroupMemberships');
+               $action->executeAction();
+               
+               return $subscriptionUser;
+       }
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::validateCreate()
+        */
+       public function validateCreate() {
+               parent::validateCreate();
+               
+               if (!isset($this->parameters['subscription']) || !($this->parameters['subscription'] instanceof PaidSubscription)) {
+                       throw new UserInputException('subscription');
+               }
+               if (!isset($this->parameters['user']) || !($this->parameters['user'] instanceof User)) {
+                       throw new UserInputException('user');
+               }
+       }
+       
+       /**
+        * Extends an existing subscription.
+        */
+       public function extend() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               foreach ($this->objects as $subscriptionUser) {
+                       $endDate = 0;
+                       if (!isset($this->parameters['data']['endDate'])) {
+                               $subscription = $subscriptionUser->getSubscription();
+                               if ($subscription->subscriptionLength) {
+                                       $d = DateUtil::getDateTimeByTimestamp(TIME_NOW);
+                                       $d->add($subscription->getDateInterval());
+                                       $endDate = $d->getTimestamp();
+                               }
+                       }
+                       else {
+                               $endDate = $this->parameters['data']['endDate'];
+                       }
+                       
+                       $subscriptionUser->update(array(
+                               'endDate' => $endDate,
+                               'isActive' => 1
+                       ));
+                       
+                       if (!$subscriptionUser->isActive) {
+                               // update group memberships
+                               $action = new PaidSubscriptionUserAction(array($subscriptionUser), 'addGroupMemberships');
+                               $action->executeAction();
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::delete()
+        */
+       public function delete() {
+               $this->revoke();
+               
+               parent::delete();
+       }
+       
+       /**
+        * Revokes an existing subscription.
+        */
+       public function revoke() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               foreach ($this->objects as $subscriptionUser) {
+                       $subscriptionUser->update(array('isActive' => 0));
+                       
+                       // update group memberships
+                       $action = new PaidSubscriptionUserAction(array($subscriptionUser), 'removeGroupMemberships');
+                       $action->executeAction();
+               }
+       }
+       
+       /**
+        * Validates the revoke action.
+        */
+       public function validateRevoke() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               foreach ($this->objects as $subscriptionUser) {
+                       if (!$subscriptionUser->isActive) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+       }
+       
+       /**
+        * Restores an existing subscription.
+        */
+       public function restore() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               foreach ($this->objects as $subscriptionUser) {
+                       $subscriptionUser->update(array('isActive' => 1));
+                               
+                       // update group memberships
+                       $action = new PaidSubscriptionUserAction(array($subscriptionUser), 'addGroupMemberships');
+                       $action->executeAction();
+               }
+       }
+       
+       /**
+        * Validates the restore action.
+        */
+       public function validateRestore() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+       
+               foreach ($this->objects as $subscriptionUser) {
+                       if ($subscriptionUser->isActive) {
+                               throw new UserInputException('objectIDs');
+                       }
+               }
+       }
+       
+       /**
+        * Applies group memberships.
+        */
+       public function addGroupMemberships() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               foreach ($this->objects as $subscriptionUser) {
+                       $groupIDs = array();
+                       foreach (explode(',', $subscriptionUser->getSubscription()->groupIDs) as $groupID) {
+                               if (UserGroup::getGroupByID($groupID) !== null) {
+                                       $groupIDs[] = $groupID;
+                               }
+                       }
+                       if (!empty($groupIDs)) {
+                               $action = new UserAction(array($subscriptionUser->userID), 'addToGroups', array(
+                                       'groups' => $groupIDs,
+                                       'deleteOldGroups' => false,
+                                       'addDefaultGroups' => false
+                               ));
+                               $action->executeAction();
+                       }
+               }
+       }
+       
+       /**
+        * Removes group memberships.
+        */
+       public function removeGroupMemberships() {
+               if (empty($this->objects)) {
+                       $this->readObjects();
+               }
+               
+               foreach ($this->objects as $subscriptionUser) {
+                       $groupIDs = array();
+                       foreach (explode(',', $subscriptionUser->getSubscription()->groupIDs) as $groupID) {
+                               if (UserGroup::getGroupByID($groupID) !== null) {
+                                       $groupIDs[] = $groupID;
+                               }
+                       }
+                       if (!empty($groupIDs)) {
+                               $action = new UserAction(array($subscriptionUser->userID), 'removeFromGroups', array(
+                                       'groups' => $groupIDs,
+                               ));
+                               $action->executeAction();
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserEditor.class.php b/wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserEditor.class.php
new file mode 100644 (file)
index 0000000..efa62f2
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\paid\subscription\user;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit paid subscription users.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription.user
+ * @category   Community Framework
+ */
+class PaidSubscriptionUserEditor extends DatabaseObjectEditor {
+       /**
+        * @see \wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\paid\subscription\user\PaidSubscriptionUser';
+}
diff --git a/wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserList.class.php b/wcfsetup/install/files/lib/data/paid/subscription/user/PaidSubscriptionUserList.class.php
new file mode 100644 (file)
index 0000000..115017f
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+namespace wcf\data\paid\subscription\user;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of paid subscription users.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.paid.subscription.user
+ * @category   Community Framework
+ */
+class PaidSubscriptionUserList extends DatabaseObjectList { }
diff --git a/wcfsetup/install/files/lib/page/PaidSubscriptionListPage.class.php b/wcfsetup/install/files/lib/page/PaidSubscriptionListPage.class.php
new file mode 100644 (file)
index 0000000..f9aa379
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+namespace wcf\page;
+use wcf\system\cache\builder\PaidSubscriptionCacheBuilder;
+use wcf\system\menu\user\UserMenu;
+use wcf\system\WCF;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+
+/**
+ * Shows a list of the available paid subscriptions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage page
+ * @category   Community Framework
+ */
+class PaidSubscriptionListPage extends AbstractPage {
+       /**
+        * @see \wcf\page\AbstractPage::$loginRequired
+        */
+       public $loginRequired = true;
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededModules
+        */
+       public $neededModules = array('MODULE_PAID_SUBSCRIPTION');
+       
+       /**
+        * list of available paid subscriptions
+        * @var array
+        */
+       public $subscriptions = array();
+       
+       /**
+        * list of user subscriptions
+        * @var \wcf\data\paid\subscription\user\PaidSubscriptionUserList
+        */
+       public $userSubscriptionList = array();
+       
+       /**
+        * @see \wcf\page\AbstractPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               // get available subscriptions
+               $this->subscriptions = PaidSubscriptionCacheBuilder::getInstance()->getData();
+               
+               // get user subscriptions
+               $this->userSubscriptionList = new PaidSubscriptionUserList();
+               $this->userSubscriptionList->getConditionBuilder()->add('userID = ?', array(WCF::getUser()->userID));
+               $this->userSubscriptionList->getConditionBuilder()->add('isActive = ?', array(1));
+               $this->userSubscriptionList->readObjects();
+               
+               foreach ($this->userSubscriptionList as $userSubscription) {
+                       if (isset($this->subscriptions[$userSubscription->subscriptionID])) {
+                               $userSubscription->setSubscription($this->subscriptions[$userSubscription->subscriptionID]);
+                               unset($this->subscriptions[$userSubscription->subscriptionID]);
+                       }
+               }
+               foreach ($this->userSubscriptionList as $userSubscription) {
+                       if ($userSubscription->getSubscription()->excludedSubscriptionIDs) {
+                               foreach (explode(',', $userSubscription->getSubscription()->excludedSubscriptionIDs) as $subscriptionID) {
+                                       if (isset($this->subscriptions[$subscriptionID])) unset($this->subscriptions[$subscriptionID]);
+                               }       
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\page\AbstractPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'subscriptions' => $this->subscriptions,
+                       'userSubscriptions' => $this->userSubscriptionList
+               ));
+       }
+       
+       /**
+        * @see \wcf\page\Page::show()
+        */
+       public function show() {
+               // set active tab
+               UserMenu::getInstance()->setActiveMenuItem('wcf.user.menu.settings.paidSubscription');
+               
+               parent::show();
+       }
+}
diff --git a/wcfsetup/install/files/lib/page/PaidSubscriptionReturnPage.class.php b/wcfsetup/install/files/lib/page/PaidSubscriptionReturnPage.class.php
new file mode 100644 (file)
index 0000000..d6ff4f6
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\page;
+use wcf\system\WCF;
+
+/**
+ * Shows the paid subscription return message.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage page
+ * @category   Community Framework
+ */
+class PaidSubscriptionReturn extends AbstractPage {
+       /**
+        * @see \wcf\page\AbstractPage::$templateName
+        */
+       public $templateName = 'redirect';
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'message' => WCF::getLanguage()->get('wcf.paidSubscription.returnMessage')
+               ));
+       }
+}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/cache/builder/PaidSubscriptionCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/PaidSubscriptionCacheBuilder.class.php
new file mode 100644 (file)
index 0000000..9b069c4
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\paid\subscription\PaidSubscriptionList;
+
+/**
+ * Caches the paid subscriptions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 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 PaidSubscriptionCacheBuilder extends AbstractCacheBuilder {
+       /**
+        * @see \wcf\system\cache\builder\AbstractCacheBuilder::rebuild()
+        */
+       protected function rebuild(array $parameters) {
+               $subscriptionList = new PaidSubscriptionList();
+               $subscriptionList->getConditionBuilder()->add('isDisabled = ?', array(0));
+               $subscriptionList->sqlOrderBy = 'showOrder';
+               $subscriptionList->readObjects();
+               
+               return $subscriptionList->getObjects();
+       }
+}
index 255fd7a74e6807979235da64e827d8c7dea68b21..3928ff93f08fe8c75ac532b1457f23bb2ff0cc16 100644 (file)
@@ -1,5 +1,8 @@
 <?php
 namespace wcf\system\cronjob;
+use wcf\data\cronjob\Cronjob;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserAction;
 
 /**
  * Cronjob for a hourly system cleanup.
@@ -11,4 +14,21 @@ namespace wcf\system\cronjob;
  * @subpackage system.cronjob
  * @category   Community Framework
  */
-class HourlyCleanUpCronjob extends AbstractCronjob { }
+class HourlyCleanUpCronjob extends AbstractCronjob {
+       /**
+        * @see \wcf\system\cronjob\ICronjob::execute()
+        */
+       public function execute(Cronjob $cronjob) {
+               // disable expired paid subscriptions
+               if (MODULE_PAID_SUBSCRIPTION) {
+                       $subscriptionUser = new PaidSubscriptionUserList();
+                       $subscriptionUser->getConditionBuilder()->add('isActive = ?', array(1));
+                       $subscriptionUser->getConditionBuilder()->add('endDate > 0 AND endDate < ?', array(TIME_NOW));
+                       $subscriptionUser->readObjects();
+                       
+                       if (count($subscriptionUser->getObjects())) {
+                               $action = new PaidSubscriptionUserAction(array($subscriptionUser->getObjects()), 'revoke');
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/dashboard/box/PaidSubscriptionsDashboardBox.class.php b/wcfsetup/install/files/lib/system/dashboard/box/PaidSubscriptionsDashboardBox.class.php
new file mode 100644 (file)
index 0000000..d9f3855
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\dashboard\box\DashboardBox;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\page\IPage;
+use wcf\system\cache\builder\PaidSubscriptionCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Dashboard box for paid subscriptions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.dashboard.box
+ * @category   Community Framework
+ */
+class PaidSubscriptionsDashboardBox extends AbstractContentDashboardBox {
+       /**
+        * list of available paid subscriptions
+        * @var array
+        */
+       public $subscriptions = array();
+       
+       /**
+        * @see \wcf\system\dashboard\box\IDashboardBox::init()
+        */
+       public function init(DashboardBox $box, IPage $page) {
+               parent::init($box, $page);
+               
+               if (WCF::getUser()->userID && MODULE_PAID_SUBSCRIPTION) {
+                       // get available subscriptions
+                       $this->subscriptions = PaidSubscriptionCacheBuilder::getInstance()->getData();
+                       
+                       // get purchased subscriptions
+                       $userSubscriptionList = new PaidSubscriptionUserList();
+                       $userSubscriptionList->getConditionBuilder()->add('userID = ?', array(WCF::getUser()->userID));
+                       $userSubscriptionList->getConditionBuilder()->add('isActive = ?', array(1));
+                       $userSubscriptionList->readObjects();
+                       
+                       // remove purchased subscriptions
+                       foreach ($userSubscriptionList as $userSubscription) {
+                               if (isset($this->subscriptions[$userSubscription->subscriptionID])) {
+                                       $userSubscription->setSubscription($this->subscriptions[$userSubscription->subscriptionID]);
+                                       unset($this->subscriptions[$userSubscription->subscriptionID]);
+                               }
+                       }
+                       // remove excluded subscriptions
+                       foreach ($userSubscriptionList as $userSubscription) {
+                               if ($userSubscription->getSubscription()->excludedSubscriptionIDs) {
+                                       foreach (explode(',', $userSubscription->getSubscription()->excludedSubscriptionIDs) as $subscriptionID) {
+                                               if (isset($this->subscriptions[$subscriptionID])) unset($this->subscriptions[$subscriptionID]);
+                                       }
+                               }
+                       }
+               }
+               $this->fetched();
+       }
+       
+       /**
+        * @see \wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+        */
+       protected function render() {
+               if (!empty($this->subscriptions)) {
+                       WCF::getTPL()->assign(array(
+                               'subscriptions' => $this->subscriptions
+                       ));
+                       
+                       return WCF::getTPL()->fetch('dashboardBoxPaidSubscriptions');
+               }
+               
+               return '';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/dashboard/box/PaidSubscriptionsSidebarDashboardBox.class.php b/wcfsetup/install/files/lib/system/dashboard/box/PaidSubscriptionsSidebarDashboardBox.class.php
new file mode 100644 (file)
index 0000000..767c6fe
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+namespace wcf\system\dashboard\box;
+use wcf\data\dashboard\box\DashboardBox;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserList;
+use wcf\page\IPage;
+use wcf\system\cache\builder\PaidSubscriptionCacheBuilder;
+use wcf\system\WCF;
+
+/**
+ * Dashboard box for paid subscriptions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.dashboard.box
+ * @category   Community Framework
+ */
+class PaidSubscriptionsSidebarDashboardBox extends AbstractSidebarDashboardBox {
+       /**
+        * list of available paid subscriptions
+        * @var array
+        */
+       public $subscriptions = array();
+       
+       /**
+        * @see \wcf\system\dashboard\box\IDashboardBox::init()
+        */
+       public function init(DashboardBox $box, IPage $page) {
+               parent::init($box, $page);
+               
+               if (WCF::getUser()->userID && MODULE_PAID_SUBSCRIPTION) {
+                       // get available subscriptions
+                       $this->subscriptions = PaidSubscriptionCacheBuilder::getInstance()->getData();
+                       
+                       // get purchased subscriptions
+                       $userSubscriptionList = new PaidSubscriptionUserList();
+                       $userSubscriptionList->getConditionBuilder()->add('userID = ?', array(WCF::getUser()->userID));
+                       $userSubscriptionList->getConditionBuilder()->add('isActive = ?', array(1));
+                       $userSubscriptionList->readObjects();
+                       
+                       // remove purchased subscriptions
+                       foreach ($userSubscriptionList as $userSubscription) {
+                               if (isset($this->subscriptions[$userSubscription->subscriptionID])) {
+                                       $userSubscription->setSubscription($this->subscriptions[$userSubscription->subscriptionID]);
+                                       unset($this->subscriptions[$userSubscription->subscriptionID]);
+                               }
+                       }
+                       // remove excluded subscriptions
+                       foreach ($userSubscriptionList as $userSubscription) {
+                               if ($userSubscription->getSubscription()->excludedSubscriptionIDs) {
+                                       foreach (explode(',', $userSubscription->getSubscription()->excludedSubscriptionIDs) as $subscriptionID) {
+                                               if (isset($this->subscriptions[$subscriptionID])) unset($this->subscriptions[$subscriptionID]);
+                                       }
+                               }
+                       }
+               }
+               $this->fetched();
+       }
+       
+       /**
+        * @see \wcf\system\dashboard\box\AbstractContentDashboardBox::render()
+        */
+       protected function render() {
+               if (!empty($this->subscriptions)) {
+                       WCF::getTPL()->assign(array(
+                               'subscriptions' => $this->subscriptions
+                       ));
+                       
+                       return WCF::getTPL()->fetch('dashboardBoxPaidSubscriptionsSidebar');
+               }
+               
+               return '';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/option/PaymentMethodSelectOptionType.class.php b/wcfsetup/install/files/lib/system/option/PaymentMethodSelectOptionType.class.php
new file mode 100644 (file)
index 0000000..082c3d8
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+namespace wcf\system\option;
+use wcf\data\option\Option;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\system\payment\method\PaymentMethodHandler;
+
+/**
+ * Option type implementation for selecting payment methods.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.option
+ * @category   Community Framework
+ */
+class PaymentMethodSelectOptionType extends AbstractOptionType {
+       /**
+        * @see \wcf\system\option\IOptionType::getFormElement()
+        */
+       public function getFormElement(Option $option, $value) {
+               $selectOptions = PaymentMethodHandler::getInstance()->getPaymentMethodSelection();
+               
+               return WCF::getTPL()->fetch('paymentMethodSelectOptionType', 'wcf', array(
+                       'selectOptions' => $selectOptions,
+                       'option' => $option,
+                       'value' => explode(',', $value)
+               ));
+       }
+       
+       /**
+        * @see \wcf\system\option\IOptionType::validate()
+        */
+       public function validate(Option $option, $newValue) {
+               if (!is_array($newValue)) {
+                       $newValue = array();
+               }
+       
+               $selectOptions = PaymentMethodHandler::getInstance()->getPaymentMethodSelection();
+               foreach ($newValue as $optionName) {
+                       if (!isset($selectOptions[$optionName])) {
+                               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);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/payment/method/AbstractPaymentMethod.class.php b/wcfsetup/install/files/lib/system/payment/method/AbstractPaymentMethod.class.php
new file mode 100644 (file)
index 0000000..aff559a
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+namespace wcf\system\payment\method;
+
+/**
+ * Abstract implementation of a payment method.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.payment.method
+ * @category   Community Framework
+ */
+abstract class AbstractPaymentMethod implements IPaymentMethod {
+       /**
+        * @see \wcf\system\payment\method\IPaymentMethod::supportsRecurringPayments()
+        */
+       public function supportsRecurringPayments() {
+               return false;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/payment/method/IPaymentMethod.class.php b/wcfsetup/install/files/lib/system/payment/method/IPaymentMethod.class.php
new file mode 100644 (file)
index 0000000..ef71d14
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace wcf\system\payment\method;
+
+/**
+ * Default interface for payment methods.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.payment.method
+ * @category   Community Framework
+ */
+interface IPaymentMethod {
+       /**
+        * Returns true, if this payment method supports recurring payments.
+        * 
+        * @return      boolean
+        */
+       public function supportsRecurringPayments();
+       
+       /**
+        * Returns a list of supported currencies.
+        * 
+        * @return      array<string>
+        */
+       public function getSupportedCurrencies();
+       
+       /**
+        * Returns the HTML code of the purchase button.
+        * 
+        * @param       float           $cost
+        * @param       string          $currency       ISO 4217 code
+        * @param       string          $name           product/item name
+        * @param       string          $token          custom token
+        * @param       string          $returnURL
+        * @param       string          $cancelReturnURL
+        * @param       boolean         $isRecurring
+        * @param       integer         $subscriptionLength
+        * @param       string          $subscriptionLengthUnit
+        * 
+        * @return      string
+        */
+       public function getPurchaseButton($cost, $currency, $name, $token, $returnURL, $cancelReturnURL, $isRecurring = false, $subscriptionLength = 0, $subscriptionLengthUnit = '');
+}
diff --git a/wcfsetup/install/files/lib/system/payment/method/PaymentMethodHandler.class.php b/wcfsetup/install/files/lib/system/payment/method/PaymentMethodHandler.class.php
new file mode 100644 (file)
index 0000000..ccf447e
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+namespace wcf\system\payment\method;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles enabled/available payment methods.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.payment.method
+ * @category   Community Framework
+ */
+class PaymentMethodHandler extends SingletonFactory {
+       /**
+        * payment methods
+        * @var array
+        */
+       protected $paymentMethods = array();
+       
+       /**
+        * payment method object types
+        * @var array
+        */
+       protected $objectTypes = array();
+       
+       /**
+        * @see \wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               $availablePaymentMethods = explode(',', AVAILABLE_PAYMENT_METHODS);
+               $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.payment.method');
+               foreach ($this->objectTypes as $objectType) {
+                       if (in_array($objectType->objectType, $availablePaymentMethods)) {
+                               $this->paymentMethods[] = $objectType->getProcessor();
+                       }
+               }
+       }
+       
+       /**
+        * Returns the available payment methods.
+        * 
+        * @return      array
+        */
+       public function getPaymentMethods() {
+               return $this->paymentMethods;
+       }
+       
+       /**
+        * Returns the available payment methods for selection.
+        *
+        * @return      array<string>
+        */
+       public function getPaymentMethodSelection() {
+               $selection = array();
+               foreach ($this->objectTypes as $objectType) {
+                       $selection[$objectType->objectType] = WCF::getLanguage()->get('wcf.payment.'.$objectType->objectType);
+               }
+       
+               return $selection;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/payment/method/PaypalPaymentMethod.class.php b/wcfsetup/install/files/lib/system/payment/method/PaypalPaymentMethod.class.php
new file mode 100644 (file)
index 0000000..6d05a56
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+namespace wcf\system\payment\method;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * IPaymentMethod implementation for Paypal.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.payment.method
+ * @category   Community Framework
+ */
+class PaypalPaymentMethod extends AbstractPaymentMethod {
+       /**
+        * @see \wcf\system\payment\method\IPaymentMethod::supportsRecurringPayments()
+        */
+       public function supportsRecurringPayments() {
+               return true;
+       }
+       
+       /**
+        * @see \wcf\system\payment\method\IPaymentMethod::getSupportedCurrencies()
+        */
+       public function getSupportedCurrencies() {
+               return array(
+                       'AUD', // Australian Dollar
+                       'BRL', // Brazilian Real
+                       'CAD', // Canadian Dollar
+                       'CZK', // Czech Koruna
+                       'DKK', // Danish Krone
+                       'EUR', // Euro
+                       'HKD', // Hong Kong Dollar
+                       'HUF', // Hungarian Forint
+                       'ILS', // Israeli New Sheqel
+                       'JPY', // Japanese Yen
+                       'MYR', // Malaysian Ringgit
+                       'MXN', // Mexican Peso
+                       'NOK', // Norwegian Krone
+                       'NZD', // New Zealand Dollar
+                       'PHP', // Philippine Peso
+                       'PLN', // Polish Zloty
+                       'GBP', // Pound Sterling
+                       'RUB', // Russian Ruble
+                       'SGD', // Singapore Dollar
+                       'SEK', // Swedish Krona
+                       'CHF', // Swiss Franc
+                       'TWD', // Taiwan New Dollar
+                       'THB', // Thai Baht
+                       'TRY', // Turkish Lira
+                       'USD'  // U.S. Dollar
+               );
+       }
+       
+       /**
+        * @see \wcf\system\payment\method\IPaymentMethod::getPurchaseButton()
+        */
+       public function getPurchaseButton($cost, $currency, $name, $token, $returnURL, $cancelReturnURL, $isRecurring = false, $subscriptionLength = 0, $subscriptionLengthUnit = '') {
+               if ($isRecurring) {
+                       // subscribe button
+                       return '<form method="post" action="https://www.paypal.com/cgi-bin/webscr">
+                                       <input type="hidden" name="a3" value="'.$cost.'" />
+                                       <input type="hidden" name="p3" value="'.$subscriptionLength.'" />
+                                       <input type="hidden" name="t3" value="'.$subscriptionLengthUnit.'" />
+                                       <input type="hidden" name="src" value="1" />
+                                       <input type="hidden" name="business" value="'.StringUtil::encodeHTML(PAYPAL_EMAIL_ADDRESS).'" />
+                                       <input type="hidden" name="cancel_return" value="'.StringUtil::encodeHTML($cancelReturnURL).'" />
+                                       <input type="hidden" name="charset" value="utf-8" />
+                                       <input type="hidden" name="cmd" value="_xclick-subscriptions" />
+                                       <input type="hidden" name="currency_code" value="'.$currency.'" />
+                                       <input type="hidden" name="custom" value="'.StringUtil::encodeHTML($token).'" />
+                                       <input type="hidden" name="email" value="'.StringUtil::encodeHTML(WCF::getUser()->email).'" />
+                                       <input type="hidden" name="item_name" value="'.StringUtil::encodeHTML($name).'" />
+                                       <input type="hidden" name="lc" value="'.strtoupper(WCF::getLanguage()->languageCode).'" />
+                                       <input type="hidden" name="no_note" value="1" />
+                                       <input type="hidden" name="no_shipping" value="1" />
+                                       <input type="hidden" name="notify_url" value="'.StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('PaypalCallback', array('appendSession' => false))).'" />
+                                       <input type="hidden" name="quantity" value="1" />
+                                       <input type="hidden" name="return" value="'.StringUtil::encodeHTML($returnURL).'" />
+                       
+                                       <button class="small" type="submit">'.WCF::getLanguage()->get('wcf.payment.paypal.button.subscribe').'</button>
+                               </form>';
+               }
+               else {
+                       return '<form method="post" action="https://www.paypal.com/cgi-bin/webscr">
+                                       <input type="hidden" name="amount" value="'.$cost.'" />
+                                       <input type="hidden" name="business" value="'.StringUtil::encodeHTML(PAYPAL_EMAIL_ADDRESS).'" />
+                                       <input type="hidden" name="cancel_return" value="'.StringUtil::encodeHTML($cancelReturnURL).'" />
+                                       <input type="hidden" name="charset" value="utf-8" />
+                                       <input type="hidden" name="cmd" value="_xclick" />
+                                       <input type="hidden" name="currency_code" value="'.$currency.'" />
+                                       <input type="hidden" name="custom" value="'.StringUtil::encodeHTML($token).'" />
+                                       <input type="hidden" name="email" value="'.StringUtil::encodeHTML(WCF::getUser()->email).'" />
+                                       <input type="hidden" name="item_name" value="'.StringUtil::encodeHTML($name).'" />
+                                       <input type="hidden" name="lc" value="'.strtoupper(WCF::getLanguage()->languageCode).'" />
+                                       <input type="hidden" name="no_note" value="1" />
+                                       <input type="hidden" name="no_shipping" value="1" />
+                                       <input type="hidden" name="notify_url" value="'.StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('PaypalCallback', array('appendSession' => false))).'" />
+                                       <input type="hidden" name="quantity" value="1" />
+                                       <input type="hidden" name="return" value="'.StringUtil::encodeHTML($returnURL).'" />    
+                                       
+                                       <button class="small" type="submit">'.WCF::getLanguage()->get('wcf.payment.paypal.button.purchase').'</button>
+                               </form>';
+               }
+       }
+}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/payment/method/SofortUeberweisungPaymentMethod.class.php b/wcfsetup/install/files/lib/system/payment/method/SofortUeberweisungPaymentMethod.class.php
new file mode 100644 (file)
index 0000000..593654e
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+namespace wcf\system\payment\method;
+
+/**
+ * IPaymentMethod implementation for SofortUeberweisung.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.payment.method
+ * @category   Community Framework
+ */
+class SofortUeberweisungPaymentMethod extends AbstractPaymentMethod {
+       /**
+        * @see \wcf\system\payment\method\IPaymentMethod::getSupportedCurrencies()
+        */
+       public function getSupportedCurrencies() {
+               return array(
+                       'EUR' // Euro
+               );
+       }
+       
+       /**
+        * @see \wcf\system\payment\method\IPaymentMethod::getPurchaseButton()
+        */
+       public function getPurchaseButton($cost, $currency, $name, $token, $returnURL, $cancelReturnURL, $isRecurring = false, $subscriptionLength = 0, $subscriptionLengthUnit = '') {
+               // @todo
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/payment/type/AbstractPaymentType.class.php b/wcfsetup/install/files/lib/system/payment/type/AbstractPaymentType.class.php
new file mode 100644 (file)
index 0000000..389f83a
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+namespace wcf\system\payment\type;
+
+/**
+ * Abstract implementation of a payment type.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.payment.type
+ * @category   Community Framework
+ */
+abstract class AbstractPaymentType implements IPaymentType {}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/payment/type/IPaymentType.class.php b/wcfsetup/install/files/lib/system/payment/type/IPaymentType.class.php
new file mode 100644 (file)
index 0000000..f5034f6
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+namespace wcf\system\payment\type;
+
+/**
+ * Default interface for payment types.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.payment.type
+ * @category   Community Framework
+ */
+interface IPaymentType {
+       /**
+        * Processes the given transaction.
+        * 
+        * @param       integer         $paymentMethodObjectTypeID
+        * @param       string          $token
+        * @param       float           $amount
+        * @param       string          $currency
+        * @param       string          $transactionID
+        * @param       string          $status
+        * @param       array           $transactionDetails
+        */
+       public function processTransaction($paymentMethodObjectTypeID, $token, $amount, $currency, $transactionID, $status, $transactionDetails);
+}
\ No newline at end of file
diff --git a/wcfsetup/install/files/lib/system/payment/type/PaidSubscriptionPaymentType.class.php b/wcfsetup/install/files/lib/system/payment/type/PaidSubscriptionPaymentType.class.php
new file mode 100644 (file)
index 0000000..2eb88ef
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+namespace wcf\system\payment\type;
+use wcf\data\paid\subscription\transaction\log\PaidSubscriptionTransactionLog;
+use wcf\data\paid\subscription\transaction\log\PaidSubscriptionTransactionLogAction;
+use wcf\data\paid\subscription\user\PaidSubscriptionUser;
+use wcf\data\paid\subscription\user\PaidSubscriptionUserAction;
+use wcf\data\paid\subscription\PaidSubscription;
+use wcf\data\user\User;
+use wcf\system\exception\SystemException;
+
+/**
+ * IPaymentType implementation for paid subscriptions.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.payment.method
+ * @category   Community Framework
+ */
+class PaidSubscriptionPaymentType extends AbstractPaymentType {
+       /**
+        * @see wcf\system\payment\type\IPaymentType::processTransaction()
+        */
+       public function processTransaction($paymentMethodObjectTypeID, $token, $amount, $currency, $transactionID, $status, $transactionDetails) {
+               $userSubscription = $user = $subscription = null;
+               try {
+                       $tokenParts = explode(':', $token);
+                       if (count($tokenParts) != 2) {
+                               throw new SystemException('invalid token');
+                       }
+                       list($userID, $subscriptionID) = $tokenParts;
+                       
+                       // get user object
+                       $user = new User(intval($userID));
+                       if (!$user->userID) {
+                               throw new SystemException('invalid user');
+                       }
+                       
+                       // get subscription object
+                       $subscription = new PaidSubscription(intval($subscriptionID));
+                       if (!$subscription->subscriptionID) {
+                               throw new SystemException('invalid subscription');
+                       }
+                       
+                       // search for existing subscription
+                       $userSubscription = PaidSubscriptionUser::getSubscriptionUser($subscription->subscriptionID, $user->userID);
+                       
+                       // search log for transaction id
+                       $logEntry = PaidSubscriptionTransactionLog::getLogByTransactionID($paymentMethodObjectTypeID, $transactionID);
+                       if ($logEntry !== null) {
+                               throw new SystemException('transaction already processed');
+                       }
+                       
+                       $logMessage = '';
+                       if ($status == 'completed') {
+                               // validate payment amout
+                               if ($amount != $subscription->cost || $currency != $subscription->currency) {
+                                       throw new SystemException('invalid payment amount');
+                               }
+                               
+                               // active/extend subscription
+                               if ($userSubscription === null) {
+                                       // create new subscription
+                                       $action = new PaidSubscriptionUserAction(array(), 'create', array(
+                                               'user' => $user,
+                                               'subscription' => $subscription
+                                       ));
+                                       $returnValues = $action->executeAction();
+                                       $userSubscription = $returnValues['returnValues'];
+                               }
+                               else {
+                                       // extend existing subscription
+                                       $action = new PaidSubscriptionUserAction(array($userSubscription), 'extend');
+                                       $action->executeAction();
+                               }
+                               $logMessage = 'payment completed';
+                       }
+                       if ($status == 'reversed') {
+                               if ($userSubscription !== null) {
+                                       // revoke subscription
+                                       $action = new PaidSubscriptionUserAction(array($userSubscription), 'revoke');
+                                       $action->executeAction();
+                               }
+                               $logMessage = 'payment reversed';
+                       }
+                       if ($status == 'canceled_reversal') {
+                               if ($userSubscription !== null) {
+                                       // restore subscription
+                                       $action = new PaidSubscriptionUserAction(array($userSubscription), 'restore');
+                                       $action->executeAction();
+                               }
+                               $logMessage = 'reversal canceled';
+                       }
+                       
+                       // log success
+                       $action = new PaidSubscriptionTransactionLogAction(array(), 'create', array('data' => array(
+                               'subscriptionUserID' => $userSubscription->subscriptionUserID,
+                               'user' => $user->userID,
+                               'subscriptionID' => $subscription->subscriptionID,
+                               'paymentMethodObjectTypeID' => $paymentMethodObjectTypeID,
+                               'logTime' => TIME_NOW,
+                               'transactionID' => $transactionID,
+                               'logMessage' => $logMessage,
+                               'transactionDetails' => serialize($transactionDetails)
+                       )));
+                       $action->executeAction();
+               }
+               catch (SystemException $e) {
+                       // log failure
+                       $action = new PaidSubscriptionTransactionLogAction(array(), 'create', array('data' => array(
+                               'subscriptionUserID' => ($userSubscription !== null ? $userSubscription->subscriptionUserID : null),
+                               'user' => ($user !== null ? $user->userID : null),
+                               'subscriptionID' => ($subscription !== null ? $subscription->subscriptionID : null),
+                               'paymentMethodObjectTypeID' => $paymentMethodObjectTypeID,
+                               'logTime' => TIME_NOW,
+                               'transactionID' => $transactionID,
+                               'logMessage' => $e,
+                               'transactionDetails' => serialize($transactionDetails)
+                       )));
+                       $action->executeAction();
+                       throw $e;
+               }
+       }
+}
\ No newline at end of file
index f9534f9f60c23de5e57433b353c30a6395e98d23..81b1344dc29b36fee5a3c53f5e9bc49902d40174 100644 (file)
 #userNotificationItemList > .groupedNotificationItem p > a {
        font-weight: bold;
 }
+
+.paidSubscriptionTeaserList {
+       max-height: 74px;
+       overflow: hidden;
+       
+       > li {
+               float: left;
+               margin-bottom: 7px;
+               padding-right: 7px;
+               white-space: nowrap;
+               width: 300px;
+               word-wrap: normal;
+               
+               .containerHeadline {
+                       > h3 {
+                               text-overflow: ellipsis;
+                               overflow: hidden;
+                       }
+               }
+       }
+}
\ No newline at end of file
index 3756f53a18f8b899b06181f6ea187dca5e63aecd..dbe64f6aa1bf08d9d55e1d05a06c418d221c210e 100644 (file)
                <item name="wcf.acp.group.copy.copyMembers.description"><![CDATA[Mitglieder der kopierten Benutzergruppe werden automatisch auch Mitglied der neuen Benutzergruppe sein.]]></item>
                <item name="wcf.acp.group.copy.copyUserGroupOptions"><![CDATA[Berechtigungen kopieren]]></item>
                <item name="wcf.acp.group.copy.copyUserGroupOptions.description"><![CDATA[Die neue Benutzergruppe wird die gleichen Berechtigungen besitzen wie die kopierte Benutzergruppe.]]></item>
+               <item name="wcf.acp.group.option.admin.paidSubscription.canManageSubscription"><![CDATA[Kann bezahlte Mitgliedschaften verwalten]]></item>
        </category>
        
        <category name="wcf.acp.index">
                <item name="wcf.acp.menu.link.captcha.question.add"><![CDATA[Frage hinzufügen]]></item>
                <item name="wcf.acp.menu.link.captcha.question.list"><![CDATA[Fragen auflisten]]></item>
                <item name="wcf.acp.menu.link.log.authentication.failure"><![CDATA[Fehlgeschlagene Anmeldungen]]></item>
+               <item name="wcf.acp.menu.link.paidSubscription"><![CDATA[Bezahlte Mitgliedschaften]]></item>
+               <item name="wcf.acp.menu.link.paidSubscription.list"><![CDATA[Mitgliedschaften auflisten]]></item>
+               <item name="wcf.acp.menu.link.paidSubscription.user.list"><![CDATA[Aktive Mitgliedschaften auflisten]]></item>
+               <item name="wcf.acp.menu.link.paidSubscription.transactionLog.list"><![CDATA[Transaktionen auflisten]]></item>
        </category>
        
        <category name="wcf.acp.notice">
@@ -1029,6 +1034,14 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.option.gravatar_default_type.wavatar"><![CDATA[Wavatar]]></item>
                <item name="wcf.acp.option.gravatar_default_type.monsterid"><![CDATA[Monster-ID]]></item>
                <item name="wcf.acp.option.gravatar_default_type.retro"><![CDATA[Retro]]></item>
+               <item name="wcf.acp.option.category.general.payment"><![CDATA[Zahlungsoptionen]]></item>
+               <item name="wcf.acp.option.category.general.payment.description"><![CDATA[TODO]]></item>
+               <item name="wcf.acp.option.available_payment_methods"><![CDATA[Unterstützte Zahlungsmethoden]]></item>
+               <item name="wcf.acp.option.available_payment_methods.description"><![CDATA[TODO]]></item>
+               <item name="wcf.acp.option.paypal_email_address"><![CDATA[Paypal-E-Mail-Adresse]]></item>
+               <item name="wcf.acp.option.paypal_email_address.description"><![CDATA[TODO]]></item>
+               <item name="wcf.acp.option.module_paid_subscription"><![CDATA[Bezahlte Mitgliedschaften aktivieren]]></item>
+               <item name="wcf.acp.option.module_paid_subscription.description"><![CDATA[TODO]]></item>
        </category>
        
        <category name="wcf.acp.package">
@@ -1155,6 +1168,41 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.package.validation.failed"><![CDATA[Das hochgeladene Paket kann nicht installiert werden, bitte beachten Sie das unten stehende Prüfungsergebnis.]]></item>
        </category>
        
+       <category name="wcf.acp.paidSubscription">
+               <item name="wcf.acp.paidSubscription.list"><![CDATA[Bezahlte Mitgliedschaften]]></item>
+               <item name="wcf.acp.paidSubscription.add"><![CDATA[Mitgliedschaft hinzufügen]]></item>
+               <item name="wcf.acp.paidSubscription.edit"><![CDATA[Mitgliedschaft bearbeiten]]></item>
+               <item name="wcf.acp.paidSubscription.userGroups"><![CDATA[Benutzergruppen]]></item>
+               <item name="wcf.acp.paidSubscription.userGroups.description"><![CDATA[Käufer dieser Mitgliedschaft werden automatisch den ausgewählten Benutzergruppen hinzugefügt.]]></item>
+               <item name="wcf.acp.paidSubscription.cost"><![CDATA[Kosten]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLength.permanent"><![CDATA[Unbegrenzte Laufzeit]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLength"><![CDATA[Laufzeit]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLengthUnit.D"><![CDATA[Tag(e)]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLengthUnit.M"><![CDATA[Monat(e)]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLengthUnit.Y"><![CDATA[Jahr(e)]]></item>
+               <item name="wcf.acp.paidSubscription.showOrder"><![CDATA[Reihenfolge]]></item>
+               <item name="wcf.acp.paidSubscription.showOrder.description"><![CDATA[Legt die Reihenfolge fest, in der die bezahlten Mitgliedschaften angezeigt werden.]]></item>
+               <item name="wcf.acp.paidSubscription.isDisabled"><![CDATA[Mitgliedschaft deaktivieren]]></item>
+               <item name="wcf.acp.paidSubscription.isDisabled.description"><![CDATA[Entfernt diese bezahlte Mitgliedschaft aus dem Angebot.]]></item>
+               <item name="wcf.acp.paidSubscription.isRecurring"><![CDATA[Regelmäßige Abbuchung]]></item>
+               <item name="wcf.acp.paidSubscription.isRecurring.description"><![CDATA[Aktiviert die automatische Abbuchung der Kosten vom Konto des Käufers, um die Mitgliedschaft automatisch zu verlängern.]]></item>
+               <item name="wcf.acp.paidSubscription.paymentOptions"><![CDATA[Zahlungsoptionen]]></item>
+               <item name="wcf.acp.paidSubscription.user.list"><![CDATA[Aktive Mitgliedschaften]]></item>
+               <item name="wcf.acp.paidSubscription.user.endDate"><![CDATA[Ablaufdatum]]></item>
+               <item name="wcf.acp.paidSubscription.subscription"><![CDATA[Mitgliedschaft]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.list"><![CDATA[Transaktionen]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.paymentMethod"><![CDATA[Zahlungsmethode]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.transactionID"><![CDATA[Transaktions-ID]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.logTime"><![CDATA[Datum]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.logMessage"><![CDATA[Aktion]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog"><![CDATA[Transaktion]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.transactionDetails"><![CDATA[Details]]></item>
+               <item name="wcf.acp.paidSubscription.excludedSubscriptions"><![CDATA[Andere Mitgliedschaften ausschließen]]></item>
+               <item name="wcf.acp.paidSubscription.excludedSubscriptions.description"><![CDATA[Sie können den Erwerb anderer bezahlter Mitgliederschaften für Käufer dieser Mitgliedschaft ausschließen.]]></item>
+               <item name="wcf.acp.paidSubscription.user.delete.confirmMessage"><![CDATA[Wollen Sie die Mitgliedschaft „{$subscriptionUser->title|language}“ für den Benutzer „{$subscriptionUser->username}“ wirklich entfernen?]]></item>
+               <item name="wcf.acp.paidSubscription.user.add"><![CDATA[Mitgliedschaft manuell zuweisen]]></item>
+       </category>
+       
        <category name="wcf.acp.pageMenu">
                <item name="wcf.acp.pageMenu.add"><![CDATA[Menüpunkt hinzufügen]]></item>
                <item name="wcf.acp.pageMenu.advanced"><![CDATA[Erweitert]]></item>
@@ -1822,6 +1870,8 @@ Fehler sind beispielsweise:
                <item name="wcf.dashboard.box.com.woltlab.wcf.user.usersOnlineSidebar"><![CDATA[Benutzer online]]></item>
                <item name="wcf.dashboard.box.com.woltlab.wcf.user.followingsOnlineSidebar"><![CDATA[Benutzer online, denen Sie folgen]]></item>
                <item name="wcf.dashboard.box.com.woltlab.wcf.user.staffOnlineSidebar"><![CDATA[Team-Mitglieder online]]></item>
+               <item name="wcf.dashboard.box.com.woltlab.wcf.paidSubscriptions"><![CDATA[Bezahlte Mitgliedschaften]]></item>
+               <item name="wcf.dashboard.box.com.woltlab.wcf.paidSubscriptionsSidebar"><![CDATA[Bezahlte Mitgliedschaften]]></item>
        </category>
        
        <category name="wcf.date">
@@ -2434,6 +2484,20 @@ Fehler sind beispielsweise:
                <item name="wcf.page.com.woltlab.wcf.UsersOnlineListPage"><![CDATA[Benutzer online]]></item>
        </category>
        
+       <category name="wcf.paidSubscription">
+               <item name="wcf.paidSubscription.availableSubscriptions"><![CDATA[Verfügbare Mitgliedschaften]]></item>
+               <item name="wcf.paidSubscription.purchasedSubscriptions"><![CDATA[Erworbene Mitgliedschaften]]></item>
+               <item name="wcf.paidSubscription.formattedCost"><![CDATA[{$subscription->currency} {$subscription->cost|currency}{if $subscription->subscriptionLength} {if $subscription->subscriptionLength != 1}für{else}pro{/if} {if $subscription->subscriptionLength != 1}{@$subscription->subscriptionLength} {/if}{if $subscription->subscriptionLengthUnit == 'D'}Tag{if $subscription->subscriptionLength != 1}e{/if}{/if}{if $subscription->subscriptionLengthUnit == 'M'}Monat{if $subscription->subscriptionLength != 1}e{/if}{/if}{if $subscription->subscriptionLengthUnit == 'Y'}Jahr{if $subscription->subscriptionLength != 1}e{/if}{/if}{/if}]]></item>
+               <item name="wcf.paidSubscription.expires"><![CDATA[Gültig bis]]></item>
+               <item name="wcf.paidSubscription.returnMessage"><![CDATA[Danke für Ihre Zahlung. Ihre Transaktion wurde abgeschlossen. Sobald Ihre Zahlung von uns verarbeitet wurde, wird die erworbene Mitgliedschaft aktiviert.]]></item>
+       </category>
+       
+       <category name="wcf.payment">
+               <item name="wcf.payment.com.woltlab.wcf.payment.method.paypal"><![CDATA[Paypal]]></item>
+               <item name="wcf.payment.paypal.button.purchase"><![CDATA[Via Paypal kaufen]]></item>
+               <item name="wcf.payment.paypal.button.subscribe"><![CDATA[Via Paypal abonnieren]]></item>
+       </category>
+       
        <category name="wcf.poll">
                <item name="wcf.poll.button.addOption"><![CDATA[Weitere Antwort hinzufügen]]></item>
                <item name="wcf.poll.button.removeOption"><![CDATA[Antwort entfernen]]></item>
@@ -2744,6 +2808,7 @@ Wenn Sie Probleme mit der Aktivierung haben, wenden Sie sich bitte an den Admini
                <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>
+               <item name="wcf.user.menu.settings.paidSubscription"><![CDATA[Bezahlte Mitgliedschaften]]></item>
        </category>
        
        <category name="wcf.user.register">
index 2254340456ab3862c968e796e1c2b63c51624e60..e971100df0e4789644b153a89e6f17162931f24d 100644 (file)
@@ -438,6 +438,7 @@ Examples for medium ID detection:
                <item name="wcf.acp.group.copy.copyMembers.description"><![CDATA[Members of the copied user group will automatically be members of the new user group.]]></item>
                <item name="wcf.acp.group.copy.copyUserGroupOptions"><![CDATA[Copy Permissions]]></item>
                <item name="wcf.acp.group.copy.copyUserGroupOptions.description"><![CDATA[The new user group will have the same permissions as the copied user group.]]></item>
+               <item name="wcf.acp.group.option.admin.paidSubscription.canManageSubscription"><![CDATA[TODO: Kann bezahlte Mitgliedschaften verwalten]]></item>
        </category>
        
        <category name="wcf.acp.index">
@@ -640,6 +641,10 @@ Examples for medium ID detection:
                <item name="wcf.acp.menu.link.captcha.question.add"><![CDATA[Add Question]]></item>
                <item name="wcf.acp.menu.link.captcha.question.list"><![CDATA[List Questions]]></item>
                <item name="wcf.acp.menu.link.log.authentication.failure"><![CDATA[Failed Login Attempts]]></item>
+               <item name="wcf.acp.menu.link.paidSubscription"><![CDATA[TODO: Bezahlte Mitgliedschaften]]></item>
+               <item name="wcf.acp.menu.link.paidSubscription.list"><![CDATA[TODO: Mitgliedschaften auflisten]]></item>
+               <item name="wcf.acp.menu.link.paidSubscription.user.list"><![CDATA[TODO: Aktive Mitgliedschaften auflisten]]></item>
+               <item name="wcf.acp.menu.link.paidSubscription.transactionLog.list"><![CDATA[TODO: Transaktionen auflisten]]></item>
        </category>
        
        <category name="wcf.acp.notice">
@@ -1028,6 +1033,14 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.option.gravatar_default_type.wavatar"><![CDATA[Wavatar]]></item>
                <item name="wcf.acp.option.gravatar_default_type.monsterid"><![CDATA[Monster id]]></item>
                <item name="wcf.acp.option.gravatar_default_type.retro"><![CDATA[Retro]]></item>
+               <item name="wcf.acp.option.category.general.payment"><![CDATA[TODO: Zahlungsoptionen]]></item>
+               <item name="wcf.acp.option.category.general.payment.description"><![CDATA[TODO]]></item>
+               <item name="wcf.acp.option.available_payment_methods"><![CDATA[TODO: Unterstützte Zahlungsmethoden]]></item>
+               <item name="wcf.acp.option.available_payment_methods.description"><![CDATA[TODO]]></item>
+               <item name="wcf.acp.option.paypal_email_address"><![CDATA[TODO: Paypal-E-Mail-Adresse]]></item>
+               <item name="wcf.acp.option.paypal_email_address.description"><![CDATA[TODO]]></item>
+               <item name="wcf.acp.option.module_paid_subscription"><![CDATA[TODO: Bezahlte Mitgliedschaften aktivieren]]></item>
+               <item name="wcf.acp.option.module_paid_subscription.description"><![CDATA[TODO]]></item>
        </category>
        
        <category name="wcf.acp.package">
@@ -1154,6 +1167,41 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.package.validation.failed"><![CDATA[The package cannot be installed, please review the validation results below.]]></item>
        </category>
        
+       <category name="wcf.acp.paidSubscription">
+               <item name="wcf.acp.paidSubscription.list"><![CDATA[TODO: Bezahlte Mitgliedschaften]]></item>
+               <item name="wcf.acp.paidSubscription.add"><![CDATA[TODO: Mitgliedschaft hinzufügen]]></item>
+               <item name="wcf.acp.paidSubscription.edit"><![CDATA[TODO: Mitgliedschaft bearbeiten]]></item>
+               <item name="wcf.acp.paidSubscription.userGroups"><![CDATA[TODO: Benutzergruppen]]></item>
+               <item name="wcf.acp.paidSubscription.userGroups.description"><![CDATA[TODO: Käufer dieser Mitgliedschaft werden automatisch den ausgewählten Benutzergruppen hinzugefügt.]]></item>
+               <item name="wcf.acp.paidSubscription.cost"><![CDATA[TODO: Kosten]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLength.permanent"><![CDATA[TODO: Unbegrenzte Laufzeit]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLength"><![CDATA[TODO: Laufzeit]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLengthUnit.D"><![CDATA[TODO: Tag(e)]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLengthUnit.M"><![CDATA[TODO: Monat(e)]]></item>
+               <item name="wcf.acp.paidSubscription.subscriptionLengthUnit.Y"><![CDATA[TODO: Jahr(e)]]></item>
+               <item name="wcf.acp.paidSubscription.showOrder"><![CDATA[TODO: Reihenfolge]]></item>
+               <item name="wcf.acp.paidSubscription.showOrder.description"><![CDATA[TODO: Legt die Reihenfolge fest, in der die bezahlten Mitgliedschaften angezeigt werden.]]></item>
+               <item name="wcf.acp.paidSubscription.isDisabled"><![CDATA[TODO: Mitgliedschaft deaktivieren]]></item>
+               <item name="wcf.acp.paidSubscription.isDisabled.description"><![CDATA[TODO: Entfernt diese bezahlte Mitgliedschaft aus dem Angebot.]]></item>
+               <item name="wcf.acp.paidSubscription.isRecurring"><![CDATA[TODO: Regelmäßige Abbuchung]]></item>
+               <item name="wcf.acp.paidSubscription.isRecurring.description"><![CDATA[TODO: Aktiviert die automatische Abbuchung der Kosten vom Konto des Käufers, um die Mitgliedschaft automatisch zu verlängern.]]></item>
+               <item name="wcf.acp.paidSubscription.paymentOptions"><![CDATA[TODO: Zahlungsoptionen]]></item>
+               <item name="wcf.acp.paidSubscription.user.list"><![CDATA[TODO: Aktive Mitgliedschaften]]></item>
+               <item name="wcf.acp.paidSubscription.user.endDate"><![CDATA[TODO: Ablaufdatum]]></item>
+               <item name="wcf.acp.paidSubscription.subscription"><![CDATA[TODO: Mitgliedschaft]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.list"><![CDATA[TODO: Transaktionen]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.paymentMethod"><![CDATA[TODO: Zahlungsmethode]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.transactionID"><![CDATA[TODO: Transaktions-ID]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.logTime"><![CDATA[TODO: Datum]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.logMessage"><![CDATA[TODO: Aktion]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog"><![CDATA[TODO: Transaktion]]></item>
+               <item name="wcf.acp.paidSubscription.transactionLog.transactionDetails"><![CDATA[TODO: Details]]></item>
+               <item name="wcf.acp.paidSubscription.excludedSubscriptions"><![CDATA[TODO: Andere Mitgliedschaften ausschließen]]></item>
+               <item name="wcf.acp.paidSubscription.excludedSubscriptions.description"><![CDATA[TODO: Sie können den Erwerb anderer bezahlter Mitgliederschaften für Käufer dieser Mitgliedschaft ausschließen.]]></item>
+               <item name="wcf.acp.paidSubscription.user.delete.confirmMessage"><![CDATA[TODO: Wollen Sie die Mitgliedschaft „{$subscriptionUser->title|language}“ für den Benutzer „{$subscriptionUser->username}“ wirklich entfernen?]]></item>
+               <item name="wcf.acp.paidSubscription.user.add"><![CDATA[TODO: Mitgliedschaft manuell zuweisen]]></item>
+       </category>
+       
        <category name="wcf.acp.pageMenu">
                <item name="wcf.acp.pageMenu.add"><![CDATA[Add Menu Item]]></item>
                <item name="wcf.acp.pageMenu.advanced"><![CDATA[Advanced]]></item>
@@ -1820,6 +1868,8 @@ Errors are:
                <item name="wcf.dashboard.box.com.woltlab.wcf.user.usersOnlineSidebar"><![CDATA[Users Online]]></item>
                <item name="wcf.dashboard.box.com.woltlab.wcf.user.followingsOnlineSidebar"><![CDATA[Users Online you Follow]]></item>
                <item name="wcf.dashboard.box.com.woltlab.wcf.user.staffOnlineSidebar"><![CDATA[Staff-Members Online]]></item>
+               <item name="wcf.dashboard.box.com.woltlab.wcf.paidSubscriptions"><![CDATA[TODO: Bezahlte Mitgliedschaften]]></item>
+               <item name="wcf.dashboard.box.com.woltlab.wcf.paidSubscriptionsSidebar"><![CDATA[TODO: Bezahlte Mitgliedschaften]]></item>
        </category>
        
        <category name="wcf.date">
@@ -2325,6 +2375,20 @@ Errors are:
                <item name="wcf.page.com.woltlab.wcf.UsersOnlineListPage"><![CDATA[Users Online]]></item>
        </category>
        
+       <category name="wcf.paidSubscription">
+               <item name="wcf.paidSubscription.availableSubscriptions"><![CDATA[TODO: Verfügbare Mitgliedschaften]]></item>
+               <item name="wcf.paidSubscription.purchasedSubscriptions"><![CDATA[TODO: Erworbene Mitgliedschaften]]></item>
+               <item name="wcf.paidSubscription.formattedCost"><![CDATA[TODO: {$subscription->currency} {$subscription->cost|currency}{if $subscription->subscriptionLength} {if $subscription->subscriptionLength != 1}für{else}pro{/if} {if $subscription->subscriptionLength != 1}{@$subscription->subscriptionLength} {/if}{if $subscription->subscriptionLengthUnit == 'D'}Tag{if $subscription->subscriptionLength != 1}e{/if}{/if}{if $subscription->subscriptionLengthUnit == 'M'}Monat{if $subscription->subscriptionLength != 1}e{/if}{/if}{if $subscription->subscriptionLengthUnit == 'Y'}Jahr{if $subscription->subscriptionLength != 1}e{/if}{/if}{/if}]]></item>
+               <item name="wcf.paidSubscription.expires"><![CDATA[TODO: Gültig bis]]></item>
+               <item name="wcf.paidSubscription.returnMessage"><![CDATA[TODO: Danke für Ihre Zahlung. Ihre Transaktion wurde abgeschlossen. Sobald Ihre Zahlung von uns verarbeitet wurde, wird die erworbene Mitgliedschaft aktiviert.]]></item>
+       </category>
+       
+       <category name="wcf.payment">
+               <item name="wcf.payment.com.woltlab.wcf.payment.method.paypal"><![CDATA[TODO: Paypal]]></item>
+               <item name="wcf.payment.paypal.button.purchase"><![CDATA[TODO: Via Paypal kaufen]]></item>
+               <item name="wcf.payment.paypal.button.subscribe"><![CDATA[TODO: Via Paypal abonnieren]]></item>
+       </category>
+       
        <category name="wcf.poll">
                <item name="wcf.poll.button.addOption"><![CDATA[Add Another Option]]></item>
                <item name="wcf.poll.button.removeOption"><![CDATA[Remove Option]]></item>
@@ -2632,6 +2696,7 @@ If you cannot activate your email address or have any troubles following the ins
                <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>
+               <item name="wcf.user.menu.settings.paidSubscription"><![CDATA[TODO: Bezahlte Mitgliedschaften]]></item>
        </category>
        
        <category name="wcf.user.register">
index ed0409c5eccfbc598246f69e4bb4d2f53f2c988b..58a0511e8a184ad3c7106a8ddcbaad63338af47e 100644 (file)
@@ -794,6 +794,48 @@ CREATE TABLE wcf1_page_menu_item (
        UNIQUE KEY (packageID, menuItem)
 );
 
+DROP TABLE IF EXISTS wcf1_paid_subscription;
+CREATE TABLE wcf1_paid_subscription (
+       subscriptionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       title VARCHAR(255) NOT NULL DEFAULT '',
+       description TEXT,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       cost DECIMAL(10,2) NOT NULL DEFAULT 0,
+       currency VARCHAR(3) NOT NULL DEFAULT 'EUR',
+       subscriptionLength SMALLINT(3) NOT NULL DEFAULT 0,
+       subscriptionLengthUnit ENUM('', 'D', 'M', 'Y') NOT NULL DEFAULT '',
+       isRecurring TINYINT(1) NOT NULL DEFAULT 0,
+       groupIDs TEXT,
+       excludedSubscriptionIDs TEXT
+);
+
+DROP TABLE IF EXISTS wcf1_paid_subscription_user;
+CREATE TABLE wcf1_paid_subscription_user (
+       subscriptionUserID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       subscriptionID INT(10) NOT NULL,
+       userID INT(10) NOT NULL,
+       startDate INT(10) NOT NULL DEFAULT 0,
+       endDate INT(10) NOT NULL DEFAULT 0,
+       isActive TINYINT(1) NOT NULL DEFAULT 1,
+       
+       UNIQUE KEY (subscriptionID, userID),
+       KEY (isActive)
+);
+
+DROP TABLE IF EXISTS wcf1_paid_subscription_transaction_log;
+CREATE TABLE wcf1_paid_subscription_transaction_log (
+       logID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       subscriptionUserID INT(10),
+       userID INT(10),
+       subscriptionID INT(10),
+       paymentMethodObjectTypeID INT(10) NOT NULL,
+       logTime INT(10) NOT NULL DEFAULT 0,
+       transactionID VARCHAR(255) NOT NULL DEFAULT '',
+       transactionDetails MEDIUMTEXT,
+       logMessage VARCHAR(255) NOT NULL DEFAULT ''
+);
+
 DROP TABLE IF EXISTS wcf1_poll;
 CREATE TABLE wcf1_poll (
        pollID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1518,6 +1560,14 @@ ALTER TABLE wcf1_package_update_optional ADD FOREIGN KEY (packageUpdateVersionID
 
 ALTER TABLE wcf1_package_update_version ADD FOREIGN KEY (packageUpdateID) REFERENCES wcf1_package_update (packageUpdateID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_paid_subscription_user ADD FOREIGN KEY (subscriptionID) REFERENCES wcf1_paid_subscription (subscriptionID) ON DELETE CASCADE;
+ALTER TABLE wcf1_paid_subscription_user ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_paid_subscription_transaction_log ADD FOREIGN KEY (subscriptionUserID) REFERENCES wcf1_paid_subscription_user (subscriptionUserID) ON DELETE SET NULL;
+ALTER TABLE wcf1_paid_subscription_transaction_log ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
+ALTER TABLE wcf1_paid_subscription_transaction_log ADD FOREIGN KEY (subscriptionID) REFERENCES wcf1_paid_subscription (subscriptionID) ON DELETE SET NULL;
+ALTER TABLE wcf1_paid_subscription_transaction_log ADD FOREIGN KEY (paymentMethodObjectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_page_menu_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_search ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;