From 564ecd5227621a2ab0d985bf5802daea9e728ce1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patrick=20M=C3=BCller?= <p.mueller@pcsg.de>
Date: Thu, 22 Jun 2017 18:30:17 +0200
Subject: [PATCH] refactor: temp commit

---
 ajax/memberships/users/get.php                |  54 +++++++
 ajax/memberships/users/getHistory.php         |  42 +++++
 ajax/memberships/users/update.php             |  89 ++++++++++
 bin/classes/MembershipUsers.js                |  56 ++++++-
 bin/controls/users/MembershipUserEdit.css     |   9 ++
 bin/controls/users/MembershipUserEdit.html    |  39 +++++
 bin/controls/users/MembershipUserEdit.js      | 153 ++++++++++++++++++
 bin/controls/users/MembershipUserHistory.css  |  44 +++++
 bin/controls/users/MembershipUserHistory.html |   9 ++
 bin/controls/users/MembershipUserHistory.js   | 149 +++++++++++++++++
 .../users/MembershipUserHistoryPopup.js       |  61 +++++++
 bin/controls/users/MembershipUsers.js         |  95 +++++++++--
 bin/controls/users/MembershipUsersArchive.js  |  79 ++-------
 locale.xml                                    |  98 +++++++++++
 src/QUI/Memberships/Cron.php                  |  47 +++---
 src/QUI/Memberships/Users/MembershipUser.php  |  82 ++++++++--
 templates/mail_autoextend.html                |   4 +-
 templates/mail_confirmcancel.html             |   5 +-
 templates/mail_expired.html                   |   4 +-
 19 files changed, 1002 insertions(+), 117 deletions(-)
 create mode 100644 ajax/memberships/users/get.php
 create mode 100644 ajax/memberships/users/getHistory.php
 create mode 100644 ajax/memberships/users/update.php
 create mode 100644 bin/controls/users/MembershipUserEdit.css
 create mode 100644 bin/controls/users/MembershipUserEdit.html
 create mode 100644 bin/controls/users/MembershipUserEdit.js
 create mode 100644 bin/controls/users/MembershipUserHistory.css
 create mode 100644 bin/controls/users/MembershipUserHistory.html
 create mode 100644 bin/controls/users/MembershipUserHistory.js
 create mode 100644 bin/controls/users/MembershipUserHistoryPopup.js

diff --git a/ajax/memberships/users/get.php b/ajax/memberships/users/get.php
new file mode 100644
index 0000000..4bd21bb
--- /dev/null
+++ b/ajax/memberships/users/get.php
@@ -0,0 +1,54 @@
+<?php
+
+use QUI\Memberships\Users\Handler as MembershipUsersHandler;
+use QUI\Memberships\Users\MembershipUser;
+
+/**
+ * Get general data of a MembershipUser
+ *
+ * @param int $membershipUserId
+ * @return array|false - history data or false on error
+ */
+QUI::$Ajax->registerFunction(
+    'package_quiqqer_memberships_ajax_memberships_users_get',
+    function ($membershipUserId) {
+        try {
+            $MembershipUsers = MembershipUsersHandler::getInstance();
+            /** @var MembershipUser $MembershipUser */
+            $MembershipUser = $MembershipUsers->getChild((int)$membershipUserId);
+            $QuiqqerUser    = $MembershipUser->getUser();
+            $Membership     = $MembershipUser->getMembership();
+
+            return array(
+                'id'              => $MembershipUser->getId(),
+                'userId'          => $QuiqqerUser->getId(),
+                'membershipId'    => $Membership->getId(),
+                'membershipTitle' => $Membership->getTitle(),
+                'username'        => $QuiqqerUser->getUsername(),
+                'fullName'        => $QuiqqerUser->getName(),
+                'addedDate'       => $MembershipUser->getAttribute('addedDate'),
+                'beginDate'       => $MembershipUser->getAttribute('beginDate'),
+                'endDate'         => $MembershipUser->getAttribute('endDate'),
+                'archived'        => $MembershipUser->isArchived(),
+                'archiveReason'   => $MembershipUser->getAttribute('archiveReason')
+            );
+        } catch (\Exception $Exception) {
+            QUI\System\Log::addError('AJAX :: package_quiqqer_memberships_ajax_memberships_users_getHistory');
+            QUI\System\Log::writeException($Exception);
+
+            QUI::getMessagesHandler()->addError(
+                QUI::getLocale()->get(
+                    'quiqqer/memberships',
+                    'message.ajax.general.error',
+                    array(
+                        'error' => $Exception->getMessage()
+                    )
+                )
+            );
+
+            return false;
+        }
+    },
+    array('membershipUserId'),
+    'Permission::checkAdminUser'
+);
diff --git a/ajax/memberships/users/getHistory.php b/ajax/memberships/users/getHistory.php
new file mode 100644
index 0000000..3bae571
--- /dev/null
+++ b/ajax/memberships/users/getHistory.php
@@ -0,0 +1,42 @@
+<?php
+
+use QUI\Memberships\Users\Handler as MembershipUsersHandler;
+use QUI\Memberships\Users\MembershipUser;
+
+/**
+ * Get history of a MembershipUser
+ *
+ * @param int $membershipUserId
+ * @return array|false - history data or false on error
+ */
+QUI::$Ajax->registerFunction(
+    'package_quiqqer_memberships_ajax_memberships_users_getHistory',
+    function ($membershipUserId) {
+        try {
+            $MembershipUsers = MembershipUsersHandler::getInstance();
+            /** @var MembershipUser $MembershipUser */
+            $MembershipUser = $MembershipUsers->getChild((int)$membershipUserId);
+            $history        = $MembershipUser->getHistory();
+
+            // reverse history entries so the latest entries come first
+            return array_reverse($history);
+        } catch (\Exception $Exception) {
+            QUI\System\Log::addError('AJAX :: package_quiqqer_memberships_ajax_memberships_users_getHistory');
+            QUI\System\Log::writeException($Exception);
+
+            QUI::getMessagesHandler()->addError(
+                QUI::getLocale()->get(
+                    'quiqqer/memberships',
+                    'message.ajax.general.error',
+                    array(
+                        'error' => $Exception->getMessage()
+                    )
+                )
+            );
+
+            return false;
+        }
+    },
+    array('membershipUserId'),
+    'Permission::checkAdminUser'
+);
diff --git a/ajax/memberships/users/update.php b/ajax/memberships/users/update.php
new file mode 100644
index 0000000..fba1389
--- /dev/null
+++ b/ajax/memberships/users/update.php
@@ -0,0 +1,89 @@
+<?php
+
+use QUI\Memberships\Users\Handler as MembershipUsersHandler;
+use QUI\Memberships\Utils;
+
+/**
+ * Update a MembershipUser
+ *
+ * @param int $membershipUserId
+ * @param array $attributes - Update attributes
+ * @return bool - success
+ */
+QUI::$Ajax->registerFunction(
+    'package_quiqqer_memberships_ajax_memberships_users_update',
+    function ($membershipUserId, $attributes) {
+        try {
+            $MembershipUsers = MembershipUsersHandler::getInstance();
+            /** @var \QUI\Memberships\Users\MembershipUser $MembershipUser */
+            $MembershipUser = $MembershipUsers->getChild((int)$membershipUserId);
+            $attributes     = json_decode($attributes, true);
+            $updated        = array();
+
+            foreach ($attributes as $k => $v) {
+                switch ($k) {
+                    case 'beginDate':
+                    case 'endDate':
+                        $v           = Utils::getFormattedTimestamp(strtotime($v));
+                        $updated[$k] = $v;
+                        break;
+
+                    default:
+                        // do not update un-updatable attributes
+                        continue 2;
+                }
+
+                $MembershipUser->setAttribute($k, $v);
+            }
+
+            $MembershipUser->addHistoryEntry(
+                MembershipUsersHandler::HISTORY_TYPE_UPDATED,
+                json_encode($updated)
+            );
+
+            $MembershipUser->update();
+        } catch (QUI\Memberships\Exception $Exception) {
+            QUI::getMessagesHandler()->addError(
+                QUI::getLocale()->get(
+                    'quiqqer/memberships',
+                    'message.ajax.memberships.users.update.error',
+                    array(
+                        'error' => $Exception->getMessage()
+                    )
+                )
+            );
+
+            return false;
+        } catch (\Exception $Exception) {
+            QUI\System\Log::addError('AJAX :: package_quiqqer_memberships_ajax_memberships_users_update');
+            QUI\System\Log::writeException($Exception);
+
+            QUI::getMessagesHandler()->addError(
+                QUI::getLocale()->get(
+                    'quiqqer/memberships',
+                    'message.ajax.general.error',
+                    array(
+                        'error' => $Exception->getMessage()
+                    )
+                )
+            );
+
+            return false;
+        }
+
+        QUI::getMessagesHandler()->addSuccess(
+            QUI::getLocale()->get(
+                'quiqqer/memberships',
+                'message.ajax.memberships.users.update.success',
+                array(
+                    'membershipUserId'   => $MembershipUser->getId(),
+                    'membershipUserName' => $MembershipUser->getUser()->getName()
+                )
+            )
+        );
+
+        return true;
+    },
+    array('membershipUserId', 'attributes'),
+    'Permission::checkAdminUser'
+);
diff --git a/bin/classes/MembershipUsers.js b/bin/classes/MembershipUsers.js
index 2a4e2b5..d4cf6d6 100644
--- a/bin/classes/MembershipUsers.js
+++ b/bin/classes/MembershipUsers.js
@@ -46,9 +46,9 @@ define('package/quiqqer/memberships/bin/classes/MembershipUsers', [
         deleteMembershipUsers: function (userIds) {
             return new Promise(function (resolve, reject) {
                 QUIAjax.post('package_quiqqer_memberships_ajax_memberships_users_delete', resolve, {
-                    'package'   : pkg,
-                    userIds     : JSON.encode(userIds),
-                    onError     : reject
+                    'package': pkg,
+                    userIds  : JSON.encode(userIds),
+                    onError  : reject
                 })
             });
         },
@@ -71,6 +71,56 @@ define('package/quiqqer/memberships/bin/classes/MembershipUsers', [
             });
         },
 
+        /**
+         * Update MembershipUser
+         *
+         * @param {Integer} membershipUserId
+         * @param {Object} Attributes
+         * @return {Promise}
+         */
+        update: function (membershipUserId, Attributes) {
+            return new Promise(function (resolve, reject) {
+                QUIAjax.post('package_quiqqer_memberships_ajax_memberships_users_update', resolve, {
+                    'package'       : pkg,
+                    membershipUserId: membershipUserId,
+                    attributes      : JSON.encode(Attributes),
+                    onError         : reject
+                })
+            });
+        },
+
+        /**
+         * Get MembershipUser data (some general attribues)
+         *
+         * @param {Integer} membershipUserId
+         * @return {Promise}
+         */
+        get: function (membershipUserId) {
+            return new Promise(function (resolve, reject) {
+                QUIAjax.get('package_quiqqer_memberships_ajax_memberships_users_get', resolve, {
+                    'package'       : pkg,
+                    membershipUserId: membershipUserId,
+                    onError         : reject
+                })
+            });
+        },
+
+        /**
+         * Get MembershipUser history
+         *
+         * @param {Integer} membershipUserId
+         * @return {Promise}
+         */
+        getHistory: function (membershipUserId) {
+            return new Promise(function (resolve, reject) {
+                QUIAjax.get('package_quiqqer_memberships_ajax_memberships_users_getHistory', resolve, {
+                    'package'       : pkg,
+                    membershipUserId: membershipUserId,
+                    onError         : reject
+                })
+            });
+        },
+
         /**
          * Get/Search memberships (archived)
          *
diff --git a/bin/controls/users/MembershipUserEdit.css b/bin/controls/users/MembershipUserEdit.css
new file mode 100644
index 0000000..faa3262
--- /dev/null
+++ b/bin/controls/users/MembershipUserEdit.css
@@ -0,0 +1,9 @@
+.quiqqer-memberships-membershipuseredit {
+    float: left;
+    width: 100%;
+}
+
+.quiqqer-memberships-membershipuseredit-submit {
+    float: left;
+    width: 100%;
+}
\ No newline at end of file
diff --git a/bin/controls/users/MembershipUserEdit.html b/bin/controls/users/MembershipUserEdit.html
new file mode 100644
index 0000000..78c89d0
--- /dev/null
+++ b/bin/controls/users/MembershipUserEdit.html
@@ -0,0 +1,39 @@
+<form name="quiqqer-memberships-membershipuseredit">
+    <table class="data-table data-table-flexbox quiqqer-memberships-membershipuseredit-table">
+        <thead>
+        <tr>
+            <th>
+                {{header}}
+            </th>
+        </tr>
+        </thead>
+
+        <tbody>
+        <tr>
+            <td>
+                <label class="field-container">
+                <span class="field-container-item">
+                    {{labelBeginDate}}
+                </span>
+                    <input type="datetime"
+                           class="field-container-field quiqqer-memberships-membershipuseredit-attribute"
+                           name="beginDate" placeholder="YYYY-MM-DD">
+                </label>
+            </td>
+        </tr>
+        <tr>
+            <td>
+                <label class="field-container">
+                <span class="field-container-item">
+                    {{labelEndDate}}
+                </span>
+                    <input type="datetime"
+                           class="field-container-field quiqqer-memberships-membershipuseredit-attribute"
+                           name="endDate" placeholder="YYYY-MM-DD">
+                </label>
+            </td>
+        </tr>
+        </tbody>
+    </table>
+    <div class="quiqqer-memberships-membershipuseredit-submit"></div>
+</form>
\ No newline at end of file
diff --git a/bin/controls/users/MembershipUserEdit.js b/bin/controls/users/MembershipUserEdit.js
new file mode 100644
index 0000000..a865c48
--- /dev/null
+++ b/bin/controls/users/MembershipUserEdit.js
@@ -0,0 +1,153 @@
+/**
+ * MembershipUserEdit JavaScript Control
+ *
+ * Edit a MembershipUser
+ *
+ * @module package/quiqqer/memberships/bin/controls/users/MembershipUserEdit
+ * @author www.pcsg.de (Patrick Müller)
+ *
+ * @require qui/controls/Control
+ * @require qui/controls/loader/Loader
+ * @require qui/controls/buttons/Button
+ * @require qui/utils/Form
+ * @require package/quiqqer/memberships/bin/MembershipUsers
+ * @require Locale
+ * @require Ajax
+ * @require Mustache
+ * @require text!package/quiqqer/memberships/bin/controls/users/MembershipUserEdit.html
+ * @require css!package/quiqqer/memberships/bin/controls/users/MembershipUserEdit.css
+ */
+define('package/quiqqer/memberships/bin/controls/users/MembershipUserEdit', [
+
+    'qui/controls/Control',
+    'qui/controls/loader/Loader',
+    'qui/controls/buttons/Button',
+    'qui/utils/Form',
+
+    'package/quiqqer/memberships/bin/MembershipUsers',
+
+    'Locale',
+    'Ajax',
+    'Mustache',
+
+    'text!package/quiqqer/memberships/bin/controls/users/MembershipUserEdit.html',
+    'css!package/quiqqer/memberships/bin/controls/users/MembershipUserEdit.css'
+
+], function (QUIControl, QUILoader, QUIButton, QUIFormUtils, MembershipUsersHandler,
+             QUILocale, QUIAjax, Mustache, template) {
+    "use strict";
+
+    var lg = 'quiqqer/memberships';
+
+    return new Class({
+
+        Extends: QUIControl,
+        Type   : 'package/quiqqer/memberships/bin/controls/users/MembershipUserEdit',
+
+        Binds: [
+            '$onInject',
+            '$load',
+            '$onCreate',
+            '$submit'
+        ],
+
+        options: {
+            membershipUserId: false // ID of MembershipUser (this is NOT the QUIQQER User ID!)
+        },
+
+        initialize: function (options) {
+            this.parent(options);
+
+            this.Loader          = new QUILoader();
+            this.$MembershipUser = null;
+
+            this.addEvents({
+                onCreate: this.$onCreate,
+                onInject: this.$onInject
+            });
+        },
+
+        /**
+         * Event: onCreate
+         */
+        $onCreate: function () {
+            this.$Elm.addClass('quiqqer-memberships-membershipuseredit');
+        },
+
+        /**
+         * Event: onImport
+         */
+        $onInject: function () {
+            this.Loader.inject(this.$Elm);
+            this.refresh();
+        },
+
+        /**
+         * Refresh data
+         */
+        refresh: function () {
+            var self = this;
+            this.Loader.show();
+
+            var mUid = this.getAttribute('membershipUserId');
+
+            MembershipUsersHandler.get(mUid).then(function (MembershipUser) {
+                self.Loader.hide();
+                self.$MembershipUser = MembershipUser;
+                self.$load();
+            });
+        },
+
+        /**
+         * Create elements
+         */
+        $load: function () {
+            var lgPrefix = 'controls.users.membershipuseredit.template.';
+
+            this.$Elm.set('html', Mustache.render(template, {
+                header        : QUILocale.get(lg, lgPrefix + 'header', {
+                    id  : this.$MembershipUser.id,
+                    name: this.$MembershipUser.fullName
+                }),
+                labelBeginDate: QUILocale.get(lg, 'controls.membershipusers.tbl.header.beginDate'),
+                labelEndDate  : QUILocale.get(lg, 'controls.membershipusers.tbl.header.endDate')
+            }));
+
+            new QUIButton({
+                textimage: 'fa fa-save',
+                text     : QUILocale.get(lg, 'controls.users.membershipuseredit.btn.save'),
+                events   : {
+                    onClick: this.$submit
+                }
+            }).inject(
+                this.$Elm.getElement('.quiqqer-memberships-membershipuseredit-submit')
+            );
+
+            var Form = this.$Elm.getElement('form');
+
+            QUIFormUtils.setDataToForm(this.$MembershipUser, Form);
+        },
+
+        /**
+         * Submit MembershipUser data
+         */
+        $submit: function () {
+            var self = this;
+            var Form = this.$Elm.getElement('form');
+
+            this.Loader.show();
+
+            MembershipUsersHandler.update(
+                this.$MembershipUser.id,
+                QUIFormUtils.getFormData(Form)
+            ).then(function (success) {
+                if (success) {
+                    self.fireEvent('submit', [self]);
+                }
+
+                self.Loader.hide();
+                self.refresh();
+            });
+        }
+    });
+});
diff --git a/bin/controls/users/MembershipUserHistory.css b/bin/controls/users/MembershipUserHistory.css
new file mode 100644
index 0000000..9f8bcdc
--- /dev/null
+++ b/bin/controls/users/MembershipUserHistory.css
@@ -0,0 +1,44 @@
+.quiqqer-memberships-membershipuserhistory {
+    float: left;
+    height: 100%;
+    width: 100%
+}
+
+.quiqqer-memberships-membershipuserhistory-history {
+    float: left;
+    margin-top: 20px;
+    width: 100%;
+}
+
+.quiqqer-memberships-membershipuserhistory-history-entry {
+    float: left;
+    width: 100%;
+}
+
+.quiqqer-memberships-membershipuserhistory-history-entry:not(:last-of-type) {
+    margin-bottom: 10px;
+}
+
+.quiqqer-memberships-membershipuserhistory-history-entry-header-action {
+    float: left;
+    font-weight: 900;
+}
+
+.quiqqer-memberships-membershipuserhistory-history-entry-header-date {
+    float: right;
+    font-size: 10px;
+}
+
+.quiqqer-memberships-membershipuserhistory-history-entry-header {
+    background-color: #dedede;
+    float: left;
+    padding: 2px 5px;
+    width: 100%;
+}
+
+.quiqqer-memberships-membershipuserhistory-history-entry-body {
+    background-color: #ededed;
+    float: left;
+    padding: 10px;
+    width: 100%;
+}
\ No newline at end of file
diff --git a/bin/controls/users/MembershipUserHistory.html b/bin/controls/users/MembershipUserHistory.html
new file mode 100644
index 0000000..6452871
--- /dev/null
+++ b/bin/controls/users/MembershipUserHistory.html
@@ -0,0 +1,9 @@
+<div class="quiqqer-memberships-membershipuserhistory-user">
+    <span class="quiqqer-memberships-membershipuserhistory-user-name">
+        {{userLabel}}: {{user}}
+    </span><br>
+    <span class="quiqqer-memberships-membershipuserhistory-user-name">
+        {{membershipLabel}}: {{membership}}
+    </span>
+</div>
+<div class="quiqqer-memberships-membershipuserhistory-history"></div>
\ No newline at end of file
diff --git a/bin/controls/users/MembershipUserHistory.js b/bin/controls/users/MembershipUserHistory.js
new file mode 100644
index 0000000..8288aa5
--- /dev/null
+++ b/bin/controls/users/MembershipUserHistory.js
@@ -0,0 +1,149 @@
+/**
+ * MembershipUserHistory JavaScript Control
+ *
+ * View the history log of a specific MembershipUser
+ *
+ * @module package/quiqqer/memberships/bin/controls/users/MembershipUserHistory
+ * @author www.pcsg.de (Patrick Müller)
+ *
+ * @require qui/controls/Control
+ * @require qui/controls/loader/Loader
+ * @require package/quiqqer/memberships/bin/MembershipUsers
+ * @require Locale
+ * @require Ajax
+ * @require Mustache
+ * @require text!package/quiqqer/memberships/bin/controls/users/MembershipUserHistory.html
+ * @require css!package/quiqqer/memberships/bin/controls/users/MembershipUserHistory.css
+ */
+define('package/quiqqer/memberships/bin/controls/users/MembershipUserHistory', [
+
+    'qui/controls/Control',
+    'qui/controls/loader/Loader',
+
+    'package/quiqqer/memberships/bin/MembershipUsers',
+
+    'Locale',
+    'Ajax',
+    'Mustache',
+
+    'text!package/quiqqer/memberships/bin/controls/users/MembershipUserHistory.html',
+    'css!package/quiqqer/memberships/bin/controls/users/MembershipUserHistory.css'
+
+], function (QUIControl, QUILoader, MembershipUsersHandler,
+             QUILocale, QUIAjax, Mustache, template) {
+    "use strict";
+
+    var lg = 'quiqqer/memberships';
+
+    return new Class({
+
+        Extends: QUIControl,
+        Type   : 'package/quiqqer/memberships/bin/controls/users/MembershipUserHistory',
+
+        Binds: [
+            '$onInject',
+            '$onCreate',
+            '$load'
+        ],
+
+        options: {
+            membershipUserId: false // ID of MembershipUser (this is NOT the QUIQQER User ID!)
+        },
+
+        initialize: function (options) {
+            this.parent(options);
+
+            this.Loader          = new QUILoader();
+            this.$MembershipUser = null;
+            this.$history        = [];
+
+            this.addEvents({
+                onCreate: this.$onCreate,
+                onInject: this.$onInject
+            });
+        },
+
+        /**
+         * Event: onCreate
+         */
+        $onCreate: function () {
+            this.$Elm.addClass('quiqqer-memberships-membershipuserhistory');
+        },
+
+        /**
+         * Event: onImport
+         */
+        $onInject: function () {
+            var self = this;
+
+            this.Loader.inject(this.$Elm);
+            this.Loader.show();
+
+            var mUid = this.getAttribute('membershipUserId');
+
+            Promise.all([
+                MembershipUsersHandler.get(mUid),
+                MembershipUsersHandler.getHistory(mUid)
+            ]).then(function (result) {
+                self.Loader.hide();
+                self.$MembershipUser = result[0];
+                self.$history        = result[1];
+                self.$load();
+            });
+        },
+
+        /**
+         * Create elements
+         */
+        $load: function () {
+            var lgPrefix = 'controls.users.membershipuserhistory.template.';
+            var username = this.$MembershipUser.fullName;
+
+            if (this.$MembershipUser.username !== this.$MembershipUser.fullName) {
+                username += ' (' + this.$MembershipUser.username + ')';
+            }
+
+            username += ' [' + this.$MembershipUser.userId + ']';
+
+            this.$Elm.set('html', Mustache.render(template, {
+                userLabel      : QUILocale.get(lg, lgPrefix + 'userLabel'),
+                membershipLabel: QUILocale.get(lg, lgPrefix + 'membershipLabel'),
+                user           : username,
+                membership     : this.$MembershipUser.membershipTitle
+                + ' [' + this.$MembershipUser.membershipId + ']'
+            }));
+
+            var HistoryElm = this.$Elm.getElement(
+                '.quiqqer-memberships-membershipuserhistory-history'
+            );
+
+            var i = this.$history.length;
+
+            this.$history.forEach(function (Entry) {
+                var EntryElm = new Element('div', {
+                    'class': 'quiqqer-memberships-membershipuserhistory-history-entry'
+                }).inject(HistoryElm);
+
+                // header
+                new Element('div', {
+                    'class': 'quiqqer-memberships-membershipuserhistory-history-entry-header',
+                    html   : '<span class="quiqqer-memberships-membershipuserhistory-history-entry-header-action">' +
+                    ' #' + i-- + ' ' +
+                    QUILocale.get(lg, 'controls.users.membershipuserhistory.entry.type.' + Entry.type) +
+                    '</span>' +
+                    '<span class="quiqqer-memberships-membershipuserhistory-history-entry-header-date">' +
+                    Entry.time + '<br>' + Entry.user +
+                    '</span>'
+                }).inject(EntryElm);
+
+                // body
+                if (Entry.msg !== '') {
+                    new Element('div', {
+                        'class': 'quiqqer-memberships-membershipuserhistory-history-entry-body',
+                        html   : Entry.msg
+                    }).inject(EntryElm);
+                }
+            });
+        }
+    });
+});
diff --git a/bin/controls/users/MembershipUserHistoryPopup.js b/bin/controls/users/MembershipUserHistoryPopup.js
new file mode 100644
index 0000000..c0c90e5
--- /dev/null
+++ b/bin/controls/users/MembershipUserHistoryPopup.js
@@ -0,0 +1,61 @@
+/**
+ * MembershipUserHistoryPopup JavaScript Control
+ *
+ * Popup vor viewing the history log of a specific MembershipUser
+ *
+ * @module package/quiqqer/memberships/bin/controls/users/MembershipUserHistoryPopup
+ * @author www.pcsg.de (Patrick Müller)
+ *
+ * @require qui/controls/windows/Popup
+ * @require Locale
+ */
+define('package/quiqqer/memberships/bin/controls/users/MembershipUserHistoryPopup', [
+
+    'qui/controls/windows/Popup',
+    'package/quiqqer/memberships/bin/controls/users/MembershipUserHistory',
+    'Locale'
+
+], function (QUIPopup, MembershipUserHistory, QUILocale) {
+    "use strict";
+
+    var lg = 'quiqqer/memberships';
+
+    return new Class({
+
+        Extends: QUIPopup,
+        Type   : 'package/quiqqer/memberships/bin/controls/users/MembershipUserHistoryPopup',
+
+        Binds: [
+            '$onOpen'
+        ],
+
+        options: {
+            membershipUserId: false, // ID of MembershipUser (this is NOT the QUIQQER User ID!)
+            maxWidth        : 550,
+            maxHeight       : 500,
+            icon            : 'fa fa-history',
+            title           : QUILocale.get(lg, 'controls.users.membershipuserhistorypopup.title'),
+            'class'         : 'quiqqer-memberships-membershipuserhistorypopup',
+
+            // buttons
+            closeButtonText: QUILocale.get('qui/controls/windows/Popup', 'btn.close')
+        },
+
+        initialize: function (options) {
+            this.parent(options);
+
+            this.addEvents({
+                onOpen: this.$onOpen
+            });
+        },
+
+        /**
+         * Event: onOpen
+         */
+        $onOpen: function () {
+            new MembershipUserHistory({
+                membershipUserId: this.getAttribute('membershipUserId')
+            }).inject(this.getContent());
+        }
+    });
+});
diff --git a/bin/controls/users/MembershipUsers.js b/bin/controls/users/MembershipUsers.js
index 6ceb782..3c80771 100644
--- a/bin/controls/users/MembershipUsers.js
+++ b/bin/controls/users/MembershipUsers.js
@@ -36,6 +36,7 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
 
     'package/quiqqer/memberships/bin/Memberships',
     'package/quiqqer/memberships/bin/MembershipUsers',
+    'package/quiqqer/memberships/bin/controls/users/MembershipUserEdit',
 
     'Locale',
     'Ajax',
@@ -46,7 +47,7 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
 
 ], function (QUIControl, QUILoader, QUIPopup, QUIConfirm, QUIButton, QUIFormUtils,
              QUIControlUtils, Grid, UserSearchWindow, Memberships, MembershipUsersHandler,
-             QUILocale, QUIAjax, Mustache, template) {
+             MembershipUserEdit, QUILocale, QUIAjax, Mustache, template) {
     "use strict";
 
     var lg = 'quiqqer/memberships';
@@ -66,7 +67,8 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
             '$removeUser',
             'refresh',
             '$removeUsers',
-            '$openUserPanel'
+            '$showHistory',
+            '$editUser'
         ],
 
         options: {
@@ -146,6 +148,20 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
                     events   : {
                         onClick: this.$addUser
                     }
+                }, {
+                    name     : 'edit',
+                    text     : QUILocale.get(lg, 'controls.membershipusers.tbl.btn.edit'),
+                    textimage: 'fa fa-edit',
+                    events   : {
+                        onClick: this.$editUser
+                    }
+                }, {
+                    name     : 'history',
+                    text     : QUILocale.get(lg, 'controls.users.membershipusersarchive.tbl.btn.history'),
+                    textimage: 'fa fa-history',
+                    events   : {
+                        onClick: this.$showHistory
+                    }
                 }, {
                     name     : 'removeuser',
                     text     : QUILocale.get(lg, 'controls.membershipusers.tbl.btn.removeuser'),
@@ -156,6 +172,11 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
                 }],
                 columnModel      : [{
                     header   : QUILocale.get('quiqqer/system', 'id'),
+                    dataIndex: 'id',
+                    dataType : 'number',
+                    width    : 100
+                }, {
+                    header   : QUILocale.get(lg, 'controls.membershipusers.tbl.header.userId'),
                     dataIndex: 'userId',
                     dataType : 'number',
                     width    : 100
@@ -189,10 +210,6 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
                     dataIndex: 'extendCounter',
                     dataType : 'number',
                     width    : 75
-                }, {
-                    dataIndex: 'id',
-                    dataType : 'number',
-                    hidden   : true
                 }],
                 pagination       : true,
                 serverSort       : true,
@@ -201,11 +218,20 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
             });
 
             this.$Grid.addEvents({
-                onDblClick: self.$openUserPanel,
+                onDblClick: self.$editUser,
                 onClick   : function () {
                     var TableButtons = self.$Grid.getAttribute('buttons');
+                    var selected     = self.$Grid.getSelectedData().length;
 
                     TableButtons.removeuser.enable();
+
+                    if (selected === 1) {
+                        TableButtons.history.enable();
+                        TableButtons.edit.enable();
+                    } else {
+                        TableButtons.history.disable();
+                        TableButtons.edit.disable();
+                    }
                 },
                 onRefresh : this.$listRefresh
             });
@@ -242,6 +268,8 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
             var TableButtons = this.$Grid.getAttribute('buttons');
 
             TableButtons.removeuser.disable();
+            TableButtons.history.disable();
+            TableButtons.edit.disable();
 
             var GridParams = {
                 sortOn : Grid.getAttribute('sortOn'),
@@ -334,6 +362,42 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
             AddUsersWindow.open();
         },
 
+        /**
+         * Edit MembershipUser
+         */
+        $editUser: function () {
+            var membershipUserId = this.$Grid.getSelectedData()[0].id;
+
+            // open popup
+            var Popup = new QUIPopup({
+                'maxHeight': 275,
+                maxWidth   : 500,
+                'autoclose': true,
+                'title'    : QUILocale.get(lg, 'controls.membershipusers.edit.popup.title'),
+                'texticon' : 'fa fa-edit',
+                'icon'     : 'fa fa-edit',
+
+                buttons: false,
+                events : {
+                    onOpen: function () {
+                        new MembershipUserEdit({
+                            membershipUserId: membershipUserId,
+                            events: {
+                                onSubmit: function() {
+                                    Popup.close();
+                                    self.refresh();
+                                }
+                            }
+                        }).inject(
+                            Popup.getContent()
+                        );
+                    }
+                }
+            });
+
+            Popup.open();
+        },
+
         /**
          * Remove all selected licenses
          */
@@ -395,17 +459,18 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsers', [
         },
 
         /**
-         * Opens a panel for a single location
+         * Show history
          */
-        $openUserPanel: function () {
-            var userId = this.$Grid.getSelectedData()[0].userId;
+        $showHistory: function () {
+            var membershipUserId = this.$Grid.getSelectedData()[0].id;
 
             require([
-                'controls/users/User',
-                'utils/Panels'
-            ], function (UserPanel, Utils) {
-                Utils.openPanelInTasks(new UserPanel(userId));
+                'package/quiqqer/memberships/bin/controls/users/MembershipUserHistoryPopup'
+            ], function (MembershipUserHistoryPopup) {
+                new MembershipUserHistoryPopup({
+                    membershipUserId: membershipUserId
+                }).open();
             });
-        },
+        }
     });
 });
diff --git a/bin/controls/users/MembershipUsersArchive.js b/bin/controls/users/MembershipUsersArchive.js
index 171e786..e5658a3 100644
--- a/bin/controls/users/MembershipUsersArchive.js
+++ b/bin/controls/users/MembershipUsersArchive.js
@@ -36,6 +36,7 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsersArchive',
 
     'package/quiqqer/memberships/bin/Memberships',
     'package/quiqqer/memberships/bin/MembershipUsers',
+    'package/quiqqer/memberships/bin/controls/users/MembershipUserHistory',
 
     'Locale',
     'Ajax',
@@ -46,7 +47,7 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsersArchive',
 
 ], function (QUIControl, QUILoader, QUIPopup, QUIConfirm, QUIButton, QUIFormUtils,
              QUIControlUtils, Grid, UserSearchWindow, Memberships, MembershipUsersHandler,
-             QUILocale, QUIAjax, Mustache, template) {
+             MembershipUserHistory, QUILocale, QUIAjax, Mustache, template) {
     "use strict";
 
     var lg = 'quiqqer/memberships';
@@ -145,6 +146,11 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsersArchive',
                 }],
                 columnModel      : [{
                     header   : QUILocale.get('quiqqer/system', 'id'),
+                    dataIndex: 'id',
+                    dataType : 'number',
+                    width    : 100
+                }, {
+                    header   : QUILocale.get(lg, 'controls.membershipusers.tbl.header.userId'),
                     dataIndex: 'userId',
                     dataType : 'number',
                     width    : 100
@@ -173,10 +179,6 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsersArchive',
                     dataIndex: 'archiveReason',
                     dataType : 'string',
                     width    : 200
-                }, {
-                    dataIndex: 'id',
-                    dataType : 'number',
-                    hidden   : true
                 }],
                 pagination       : true,
                 serverSort       : true,
@@ -185,7 +187,7 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsersArchive',
             });
 
             this.$Grid.addEvents({
-                onDblClick: self.$openUserPanel,
+                onDblClick: self.$showHistory,
                 onClick   : function () {
                     var TableButtons = self.$Grid.getAttribute('buttons');
                     var selected     = self.$Grid.getSelectedData().length;
@@ -253,8 +255,6 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsersArchive',
          * @param {Object} GridData
          */
         $setGridData: function (GridData) {
-            var self = this;
-
             for (var i = 0, len = GridData.data.length; i < len; i++) {
                 var Row = GridData.data[i];
 
@@ -271,63 +271,18 @@ define('package/quiqqer/memberships/bin/controls/users/MembershipUsersArchive',
         },
 
         /**
-         * Remove all selected licenses
+         * Show history
          */
         $showHistory: function () {
-            var self       = this;
-            var deleteData = [];
-            var deleteIds  = [];
-            var rows       = this.$Grid.getSelectedData();
-
-            for (var i = 0, len = rows.length; i < len; i++) {
-                deleteData.push(
-                    rows[i].username + ' (ID: #' + rows[i].id + ')'
-                );
-
-                deleteIds.push(rows[i].id);
-            }
-
-            // open popup
-            var Popup = new QUIConfirm({
-                'maxHeight': 300,
-                'autoclose': true,
-
-                'information': QUILocale.get(
-                    lg,
-                    'controls.membershipusersarchive.delete.popup.info', {
-                        users: deleteData.join('<br/>')
-                    }
-                ),
-                'title'      : QUILocale.get(lg, 'controls.membershipusersarchive.delete.popup.title'),
-                'texticon'   : 'fa fa-trash',
-                'icon'       : 'fa fa-trash',
-
-                cancel_button: {
-                    text     : false,
-                    textimage: 'icon-remove fa fa-remove'
-                },
-                ok_button    : {
-                    text     : false,
-                    textimage: 'icon-ok fa fa-check'
-                },
-                events       : {
-                    onSubmit: function () {
-                        Popup.Loader.show();
-
-                        MembershipUsersArchiveHandler.deleteMembershipUsersArchive(deleteIds).then(function (success) {
-                            if (!success) {
-                                Popup.Loader.hide();
-                                return;
-                            }
-
-                            Popup.close();
-                            self.refresh();
-                        });
-                    }
-                }
+            var membershipUserId = this.$Grid.getSelectedData()[0].id;
+
+            require([
+                'package/quiqqer/memberships/bin/controls/users/MembershipUserHistoryPopup'
+            ], function (MembershipUserHistoryPopup) {
+                new MembershipUserHistoryPopup({
+                    membershipUserId: membershipUserId
+                }).open();
             });
-
-            Popup.open();
         }
     });
 });
diff --git a/locale.xml b/locale.xml
index 8bd5017..9e09460 100644
--- a/locale.xml
+++ b/locale.xml
@@ -255,6 +255,14 @@
             <de><![CDATA[Benutzer entfernen]]></de>
             <en><![CDATA[Remove user(s)]]></en>
         </locale>
+        <locale name="controls.membershipusers.tbl.btn.edit">
+            <de><![CDATA[Benutzer bearbeiten]]></de>
+            <en><![CDATA[Edit user]]></en>
+        </locale>
+        <locale name="controls.membershipusers.tbl.header.userId">
+            <de><![CDATA[User ID]]></de>
+            <en><![CDATA[User ID]]></en>
+        </locale>
         <locale name="controls.membershipusers.tbl.header.username">
             <de><![CDATA[Benutzername]]></de>
             <en><![CDATA[Username]]></en>
@@ -287,6 +295,10 @@
             <de><![CDATA[Benutzer aus Mitgliedschaft entfernen]]></de>
             <en><![CDATA[Remove user(s) from membership]]></en>
         </locale>
+        <locale name="controls.membershipusers.edit.popup.title">
+            <de><![CDATA[Mitgliedschafts-Benutzer bearbeiten]]></de>
+            <en><![CDATA[Edit membership user]]></en>
+        </locale>
 
         <!-- Controls: users/MembershipUsersArchive -->
         <locale name="controls.users.membershipusersarchive.tbl.btn.history">
@@ -314,6 +326,72 @@
             <en><![CDATA[Membership expired]]></en>
         </locale>
 
+        <!-- Controls: users/MembershipUserHistory -->
+        <locale name="controls.users.membershipuserhistory.template.userLabel">
+            <de><![CDATA[Benutzer]]></de>
+            <en><![CDATA[User]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.template.membershipLabel">
+            <de><![CDATA[Mitgliedschaft]]></de>
+            <en><![CDATA[Membership]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.footer">
+            <de><![CDATA[ausgeführt von [user]]]></de>
+            <en><![CDATA[executed by [user]]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.created">
+            <de><![CDATA[Zur Mitgliedschaft hinzugefügt]]></de>
+            <en><![CDATA[Added to the membership]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.updated">
+            <de><![CDATA[Mitgliedschafts-Daten aktualisiert]]></de>
+            <en><![CDATA[Updated membership data]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.cancel_start">
+            <de><![CDATA[Kündigungs-Anfrage gestellt]]></de>
+            <en><![CDATA[Termination requested]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.cancel_confirm">
+            <de><![CDATA[Kündigung bestätigt]]></de>
+            <en><![CDATA[Termination confirmed]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.cancelled">
+            <de><![CDATA[Mitgliedschaft gekündigt]]></de>
+            <en><![CDATA[Membership terminated]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.expired">
+            <de><![CDATA[Mitgliedschafts-Zeit abgelaufen]]></de>
+            <en><![CDATA[Membership expired]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.deleted">
+            <de><![CDATA[Aus Mitgliedschaft gelöscht]]></de>
+            <en><![CDATA[Deleted from membership]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.archived">
+            <de><![CDATA[Mitgliedschafts-Benutzer archiviert]]></de>
+            <en><![CDATA[Membership user archived]]></en>
+        </locale>
+        <locale name="controls.users.membershipuserhistory.entry.type.extended">
+            <de><![CDATA[Mitgliedschafts-Zeit verlängert]]></de>
+            <en><![CDATA[Membership extended]]></en>
+        </locale>
+
+        <!-- Controls: users/MembershipUserHistoryPopup -->
+        <locale name="controls.users.membershipuserhistorypopup.title">
+            <de><![CDATA[Mitgliedschafts-Benutzer History]]></de>
+            <en><![CDATA[Membership user history]]></en>
+        </locale>
+
+        <!-- Controls: users/MembershipUserEdit -->
+        <locale name="controls.users.membershipuseredit.template.header">
+            <de><![CDATA[Mitgliedschafts-Benutzer [name] (#[id])]]></de>
+            <en><![CDATA[Membership user [name] (#[id])]]></en>
+        </locale>
+        <locale name="controls.users.membershipuseredit.btn.save">
+            <de><![CDATA[Benutzerdaten speichern]]></de>
+            <en><![CDATA[Save user data]]></en>
+        </locale>
+
     </groups>
 
     <groups name="quiqqer/memberships" datatype="php">
@@ -373,6 +451,14 @@
             <de><![CDATA[Die Benutzer wurden erfolgreich aus der Mitgliedschaft gelöscht.]]></de>
             <en><![CDATA[The users have been removed successfully from the membership.]]></en>
         </locale>
+        <locale name="message.ajax.memberships.users.update.error" html="true">
+            <de><![CDATA[Der Mitgliedschaftsbenutzer konnte nicht bearbeitet werden:<br><br>[error]]]></de>
+            <en><![CDATA[The membership user could not be updated:<br><br>[error]]]></en>
+        </locale>
+        <locale name="message.ajax.memberships.users.update.success" html="true">
+            <de><![CDATA[Der Mitgliedschafts-Benutzer <b>[membershipUserName]</b> (#[membershipUserId]) wurde erfolgreich aktualisiert.]]></de>
+            <en><![CDATA[The membership user <b>[membershipUserName]</b> (#[membershipUserId]) was successfully updated.]]></en>
+        </locale>
 
         <!-- Class: Membership -->
         <locale name="exception.membership.cannot.update.when.locked">
@@ -419,6 +505,18 @@
             <de><![CDATA[Der Prüf-Hash für die Mitgliedschafts-Kündigung ist fehlerhaft.]]></de>
             <en><![CDATA[The cancel check hash for this membership cancellation is wrong.]]></en>
         </locale>
+        <locale name="exception.users.membershipuser.wrong.dates">
+            <de><![CDATA[Die Datumsangaben für den Start- oder Endzeitpunkt sind in einem inkorrekten Format.]]></de>
+            <en><![CDATA[The dates for the start or end time are in an incorrect format.]]></en>
+        </locale>
+        <locale name="exception.users.membershipuser.begin.after.end">
+            <de><![CDATA[Der Startzeitpunkt darf nicht nach dem Endzeitpunkt liegen.]]></de>
+            <en><![CDATA[The start time must not be after the end time.]]></en>
+        </locale>
+        <locale name="exception.users.membershipuser.no.email">
+            <de><![CDATA[Es können keine E-Mails an den Mitgliedschafts-Benutzer gesendet werden, da er keine eingetragene E-Mail-Adresse hat.]]></de>
+            <en><![CDATA[No e-mails can be sent to the membership user, since he does not have a registered e-mail address.]]></en>
+        </locale>
 
         <!-- Templates -->
         <locale name="templates.mail.greeting">
diff --git a/src/QUI/Memberships/Cron.php b/src/QUI/Memberships/Cron.php
index c551433..780c71e 100644
--- a/src/QUI/Memberships/Cron.php
+++ b/src/QUI/Memberships/Cron.php
@@ -30,33 +30,40 @@ public static function checkMembershipUsers()
         $now = time();
 
         foreach ($result as $row) {
-            /** @var MembershipUser $MembershipUser */
-            $MembershipUser = $MembershipUsers->getChild($row['id']);
-            $Membership     = $MembershipUser->getMembership();
+            try {
+                /** @var MembershipUser $MembershipUser */
+                $MembershipUser = $MembershipUsers->getChild($row['id']);
+                $Membership     = $MembershipUser->getMembership();
 
-            // @todo prüfen, ob benutzer existiert und ggf. löschen
+                // @todo prüfen, ob benutzer existiert und ggf. löschen
 
-            // check if membership has expired
-            $endTimestamp = strtotime($Membership->getAttribute('endDate'));
+                // check if membership has expired
+                $endTimestamp = strtotime($Membership->getAttribute('endDate'));
 
-            if ($now < $endTimestamp) {
-                continue;
-            }
+                if ($now < $endTimestamp) {
+                    continue;
+                }
 
-            // if membership has been cancelled -> archive it immediately
-            if ($MembershipUser->isCancelled()) {
-                $MembershipUser->cancel();
-                continue;
-            }
+                // if membership has been cancelled -> archive it immediately
+                if ($MembershipUser->isCancelled()) {
+                    $MembershipUser->cancel();
+                    continue;
+                }
+
+                // extend if membership is extended automatically
+                if ($Membership->isAutoExtend()) {
+                    $MembershipUser->extend();
+                    continue;
+                }
 
-            // extend if membership is extended automatically
-            if ($Membership->isAutoExtend()) {
-                $MembershipUser->extend();
-                continue;
+                // expire membership
+                $MembershipUser->expire();
+            } catch (\Exception $Exception) {
+                QUI\System\Log::addError(
+                    self::class . ' :: checkMembershipUsers() -> ' . $Exception->getMessage()
+                );
             }
 
-            // expire membership
-            $MembershipUser->expire();
         }
     }
 }
diff --git a/src/QUI/Memberships/Users/MembershipUser.php b/src/QUI/Memberships/Users/MembershipUser.php
index 98124d5..06dfd52 100644
--- a/src/QUI/Memberships/Users/MembershipUser.php
+++ b/src/QUI/Memberships/Users/MembershipUser.php
@@ -18,6 +18,34 @@
  */
 class MembershipUser extends Child
 {
+    /**
+     * @inheritdoc
+     */
+    public function update()
+    {
+        // check certain attributes
+        $beginDate = strtotime($this->getAttribute('beginDate'));
+        $endDate   = strtotime($this->getAttribute('endDate'));
+
+        if ($beginDate === false
+            || $endDate === false
+        ) {
+            throw new QUI\Memberships\Exception(array(
+                'quiqqer/memberships',
+                'exception.users.membershipuser.wrong.dates'
+            ));
+        }
+
+        if ($beginDate >= $endDate) {
+            throw new QUI\Memberships\Exception(array(
+                'quiqqer/memberships',
+                'exception.users.membershipuser.begin.after.end'
+            ));
+        }
+
+        parent::update();
+    }
+
     /**
      * Extend the current membership cycle of this membership user
      *
@@ -108,6 +136,8 @@ public function startManualCancel()
             'cancelDate' => $cancelDate
         ));
 
+        $this->addHistoryEntry(MembershipUsersHandler::HISTORY_TYPE_CANCEL_START);
+
         // save cancel hash and date to database
         $this->update();
 
@@ -132,6 +162,13 @@ public function startManualCancel()
      */
     public function confirmManualCancel($confirmHash)
     {
+        if ($this->isCancelled()) {
+            throw new QUI\Memberships\Exception(array(
+                'quiqqer/memberships',
+                'exception.users.membershipuser.confirmManualCancel.already.cancelled'
+            ));
+        }
+
         $cancelHash = $this->getAttribute('cancelHash');
 
 //        if (empty($cancelHash)) {
@@ -148,18 +185,12 @@ public function confirmManualCancel($confirmHash)
             ));
         }
 
-        if ($this->isCancelled()) {
-            throw new QUI\Memberships\Exception(array(
-                'quiqqer/memberships',
-                'exception.users.membershipuser.confirmManualCancel.already.cancelled'
-            ));
-        }
-
         $this->setAttributes(array(
             'cancelled' => true
         ));
 
         $this->addHistoryEntry(MembershipUsersHandler::HISTORY_TYPE_CANCEL_CONFIRM);
+        $this->update();
 
         // send confirm cancel mail
         $subject = $this->getUser()->getLocale()->get('quiqqer/memberships', 'templates.mail.confirmcancel.subject');
@@ -328,7 +359,7 @@ public function getUser()
      * @param string $type - History entry type (see \QUI\Memberships\Users\Handler)
      * @param string $msg (optional) - additional custom message
      */
-    public function addHistoryEntry($type, $msg = null)
+    public function addHistoryEntry($type, $msg = "")
     {
         $history = $this->getAttribute('history');
 
@@ -344,12 +375,30 @@ public function addHistoryEntry($type, $msg = null)
             'type' => $type,
             'time' => Utils::getFormattedTimestamp(),
             'user' => $User->getUsername() . ' (' . $User->getId() . ')',
-            'msg'  => $msg ?: '-'
+            'msg'  => $msg
         );
 
         $this->setAttribute('history', json_encode($history));
     }
 
+    /**
+     * Get history data of this MembershipUser
+     *
+     * @return array
+     */
+    public function getHistory()
+    {
+        $history = $this->getAttribute('history');
+
+        if (empty($history)) {
+            $history = array();
+        } else {
+            $history = json_decode($history, true);
+        }
+
+        return $history;
+    }
+
     /**
      * Send an email to the membership user
      *
@@ -357,9 +406,21 @@ public function addHistoryEntry($type, $msg = null)
      * @param string $templateFile
      * @param array $templateVars (optional) - additional template variables (besides $this)
      * @return void
+     *
+     * @throws QUI\Memberships\Exception
      */
     protected function sendMail($subject, $templateFile, $templateVars = array())
     {
+        $User  = $this->getUser();
+        $email = $User->getAttribute('email');
+
+        if (empty($email)) {
+            throw new QUI\Memberships\Exception(array(
+                'quiqqer/memberships',
+                'exception.users.membershipuser.no.email'
+            ));
+        }
+
         $Engine = QUI::getTemplateManager()->getEngine();
 
         $Engine->assign(array_merge(
@@ -374,8 +435,7 @@ protected function sendMail($subject, $templateFile, $templateVars = array())
 
         $Mailer = new Mailer();
 
-        // @todo E-Mail aus Benutzer holen
-        $Mailer->addRecipient('peat@pcsg.de', $this->getUser()->getName());
+        $Mailer->addRecipient($email, $User->getName());
         $Mailer->setSubject($subject);
         $Mailer->setBody($template);
         $Mailer->send();
diff --git a/templates/mail_autoextend.html b/templates/mail_autoextend.html
index e8754c4..8f7662b 100644
--- a/templates/mail_autoextend.html
+++ b/templates/mail_autoextend.html
@@ -1,10 +1,10 @@
 <h1>
-    {locale group="quiqqer/memberships" value="templates.mail.greeting"
+    {locale group="quiqqer/memberships" value="templates.mail.greeting" Locale=$Locale
     name=$MembershipUser->getUser()->getName()}
 </h1>
 
 <p>
-    {locale group="quiqqer/memberships" value="templates.mail.autoextend.body"
+    {locale group="quiqqer/memberships" value="templates.mail.autoextend.body" Locale=$Locale
     membershipTitle=$MembershipUser->getMembership()->getTitle()
     endDate=$MembershipUser->getAttribute('endDate')
     }
diff --git a/templates/mail_confirmcancel.html b/templates/mail_confirmcancel.html
index 510cd48..632fcd1 100644
--- a/templates/mail_confirmcancel.html
+++ b/templates/mail_confirmcancel.html
@@ -1,9 +1,10 @@
 <h1>
-    {$Locale->get('quiqqer/memberships', 'templates.mail.greeting', array('name' => $MembershipUser->getUser()->getName()}
+    {locale group="quiqqer/memberships" value="templates.mail.greeting" Locale=$Locale
+    name=$MembershipUser->getUser()->getName()}
 </h1>
 
 <p>
-    {locale group="quiqqer/memberships" value="templates.mail.confirmcancel.body"
+    {locale group="quiqqer/memberships" value="templates.mail.confirmcancel.body" Locale=$Locale
     membershipTitle=$MembershipUser->getMembership()->getTitle()
     endDate=$MembershipUser->getAttribute('endDate')
     }
diff --git a/templates/mail_expired.html b/templates/mail_expired.html
index c0966b1..23e1fa5 100644
--- a/templates/mail_expired.html
+++ b/templates/mail_expired.html
@@ -1,10 +1,10 @@
 <h1>
-    {locale group="quiqqer/memberships" value="templates.mail.greeting"
+    {locale group="quiqqer/memberships" value="templates.mail.greeting" Locale=$Locale
     name=$MembershipUser->getUser()->getName()}
 </h1>
 
 <p>
-    {locale group="quiqqer/memberships" value="templates.mail.expired.body"
+    {locale group="quiqqer/memberships" value="templates.mail.expired.body" Locale=$Locale
     membershipTitle=$MembershipUser->getMembership()->getTitle()
     endDate=$MembershipUser->getAttribute('endDate')
     }
-- 
GitLab