فهرست منبع

Adding optional feature so "users can create redirects on their own" (amount can be limited by admin) #27

ohartl 9 سال پیش
والد
کامیت
ecd556cdba

+ 21 - 5
config/config.php.example

@@ -59,6 +59,7 @@ return array(
 				'domain' => 'domain',
 				'domain' => 'domain',
 				'password' => 'password',
 				'password' => 'password',
 				'mailbox_limit' => 'mailbox_limit', // (Optional see 'options.enable_mailbox_limits')
 				'mailbox_limit' => 'mailbox_limit', // (Optional see 'options.enable_mailbox_limits')
+				'max_user_redirects' => 'max_user_redirects', // (Optional see 'options.enable_user_redirects')
 			),
 			),
 
 
 			// Domains table columns
 			// Domains table columns
@@ -73,6 +74,7 @@ return array(
 				'source' => 'source',
 				'source' => 'source',
 				'destination' => 'destination',
 				'destination' => 'destination',
 				'multi_source' => 'multi_source', // (Optional see 'options.enable_multi_source_redirects')
 				'multi_source' => 'multi_source', // (Optional see 'options.enable_multi_source_redirects')
+				'is_created_by_user' => 'is_created_by_user', // (Optional see 'options.enable_user_redirects')
 			),
 			),
 		),
 		),
 	),
 	),
@@ -87,7 +89,8 @@ return array(
 		/**
 		/**
 		 * Enable mailbox limits. (Default false == off)
 		 * Enable mailbox limits. (Default false == off)
 		 *
 		 *
-		 * Needs a new db attribute in users table with INT(10)
+		 * Needs a new db attribute in users table with INT(10).
+		 * (see 'schema.attributes.users.mailbox_limit')
 		 */
 		 */
 
 
 		'enable_mailbox_limits' => false,
 		'enable_mailbox_limits' => false,
@@ -103,7 +106,8 @@ return array(
 		/**
 		/**
 		 * Enable multi source redirects. (Default false == off)
 		 * Enable multi source redirects. (Default false == off)
 		 *
 		 *
-		 * Needs a new db attribute in aliases table with VARCHAR(32)
+		 * Needs a new db attribute in aliases table with VARCHAR(32).
+		 * (see 'schema.attributes.aliases.multi_source')
 		 */
 		 */
 
 
 		'enable_multi_source_redirects' => false,
 		'enable_multi_source_redirects' => false,
@@ -112,17 +116,29 @@ return array(
 		/**
 		/**
 		 * Enable limited admin domain access. (Default false == off)
 		 * Enable limited admin domain access. (Default false == off)
 		 *
 		 *
-		 * Limitations can be configured under 'admin_domain_limits'
+		 * Limitations can be configured under 'admin_domain_limits'.
 		 */
 		 */
 
 
 		'enable_admin_domain_limits' => false,
 		'enable_admin_domain_limits' => false,
 
 
 
 
+		/**
+		 * Enable users can create own redirects. (Default false == off)
+		 *
+		 * Needs two new db attributes in users table with INT(10) and aliases table with INT(1) + DEFAULT 0
+		 * (see 'schema.attributes.users.max_user_redirects' and 'schema.attributes.aliases.is_created_by_user')
+		 *
+		 * A maximum number of redirects per user can be configured.
+		 */
+
+		'enable_user_redirects' => false,
+
+
 		/**
 		/**
 		 * Enable logging for failed login attempts. (Default false == off)
 		 * Enable logging for failed login attempts. (Default false == off)
 		 *
 		 *
-		 * You can mointor the logfile with fail2ban and ban attackers' IP-addresses.
-		 * Path to logfile can be configured under 'log_path'
+		 * You can monitor the logfile with fail2ban and ban attackers' IP-addresses.
+		 * Path to logfile can be configured under 'log_path'.
 		 */
 		 */
 
 
 		'enable_logging' => false,
 		'enable_logging' => false,

+ 30 - 0
include/php/models/AbstractRedirect.php

@@ -49,6 +49,7 @@ abstract class AbstractRedirect extends AbstractModel
 				'source' => Config::get('schema.attributes.aliases.source', 'source'),
 				'source' => Config::get('schema.attributes.aliases.source', 'source'),
 				'destination' => Config::get('schema.attributes.aliases.destination', 'destination'),
 				'destination' => Config::get('schema.attributes.aliases.destination', 'destination'),
 				'multi_hash' => Config::get('schema.attributes.aliases.multi_source', 'multi_source'),
 				'multi_hash' => Config::get('schema.attributes.aliases.multi_source', 'multi_source'),
+				'is_created_by_user' => Config::get('schema.attributes.aliases.is_created_by_user', 'is_created_by_user'),
 			);
 			);
 		}
 		}
 	}
 	}
@@ -64,6 +65,10 @@ abstract class AbstractRedirect extends AbstractModel
 		$data['source'] = emailsToString($data['source']);
 		$data['source'] = emailsToString($data['source']);
 		$data['destination'] = emailsToString($data['destination']);
 		$data['destination'] = emailsToString($data['destination']);
 
 
+		if(Config::get('options.enable_user_redirects', false)){
+			$data['is_created_by_user'] = $data['is_created_by_user'] ? 1 : 0;
+		}
+
 		return $data;
 		return $data;
 	}
 	}
 
 
@@ -92,6 +97,10 @@ abstract class AbstractRedirect extends AbstractModel
 		if(Config::get('options.enable_multi_source_redirects', false)){
 		if(Config::get('options.enable_multi_source_redirects', false)){
 			$this->setMultiHash($data[static::attr('multi_hash')]);
 			$this->setMultiHash($data[static::attr('multi_hash')]);
 		}
 		}
+
+		if(Config::get('options.enable_user_redirects', false)){
+			$this->setIsCreatedByUser($data[static::attr('is_created_by_user')]);
+		}
 	}
 	}
 
 
 
 
@@ -175,6 +184,7 @@ abstract class AbstractRedirect extends AbstractModel
 		}
 		}
 	}
 	}
 
 
+
 	/**
 	/**
 	 * @return string
 	 * @return string
 	 */
 	 */
@@ -193,6 +203,24 @@ abstract class AbstractRedirect extends AbstractModel
 	}
 	}
 
 
 
 
+	/**
+	 * @return bool
+	 */
+	public function isCreatedByUser()
+	{
+		return $this->getAttribute('is_created_by_user');
+	}
+
+
+	/**n
+	 * @param bool $value
+	 */
+	public function setIsCreatedByUser($value)
+	{
+		$this->setAttribute('is_created_by_user', $value ? true : false);
+	}
+
+
 	/**
 	/**
 	 * @return array
 	 * @return array
 	 */
 	 */
@@ -290,6 +318,7 @@ abstract class AbstractRedirect extends AbstractModel
 		GROUP_CONCAT(g.`".static::attr('source')."` SEPARATOR ',') AS `".static::attr('source')."`,
 		GROUP_CONCAT(g.`".static::attr('source')."` SEPARATOR ',') AS `".static::attr('source')."`,
 		g.`".static::attr('destination')."`,
 		g.`".static::attr('destination')."`,
 		g.`".static::attr('multi_hash')."`
 		g.`".static::attr('multi_hash')."`
+".(Config::get('options.enable_user_redirects', false) ? ", g.`".static::attr('is_created_by_user')."`" : "")."
 	FROM `".static::$table."` AS g
 	FROM `".static::$table."` AS g
 	WHERE g.`".static::attr('multi_hash')."` IS NOT NULL
 	WHERE g.`".static::attr('multi_hash')."` IS NOT NULL
 	GROUP BY g.`".static::attr('multi_hash')."`
 	GROUP BY g.`".static::attr('multi_hash')."`
@@ -299,6 +328,7 @@ UNION
 		s.`".static::attr('source')."`,
 		s.`".static::attr('source')."`,
 		s.`".static::attr('destination')."`,
 		s.`".static::attr('destination')."`,
 		s.`".static::attr('multi_hash')."`
 		s.`".static::attr('multi_hash')."`
+".(Config::get('options.enable_user_redirects', false) ? ", s.`".static::attr('is_created_by_user')."`" : "")."
 	FROM `".static::$table."` AS s
 	FROM `".static::$table."` AS s
 	WHERE s.`".static::attr('multi_hash')."` IS NULL
 	WHERE s.`".static::attr('multi_hash')."` IS NULL
 ) AS r";
 ) AS r";

+ 113 - 8
include/php/models/User.php

@@ -60,6 +60,7 @@ class User extends AbstractModel
 				'domain' => Config::get('schema.attributes.users.domain', 'domain'),
 				'domain' => Config::get('schema.attributes.users.domain', 'domain'),
 				'password_hash' => Config::get('schema.attributes.users.password', 'password'),
 				'password_hash' => Config::get('schema.attributes.users.password', 'password'),
 				'mailbox_limit' => Config::get('schema.attributes.users.mailbox_limit'),
 				'mailbox_limit' => Config::get('schema.attributes.users.mailbox_limit'),
+				'max_user_redirects' => Config::get('schema.attributes.users.max_user_redirects'),
 			);
 			);
 		}
 		}
 	}
 	}
@@ -79,6 +80,10 @@ class User extends AbstractModel
 			? intval($data[static::attr('mailbox_limit')])
 			? intval($data[static::attr('mailbox_limit')])
 			: 0
 			: 0
 		);
 		);
+		$this->setMaxUserRedirects(Config::get('options.enable_user_redirects', false)
+			? intval($data[static::attr('max_user_redirects')])
+			: 0
+		);
 
 
 		$this->setAttribute('role', static::getRoleByEmail($this->getEmail()));
 		$this->setAttribute('role', static::getRoleByEmail($this->getEmail()));
 	}
 	}
@@ -161,30 +166,87 @@ class User extends AbstractModel
 	 */
 	 */
 	public function setMailboxLimit($value)
 	public function setMailboxLimit($value)
 	{
 	{
-		$this->setAttribute('mailbox_limit', $value);
+		$this->setAttribute('mailbox_limit', intval($value));
 	}
 	}
 
 
 
 
 	/**
 	/**
-	 * Get mailbox limit default via database default value
-	 *
 	 * @return int
 	 * @return int
 	 */
 	 */
-	public static function getMailboxLimitDefault()
+	public function getMaxUserRedirects()
 	{
 	{
-		static::initModel();
+		return $this->getAttribute('max_user_redirects');
+	}
 
 
-		if(Config::get('options.enable_mailbox_limits', false)){
 
 
-			$sql = "SELECT DEFAULT(".static::attr('mailbox_limit').") FROM `".static::$table."` LIMIT 1";
+	/**
+	 * @param int $value
+	 */
+	public function setMaxUserRedirects($value)
+	{
+		$this->setAttribute('max_user_redirects', intval($value));
+	}
+
 
 
+	/**
+	 * @param string $attr
+	 * @param mixed $default
+	 *
+	 * @return mixed
+	 *
+	 * @throws Exception
+	 */
+	protected static function getAttributeDefaultValue($attr, $default)
+	{
+		static::initModel();
+
+		$sql = "SELECT DEFAULT(".static::attr($attr).") FROM `".static::$table."` LIMIT 1";
+
+		try {
 			$result = Database::getInstance()->query($sql);
 			$result = Database::getInstance()->query($sql);
 
 
 			if($result->num_rows === 1){
 			if($result->num_rows === 1){
 				$row = $result->fetch_array();
 				$row = $result->fetch_array();
 
 
-				return intval($row[0]);
+				return $row[0];
+			}
+		}
+		catch(Exception $e) {
+			if (strpos($e->getMessage(), 'doesn\'t have a default') !== false) {
+				throw new Exception('Database table "'.static::$table.'" is missing a default value for attribute "'.static::attr($attr).'".');
 			}
 			}
+
+			return $default;
+		}
+
+		return $default;
+	}
+
+
+	/**
+	 * Get mailbox limit default via database default value
+	 *
+	 * @return int
+	 */
+	public static function getMailboxLimitDefault()
+	{
+		if(Config::get('options.enable_mailbox_limits', false)){
+			return intval(static::getAttributeDefaultValue('mailbox_limit', 0));
+		}
+
+		return 0;
+	}
+
+
+	/**
+	 * Get max user redirects default via database default value
+	 *
+	 * @return int
+	 */
+	public static function getMaxUserRedirectsDefault()
+	{
+		if(Config::get('options.enable_user_redirects', false)){
+			return intval(static::getAttributeDefaultValue('max_user_redirects', 0));
 		}
 		}
 
 
 		return 0;
 		return 0;
@@ -250,6 +312,33 @@ class User extends AbstractModel
 	}
 	}
 
 
 
 
+	/**
+	 * @return bool
+	 */
+	public function isAllowedToCreateUserRedirects()
+	{
+		return $this->getMaxUserRedirects() >= 0;
+	}
+
+
+	/**
+	 * @return bool
+	 */
+	public function canCreateUserRedirects()
+	{
+		if(!$this->isAllowedToCreateUserRedirects()
+			|| (
+				$this->getMaxUserRedirects() > 0
+				&& $this->getSelfCreatedRedirects()->count() >= $this->getMaxUserRedirects()
+			)
+		){
+			return false;
+		}
+
+		return true;
+	}
+
+
 	/**
 	/**
 	 * @return AbstractRedirect
 	 * @return AbstractRedirect
 	 */
 	 */
@@ -299,6 +388,22 @@ class User extends AbstractModel
 	}
 	}
 
 
 
 
+	/**
+	 * @return ModelCollection|AbstractRedirect[]
+	 */
+	public function getSelfCreatedRedirects()
+	{
+		$redirects = $this->getRedirects();
+
+		return $redirects->searchAll(
+			function($redirect) {
+				/** @var AbstractRedirect $redirect */
+				return $redirect->isCreatedByUser();
+			}
+		);
+	}
+
+
 	/**
 	/**
 	 * Change this users password, throws Exception if password is invalid.
 	 * Change this users password, throws Exception if password is invalid.
 	 *
 	 *

+ 6 - 8
include/php/pages/admin/editredirect.php

@@ -134,6 +134,7 @@ if(isset($_POST['savemode'])){
 							$thisRedirect->setSource($sourceAddress);
 							$thisRedirect->setSource($sourceAddress);
 							$thisRedirect->setDestination($inputDestinations);
 							$thisRedirect->setDestination($inputDestinations);
 							$thisRedirect->setMultiHash($hash);
 							$thisRedirect->setMultiHash($hash);
+							// Don't set 'isCreatedByUser' here, it will overwrite redirects created by user
 							$thisRedirect->save();
 							$thisRedirect->save();
 
 
 							$existingRedirectsToEdit->delete($thisRedirect->getId()); // mark updated
 							$existingRedirectsToEdit->delete($thisRedirect->getId()); // mark updated
@@ -142,10 +143,9 @@ if(isset($_POST['savemode'])){
 							$data = array(
 							$data = array(
 								AbstractRedirect::attr('source') => $sourceAddress,
 								AbstractRedirect::attr('source') => $sourceAddress,
 								AbstractRedirect::attr('destination') => emailsToString($inputDestinations),
 								AbstractRedirect::attr('destination') => emailsToString($inputDestinations),
+								AbstractRedirect::attr('multi_hash') => $hash,
+								AbstractRedirect::attr('is_created_by_user') => false,
 							);
 							);
-							if(Config::get('options.enable_multi_source_redirects', false)){
-								$data[AbstractRedirect::attr('multi_hash')] = $hash;
-							}
 
 
 							AbstractRedirect::createAndSave($data);
 							AbstractRedirect::createAndSave($data);
 						}
 						}
@@ -189,12 +189,10 @@ if(isset($_POST['savemode'])){
 						$data = array(
 						$data = array(
 							AbstractRedirect::attr('source') => $inputSource,
 							AbstractRedirect::attr('source') => $inputSource,
 							AbstractRedirect::attr('destination') => $inputDestination,
 							AbstractRedirect::attr('destination') => $inputDestination,
+							AbstractRedirect::attr('multi_hash') => $hash,
+							AbstractRedirect::attr('is_created_by_user') => false,
 						);
 						);
 
 
-						if(Config::get('options.enable_multi_source_redirects', false)){
-							$data[AbstractRedirect::attr('multi_hash')] = $hash;
-						}
-
 						$a = AbstractRedirect::createAndSave($data);
 						$a = AbstractRedirect::createAndSave($data);
 					}
 					}
 
 
@@ -260,7 +258,7 @@ $domains = Domain::getByLimitedDomains();
 						<?php endforeach; ?>
 						<?php endforeach; ?>
 					</ul>
 					</ul>
 				<?php else: ?>
 				<?php else: ?>
-					There are no domains managed by in WebMUM yet.
+					There are no domains managed by WebMUM yet.
 				<?php endif; ?>
 				<?php endif; ?>
 			</div>
 			</div>
 			<div class="input">
 			<div class="input">

+ 62 - 2
include/php/pages/admin/edituser.php

@@ -2,6 +2,9 @@
 
 
 $mailboxLimitDefault = User::getMailboxLimitDefault();
 $mailboxLimitDefault = User::getMailboxLimitDefault();
 
 
+$canCreateUserRedirectsDefault = false;
+$maxUserRedirectsDefault = User::getMaxUserRedirectsDefault();
+
 $saveMode = (isset($_POST['savemode']) && in_array($_POST['savemode'], array('edit', 'create')))
 $saveMode = (isset($_POST['savemode']) && in_array($_POST['savemode'], array('edit', 'create')))
 	? $_POST['savemode']
 	? $_POST['savemode']
 	: null;
 	: null;
@@ -19,6 +22,18 @@ if(!is_null($saveMode)){
 		}
 		}
 	}
 	}
 
 
+	$inputMaxUserRedirects = null;
+	if(Config::get('options.enable_user_redirects', false)){
+		$inputMaxUserRedirects = isset($_POST['max_user_redirects']) ? intval($_POST['max_user_redirects']) : $maxUserRedirectsDefault;
+		if(!$inputMaxUserRedirects === 0 && empty($inputMaxUserRedirects)){
+			$inputMaxUserRedirects = $maxUserRedirectsDefault;
+		}
+
+		if(isset($_POST['user_redirects']) && $_POST['user_redirects'] === 'no'){
+			$inputMaxUserRedirects = -1;
+		}
+	}
+
 	if($saveMode === 'edit'){
 	if($saveMode === 'edit'){
 		// Edit mode entered
 		// Edit mode entered
 
 
@@ -41,10 +56,14 @@ if(!is_null($saveMode)){
 			Router::redirect("admin/listusers/?missing-permission=1");
 			Router::redirect("admin/listusers/?missing-permission=1");
 		}
 		}
 
 
-		if(Config::get('options.enable_mailbox_limits', false) && !is_null($inputMailboxLimit)){
+		if(!is_null($inputMailboxLimit)){
 			$userToEdit->setMailboxLimit($inputMailboxLimit);
 			$userToEdit->setMailboxLimit($inputMailboxLimit);
 		}
 		}
 
 
+		if(!is_null($inputMaxUserRedirects)){
+			$userToEdit->setMaxUserRedirects($inputMaxUserRedirects);
+		}
+
 		$passwordError = false;
 		$passwordError = false;
 
 
 		// Is there a changed password?
 		// Is there a changed password?
@@ -108,10 +127,14 @@ if(!is_null($saveMode)){
 							User::attr('password_hash') => Auth::generatePasswordHash($inputPassword)
 							User::attr('password_hash') => Auth::generatePasswordHash($inputPassword)
 						);
 						);
 
 
-						if(Config::get('options.enable_mailbox_limits', false) && !is_null($inputMailboxLimit)){
+						if(!is_null($inputMailboxLimit)){
 							$data[User::attr('mailbox_limit')] = $inputMailboxLimit;
 							$data[User::attr('mailbox_limit')] = $inputMailboxLimit;
 						}
 						}
 
 
+						if(!is_null($inputMaxUserRedirects)){
+							$data[User::attr('max_user_redirects')] = $inputMaxUserRedirects;
+						}
+
 						/** @var User $user */
 						/** @var User $user */
 						$user = User::createAndSave($data);
 						$user = User::createAndSave($data);
 
 
@@ -229,6 +252,43 @@ if(isset($_GET['id'])){
 		</div>
 		</div>
 	<?php endif; ?>
 	<?php endif; ?>
 
 
+	<?php if(Config::get('options.enable_user_redirects', false)):
+		$canCreateUserRedirects = isset($_POST['user_redirects'])
+			? $_POST['user_redirects'] === 'yes'
+			: (isset($user) ? $user->isAllowedToCreateUserRedirects() : $canCreateUserRedirectsDefault);
+		$maxUserRedirects = !$canCreateUserRedirects
+			? $maxUserRedirectsDefault
+			: (
+				isset($_POST['max_user_redirects'])
+					? strip_tags($_POST['max_user_redirects'])
+					: (
+						isset($user)
+							? $user->getMaxUserRedirects()
+							: $maxUserRedirectsDefault
+					)
+			)
+		?>
+		<div class="input-group">
+			<label>User can create redirects to himself?</label>
+			<div class="input-info">The maximum number of redirects can be limited by the limit redirects setting.</div>
+			<div class="input">
+				<select name="user_redirects" autofocus required>
+					<option value="no" <?php echo !$canCreateUserRedirects ? 'selected' : ''; ?>>Disabled</option>
+					<option value="yes" <?php echo $canCreateUserRedirects ? 'selected' : ''; ?>>Yes, creating redirects is allowed</option>
+				</select>
+			</div>
+
+			<div class="input-group">
+				<label>Limit redirects</label>
+				<div class="input-info">The default limit is "<?php echo $maxUserRedirectsDefault; ?>". Set to "0" means unlimited redirects.</div>
+				<div class="input input-labeled input-labeled-right">
+					<input name="max_user_redirects" type="number" value="<?php echo $maxUserRedirects; ?>" placeholder="Mailbox limit in MB" min="0" required/>
+					<span class="input-label">Redirects</span>
+				</div>
+			</div>
+		</div>
+	<?php endif; ?>
+
 	<div class="buttons">
 	<div class="buttons">
 		<button type="submit" class="button button-primary">Save settings</button>
 		<button type="submit" class="button button-primary">Save settings</button>
 	</div>
 	</div>

+ 1 - 1
include/php/pages/admin/listdomains.php

@@ -55,7 +55,7 @@ $domains = Domain::findAll();
 		</tbody>
 		</tbody>
 		<tfoot>
 		<tfoot>
 		<tr>
 		<tr>
-			<th><?php echo ($domains->count() > 1) ? $domains->count().' Domains' : '1 Domain'; ?></th>
+			<th><?php echo textValue('_ domain', $domains->count()); ?></th>
 		</tr>
 		</tr>
 		</tfoot>
 		</tfoot>
 	</table>
 	</table>

+ 17 - 4
include/php/pages/admin/listredirects.php

@@ -14,7 +14,6 @@ else if(isset($_GET['missing-permission']) && $_GET['missing-permission'] == "1"
 }
 }
 
 
 $redirects = AbstractRedirect::getMultiByLimitedDomains();
 $redirects = AbstractRedirect::getMultiByLimitedDomains();
-
 ?>
 ?>
 
 
 	<h1>Redirects</h1>
 	<h1>Redirects</h1>
@@ -37,6 +36,9 @@ $redirects = AbstractRedirect::getMultiByLimitedDomains();
 		<tr>
 		<tr>
 			<th>Source</th>
 			<th>Source</th>
 			<th>Destination</th>
 			<th>Destination</th>
+		<?php if(Config::get('options.enable_user_redirects', false)): ?>
+			<th>Created by user</th>
+		<?php endif; ?>
 			<th></th>
 			<th></th>
 			<th></th>
 			<th></th>
 		<tr>
 		<tr>
@@ -51,6 +53,9 @@ $redirects = AbstractRedirect::getMultiByLimitedDomains();
 					<?php echo formatEmailsText($redirect->getConflictingMarkedSource()); ?>
 					<?php echo formatEmailsText($redirect->getConflictingMarkedSource()); ?>
 				</td>
 				</td>
 				<td><?php echo formatEmailsText($redirect->getDestination()); ?></td>
 				<td><?php echo formatEmailsText($redirect->getDestination()); ?></td>
+			<?php if(Config::get('options.enable_user_redirects', false)): ?>
+				<td><?php echo $redirect->isCreatedByUser() ? 'Yes' : 'No'; ?></td>
+			<?php endif; ?>
 				<td>
 				<td>
 					<a href="<?php echo Router::url('admin/editredirect/?id='.$redirect->getId()); ?>">[Edit]</a>
 					<a href="<?php echo Router::url('admin/editredirect/?id='.$redirect->getId()); ?>">[Edit]</a>
 				</td>
 				</td>
@@ -61,9 +66,17 @@ $redirects = AbstractRedirect::getMultiByLimitedDomains();
 		<?php endforeach; ?>
 		<?php endforeach; ?>
 		</tbody>
 		</tbody>
 		<tfoot>
 		<tfoot>
-		<tr>
-			<th><?php echo ($redirects->count() > 1) ? $redirects->count().' Redirects' : '1 Redirect'; ?></th>
-		</tr>
+			<tr>
+				<th><?php echo textValue('_ redirect', $redirects->count()); ?></th>
+			<?php if(Config::get('options.enable_user_redirects', false)):
+				$userRedirectsCount = AbstractRedirect::countWhere(
+					array(AbstractRedirect::attr('is_created_by_user'), 1)
+				);
+				?>
+				<th></th>
+				<th><?php echo textValue('_ user redirect', $userRedirectsCount); ?></th>
+			<?php endif; ?>
+			</tr>
 		</tfoot>
 		</tfoot>
 	</table>
 	</table>
 <?php elseif(!(Auth::getUser()->isDomainLimited() && count(Domain::getByLimitedDomains()) === 0)): ?>
 <?php elseif(!(Auth::getUser()->isDomainLimited() && count(Domain::getByLimitedDomains()) === 0)): ?>

+ 20 - 4
include/php/pages/admin/listusers.php

@@ -44,6 +44,9 @@ $users = User::getByLimitedDomains();
 				<th>Mailbox Limit</th>
 				<th>Mailbox Limit</th>
 			<?php endif; ?>
 			<?php endif; ?>
 				<th>Redirect count</th>
 				<th>Redirect count</th>
+			<?php if(Config::get('options.enable_user_redirects', false)): ?>
+				<th>User Redirects</th>
+			<?php endif; ?>
 				<th>Role</th>
 				<th>Role</th>
 				<th></th>
 				<th></th>
 				<th></th>
 				<th></th>
@@ -59,10 +62,23 @@ $users = User::getByLimitedDomains();
 					<?php echo $user->getUsername(); ?>
 					<?php echo $user->getUsername(); ?>
 				</td>
 				</td>
 				<td><?php echo $user->getDomain(); ?></td>
 				<td><?php echo $user->getDomain(); ?></td>
-				<?php if(Config::get('options.enable_mailbox_limits', false)): ?>
-					<td style="text-align: right"><?php echo ($user->getMailboxLimit() > 0) ? $user->getMailboxLimit().' MB' : 'No limit'; ?></td>
+			<?php if(Config::get('options.enable_mailbox_limits', false)): ?>
+				<td style="text-align: right"><?php echo ($user->getMailboxLimit() > 0) ? $user->getMailboxLimit().' MB' : 'No limit'; ?></td>
+			<?php endif; ?>
+				<td style="text-align: right">
+					<?php echo $user->getRedirects()->count(); ?>
+				</td>
+			<?php if(Config::get('options.enable_user_redirects', false)): ?>
+				<td>
+				<?php if($user->getMaxUserRedirects() < 0): ?>
+					Not Allowed
+				<?php elseif($user->getMaxUserRedirects() > 0): ?>
+					Limited (<?php echo $user->getMaxUserRedirects(); ?>)
+				<?php else: ?>
+					Unlimited
 				<?php endif; ?>
 				<?php endif; ?>
-				<td><?php echo $user->getRedirects()->count(); ?></td>
+				</td>
+			<?php endif; ?>
 				<td><?php echo ($user->getRole() === User::ROLE_ADMIN) ? 'Admin' : 'User'; ?></td>
 				<td><?php echo ($user->getRole() === User::ROLE_ADMIN) ? 'Admin' : 'User'; ?></td>
 				<td>
 				<td>
 					<a href="<?php echo Router::url('admin/edituser/?id='.$user->getId()); ?>">[Edit]</a>
 					<a href="<?php echo Router::url('admin/edituser/?id='.$user->getId()); ?>">[Edit]</a>
@@ -75,7 +91,7 @@ $users = User::getByLimitedDomains();
 		</tbody>
 		</tbody>
 		<tfoot>
 		<tfoot>
 			<tr>
 			<tr>
-				<th><?php echo ($users->count() > 1) ? $users->count().' Users' : '1 User'; ?></th>
+				<th><?php echo textValue('_ user', $users->count()); ?></th>
 			</tr>
 			</tr>
 		</tfoot>
 		</tfoot>
 	</table>
 	</table>

+ 133 - 0
include/php/pages/private/createredirect.php

@@ -0,0 +1,133 @@
+<?php
+
+if(!Config::get('options.enable_user_redirects', false)
+	|| !Auth::getUser()->canCreateUserRedirects()
+){
+	Router::redirect('private/redirects');
+}
+
+if(isset($_POST['source'])){
+
+	$destination = Auth::getUser()->getEmail();
+	$domain = Auth::getUser()->getDomain();
+
+	$inputSources = stringToEmails($_POST['source']);
+
+	// validate emails
+	$emailErrors = array();
+
+	// basic email validation isn't working 100% correct though
+	foreach($inputSources as $email){
+		if(strpos($email, '@') === false){
+			$emailErrors[$email] = "Address \"{$email}\" isn't a valid email address.";
+		}
+	}
+
+	// validate source emails are on domains
+	if(Config::get('options.enable_validate_aliases_source_domain', true)){
+		$domains = Domain::getByLimitedDomains();
+
+		foreach($inputSources as $email){
+			if(isset($emailErrors[$email])){
+				continue;
+			}
+
+			$emailParts = explode('@', $email);
+			if($emailParts[1] != $domain){
+				$emailErrors[$email] = "Domain of source address \"{$email}\" must be \"{$domain}\".";
+			}
+		}
+	}
+
+	// validate no redirect loops
+	if(in_array($destination, $inputSources)){
+		$emailErrors[$destination] = "Address \"{$destination}\" cannot be in source and destination in same redirect.";
+	}
+
+
+	if(count($emailErrors) > 0){
+		Message::getInstance()->fail(implode("<br>", $emailErrors));
+	}
+	elseif(count($inputSources) !== 1){
+		Message::getInstance()->fail("Only one email address as source.");
+	}
+	else{
+		if(count($inputSources) > 0){
+
+			$existingRedirects = AbstractRedirect::findWhere(
+				array(AbstractRedirect::attr('source'), 'IN', $inputSources)
+			);
+
+			if($existingRedirects->count() > 0){
+				$errorMessages = array();
+				/** @var AbstractRedirect $existingRedirect */
+				foreach($existingRedirects as $existingRedirect){
+					$errorMessages[] = "Source address \"{$existingRedirect->getSource()}\" is already redirected to some destination.";
+				}
+
+				Message::getInstance()->fail(implode("<br>", $errorMessages));
+			}
+			else{
+				foreach($inputSources as $inputSource){
+					$data = array(
+						AbstractRedirect::attr('source') => $inputSource,
+						AbstractRedirect::attr('destination') => $destination,
+						AbstractRedirect::attr('multi_hash') => null,
+						AbstractRedirect::attr('is_created_by_user') => true,
+					);
+
+					$a = Alias::createAndSave($data);
+				}
+
+				// Redirect created, redirect to overview
+				Router::redirect('private/redirects');
+			}
+		}
+		else{
+			Message::getInstance()->fail("Redirect couldn't be created. Fill out all fields.");
+		}
+	}
+}
+
+
+$domains = Domain::getByLimitedDomains();
+?>
+
+	<h1>Create Redirect</h1>
+	
+	<div class="buttons">
+		<a class="button" href="<?php echo Router::url('private/redirects'); ?>">&#10092; Back to your redirects</a>
+	</div>
+	
+	<?php echo Message::getInstance()->render(); ?>
+	
+	<form class="form" action="" method="post" autocomplete="off">
+	
+		<div class="input-group">
+			<label for="source">Source</label>
+			<div class="input-info">
+				<?php if($domains->count() > 0): ?>
+					You can only create redirects with this domain:
+					<ul>
+						<li><?php echo Auth::getUser()->getDomain(); ?></li>
+					</ul>
+				<?php else: ?>
+					There are no domains managed by WebMUM yet.
+				<?php endif; ?>
+			</div>
+			<div class="input">
+				<input type="text" name="source" placeholder="Source address" required autofocus value="<?php echo formatEmailsForm(isset($_POST['source']) ? $_POST['source'] : ''); ?>"/>
+			</div>
+		</div>
+	
+		<div class="input-group">
+			<label for="destination">Destination</label>
+			<div class="input">
+				<?php echo formatEmailsText(Auth::getUser()->getEmail()); ?>
+			</div>
+		</div>
+	
+		<div class="buttons">
+			<button type="submit" class="button button-primary">Create redirect</button>
+		</div>
+	</form>

+ 82 - 0
include/php/pages/private/deleteredirect.php

@@ -0,0 +1,82 @@
+<?php
+
+if(!Config::get('options.enable_user_redirects', false)
+	|| !Auth::getUser()->isAllowedToCreateUserRedirects()
+){
+	Router::redirect('private/redirects');
+}
+
+if(!isset($_GET['id'])){
+	// Redirect id not set, redirect to overview
+	Router::redirect('private/redirects');
+}
+
+$id = $_GET['id'];
+
+/** @var AbstractRedirect $redirect */
+$redirect = AbstractRedirect::findMultiWhereFirst(
+	array(
+		array(AbstractRedirect::attr('id'), $id),
+		array(AbstractRedirect::attr('is_created_by_user'), true),
+		array(AbstractRedirect::attr('destination'), Auth::getUser()->getEmail()),
+	)
+);
+
+if(is_null($redirect)){
+	// Redirect doesn't exist, redirect to overview
+	Router::redirect('private/redirects');
+}
+
+if(isset($_POST['confirm'])){
+	$confirm = $_POST['confirm'];
+
+	if($confirm === "yes"){
+
+		$redirect->delete();
+
+		// Delete redirect successfull, redirect to overview
+		Router::redirect('private/redirects/?deleted=1');
+	}
+	else{
+		// Choose to not delete redirect, redirect to overview
+		Router::redirect('private/redirects');
+	}
+}
+
+else{
+	?>
+
+	<h1>Delete redirection?</h1>
+
+	<div class="buttons">
+		<a class="button" href="<?php echo Router::url('private/redirects'); ?>">&#10092; Back to your redirects</a>
+	</div>
+
+	<form class="form" action="" method="post" autocomplete="off">
+		<div class="input-group">
+			<label>Source</label>
+			<div class="input-info"><?php echo formatEmailsText($redirect->getSource()); ?></div>
+		</div>
+
+		<div class="input-group">
+			<label>Destination</label>
+			<div class="input-info"><?php echo formatEmailsText($redirect->getDestination()); ?></div>
+		</div>
+
+		<div class="input-group">
+			<label for="confirm">Do you realy want to delete this redirection?</label>
+			<div class="input">
+				<select name="confirm" autofocus required>
+					<option value="no">No!</option>
+					<option value="yes">Yes!</option>
+				</select>
+			</div>
+		</div>
+
+		<div class="buttons">
+			<button type="submit" class="button button-primary">Delete</button>
+		</div>
+	</form>
+	<?php
+}
+?>

+ 1 - 1
include/php/pages/private/start.php

@@ -9,5 +9,5 @@
 </div>
 </div>
 
 
 <div class="buttons buttons-horizontal button-large">
 <div class="buttons buttons-horizontal button-large">
-	<a class="button" href="<?php echo Router::url('private/yourredirects'); ?>">Redirects to your mailbox</a>
+	<a class="button" href="<?php echo Router::url('private/redirects'); ?>">Redirects to your mailbox</a>
 </div>
 </div>

+ 56 - 9
include/php/pages/private/yourredirects.php

@@ -1,37 +1,84 @@
 <?php
 <?php
 
 
-$redirects = Auth::getUser()->getAnonymizedRedirects();
+$user = Auth::getUser();
+$activateUserRedirects = Config::get('options.enable_user_redirects', false) && $user->isAllowedToCreateUserRedirects();
 
 
+$redirects = $user->getAnonymizedRedirects();
+
+$userRedirectsCount = $user->getSelfCreatedRedirects()->count();
 ?>
 ?>
 
 
 	<h1>Redirects to your mailbox</h1>
 	<h1>Redirects to your mailbox</h1>
 
 
 	<div class="buttons">
 	<div class="buttons">
 		<a class="button" href="<?php echo Router::url('private'); ?>">&#10092; Back to personal dashboard</a>
 		<a class="button" href="<?php echo Router::url('private'); ?>">&#10092; Back to personal dashboard</a>
+	<?php if($activateUserRedirects): ?>
+		<?php if($user->canCreateUserRedirects()): ?>
+			<a class="button" href="<?php echo Router::url('private/redirect/create'); ?>">Create new redirect</a>
+		<?php else: ?>
+			<a class="button button-disabled" title="You reached your user redirect limit of <?php echo $user->getMaxUserRedirects(); ?>.">Create new redirect</a>
+		<?php endif; ?>
+	<?php endif; ?>
 	</div>
 	</div>
 
 
-<?php echo Message::getInstance()->render(); ?>
+	<?php echo Message::getInstance()->render(); ?>
+
+<?php if($activateUserRedirects): ?>
+	<div class="notifications notification">
+		You are allowed to create <strong><?php echo $user->getMaxUserRedirects() === 0 ? 'unlimited user redirects' : textValue('up to _ user redirect', $user->getMaxUserRedirects()); ?></strong> on your own.
+	<?php if($user->getMaxUserRedirects() > 0): ?>
+		<?php if($user->canCreateUserRedirects()): ?>
+			<br><br>You can still create <strong><?php echo textValue('_ more user redirect', $user->getMaxUserRedirects() - $userRedirectsCount); ?></strong>.
+		<?php else: ?>
+			<br><br>You cannot create anymore redirects as your limit is reached.
+			<br>Consider deleting unused redirects or ask an admin to extend your limit.
+		<?php endif; ?>
+	<?php endif; ?>
+	</div>
+<?php endif; ?>
 
 
 <?php if($redirects->count() > 0): ?>
 <?php if($redirects->count() > 0): ?>
 	<table class="table">
 	<table class="table">
 		<thead>
 		<thead>
-		<tr>
-			<th>Source</th>
-			<th>Destination</th>
-		<tr>
+			<tr>
+				<th>Source</th>
+				<th>Destination</th>
+			<?php if($activateUserRedirects): ?>
+				<th>Created by you</th>
+				<th></th>
+			<?php endif; ?>
+			<tr>
 		</thead>
 		</thead>
 		<tbody>
 		<tbody>
 		<?php foreach($redirects as $redirect): /** @var AbstractRedirect $redirect */ ?>
 		<?php foreach($redirects as $redirect): /** @var AbstractRedirect $redirect */ ?>
 			<tr>
 			<tr>
 				<td><?php echo formatEmailsText($redirect->getSource()); ?></td>
 				<td><?php echo formatEmailsText($redirect->getSource()); ?></td>
 				<td><?php echo formatEmailsText($redirect->getDestination()); ?></td>
 				<td><?php echo formatEmailsText($redirect->getDestination()); ?></td>
+			<?php if($activateUserRedirects): ?>
+				<td><?php echo $redirect->isCreatedByUser() ? 'Yes' : 'No'; ?></td>
+				<td>
+				<?php if($redirect->isCreatedByUser()): ?>
+					<a href="<?php echo Router::url('private/redirect/delete/?id='.$redirect->getId()); ?>">[Delete]</a>
+				<?php endif; ?>
+				</td>
+			<?php endif; ?>
 			</tr>
 			</tr>
 		<?php endforeach; ?>
 		<?php endforeach; ?>
 		</tbody>
 		</tbody>
 		<tfoot>
 		<tfoot>
-		<tr>
-			<th><?php echo ($redirects->count() > 1) ? $redirects->count().' Redirects' : '1 Redirect'; ?></th>
-		</tr>
+			<tr>
+				<th><?php echo textValue('_ redirect', $redirects->count()); ?></th>
+			<?php if($activateUserRedirects): ?>
+				<th></th>
+				<th>
+				<?php if($user->getMaxUserRedirects() === 0): ?>
+					<?php echo textValue('_ user redirect', $userRedirectsCount); ?>
+				<?php else: ?>
+					<?php echo $userRedirectsCount.textValue(' of _ user redirect', $user->getMaxUserRedirects()); ?>
+				<?php endif; ?>
+				</th>
+			<?php endif; ?>
+			</tr>
 		</tfoot>
 		</tfoot>
 	</table>
 	</table>
 <?php else: ?>
 <?php else: ?>

+ 5 - 2
include/php/routes.inc.php

@@ -18,8 +18,11 @@ Router::addGet('/logout', function(){
  */
  */
 Router::addGet('/private', 'include/php/pages/private/start.php', User::ROLE_USER);
 Router::addGet('/private', 'include/php/pages/private/start.php', User::ROLE_USER);
 Router::addMixed('/private/changepass', 'include/php/pages/private/changepass.php', User::ROLE_USER);
 Router::addMixed('/private/changepass', 'include/php/pages/private/changepass.php', User::ROLE_USER);
-Router::addGet('/private/yourredirects', 'include/php/pages/private/yourredirects.php', User::ROLE_USER);
-
+Router::addGet('/private/redirects', 'include/php/pages/private/yourredirects.php', User::ROLE_USER);
+if(Config::get('options.enable_user_redirects', false)){
+	Router::addMixed('/private/redirect/create', 'include/php/pages/private/createredirect.php', User::ROLE_USER);
+	Router::addMixed('/private/redirect/delete', 'include/php/pages/private/deleteredirect.php', User::ROLE_USER);
+}
 
 
 /**
 /**
  * Admin area
  * Admin area