Merge branch 'v1.3-dev' into resend-member-invitations

This commit is contained in:
Lucas Bartholemy 2018-01-04 14:44:06 +01:00 committed by GitHub
commit 04fc5b2b9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
171 changed files with 11179 additions and 2133 deletions

1
.gitignore vendored
View File

@ -25,7 +25,6 @@ nbproject
.idea/*
.gitmodules
.github
/composer.lock
themes/*
!themes/HumHub

View File

@ -11,6 +11,10 @@ RewriteEngine on
# prevent httpd from serving dotfiles (.htaccess, .svn, .git, etc.) - except let's encrypt challenge
RedirectMatch 403 ^/?\.(?!/well-known/acme-challenge/[\w-]{43}$)
# ensure permalink when url rewriting was enabled (index.php?r=content/perma&id=6 => /content/perma/?id=6
RewriteCond %{QUERY_STRING} ^r=content(/|%2)perma&id=([0-9]*)$
RewriteRule ^index\.php$ %{REQUEST_URI}/content/perma/?id=%2 [R=302,L]
RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
RewriteRule ^(.*) - [E=BASE:%1]

View File

@ -2,14 +2,13 @@
"name": "humhub/humhub",
"description": "HumHub - The flexible Open Source Social Network Kit for Collaboration",
"keywords": ["humhub", "yii2", "framework"],
"homepage": "http://www.humhub.com/",
"homepage": "https://www.humhub.org/",
"type": "project",
"license": "AGPL-3.0",
"support": {
"issues": "https://github.com/humhub/humhub/issues?state=open",
"forum": "http://community.humhub.com",
"wiki": "http://community.humhub.com",
"irc": "irc://irc.freenode.net/humhub",
"forum": "https://community.humhub.com",
"wiki": "https://community.humhub.com",
"source": "https://github.com/humhub/humhub"
},
"minimum-stability": "stable",
@ -24,35 +23,39 @@
"raoul2000/yii2-jcrop-widget": "*",
"kartik-v/yii2-widgets": "*",
"phpoffice/phpexcel": "*",
"cebe/markdown": "1.0.2",
"cebe/markdown": "~1.0.2",
"yiisoft/yii2-jui": "~2.0.0",
"zendframework/zend-http": "*",
"jbroadway/urlify": "^1.0",
"nqxcode/zendsearch": "^2.0",
"xj/yii2-jplayer-widget": "*",
"zendframework/zend-ldap": "^2.5",
"zhuravljov/yii2-queue": "^0.11",
"bower-asset/jquery-timeago": "1.4.*",
"bower-asset/jquery-timeago": "1.5.*",
"bower-asset/jquery-nicescroll": "3.6.*",
"bower-asset/jquery-knob": "1.2.*",
"bower-asset/jquery-placeholder": "^2.3.0",
"bower-asset/blueimp-file-upload": "9.11.*",
"bower-asset/blueimp-file-upload": "9.18.*",
"bower-asset/fontawesome": "^4.7.0",
"bower-asset/bootstrap-markdown": "2.10.*",
"bower-asset/select2": "^4.0.2",
"bower-asset/select2": "^4.0.4",
"bower-asset/bluebird": "^3.3.5",
"bower-asset/select2-bootstrap-theme": "0.1.0-beta.4",
"bower-asset/jquery.cookie": "^1.4.1",
"bower-asset/jquery-color": "^2.1.2",
"bower-asset/autosize": "1.*",
"bower-asset/nprogress": "*",
"bower-asset/At.js": "^1.5.1",
"bower-asset/animate.css": "*",
"bower-asset/html5shiv": "^3.7",
"bower-asset/clipboard.js": "*",
"bower-asset/jPlayer": "2.9.2",
"bower-asset/jPlayer": "2.9.*",
"bower-asset/imagesloaded": "*",
"bower-asset/jquery-timeentry": "^2.0"
"bower-asset/jquery-timeentry": "^2.0",
"bower-asset/caret.js": "0.2.2",
"npm-asset/at.js": "^1.5.1",
"yiisoft/yii2-queue": "^2.0",
"yiisoft/yii2-redis": "^2.0",
"firebase/php-jwt": "^5.0",
"npm-asset/socket.io-client": "^2.0"
},
"require-dev": {
"codeception/codeception": "*",
@ -61,20 +64,18 @@
"yiisoft/yii2-faker": "~2.0.0",
"yiisoft/yii2-apidoc": "~2.0.0"
},
"config": {
"process-timeout": 1800,
"vendor-dir": "protected/vendor",
"fxp-asset":{
"installer-paths": {
"npm-asset-library": "protected/vendor/npm",
"bower-asset-library": "protected/vendor/bower"
},
"vcs-driver-options": {
"github-no-api": true
},
"git-skip-update": "2 days",
"pattern-skip-version": "(-build|-patch)"
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
],
"config": {
"platform": {
"php": "5.6"
},
"process-timeout": 1800,
"vendor-dir": "protected/vendor"
},
"scripts": {
"post-create-project-cmd": [
@ -94,10 +95,6 @@
"generateCookieValidationKey": [
"protected/config/web.php"
]
},
"asset-installer-paths": {
"npm-asset-library": "protected/vendor/npm",
"bower-asset-library": "protected/vendor/bower"
}
}
}

6605
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +1,4 @@
/dynamic.php
/common.php
/console.php
/web.php

View File

@ -1,4 +1,9 @@
<?php
/**
* This file provides to overwrite the default HumHub / Yii configuration by your local common (Console and Web) environments
* @see http://www.yiiframework.com/doc-2.0/guide-concept-configurations.html
* @see http://docs.humhub.org/admin-installation-configuration.html
* @see http://docs.humhub.org/dev-environment.html
*/
return [
];

View File

@ -1,4 +1,9 @@
<?php
/**
* This file provides to overwrite the default HumHub / Yii configuration by your local Console environments
* @see http://www.yiiframework.com/doc-2.0/guide-concept-configurations.html
* @see http://docs.humhub.org/admin-installation-configuration.html
* @see http://docs.humhub.org/dev-environment.html
*/
return [
];

View File

@ -1,5 +1,10 @@
<?php
/**
* This file provides to overwrite the default HumHub / Yii configuration by your local Web environments
* @see http://www.yiiframework.com/doc-2.0/guide-concept-configurations.html
* @see http://docs.humhub.org/admin-installation-configuration.html
* @see http://docs.humhub.org/dev-environment.html
*/
return [
];

View File

@ -94,6 +94,7 @@ class AppAsset extends AssetBundle
'humhub\assets\PagedownConverterAsset',
'humhub\assets\ClipboardJsAsset',
'humhub\assets\ImagesLoadedAsset',
'humhub\assets\SocketIoAsset',
];
/**

View File

@ -21,7 +21,7 @@ class AtJsAsset extends AssetBundle
/**
* @inheritdoc
*/
public $sourcePath = '@bower/At.js';
public $sourcePath = '@npm/at.js';
/**
* @inheritdoc

View File

@ -0,0 +1,32 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\assets;
use yii\web\AssetBundle;
/**
* Socket.IO client files
*
* @since 1.3
* @author luke
*/
class SocketIoAsset extends AssetBundle
{
/**
* @inheritdoc
*/
public $sourcePath = '@npm/socket.io-client';
/**
* @inheritdoc
*/
public $js = ['dist/socket.io.slim.js'];
}

View File

@ -8,10 +8,12 @@
namespace humhub\components;
use humhub\modules\file\libs\FileHelper;
use humhub\modules\notification\components\BaseNotification;
use Yii;
use yii\helpers\Json;
use humhub\models\Setting;
use humhub\modules\file\libs\FileHelper;
use humhub\modules\notification\components\BaseNotification;
use humhub\modules\content\models\ContentContainerSetting;
/**
* Base Class for Modules / Extensions
@ -248,22 +250,9 @@ class Module extends \yii\base\Module
}
}
foreach (\humhub\modules\content\models\ContentContainerSetting::findAll(['module_id' => $this->id]) as $containerSetting) {
$containerSetting->delete();
}
foreach (\humhub\models\Setting::findAll(['module_id' => $this->id]) as $containerSetting) {
$containerSetting->delete();
}
foreach (\humhub\modules\user\models\Module::findAll(['module_id' => $this->id]) as $userModule) {
$userModule->delete();
}
foreach (\humhub\modules\space\models\Module::findAll(['module_id' => $this->id]) as $spaceModule) {
$spaceModule->delete();
}
ContentContainerSetting::deleteAll(['module_id' => $this->id]);
Setting::deleteAll(['module_id' => $this->id]);
Yii::$app->moduleManager->disable($this);
}
@ -347,7 +336,7 @@ class Module extends \yii\base\Module
if (is_dir($notificationDirectory)) {
foreach (FileHelper::findFiles($notificationDirectory, ['recursive' => false,]) as $file) {
$notificationClass = $notificationNamespace . '\\' . basename($file, '.php');
if(is_subclass_of($notificationClass, BaseNotification::class)) {
if (is_subclass_of($notificationClass, BaseNotification::class)) {
$notifications[] = $notificationClass;
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\components;
use humhub\modules\content\components\ContentContainerActiveRecord;
/**
* UrlManager
*
* @since 1.3
* @author Luke
*/
class UrlManager extends \yii\web\UrlManager
{
/**
* @inheritdoc
*/
public function createUrl($params)
{
$params = (array) $params;
if (isset($params['contentContainer']) && $params['contentContainer'] instanceof ContentContainerActiveRecord) {
$params['cguid'] = $params['contentContainer']->contentContainerRecord->guid;
unset($params['contentContainer']);
}
if (isset($params['container']) && $params['container'] instanceof ContentContainerActiveRecord) {
$params['cguid'] = $params['container']->contentContainerRecord->guid;
unset($params['container']);
}
return parent::createUrl($params);
}
}

View File

@ -9,18 +9,16 @@
namespace humhub\components\queue;
use yii\base\Object;
use zhuravljov\yii\queue\Job;
/**
* ActiveJob
*
*
* @see \humhub\modules\queue\ActiveJob
* @deprecated since version 1.3
* @since 1.2
* @author Luke
*/
abstract class ActiveJob extends Object implements Job
abstract class ActiveJob extends \humhub\modules\queue\ActiveJob
{
/**
* Runs this job
*/
abstract public function run();
}

View File

@ -1,47 +0,0 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\components\queue\driver;
use Yii;
use yii\base\Event;
use zhuravljov\yii\queue\ErrorEvent;
use zhuravljov\yii\queue\Queue;
/**
* Instant queue driver, mainly used for testing purposes
*
* @since 1.2
* @author buddha
*/
class Instant extends Queue
{
/**
* @inheritdoc
*/
public function init()
{
parent::init();
Event::on(Queue::class, Queue::EVENT_AFTER_ERROR, function(ErrorEvent $errorEvent) {
/* @var $exception \Expection */
$exception = $errorEvent->error;
Yii::error('Could not execute queued job! Message: ' . $exception->getMessage() . ' Trace:' . $exception->getTraceAsString(), 'queue');
});
}
/**
* @inheritdoc
*/
protected function sendMessage($message, $timeout)
{
$this->handleMessage($message);
}
}

View File

@ -1,44 +0,0 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\components\queue\driver;
use Yii;
use yii\base\Event;
use zhuravljov\yii\queue\ErrorEvent;
use zhuravljov\yii\queue\db\Queue;
/**
* MySQL queue driver
*
* @since 1.2
* @author Luke
*/
class MySQL extends Queue
{
/**
* @inheritdoc
*/
public $mutex = 'yii\mutex\MysqlMutex';
/**
* @inheritdoc
*/
public function init()
{
parent::init();
Event::on(Queue::class, Queue::EVENT_AFTER_ERROR, function(ErrorEvent $errorEvent) {
/* @var $exception \Expection */
$exception = $errorEvent->error;
Yii::error('Could not execute queued job! Message: ' . $exception->getMessage() . ' Trace:' . $exception->getTraceAsString(), 'queue');
});
}
}

View File

@ -1,84 +0,0 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\components\queue\driver;
use Yii;
use yii\base\Event;
use yii\base\Application;
use yii\base\NotSupportedException;
use zhuravljov\yii\queue\ErrorEvent;
use zhuravljov\yii\queue\Queue;
/**
* Sync queue driver
*
* @since 1.2
* @author Luke
*/
class Sync extends Queue
{
/**
* @var boolean
*/
public $handle = true;
/**
* @var array
*/
private $messages = [];
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->handle) {
Yii::$app->on(Application::EVENT_AFTER_REQUEST, function () {
ob_start();
$this->run();
// Important, breaks downloads
ob_end_clean();
});
}
Event::on(Queue::class, Queue::EVENT_AFTER_ERROR, function(ErrorEvent $errorEvent) {
/* @var $exception \Expection */
$exception = $errorEvent->error;
Yii::error('Could not execute queued job! Message: ' . $exception->getMessage() . ' Trace:' . $exception->getTraceAsString(), 'queue');
});
}
/**
* Runs all jobs from queue.
*/
public function run()
{
while (($message = array_shift($this->messages)) !== null) {
$this->handleMessage($message);
}
}
/**
* @inheritdoc
*/
protected function sendMessage($message, $timeout)
{
if ($timeout) {
throw new NotSupportedException('Delayed work is not supported in the driver.');
}
$this->messages[] = $message;
}
}

View File

@ -16,6 +16,10 @@ $config = [
'basePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR,
'bootstrap' => ['log', 'humhub\components\bootstrap\ModuleAutoLoader', 'queue'],
'sourceLanguage' => 'en',
'aliases' => [
'@bower' => '@vendor/bower-asset',
'@npm' => '@vendor/npm-asset',
],
'components' => [
'moduleManager' => [
'class' => '\humhub\components\ModuleManager'
@ -120,7 +124,7 @@ $config = [
'dsn' => 'mysql:host=localhost;dbname=humhub',
'username' => '',
'password' => '',
'charset' => 'utf8',
'charset' => 'utf8mb4',
'enableSchemaCache' => true,
'on afterOpen' => ['humhub\libs\Helpers', 'SqlMode'],
],
@ -129,12 +133,15 @@ $config = [
'clients' => [],
],
'queue' => [
'class' => 'humhub\components\queue\driver\Sync',
'class' => 'humhub\modules\queue\driver\Sync',
],
'urlManager' => [
'class' => 'humhub\components\UrlManager',
],
'live' => [
'class' => 'humhub\modules\live\components\Sender',
'driver' => [
'class' => 'humhub\modules\live\driver\Database',
'class' => 'humhub\modules\live\driver\Poll',
],
],
],

View File

@ -27,11 +27,10 @@ $config = [
'loginUrl' => ['/user/auth/login']
],
'errorHandler' => [
'errorAction' => 'error/index',
'errorAction' => '/error/index',
],
'session' => [
'class' => 'humhub\modules\user\components\Session',
'sessionTable' => 'user_http_session',
],
],
'modules' => [],

View File

@ -0,0 +1,25 @@
HumHub Change Log - v1.3-dev Branch
===================================
1.3.0-beta.1 (Not released yet)
--------------------------------
- Enh: Added file search indexing
- Enh: Updated composer.json (acs-ferreira)
- Chg: Switched from Composer FXP plugin to Asset Packagist repository
- Enh: Committed composer.lock
- Enh: Refactored ContentContainer Controller
- Chg: Added ContentContainer ModuleManager, instead of individual handling (Space/User)
- Fix: Rebind LDAP connection after successful login with administrative user
- Enh: Make utf8_mb4 as default database charset
- Enh: Moved queueing into own submodule and updated to yii2/queue extension
- Enh: Added user soft deletion without contributions
- Enh: Moved user deletion into asynchronous tasks
- Enh: Improved user grid view design (Administration, User Approval, Space Members)
- Enh: Moved SyncUsers (LDAP) and session table cleanup handling into ActiveJob
- Enh: Added Push live module driver using Redis and Node.JS
- Enh: Added tooltip option to space Image widget.
- Enh: Added option ContentContainerController to restrict container type
- Enh: Ensure valid permalinks when URL rewriting is enabled

View File

@ -22,6 +22,9 @@ Administration Topics
* [Backup HumHub](backup.md)
* [Performance Tuning](performance.md)
* [Custom Configurations](advanced-configuration.md)
* [Redis Integration](redis.md)
* [Asynchronous Task Processing](asynchronous-tasks.md)
* [Push Updates](push-updates.md)
* [Authentication and LDAP](authentication.md)
* [Search System](search.md)
* [Translations](translations.md)

View File

@ -6,10 +6,10 @@ You can overwrite the default HumHub / Yii configuration in the folder `/protect
File Overview
-------------
- **common.php** - Configuration used in Console & Web Application
- **web.php** - Configuration used in Web Application only
- **console.log** - Configuration used in Console Application only
- **dynamic.php** - Dynamic generated configuration - do not edit manually!
- **common.php** - Configuration used for the Console and the Web Application
- **web.php** - Configuration used for the Web Application only
- **console.log** - Configuration used for the Console Application only
- **dynamic.php** - Dynamically generated configuration - do not edit manually!
@ -37,7 +37,10 @@ Configuration file loading order
# Application Params
Some application behaviours can be configured, by changing application parameters within your `common.php`, `web.php` or `console.php`:
Some application behaviour can be configured by changing the application parameters within your `common.php`, `web.php` or `console.php`:
The following configuration block will disable pjax support on your site for example:
```
return [
@ -47,7 +50,6 @@ return [
];
```
Thre previous configuration will disable pjax support on your site.
Available params:
@ -59,7 +61,7 @@ Available params:
Your tracking code can be managed under `Administration -> Settings -> Advanced -> Statistics`.
In order to send the tracking code in case of pjax page loads as well as full page loads, you have to add the following to your statistics code by the example of google analytics:
In order to send the tracking code in case that both, the pjax as well as the full page loads, you have to add the following to your statistics code (refers to Google Analytics):
```javascript

View File

@ -0,0 +1,116 @@
Asynchronous Task Processing
============================
Introduction
------------
To provide a fast and responive user experience, extensive processes are handeld activly by background processes instead being directly executed on request.
Some examples for such background processes are:
- Notifications (informing the users via e-ails or mobile push notifications)
- Search index rebuilds
- File indexing
Queue Driver
------------
### Sychronous Driver
By default this driver is used to immediately execute asychronous tasks.
It doesn't require any Worker configuration below.
We recommend to switch to the MySQL or Redis driver on production environments.
### MySQL Database Driver
If you don't have Redis or any other supported queuing software (RabbitMQ, Beanstalk or Gearman) running, this is the recommended driver.
To enable this driver you need to add following block to your local configuration file (protected/config/common.php):
```
// ...
'components' => [
// ...
'queue' => [
'class' => 'humhub\components\queue\driver\MySQL',
],
// ...
],
// ...
```
> Note: You'll need to configure Workers (see description below).
### Redis
If you're already using Redis (e.g. for caching or push) we recommend this queue driver.
Please make sure you already configured Redis as described here: [Redis Configuration](redis.md).
To enable this driver you need to add following block to your local configuration file (protected/config/common.php):
```
// ...
'components' => [
// ...
'queue' => [
'class' => 'humhub\components\queue\driver\Redis',
],
// ...
],
// ...
```
> Note: You'll need to configure Workers (see description below).
Workers
------
### Cronjob
You can start workers using cron by executing the queue/run command. It works as long as the queues contain jobs.
CronTab Example:
```
* * * * * /usr/bin/php <INSERT HUMHUB PATH HERE>/yii queue/run
```
In this case the cron will start the command every minute and execute schedulded tasks.
### Daemon
You can start a worker deamon using following command:
```
cd protected
php yii queue/listen
```
***Using Supervisor (recommended)***
Supervisor is a process monitoring tool for Linux. It automatically starts, monitors and restarts your workers if they crash.
Example configuration (e.g. /etc/supervisor/conf.d/humhub.conf):
```conf
[program:humhub-workers]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php <INSERT HUMHUB PATH HERE>/protected/yii queue/listen --verbose=1 --color=0
autostart=true
autorestart=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=<INSERT HUMHUB PATH HERE>/protected/runtime/logs/yii-queue-worker.log
```

View File

@ -0,0 +1,64 @@
Push Updates / Push Service
===========================
The PushService directly sends updates (e.g. new notifications) to the users using WebSockets or Long Polling techniques.
Prerequisites
-------------
The PushService requires following additional installed software:
- NodeJS
- Redis
PushService Installation
------------------------
You can install the HumHub PushService as NPM Package by entering following command:
```
npm install humhub-pushservice
```
Once the installation is finished, you need to create a configuration file:
```
cp config.json.dist config.json
```
Modify the config.json file and adjust the available settings.
Now you can start the PushService using following command:
```
node pushService.js
```
HumHub - Configuration
----------------------
Once the PushService NodeJS application is up and running you need to add following
configuration options to the HumHub file (protected/config/common.php):
```
// ...
'components' => [
// ...
'live' => [
'driver' => [
'class' => \humhub\modules\live\driver\Push::class,
'pushServiceUrl' => 'http://example.com:3000/',
'jwtKey' => '---EnteraSuperSecretKeyToSignAuthorizationHere---'
]
],
// ...
],
// ...
```

View File

@ -0,0 +1,47 @@
Redis
=====
We recommend installing an additional Redis server which can act as a caching, push service and job queuing service for HumHub.
Basic Configuration
------------------
To enable Redis, you have to add following block to your local configuration file (protected/config/common.php):
```
// ...
'components' => [
// ...
'redis' => [
'class' => 'yii\redis\Connection',
'hostname' => 'localhost',
'port' => 6379,
'database' => 0,
],
// ...
],
// ...
```
Caching
-------
Once Redis is configured, you can also select it as a caching service: Administration -> Settings -> Advanced -> Caching.
Queuing of Asychronous Tasks
----------------------------
See [Asychronous Tasks](asychornous-tasks.md) for further information.
Push Updates
------------
See [Push Updates](push-updates.md) for further information.

View File

@ -24,6 +24,49 @@ or by means of [grunt](../developer/core-build.md):
grunt build-search
```
File Indexing
-------------
If you like to also index contents of a file (e.g. PDF documents) you need to specify additional parsers.
These parsers are defined in the [configuration file](advanced-configuration.md).
Parsers:
- Apache Tika (https://tika.apache.org/)
- Poppler PDF Utils (https://poppler.freedesktop.org/)
Example:
```php
return [
// ...
'modules' => [
// ...
'file' => [
'converterOptions' => [
'humhub\modules\file\converter\TextConverter' => [
'converter' => [
[
'cmd' => '/usr/bin/pdftotext -q -enc UTF-8 {fileName} {outputFileName}',
'only' => ['pdf']
],
[
'cmd' => '/usr/bin/java -jar /opt/tika-app-1.16.jar --text {fileName} 2>/dev/null',
'except' => ['image/']
]
]
]
]
]
// ...
],
// ...
];
```
Zend Lucence Engine
--------------------
@ -34,12 +77,11 @@ Default database folder: `/protected/runtime/searchdb/`
You can modify the default search directory in the [configuration](advanced-configuration.md):
```php
return [
// ...
'params' => [
'search' => [
'zendLucenceDataDir' => '/some/other/path',
]
return [
// ...
'params' => [
'search' => [
'zendLucenceDataDir' => '/some/other/path',
]
// ...
];

View File

@ -5,7 +5,7 @@ Disable Errors / Debugging
--------------------------
- Modify *index.php* in your humhub root directory
```php
[...]
// comment out the following two lines when deployed to production
@ -16,19 +16,21 @@ Disable Errors / Debugging
- Delete *index-test.php* in your humhub root directory if exists
Protected Directories
---------------------
Make sure the following directories are not accessible by web:
Make sure following directories are not accessible by web:
- protected
- uploads/file
By default these folders are protected with a ".htaccess" file.
Limit User Access
-----------------
If you're running a private social network, make sure the user registration has been disabled or the approval system for new users has been enabled.
If you're running a private social network, make sure the user registration is disabled or the approval system for new users is enabled.
- Disable user registration: `Administration -> Users -> Settings -> Anonymous users can register`
- Enable user approvals: `Administration -> Users -> Settings -> Require group admin approval after registration`
@ -37,6 +39,9 @@ If you're running a private social network, make sure the user registration has
Keep up with the latest HumHub version
---------------------------------------
As an admin you'll receive a notification when a new HumHub version has been released. We strongly recommend to always use the latest stable version when possible.
As admin you'll receive a notification when a new HumHub version is released. We recommend to always use the latest stable version.
We take security very seriously and continuously improving the security features of HumHub.
We take security very seriously, and we're continuously improving the security features of HumHub.

View File

@ -13,10 +13,12 @@ Module Development
* [Introduction](modules-index.md)
* [Basic Structure](modules-structure.md)
* [Migration/Updates](modules-migrate.md)
* [Content](content.md)
* [Content](modules-content.md)
* [Users](modules-users.md)
* [Events](modules-events.md)
* [Settings and Configuration](modules-settings.md)
* [Models / Database](modules-db.md)
* [Models / Database](modules-db.md)
* [Internationalization](modules-i18n.md)
Javascript API

View File

@ -32,8 +32,7 @@ git clone https://github.com/humhub/humhub.git
3. Navigate to your HumHub webroot and fetch dependencies:
```
php composer.phar global require "fxp/composer-asset-plugin:^1.4.2"
php composer.phar update
php composer.phar install
```
> Note: The composer update may have to be executed again after an update of your local repository by a git pull. Read more about updating ([Update Guide](admin-updating.html#gitcomposer-based-installations))

View File

@ -1,5 +1,5 @@
Content
=======
Users
=====
## ContentContainer

View File

@ -1,8 +1,31 @@
# Module Migration Guide
Module Migration Guide
======================
Here you will learn how you can adapt existing modules to working fine with actually versions.
## Migrate from 1.1 to 1.2
Migrate from 1.2 to 1.3
-----------------------
### ContentContainer Controller
The base controller attributes `autoCheckContainerAccess` and `hideSidebar` are not longer available.
### Removed Deprecated
- formatterApp Application Component (Yii::$app->formatterApp)
### Queuing
The queuing is now moved into an own module `humhub\modues\queue`.
The existing `humhub\components\queue\ActiveJob` is declared as deprecated and will be removed in 1.4.
### Partial user deletion (Soft Delete)
Added new user status (User::SOFT_DELETED). You can find more information here: [Users](modules-users.md)
Migrate from 1.1 to 1.2
-----------------------
### Stream / Content Changes
@ -53,7 +76,8 @@ within your controller for pjax topmenu support.
TBD
## Migrate from 1.0 to 1.1
Migrate from 1.0 to 1.1
-----------------------
- Dropped unused space attribute "website"
@ -77,9 +101,11 @@ TBD
- New administration menu structure
## Migrate from 0.20 to 1.0
Migrate from 0.20 to 1.0
------------------------
## Migrate from 0.12 to 0.20
**Important: This release upgrades from Yii1 to Yii2 Framework!**
@ -87,12 +113,17 @@ TBD
This requires an extensive migration of all custom modules/themes.
Find more details here: [HumHub 0.20 Migration](modules-migrate-0.20.md)
## Migrate from 0.11 to 0.12
Migrate from 0.11 to 0.12
-------------------------
- Rewritten Search
## Migrate from 0.10 to 0.11
Migrate from 0.10 to 0.11
-------------------------
No breaking changes.
- Now handle ContentContainerController layouts, new option showSidebar

View File

@ -0,0 +1,76 @@
Users
=====
Deleting Users
---------------------
Users can either be deleted with all their contributions (hard delete) or without, means only their personal/profile data will be deleted (soft delete)
### Soft delete
A common use cases for the soft delete option is:
- Delete participation statuses (e.g. task assignments)
- Delete personal information and images
You can manage the soft delete option by intercepting the event [[\humhub\modules\user\models\User::EVENT_BEFORE_SOFT_DELETE]].
Example 'config.php':
```php
<?php
use humhub\modules\user\models\User;
// ...
return [
// ...
'events' => [
[User::class, User::EVENT_BEFORE_SOFT_DELETE, [Events::class, 'onUserSoftDelete']],
// ...
],
// ...
];
?>
```
Example callback in your modules **Events** class:
```php
public static function onUserSoftDelete(UserEvent $event)
{
$user = $event->user;
MyParticipations::deleteAll(['user_id' => $user->id]);
}
```
### Hard delete
The hard delete option will wipe all data in relation with the deleted user.
HumHub objects created by the user like comments, files, posts, notification or activities will automatically be removed with the user profile.
Example 'config.php':
```php
<?php
use humhub\modules\user\models\User;
// ...
return [
// ...
'events' => [
[User::class, User::EVENT_BEFORE_DELETE, [Events::class, 'onUserDelete']],
// ...
],
// ...
];
?>
```
Example callback in your modules **Events** class:
```php
public static function onUserDelete(Event $event)
{
$user = $event->sender;
MyRecord::deleteAll(['user_id' => $user->id]);
}
```

View File

@ -0,0 +1,5 @@
# Theme Migration to HumHub 1.3
## Space & Profile Layouts
The sidebars are now moved into own files `_sidebar.php` view files.

View File

@ -0,0 +1,106 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\libs;
use yii\grid\Column;
use humhub\libs\Html;
/**
* Description of ActionColumn
*
* @author Luke
*/
class ActionColumn extends Column
{
/**
* @var string the ID attribute of the model, to generate action URLs.
*/
public $modelIdAttribute = 'id';
/**
* @var array list of actions (key = title, value = url)
*/
public $actions = [];
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->options['style'] = 'width:56px';
}
/**
* @inheritdoc
*/
protected function renderDataCellContent($model, $key, $index)
{
$actions = $this->getActions($model, $key, $index);
if (empty($actions)) {
return '';
}
$html = Html::beginTag('div', ['class' => 'btn-group dropdown-navigation']);
$html .= Html::button('<i class="fa fa-cog"></i> <span class="caret"></span>', ['class' => 'btn btn-default dropdown-toggle', 'data-toggle' => 'dropdown']);
$html .= Html::beginTag('ul', ['class' => 'dropdown-menu pull-right']);
foreach ($actions as $title => $url) {
if ($url === '---') {
$html .= '<li class="divider"></li>';
} else {
$linkOptions = null;
if (isset($url['linkOptions'])) {
$linkOptions = $url['linkOptions'];
unset($url['linkOptions']);
}
$html .= Html::beginTag('li');
$html .= Html::a($title, $this->handleUrl($url, $model), $linkOptions);
$html .= Html::endTag('li');
}
}
$html .= Html::endTag('ul');
$html .= Html::endTag('div');
return $html;
}
protected function getActions($model, $key, $index)
{
if ($this->actions === null) {
return [];
} elseif (is_callable($this->actions)) {
return call_user_func($this->actions, $model, $key, $index, $this);
}
return $this->actions;
}
/**
* Builds the URL for a given Action
*
* @param array $url
* @param \yii\base\Model $model
* @return string the url
*/
protected function handleUrl($url, $model)
{
if (!isset($url[$this->modelIdAttribute])) {
$url[$this->modelIdAttribute] = $model->getAttribute($this->modelIdAttribute);
}
return \yii\helpers\Url::to($url);
}
}

View File

@ -9,6 +9,7 @@
namespace humhub\libs;
use Yii;
use yii\base\Object;
use yii\helpers\ArrayHelper;
/**
@ -16,7 +17,7 @@ use yii\helpers\ArrayHelper;
*
* @author luke
*/
class DynamicConfig extends \yii\base\Object
class DynamicConfig extends Object
{
/**
@ -26,7 +27,7 @@ class DynamicConfig extends \yii\base\Object
*/
public static function merge($new)
{
$config = \yii\helpers\ArrayHelper::merge(self::load(), $new);
$config = ArrayHelper::merge(self::load(), $new);
self::save($config);
}
@ -49,7 +50,7 @@ class DynamicConfig extends \yii\base\Object
$config = eval($configContent);
if (!is_array($config))
return array();
return [];
return $config;
}
@ -61,15 +62,14 @@ class DynamicConfig extends \yii\base\Object
*/
public static function save($config)
{
$content = "<" . "?php return ";
$content = '<' . '?php return ';
$content .= var_export($config, true);
$content .= "; ?" . ">";
$content .= '; ?' . '>';
$configFile = self::getConfigFilePath();
file_put_contents($configFile, $content);
if (function_exists('opcache_invalidate')) {
opcache_reset();
opcache_invalidate($configFile);
}
@ -91,14 +91,14 @@ class DynamicConfig extends \yii\base\Object
// Add Default language
$defaultLanguage = Yii::$app->settings->get('defaultLanguage');
if ($defaultLanguage !== null && $defaultLanguage != "") {
if ($defaultLanguage !== null && $defaultLanguage != '') {
$config['language'] = Yii::$app->settings->get('defaultLanguage');
} else {
$config['language'] = Yii::$app->language;
}
$timeZone = Yii::$app->settings->get('timeZone');
if ($timeZone != "") {
if ($timeZone != '') {
$config['timeZone'] = $timeZone;
$config['components']['formatter']['defaultTimeZone'] = $timeZone;
$config['components']['formatterApp']['defaultTimeZone'] = $timeZone;
@ -118,17 +118,22 @@ class DynamicConfig extends \yii\base\Object
'keyPrefix' => Yii::$app->id,
'useApcu' => (function_exists('apcu_add'))
];
} elseif ($cacheClass === \yii\redis\Cache::class) {
$config['components']['cache'] = [
'class' => \yii\redis\Cache::class,
'keyPrefix' => Yii::$app->id,
];
}
// Add User settings
$config['components']['user'] = array();
$config['components']['user'] = [];
if (Yii::$app->getModule('user')->settings->get('auth.defaultUserIdleTimeoutSec')) {
$config['components']['user']['authTimeout'] = Yii::$app->getModule('user')->settings->get('auth.defaultUserIdleTimeoutSec');
}
// Install Mail Component
$mail = [];
$mail['transport'] = array();
$mail['transport'] = [];
if (Yii::$app->settings->get('mailer.transportType') == 'smtp') {
$mail['transport']['class'] = 'Swift_SmtpTransport';

View File

@ -11,6 +11,8 @@ namespace humhub\libs;
use Yii;
use yii\base\InvalidParamException;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\user\models\User;
use humhub\modules\space\models\Space;
/**
* HTML Helpers
@ -94,9 +96,12 @@ class Html extends \yii\bootstrap\Html
*/
public static function containerLink(ContentContainerActiveRecord $container, $options = [])
{
if ($container instanceof \humhub\modules\space\models\Space) {
if ($container instanceof Space) {
return static::a(static::encode($container->name), $container->getUrl(), $options);
} elseif ($container instanceof \humhub\modules\user\models\User) {
} elseif ($container instanceof User) {
if ($container->status == User::STATUS_SOFT_DELETED) {
return static::beginTag('strike') . static::encode($container->displayName) . static::endTag('strike');
}
return static::a(static::encode($container->displayName), $container->getUrl(), $options);
} else {
throw new InvalidParamException('Content container type not supported!');

View File

@ -0,0 +1,62 @@
<?php
use yii\db\Migration;
class m171015_155102_contentcontainer_module extends Migration
{
public function safeUp()
{
$this->createTable('contentcontainer_module', [
'contentcontainer_id' => $this->integer()->notNull(),
'module_id' => $this->char(100),
'module_state' => $this->smallInteger(),
]);
$this->addPrimaryKey('pk_contentcontainer_module', 'contentcontainer_module', ['contentcontainer_id', 'module_id']);
$this->addForeignKey('fk_contentcontainer', 'contentcontainer_module', 'contentcontainer_id', 'contentcontainer', 'id', 'CASCADE', 'CASCADE');
$sqlInsert = 'INSERT INTO contentcontainer_module (contentcontainer_id, module_id, module_state) ';
$this->db->createCommand($sqlInsert . 'SELECT space.contentcontainer_id, module_id, state FROM space_module LEFT JOIN space ON space_module.space_id=space.id WHERE space.id IS NOT NULL')->execute();
$this->db->createCommand($sqlInsert . 'SELECT user.contentcontainer_id, module_id, state FROM user_module LEFT JOIN user ON user_module.user_id=user.id WHERE user.id IS NOT NULL')->execute();
$rows = (new \yii\db\Query())->select("*")->from('space_module')->where('space_id IS NULL OR space_id=0')->all();
foreach ($rows as $row) {
$reflect = new ReflectionClass(humhub\modules\space\models\Space::class);
$module = Yii::$app->getModule($row['module_id']);
$module->settings->set('moduleManager.defaultState.' . $reflect->getShortName(), $row['state']);
}
$rows = (new \yii\db\Query())->select("*")->from('user_module')->where('user_id IS NULL OR user_id=0')->all();
foreach ($rows as $row) {
$reflect = new ReflectionClass(\humhub\modules\user\models\User::class);
$module = Yii::$app->getModule($row['module_id']);
$module->settings->set('moduleManager.defaultState.' . $reflect->getShortName(), $row['state']);
}
$this->dropTable('user_module');
$this->dropTable('space_module');
}
public function safeDown()
{
echo "m171015_155102_contentcontainer_module cannot be reverted.\n";
return false;
}
/*
// Use up()/down() to run migration code without a transaction.
public function up()
{
}
public function down()
{
echo "m171015_155102_contentcontainer_module cannot be reverted.\n";
return false;
}
*/
}

View File

@ -9,7 +9,7 @@
namespace humhub\modules\activity\jobs;
use Yii;
use humhub\components\queue\ActiveJob;
use humhub\modules\queue\ActiveJob;
use humhub\modules\activity\components\MailSummaryProcessor;
use humhub\modules\activity\components\MailSummary;

View File

@ -48,6 +48,11 @@ class Module extends \humhub\components\Module
*/
public $dailyCheckForNewVersion = true;
/**
* @var boolean allow admins to impersonate other users
*/
public $allowUserImpersonate = true;
/**
* @inheritdoc
*/

View File

@ -15,6 +15,8 @@ use humhub\modules\admin\libs\OnlineModuleManager;
use humhub\modules\content\components\ContentContainerModule;
use humhub\modules\user\models\User;
use humhub\modules\space\models\Space;
use humhub\modules\admin\models\forms\ModuleSetAsDefaultForm;
use humhub\modules\content\components\ContentContainerModuleManager;
/**
* Module Controller controls all third party modules in a humhub installation.
@ -300,45 +302,13 @@ class ModuleController extends Controller
throw new HttpException(500, 'Invalid module type!');
}
$model = new \humhub\modules\admin\models\forms\ModuleSetAsDefaultForm();
$spaceDefaultModule = null;
if ($module->hasContentContainerType(Space::className())) {
$spaceDefaultModule = \humhub\modules\space\models\Module::find()->where(['module_id' => $moduleId])->andWhere(['IS', 'space_id', new \yii\db\Expression('NULL')])->one();
if ($spaceDefaultModule === null) {
$spaceDefaultModule = new \humhub\modules\space\models\Module();
$spaceDefaultModule->module_id = $moduleId;
$spaceDefaultModule->state = \humhub\modules\space\models\Module::STATE_DISABLED;
}
$model->spaceDefaultState = $spaceDefaultModule->state;
}
$userDefaultModule = null;
if ($module->hasContentContainerType(User::className())) {
$userDefaultModule = \humhub\modules\user\models\Module::find()->where(['module_id' => $moduleId])->andWhere(['IS', 'user_id', new \yii\db\Expression('NULL')])->one();
if ($userDefaultModule === null) {
$userDefaultModule = new \humhub\modules\user\models\Module();
$userDefaultModule->module_id = $moduleId;
$userDefaultModule->state = \humhub\modules\user\models\Module::STATE_DISABLED;
}
$model->userDefaultState = $userDefaultModule->state;
}
$model = new ModuleSetAsDefaultForm();
$model->spaceDefaultState = ContentContainerModuleManager::getDefaultState(Space::class, $moduleId);
$model->userDefaultState = ContentContainerModuleManager::getDefaultState(User::class, $moduleId);
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
if ($module->hasContentContainerType(Space::className())) {
$spaceDefaultModule->state = $model->spaceDefaultState;
if (!$spaceDefaultModule->save()) {
throw new HttpException('Could not save: ' . print_r($spaceDefaultModule->getErrors(), 1));
}
}
if ($module->hasContentContainerType(User::className())) {
$userDefaultModule->state = $model->userDefaultState;
if (!$userDefaultModule->save()) {
throw new HttpException('Could not save: ' . print_r($userDefaultModule->getErrors(), 1));
}
}
ContentContainerModuleManager::setDefaultState(User::class, $moduleId, $model->userDefaultState);
ContentContainerModuleManager::setDefaultState(Space::class, $moduleId, $model->spaceDefaultState);
return $this->renderModalClose();
}

View File

@ -49,10 +49,7 @@ class SpaceController extends Controller
public function getAccessRules()
{
return [
['permissions' => [
ManageSpaces::className(),
ManageSettings::className()
]],
['permissions' => [ManageSpaces::className(), ManageSettings::className()]],
];
}
@ -61,21 +58,42 @@ class SpaceController extends Controller
*/
public function actionIndex()
{
if (Yii::$app->user->can(new ManageSpaces())) {
$searchModel = new SpaceSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel
]);
} else if (Yii::$app->user->can(new ManageSettings())) {
return $this->redirect([
'settings'
]);
if (!Yii::$app->user->can(new ManageSpaces())) {
return $this->redirect(['settings']);
}
throw new HttpException(403);
$searchModel = new SpaceSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel
]);
}
/**
* Deep link into space
*/
public function actionOpen($id, $section)
{
$space = Space::findOne(['id' => $id]);
if ($space === null) {
throw new HttpException(404);
}
if ($section == 'members') {
return $this->redirect($space->createUrl('/space/manage/member'));
} elseif ($section == 'owner') {
return $this->redirect($space->createUrl('/space/manage/member/change-owner'));
} elseif ($section == 'edit') {
return $this->redirect($space->createUrl('/space/manage'));
} elseif ($section == 'modules') {
return $this->redirect($space->createUrl('/space/manage/module'));
} elseif ($section == 'delete') {
return $this->redirect($space->createUrl('/space/manage/default/delete'));
} else {
return $this->redirect($space->getUrl());
}
}
/**
@ -109,11 +127,11 @@ class SpaceController extends Controller
Content::VISIBILITY_PUBLIC => Yii::t('SpaceModule.base', 'Public')];
return $this->render('settings', [
'model' => $form,
'joinPolicyOptions' => $joinPolicyOptions,
'visibilityOptions' => $visibilityOptions,
'contentVisibilityOptions' => $contentVisibilityOptions
]
'model' => $form,
'joinPolicyOptions' => $joinPolicyOptions,
'visibilityOptions' => $visibilityOptions,
'contentVisibilityOptions' => $contentVisibilityOptions
]
);
}

View File

@ -12,14 +12,16 @@ use Yii;
use yii\helpers\Url;
use yii\web\HttpException;
use humhub\compat\HForm;
use humhub\modules\user\models\User;
use humhub\modules\user\models\Invite;
use humhub\modules\user\models\forms\Registration;
use humhub\modules\admin\components\Controller;
use humhub\modules\user\models\User;
use humhub\modules\admin\models\forms\UserEditForm;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\admin\permissions\ManageGroups;
use humhub\modules\admin\permissions\ManageSettings;
use humhub\modules\space\models\Membership;
use humhub\modules\admin\models\forms\UserDeleteForm;
use humhub\modules\admin\models\UserSearch;
/**
* User management
@ -34,12 +36,15 @@ class UserController extends Controller
*/
public $adminOnly = false;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->appendPageTitle(Yii::t('AdminModule.base', 'Users'));
$this->subLayout = '@admin/views/layouts/user';
return parent::init();
}
/**
@ -48,36 +53,37 @@ class UserController extends Controller
public function getAccessRules()
{
return [
[
'permissions' => [
ManageUsers::class,
ManageGroups::class,
]
],
[
'permissions' => [ManageSettings::class],
'actions' => ['index']
]
['permissions' => [ManageUsers::class, ManageGroups::class]],
['permissions' => [ManageSettings::class], 'actions' => ['index']]
];
}
public function actionIndex()
{
if (Yii::$app->user->can([new ManageUsers(), new ManageGroups()])) {
return $this->redirect(['list']);
} else if (Yii::$app->user->can(ManageSettings::class)) {
return $this->redirect(['/admin/authentication']);
} else {
return $this->forbidden();
}
}
/**
* Returns a List of Users
*/
public function actionIndex()
public function actionList()
{
if (Yii::$app->user->can([new ManageUsers(), new ManageGroups()])) {
$searchModel = new \humhub\modules\admin\models\UserSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel
]);
} else if (Yii::$app->user->can(ManageSettings::class)) {
$this->redirect(['/admin/authentication']);
} else {
$this->forbidden();
}
$searchModel = new UserSearch();
$searchModel->status = User::STATUS_ENABLED;
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$showPendingRegistrations = (Invite::find()->count() > 0 && Yii::$app->user->can([new ManageUsers(), new ManageGroups()]));
return $this->render('list', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
'showPendingRegistrations' => $showPendingRegistrations
]);
}
/**
@ -148,12 +154,6 @@ class UserController extends Controller
'label' => Yii::t('AdminModule.controllers_UserController', 'Save'),
'class' => 'btn btn-primary',
],
'become' => [
'type' => 'submit',
'label' => Yii::t('AdminModule.controllers_UserController', 'Become this user'),
'class' => 'btn btn-danger',
'isVisible' => $this->canBecomeUser($user)
],
'delete' => [
'type' => 'submit',
'label' => Yii::t('AdminModule.controllers_UserController', 'Delete'),
@ -172,15 +172,8 @@ class UserController extends Controller
}
}
// This feature is used primary for testing, maybe remove this in future
if ($form->submitted('become') && $this->canBecomeUser($user)) {
Yii::$app->user->switchIdentity($form->models['User']);
return $this->redirect(Url::home());
}
if ($form->submitted('delete')) {
return $this->redirect(['/admin/user/delete', 'id' => $user->id]);
return $this->redirect(['delete', 'id' => $user->id]);
}
return $this->render('edit', [
@ -189,11 +182,6 @@ class UserController extends Controller
]);
}
public function canBecomeUser($user)
{
return Yii::$app->user->isAdmin() && $user->id != Yii::$app->user->getIdentity()->id;
}
public function actionAdd()
{
$registration = new Registration();
@ -209,33 +197,109 @@ class UserController extends Controller
/**
* Deletes a user permanently
*/
public function actionDelete()
public function actionDelete($id)
{
$id = (int) Yii::$app->request->get('id');
$doit = (int) Yii::$app->request->get('doit');
$user = User::findOne(['id' => $id]);
if ($user == null) {
throw new HttpException(404, Yii::t('AdminModule.user', 'User not found!'));
} elseif (Yii::$app->user->id == $id) {
throw new HttpException(400, Yii::t('AdminModule.user', 'You cannot delete yourself!'));
}
$model = new UserDeleteForm(['user' => $user]);
if ($model->load(Yii::$app->request->post()) && $model->performDelete()) {
$this->view->info(Yii::t('AdminModule.user', 'User deletion process queued.'));
return $this->redirect(['list']);
}
return $this->render('delete', ['model' => $model]);
}
/**
* Redirect to user profile
*
* @param int $id
* @return \yii\base\Response the response
* @throws HttpException
*/
public function actionViewProfile($id)
{
$user = User::findOne(['id' => $id]);
if ($user === null) {
throw new HttpException(404);
}
return $this->redirect($user->getUrl());
}
public function actionEnable($id)
{
$this->forcePostRequest();
$user = User::findOne(['id' => $id]);
if ($user == null) {
throw new HttpException(404, Yii::t('AdminModule.controllers_UserController', 'User not found!'));
} elseif (Yii::$app->user->id == $id) {
throw new HttpException(400, Yii::t('AdminModule.controllers_UserController', 'You cannot delete yourself!'));
if ($user === null) {
throw new HttpException(404);
}
if ($doit == 2) {
$this->forcePostRequest();
$user->status = User::STATUS_ENABLED;
$user->save();
foreach (Membership::GetUserSpaces($user->id) as $space) {
if ($space->isSpaceOwner($user->id)) {
$space->addMember(Yii::$app->user->id);
$space->setSpaceOwner(Yii::$app->user->id);
}
}
$user->delete();
return $this->redirect(['/admin/user']);
return $this->redirect(['list']);
}
public function actionDisable($id)
{
$this->forcePostRequest();
$user = User::findOne(['id' => $id]);
if ($user === null) {
throw new HttpException(404);
}
return $this->render('delete', ['model' => $user]);
$user->status = User::STATUS_DISABLED;
$user->save();
return $this->redirect(['list']);
}
/**
* Redirect to user profile
*
* @param int $id
* @return \yii\base\Response the response
* @throws HttpException
*/
public function actionImpersonate($id)
{
$this->forcePostRequest();
$user = User::findOne(['id' => $id]);
if ($user === null) {
throw new HttpException(404);
}
if (!static::canImpersonate($user)) {
throw new HttpException(403);
}
Yii::$app->user->switchIdentity($user);
return $this->goHome();
}
/**
* Determines if the current user can impersonate given user.
*
* @param User $user
* @return boolean can impersonate
*/
public static function canImpersonate($user)
{
if (!Yii::$app->getModule('admin')->allowUserImpersonate) {
return false;
}
return Yii::$app->user->isAdmin() && $user->id != Yii::$app->user->getIdentity()->id;
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\admin\grid;
use Yii;
use humhub\libs\ActionColumn;
use humhub\modules\space\models\Space;
use humhub\modules\admin\controllers\UserController;
/**
* SpaceActionColumn
*
* @author Luke
*/
class SpaceActionColumn extends ActionColumn
{
/**
* @inheritdoc
*/
protected function renderDataCellContent($model, $key, $index)
{
$actions = [];
$actions[Yii::t('base', 'Edit')] = ['open', 'section' => 'edit'];
$actions[] = '---';
$actions[Yii::t('AdminModule.space', 'Manage members')] = ['open', 'section' => 'members'];
$actions[Yii::t('AdminModule.space', 'Change owner')] = ['open', 'section' => 'owner'];
$actions[Yii::t('AdminModule.space', 'Manage modules')] = ['open', 'section' => 'modules'];
$actions[Yii::t('base', 'Delete')] = ['open', 'section' => 'delete'];
$actions[] = '---';
$actions[Yii::t('AdminModule.space', 'Open space')] = ['open'];
$this->actions = $actions;
return parent::renderDataCellContent($model, $key, $index);
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\admin\grid;
use yii\db\ActiveRecord;
use yii\grid\DataColumn;
/**
* BaseColumn for space grid fields
*
* @since 1.3
* @author Luke
*/
abstract class SpaceBaseColumn extends DataColumn
{
/**
* @var string|null name of space model attribute
*/
public $spaceAttribute = null;
/**
* Returns the space record
*
* @param ActiveRecord $record
* @return \humhub\modules\space\models\Space the space model
*/
public function getSpace(ActiveRecord $record)
{
if ($this->spaceAttribute === null) {
return $record;
}
$attributeName = $this->spaceAttribute;
return $record->$attributeName;
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\admin\grid;
use humhub\modules\space\widgets\Image as SpaceImage;
use humhub\modules\space\models\Space;
/**
* SpaceColumn
*
* @since 1.3
* @author Luke
*/
class SpaceImageColumn extends SpaceBaseColumn
{
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->options['style'] = 'width:38px';
}
/**
* @inheritdoc
*/
protected function renderDataCellContent($model, $key, $index)
{
return SpaceImage::widget(['space' => $this->getSpace($model), 'width' => 34, 'link' => true]);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\admin\grid;
use Yii;
use yii\bootstrap\Html;
use humhub\modules\space\models\Space;
use humhub\libs\Helpers;
/**
* TitleColumn
*
* @since 1.3
* @author Luke
*/
class SpaceTitleColumn extends SpaceBaseColumn
{
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->attribute === null) {
$this->attribute = 'name';
}
if ($this->label === null) {
$this->label = Yii::t('SpaceModule.base', 'Name');
}
}
/**
* @inheritdoc
*/
protected function renderDataCellContent($model, $key, $index)
{
$space = $this->getSpace($model);
$badge = '';
if ($space->status == Space::STATUS_ARCHIVED) {
$badge = '&nbsp;<span class="badge">'.Yii::t('SpaceModule.base', 'Archived').'</span>';
}
return '<div>' . Html::encode($space->name) . $badge . '<br> ' .
'<small>' . Html::encode(Helpers::trimText($space->description, 100)) . '</small></div>';
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\admin\grid;
use Yii;
use humhub\libs\ActionColumn;
use humhub\modules\user\models\User;
use humhub\modules\admin\controllers\UserController;
/**
* UserActionColumn
*
* @author Luke
*/
class UserActionColumn extends ActionColumn
{
/**
* @inheritdoc
*/
protected function renderDataCellContent($model, $key, $index)
{
$actions = [];
if ($model->status == User::STATUS_SOFT_DELETED) {
$actions[Yii::t('AdminModule.user', 'Permanently delete')] = ['delete'];
} else {
$actions[Yii::t('base', 'Edit')] = ['edit'];
$actions[] = '---';
if ($model->status == User::STATUS_DISABLED) {
$actions[Yii::t('AdminModule.user', 'Enable')] = ['enable', 'linkOptions' => ['data-method' => 'post', 'data-confirm' => Yii::t('AdminModule.user', 'Are you really sure that you want to enable this user?')]];
} elseif ($model->status == User::STATUS_ENABLED) {
$actions[Yii::t('AdminModule.user', 'Disable')] = ['disable', 'linkOptions' => ['data-method' => 'post', 'data-confirm' => Yii::t('AdminModule.user', 'Are you really sure that you want to disable this user?')]];
}
$actions[Yii::t('base', 'Delete')] = ['delete'];
if ($model->status == User::STATUS_ENABLED) {
$actions[] = '---';
if (UserController::canImpersonate($model)) {
$actions[Yii::t('AdminModule.user', 'Impersonate')] = ['impersonate', 'linkOptions' => ['data-method' => 'post', 'data-confirm' => Yii::t('AdminModule.user', 'Are you really sure that you want to impersonate this user?')]];
}
$actions[Yii::t('AdminModule.user', 'View profile')] = ['view-profile'];
}
}
$this->actions = $actions;
return parent::renderDataCellContent($model, $key, $index);
}
}

View File

@ -9,7 +9,7 @@
namespace humhub\modules\admin\jobs;
use Yii;
use humhub\components\queue\ActiveJob;
use humhub\modules\queue\ActiveJob;
use humhub\modules\user\models\Group;
use humhub\modules\admin\libs\HumHubAPI;
use humhub\modules\admin\notifications\NewVersionAvailable;

View File

@ -8,7 +8,7 @@
namespace humhub\modules\admin\jobs;
use humhub\components\queue\ActiveJob;
use humhub\modules\queue\ActiveJob;
use humhub\modules\admin\models\Log;
/**

View File

@ -8,24 +8,32 @@
namespace humhub\modules\admin\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use humhub\modules\space\models\Space;
use humhub\modules\space\models\Membership;
/**
* Description of UserSearch
* SpaceSearch for administration
*
* @author luke
*/
class SpaceSearch extends Space
{
public $freeText;
public $memberCount;
public $owner;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['id', 'visibility', 'join_policy'], 'integer'],
[['name'], 'safe'],
[['freeText'], 'safe'],
];
}
@ -34,10 +42,28 @@ class SpaceSearch extends Space
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
//Bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* @inheritdoc
*/
public static function className()
{
return Space::class;
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return array_merge(parent::attributeLabels(), [
'memberCount' => 'Members'
]);
}
/**
* Creates data provider instance with search query applied
*
@ -47,7 +73,10 @@ class SpaceSearch extends Space
*/
public function search($params)
{
$query = Space::find();
$memberCountSubSelect = Membership::find()->select('COUNT(*) as counter')->where('space_id=space.id')->andWhere(['space_membership.status' => Membership::STATUS_MEMBER]);
$query = self::find();
$query->joinWith(['ownerUser', 'ownerUser.profile']);
$query->addSelect(['space.*', 'memberCount' => $memberCountSubSelect]);
$dataProvider = new ActiveDataProvider([
'query' => $query,
@ -60,22 +89,56 @@ class SpaceSearch extends Space
'name',
'visibility',
'join_policy',
'memberCount',
]
]);
$dataProvider->sort->attributes['ownerUser.profile.lastname'] = [
'asc' => ['profile.lastname' => SORT_ASC],
'desc' => ['profile.lastname' => SORT_DESC],
];
$this->load($params);
if (!$this->validate()) {
$query->where('0=1');
$query->emulateExecution();
return $dataProvider;
}
$query->andFilterWhere(['id' => $this->id]);
$query->andFilterWhere(['join_policy' => $this->join_policy]);
$query->andFilterWhere(['visibility' => $this->visibility]);
$query->andFilterWhere(['like', 'name', $this->name]);
// Freetext filters
if (!empty($this->freeText)) {
$query->andWhere([
'OR',
['like', 'space.name', $this->freeText],
['like', 'user.id', $this->freeText],
['like', 'user.username', $this->freeText],
['like', 'user.email', $this->freeText],
['like', 'profile.firstname', $this->freeText],
['like', 'profile.lastname', $this->freeText]
]);
}
if ($this->visibility == Space::VISIBILITY_NONE) {
$query->andFilterWhere(['space.visibility' => Space::VISIBILITY_NONE]);
} else {
$query->andWhere([
'OR',
['space.visibility' => Space::VISIBILITY_REGISTERED_ONLY],
['space.visibility' => Space::VISIBILITY_ALL]
]);
}
return $dataProvider;
}
public function getVisibilityAttributes()
{
$countPublic = Space::find()->where(['visibility' => Space::VISIBILITY_ALL])->orWhere(['visibility' => Space::VISIBILITY_REGISTERED_ONLY])->count();
$countPrivate = Space::find()->where(['visibility' => Space::VISIBILITY_NONE])->count();
return [
Space::VISIBILITY_REGISTERED_ONLY => Yii::t('SpaceModule.base', 'Public') . ' (' . $countPublic . ')',
Space::VISIBILITY_NONE => Yii::t('SpaceModule.base', 'Private') . ' (' . $countPrivate . ')',
];
}
}

View File

@ -8,6 +8,7 @@
namespace humhub\modules\admin\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use humhub\modules\user\models\User;
@ -20,19 +21,33 @@ use humhub\modules\user\models\User;
class UserSearch extends User
{
/**
* @var \humhub\modules\user\components\ActiveQueryUser
*/
public $query;
/**
* @var string a free text search
*/
public $freeText;
/**
* @inheritdoc
*/
public function attributes()
{
// add related fields to searchable attributes
return array_merge(parent::attributes(), ['profile.firstname', 'profile.lastname']);
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['id'], 'integer'],
[['username', 'email', 'created_at', 'profile.firstname', 'profile.lastname', 'last_login'], 'safe'],
[['id', 'status'], 'integer'],
[['username', 'email', 'created_at', 'profile.firstname', 'profile.lastname', 'last_login', 'freeText'], 'safe'],
];
}
@ -55,38 +70,62 @@ class UserSearch extends User
public function search($params)
{
$query = ($this->query == null) ? User::find()->joinWith('profile') : $this->query;
/* @var $query \humhub\modules\user\components\ActiveQueryUser */
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => ['pageSize' => 50],
]);
$dataProvider->setSort([
'attributes' => [
'id',
'username',
'email',
'last_login',
'last_login',
'profile.firstname',
'profile.lastname',
'created_at',
]
]);
$dataProvider->sort->defaultOrder = ['id' => SORT_DESC];
$this->load($params);
if (!$this->validate()) {
$query->where('0=1');
$query->emulateExecution();
return $dataProvider;
}
$query->joinWith(['profile']);
// Freetext filters
if (!empty($this->freeText)) {
$query->andWhere([
'OR',
['like', 'user.id', $this->freeText],
['like', 'user.username', $this->freeText],
['like', 'user.email', $this->freeText],
['like', 'profile.firstname', $this->freeText],
['like', 'profile.lastname', $this->freeText]
]);
if (!empty($this->status)) {
$query->andFilterWhere(['user.status' => $this->status]);
}
return $dataProvider;
}
$query->andFilterWhere(['id' => $this->id]);
$query->andFilterWhere(['user.status' => $this->status]);
$query->andFilterWhere(['like', 'user.id', $this->id]);
$query->andFilterWhere(['like', 'user.username', $this->username]);
$query->andFilterWhere(['like', 'user.email', $this->email]);
$query->andFilterWhere(['like', 'profile.firstname', $this->getAttribute('profile.firstname')]);
$query->andFilterWhere(['like', 'profile.lastname', $this->getAttribute('profile.lastname')]);
if ($this->getAttribute('last_login') != "") {
try {
$last_login = \humhub\libs\DateHelper::parseDateTime($this->getAttribute('last_login'));
@ -94,8 +133,8 @@ class UserSearch extends User
$query->andWhere([
'=',
new \yii\db\Expression("DATE(last_login)"),
new \yii\db\Expression("DATE(:last_login)", [':last_login'=>$last_login])
]);
new \yii\db\Expression("DATE(:last_login)", [':last_login' => $last_login])
]);
} catch (InvalidParamException $e) {
// do not change the query if the date is wrong formatted
}
@ -104,4 +143,17 @@ class UserSearch extends User
return $dataProvider;
}
public function getStatusAttributes()
{
$countActive = User::find()->where(['user.status' => User::STATUS_ENABLED])->count();
$countDisabled = User::find()->where(['user.status' => User::STATUS_DISABLED])->count();
$countSoftDeleted = User::find()->where(['user.status' => User::STATUS_SOFT_DELETED])->count();
return [
User::STATUS_ENABLED => Yii::t('AdminModule.user', 'Active users') . ' (' . $countActive . ')',
User::STATUS_DISABLED => Yii::t('AdminModule.user', 'Disabled users') . ' (' . $countDisabled . ')',
User::STATUS_SOFT_DELETED => Yii::t('AdminModule.user', 'Deleted users') . ' (' . $countSoftDeleted . ')',
];
}
}

View File

@ -57,11 +57,17 @@ class CacheSettingsForm extends Model
*/
public function getTypes()
{
return [
$cacheTypes = [
'yii\caching\DummyCache' => \Yii::t('AdminModule.forms_CacheSettingsForm', 'No caching'),
'yii\caching\FileCache' => \Yii::t('AdminModule.forms_CacheSettingsForm', 'File'),
'yii\caching\ApcCache' => \Yii::t('AdminModule.forms_CacheSettingsForm', 'APC(u)'),
];
if (isset(Yii::$app->redis)) {
$cacheTypes['yii\redis\Cache'] = \Yii::t('AdminModule.forms_CacheSettingsForm', 'Redis');
}
return $cacheTypes;
}
/**

View File

@ -0,0 +1,137 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\admin\models\forms;
use Yii;
use yii\base\Model;
use humhub\modules\user\models\User;
use humhub\modules\user\jobs\SoftDeleteUser;
use humhub\modules\user\jobs\DeleteUser;
use humhub\modules\space\helpers\MembershipHelper;
use humhub\modules\space\models\Space;
/**
* UserDeleteForm shows the deletion options for the admin.
*
* @since 1.3
*/
class UserDeleteForm extends Model
{
/**
* @var User the user record to delete
*/
public $user;
/**
* @var boolean delete also user contributions
*/
public $deleteContributions = false;
/**
* @var boolean delete also user spaces
*/
public $deleteSpaces = false;
/**
* @var Space[]
*/
protected $_spaces = null;
/**
* @inheritdoc
*/
public function init()
{
if ($this->user->status == User::STATUS_SOFT_DELETED) {
$this->deleteContributions = true;
}
}
/**
* @inheritdoc
*/
public function rules()
{
$rules = [];
$rules[] = [['deleteSpaces'], 'boolean'];
if ($this->user->status != User::STATUS_SOFT_DELETED) {
$rules[] = [['deleteContributions'], 'boolean'];
}
return $rules;
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return array(
'deleteContributions' => Yii::t('AdminModule.user', 'Delete all contributions of this user'),
'deleteSpaces' => Yii::t('AdminModule.user', 'Delete spaces which are owned by this user'),
);
}
/**
* @inheritdoc
*/
public function attributeHints()
{
return array(
'deleteContributions' => Yii::t('AdminModule.user', 'Using this option any contributions (e.g. contents, comments or likes) of this user will be irrevocably deleted.'),
'deleteSpaces' => Yii::t('AdminModule.user', 'If this option is not selected, the ownership of the spaces will be transferred to your account.'),
);
}
/**
* Perform user deletion
* @since 1.3
*/
public function performDelete()
{
if (!$this->validate()) {
return false;
}
// Handle owned spaces by the deleted user
$ownedSpaces = MembershipHelper::getOwnSpaces($this->user);
if (count($ownedSpaces) !== 0 && empty($this->deleteSpaces)) {
foreach ($ownedSpaces as $space) {
$space->addMember(Yii::$app->user->id);
$space->setSpaceOwner(Yii::$app->user->id);
}
}
if (empty($this->deleteContributions)) {
Yii::$app->queue->push(new SoftDeleteUser(['user_id' => $this->user->id]));
} else {
Yii::$app->queue->push(new DeleteUser(['user_id' => $this->user->id]));
}
return true;
}
/**
* Returns all spaces which are owned by the user
*
* @return Space[] the spaces
*/
public function getOwningSpaces()
{
if ($this->_spaces !== null) {
return $this->_spaces;
}
$this->_spaces = MembershipHelper::getOwnSpaces($this->user);
return $this->_spaces;
}
}

View File

@ -3,6 +3,8 @@
use yii\helpers\Url;
use yii\helpers\Html;
use humhub\widgets\GridView;
use humhub\modules\user\grid\ImageColumn;
use humhub\modules\user\grid\DisplayNameColumn;
?>
<div class="panel-body">
@ -15,15 +17,12 @@ use humhub\widgets\GridView;
<?=
GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
# 'filterModel' => $searchModel,
'columns' => [
'username',
'email',
'profile.firstname',
'profile.lastname',
'profile.lastname',
['class' => ImageColumn::class],
['class' => DisplayNameColumn::class],
[
'header' => Yii::t('AdminModule.views_approval_index', 'Actions'),
# 'header' => Yii::t('AdminModule.views_approval_index', 'Actions'),
'class' => 'yii\grid\ActionColumn',
'options' => ['width' => '150px'],
'buttons' => [

View File

@ -5,6 +5,10 @@ use yii\helpers\Html;
use humhub\widgets\GridView;
?>
<div class="panel-body">
<div class="pull-right">
<?= Html::a('<i class="fa fa-plus" aria-hidden="true"></i>&nbsp;&nbsp;' . Yii::t('AdminModule.views_groups_index', "Create new group"), Url::to(['edit']), ['class' => 'btn btn-sm btn-success']); ?>
</div>
<h4><?= Yii::t('AdminModule.views_group_index', 'Manage groups'); ?></h4>
<div class="help-block">
@ -15,9 +19,6 @@ use humhub\widgets\GridView;
<?= \humhub\modules\admin\widgets\GroupMenu::widget(); ?>
<div class="panel-body">
<div class="pull-right">
<?= Html::a('<i class="fa fa-plus" aria-hidden="true"></i>&nbsp;&nbsp;' . Yii::t('AdminModule.views_groups_index', "Create new group"), Url::to(['edit']), ['class' => 'btn btn-success']); ?>
</div>
<?php
echo GridView::widget([
@ -37,20 +38,13 @@ use humhub\widgets\GridView;
}
],
[
'header' => Yii::t('AdminModule.views_group_index', 'Actions'),
'class' => 'yii\grid\ActionColumn',
'options' => ['width' => '80px'],
'buttons' => [
'view' => function() {
return;
},
'delete' => function() {
return;
},
'update' => function($url, $model) {
return Html::a('<i class="fa fa-pencil"></i>', Url::toRoute(['edit', 'id' => $model->id]), ['class' => 'btn btn-primary btn-xs tt']);
},
],
'class' => \humhub\libs\ActionColumn::class,
'actions' => [
Yii::t('AdminModule.user', 'Settings') => ['edit'],
'---',
Yii::t('AdminModule.user', "Permissions") => ['manage-permissions'],
Yii::t('AdminModule.user', "Members") => ['manage-group-users'],
]
],
],
]);

View File

@ -4,90 +4,100 @@ use yii\helpers\Url;
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use humhub\widgets\GridView;
use humhub\modules\user\grid\ImageColumn;
use humhub\modules\user\grid\DisplayNameColumn;
\humhub\modules\admin\assets\AdminGroupAsset::register($this);
?>
<?php $this->beginContent('@admin/views/group/_manageLayout.php', ['group' => $group]) ?>
<div class="panel-body">
<?php $form = ActiveForm::begin(['action' => ['/admin/group/add-members']]); ?>
<div class="form-group">
<div class="input-group select2-humhub-append">
<?=
humhub\modules\user\widgets\UserPickerField::widget([
'model' => $addGroupMemberForm,
'attribute' => 'userGuids',
'url' => Url::to(['/admin/group/new-member-search', 'id' => $group->id]),
'placeholder' => Yii::t('AdminModule.views_group_manageGroupUser', 'Add new members...'),
'focus' => true,
])
?>
<?= Html::activeHiddenInput($addGroupMemberForm, 'groupId', ['value' => $group->id]) ?>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary" style="height:40px;" data-ui-loader><i class="fa fa-plus"></i></button>
</span>
<div class="row">
<div class="form-group col-md-6">
<?php $form = ActiveForm::begin(['action' => ['/admin/group/add-members']]); ?>
<div class="input-group select2-humhub-append">
<?=
humhub\modules\user\widgets\UserPickerField::widget([
'model' => $addGroupMemberForm,
'attribute' => 'userGuids',
'url' => Url::to(['/admin/group/new-member-search', 'id' => $group->id]),
'placeholder' => Yii::t('AdminModule.views_group_manageGroupUser', 'Add new members...'),
'focus' => true,
])
?>
<?= Html::activeHiddenInput($addGroupMemberForm, 'groupId', ['value' => $group->id]) ?>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary" style="height:40px;" data-ui-loader><i class="fa fa-plus"></i></button>
</span>
</div>
<?php ActiveForm::end(); ?>
</div>
<div class="col-md-6">
<?php $form = ActiveForm::begin(['method' => 'get']); ?>
<div class="input-group">
<?= Html::activeTextInput($searchModel, 'freeText', ['class' => 'form-control', 'placeholder' => Yii::t('AdminModule.user', 'Search by name, email or id.')]); ?>
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><i class="fa fa-search"></i></button>
</span>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>
<?php ActiveForm::end(); ?>
<div class="table-responsive">
<?php
$actionUrl = Url::to(['edit-manager-role']);
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
[
'attribute' => 'id',
'options' => ['style' => 'width:40px;'],
'format' => 'raw',
'value' => function($data) {
return $data->id;
$actionUrl = Url::to(['edit-manager-role']);
echo GridView::widget([
'dataProvider' => $dataProvider,
#'filterModel' => $searchModel,
'summary' => '',
'columns' => [
['class' => ImageColumn::class],
['class' => DisplayNameColumn::class],
[
'attribute' => 'created_at',
'label' => Yii::t('AdminModule.user', 'Member since'),
'format' => 'datetime',
'options' => ['style' => 'width:160px; min-width:160px;'],
],
[
'attribute' => 'is_manager',
'visible' => $isManagerApprovalSetting,
'label' => Yii::t('AdminModule.user', 'Group Manager'),
'format' => 'raw',
'value' => function ($data) use ($group, $actionUrl) {
$isManager = $group->isManager($data);
$yesSelected = ($isManager) ? 'selected' : '';
$noSelected = ($isManager) ? '' : 'selected';
$result = '<select class="editableCell form-control" data-action-change="admin.group.setManagerRole" data-action-url="' . $actionUrl . '" data-userid="' . $data->id . '" data-groupid="' . $group->id . '">';
$result .= '<option value="0" ' . $noSelected . '>' . Yii::t('AdminModule.views_group_manageGroupUser', 'No') . '</option>';
$result .= '<option value="1" ' . $yesSelected . '>' . Yii::t('AdminModule.views_group_manageGroupUser', 'Yes') . '</option>';
return $result;
}
],
[
'class' => 'yii\grid\ActionColumn',
'options' => ['style' => 'width:40px; min-width:40px;'],
'buttons' => [
'view' => function($url, $model) {
return false;
},
],
'username',
'email',
'profile.firstname',
'profile.lastname',
[
'attribute' => 'is_manager',
'visible' => $isManagerApprovalSetting,
'label' => Yii::t('AdminModule.views_user_index', 'Group Manager'),
'format' => 'raw',
'value' => function ($data) use ($group, $actionUrl) {
$isManager = $group->isManager($data);
$yesSelected = ($isManager) ? 'selected' : '';
$noSelected = ($isManager) ? '' : 'selected';
$result = '<select class="editableCell form-control" data-action-change="admin.group.setManagerRole" data-action-url="'.$actionUrl.'" data-userid="' . $data->id . '" data-groupid="' . $group->id . '">';
$result .= '<option value="0" ' . $noSelected . '>' . Yii::t('AdminModule.views_group_manageGroupUser', 'No') . '</option>';
$result .= '<option value="1" ' . $yesSelected . '>' . Yii::t('AdminModule.views_group_manageGroupUser', 'Yes') . '</option>';
return $result;
'update' => function($url, $model) use ($group) {
return false;
},
'delete' => function($url, $model) use ($group) {
return Html::a('<i class="fa fa-times"></i>', '#', [
'data-action-click' => 'admin.group.removeMember',
'data-action-url' => Url::to(['remove-group-user', 'id' => $group->id, 'userId' => $model->id]),
'title' => Yii::t('AdminModule.views_group_manageGroupUser', 'Remove from group'),
'class' => 'btn btn-danger btn-xs tt']);
}
],
[
'header' => Yii::t('AdminModule.views_user_index', 'Actions'),
'class' => 'yii\grid\ActionColumn',
'options' => ['style' => 'width:80px; min-width:80px;'],
'buttons' => [
'view' => function($url, $model) {
return false;
},
'update' => function($url, $model) use ($group) {
return false;
},
'delete' => function($url, $model) use ($group) {
return Html::a('<i class="fa fa-times"></i>', '#', [
'data-action-click' => 'admin.group.removeMember',
'data-action-url' => Url::to(['remove-group-user', 'id' => $group->id, 'userId' => $model->id]),
'title' => Yii::t('AdminModule.views_group_manageGroupUser', 'Remove from group'),
'class' => 'btn btn-danger btn-xs tt']);
}
],
],
],
]
);?>
],
]
);
?>
</div>
</div>
<?php $this->endContent(); ?>

View File

@ -1,81 +1,56 @@
<?php
use yii\helpers\Html;
use humhub\modules\space\models\Space;
use humhub\widgets\ActiveForm;
use humhub\modules\admin\widgets\SpaceGridView;
use humhub\modules\admin\grid\SpaceActionColumn;
use humhub\modules\admin\grid\SpaceTitleColumn;
use humhub\modules\admin\grid\SpaceImageColumn;
use humhub\modules\admin\models\SpaceSearch;
?>
<?= Html::a('<i class="fa fa-plus" aria-hidden="true"></i>&nbsp;&nbsp;' . Yii::t('AdminModule.space', 'Add new space'), ['/space/create'], ['class' => 'btn btn-sm btn-success pull-right', 'data-target' => '#globalModal']); ?>
<h4><?= Yii::t('AdminModule.views_space_index', 'Overview'); ?></h4>
<div class="help-block">
<?= Yii::t('AdminModule.views_space_index', 'This overview contains a list of each space with actions to view, edit and delete spaces.'); ?>
</div>
<br />
<?php $form = ActiveForm::begin(['method' => 'get']); ?>
<div class="row">
<div class="col-md-8">
<div class="input-group">
<?= Html::activeTextInput($searchModel, 'freeText', ['class' => 'form-control', 'placeholder' => Yii::t('AdminModule.space', 'Search by name, description, id or owner.')]); ?>
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><i class="fa fa-search"></i></button>
</span>
</div>
</div>
<div class="col-md-4">
<?= Html::activeDropDownList($searchModel, 'visibility', SpaceSearch::getVisibilityAttributes(), ['class' => 'form-control', 'onchange' => 'this.form.submit()']); ?>
</div>
</div>
<?php ActiveForm::end(); ?>
<div class="table-responsive">
<?= Html::a('<i class="fa fa-plus" aria-hidden="true"></i>&nbsp;&nbsp;' . Yii::t('AdminModule.space', 'Add new space'), ['/space/create'], ['class' => 'btn btn-success pull-right', 'data-target' => '#globalModal']); ?>
<?php
$visibilities = [
Space::VISIBILITY_NONE => Yii::t('SpaceModule.base', 'Private (Invisible)'),
Space::VISIBILITY_REGISTERED_ONLY => Yii::t('SpaceModule.base', 'Public (Visible)'),
Space::VISIBILITY_ALL => 'All',
];
$joinPolicies = [
Space::JOIN_POLICY_NONE => Yii::t('SpaceModule.base', 'Only by invite'),
Space::JOIN_POLICY_APPLICATION => Yii::t('SpaceModule.base', 'Invite and request'),
Space::JOIN_POLICY_FREE => Yii::t('SpaceModule.base', 'Everyone can enter'),
];
echo SpaceGridView::widget([
<?=
SpaceGridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'summary' => '',
'columns' => [
['class' => SpaceImageColumn::class],
['class' => SpaceTitleColumn::class],
'memberCount',
['class' => \humhub\modules\user\grid\ImageColumn::class, 'userAttribute' => 'ownerUser'],
[
'attribute' => 'id',
'options' => ['width' => '40px'],
'format' => 'raw',
'value' => function($data) {
return $data->id;
},
],
'name',
[
'attribute' => 'visibility',
'filter' => \yii\helpers\Html::activeDropDownList($searchModel, 'visibility', array_merge(['' => ''], $visibilities)),
'options' => ['width' => '40px'],
'format' => 'raw',
'value' => function($data) use ($visibilities) {
if (isset($visibilities[$data->visibility]))
return $visibilities[$data->visibility];
return Html::encode($data->visibility);
},
],
[
'attribute' => 'join_policy',
'options' => ['width' => '40px'],
'filter' => \yii\helpers\Html::activeDropDownList($searchModel, 'join_policy', array_merge(['' => ''], $joinPolicies)),
'format' => 'raw',
'value' => function($data) use ($joinPolicies) {
if (isset($joinPolicies[$data->join_policy]))
return $joinPolicies[$data->join_policy];
return Html::encode($data->join_policy);
},
],
[
'header' => Yii::t('AdminModule.views_space_index', 'Actions'),
'class' => 'yii\grid\ActionColumn',
'options' => ['width' => '80px'],
'buttons' => [
'view' => function($url, $model) {
return Html::a('<i class="fa fa-eye"></i>', $model->getUrl(), ['class' => 'btn btn-primary btn-xs tt']);
},
'update' => function($url, $model) {
return Html::a('<i class="fa fa-pencil"></i>', $model->createUrl('/space/manage'), ['class' => 'btn btn-primary btn-xs tt']);
},
'delete' => function($url, $model) {
return Html::a('<i class="fa fa-times"></i>', $model->createUrl('/space/manage/default/delete'), ['class' => 'btn btn-danger btn-xs tt']);
}
],
'attribute' => 'ownerUser.profile.lastname',
'class' => \humhub\modules\user\grid\DisplayNameColumn::class,
'userAttribute' => 'ownerUser',
'label' => 'Owner'
],
['class' => SpaceActionColumn::class],
],
]);
?>

View File

@ -7,7 +7,12 @@ humhub\assets\TabbedFormAsset::register($this);
<div class="panel-body">
<div class="clearfix">
<?= Html::backButton(['index'], ['label' => Yii::t('AdminModule.base', 'Back to overview'), 'class' => 'pull-right']); ?>
<div class="pull-right">
<?= Html::backButton(['index'], ['label' => Yii::t('AdminModule.base', 'Back to overview')]); ?>
<?= Html::a('<i class="fa fa-paper-plane" aria-hidden="true"></i>&nbsp;&nbsp;' . Yii::t('AdminModule.views_user_index', 'Send invite'), ['/user/invite'], ['class' => 'btn btn-success', 'data-target' => '#globalModal']); ?>
</div>
<h4 class="pull-left"><?= Yii::t('AdminModule.views_user_index', 'Add new user'); ?></h4>
</div>
<br>

View File

@ -1,36 +1,56 @@
<?php
use yii\helpers\Html;
use yii\helpers\Url;
use humhub\libs\Html;
use humhub\modules\user\widgets\Image as UserImage;
use humhub\modules\space\widgets\Image as SpaceImage;
use humhub\widgets\ActiveForm;
/* @var $model \yii\base\Model */
?>
<div class="panel-body">
<h4><?= Yii::t('AdminModule.views_user_delete', 'Confirm user deletion'); ?></h4>
<h4><?= Yii::t('AdminModule.user', 'Confirm user deletion'); ?></h4>
<br>
<p><?= Yii::t('AdminModule.views_user_delete', 'Are you sure you want to delete this user?'); ?></p>
<p><strong><?= Yii::t('AdminModule.user', 'Are you sure that you want to delete following user?'); ?></strong></p>
<ul>
<li><?= Yii::t('AdminModule.views_user_delete', 'All created contents of this user will be <b>deleted</b>.'); ?></li>
<li><?= Yii::t('AdminModule.views_user_delete', 'If this user is owner of some spaces, <b>you</b> will automatically become owner of these spaces.'); ?></li>
</ul>
<div class="media">
<div class="media-left" style="padding-right:6px">
<?= UserImage::widget(['user' => $model->user, 'width' => 38, 'link' => true]); ?>
</div>
<div class="media-body">
<h4 class="media-heading"><?= Html::containerLink($model->user); ?></h4>
<?= Html::encode($model->user->email) ?>
</div>
</div>
<br />
<p><?= Yii::t('AdminModule.account', 'All the personal data of this user will be irrevocably deleted.'); ?></p>
<?php if (count($model->getOwningSpaces()) !== 0): ?>
<br />
<p><?= Yii::t('AdminModule.account', 'The user is the owner of these spaces:'); ?></p>
<?php foreach ($model->getOwningSpaces() as $space): ?>
<div class="media">
<div class="media-left" style="padding-right:6px">
<?= SpaceImage::widget(['space' => $space, 'width' => 38, 'link' => true]); ?>
</div>
<div class="media-body">
<h4 class="media-heading"><?= Html::containerLink($space); ?></h4>
<?= Yii::t('SpaceModule.base', '{count} members', ['count' => $space->getMemberships()->count()]); ?>
</div>
</div>
<?php endforeach; ?>
<br />
<?php endif; ?>
<br>
<br />
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'deleteContributions')->checkbox(['disabled' => !$model->isAttributeSafe('deleteContributions')]); ?>
<?php if (count($model->getOwningSpaces()) !== 0): ?>
<?= $form->field($model, 'deleteSpaces')->checkbox(); ?>
<?php endif; ?>
<?=
\yii\widgets\DetailView::widget([
'model' => $model,
'attributes' => [
'username',
'profile.firstname',
'profile.lastname',
'email:email',
'created_at:datetime',
],
]);
?>
<br>
<br>
<?= Html::a(Yii::t('AdminModule.views_user_delete', 'Delete user'), Url::to(['/admin/user/delete', 'id' => $model->id, 'doit' => 2]), ['class' => 'btn btn-danger', 'data-method' => 'POST']); ?>
<?= Html::a(Yii::t('AdminModule.views_user_delete', 'Cancel'), Url::to(['/admin/user/edit', 'id' => $model->id]), ['class' => 'btn btn-primary pull-right']); ?>
<br />
<hr>
<?= Html::submitButton(Yii::t('UserModule.account', 'Delete account'), ['class' => 'btn btn-danger', 'data-ui-loader' => '']); ?>
<?= Html::a(Yii::t('AdminModule.user', 'Cancel'), Url::to(['/admin/user/edit', 'id' => $model->user->id]), ['class' => 'btn btn-primary pull-right']); ?>
<?php ActiveForm::end(); ?>
</div>

View File

@ -1,71 +0,0 @@
<?php
use yii\helpers\Url;
use yii\helpers\Html;
use humhub\widgets\GridView;
?>
<div class="panel-body">
<h4><?= Yii::t('AdminModule.user', 'Overview'); ?></h4>
<div class="help-block">
<?= Yii::t('AdminModule.views_user_index', 'This overview contains a list of each registered user with actions to view, edit and delete users.'); ?>
</div>
<div class="table-responsive">
<div class="pull-right">
<?= Html::a('<i class="fa fa-plus" aria-hidden="true"></i>&nbsp;&nbsp;' . Yii::t('AdminModule.views_user_index', 'Add new user'), ['/admin/user/add'], ['class' => 'btn btn-success', 'data-ui-loader'=>'']); ?>
<?= Html::a('<i class="fa fa-paper-plane" aria-hidden="true"></i>&nbsp;&nbsp;' . Yii::t('AdminModule.views_user_index', 'Send invite'), ['/user/invite'], ['class' => 'btn btn-success', 'data-target' => '#globalModal']); ?>
<?= humhub\widgets\Button::defaultType('Registrations overview')
->link(Url::to(['/admin/pending-registrations']))
->visible(\humhub\modules\user\models\Invite::find()->count() > 0 && Yii::$app->user->can([new \humhub\modules\admin\permissions\ManageUsers(), new \humhub\modules\admin\permissions\ManageGroups()])); ?>
</div>
<?php
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
[
'attribute' => 'id',
'options' => ['style' => 'width:40px;'],
'format' => 'raw',
'value' => function($data) {
return $data->id;
},
],
'username',
'email',
'profile.firstname',
'profile.lastname',
[
'attribute' => 'last_login',
'label' => Yii::t('AdminModule.views_user_index', 'Last login'),
'filter' => \yii\jui\DatePicker::widget([
'model' => $searchModel,
'attribute' => 'last_login',
'options' => ['style' => 'width:86px;', 'class' => 'form-control'],
]),
'value' => function ($data) {
return ($data->last_login == NULL) ? Yii::t('AdminModule.views_user_index', 'never') : Yii::$app->formatter->asDate($data->last_login);
}
],
[
'header' => Yii::t('AdminModule.views_user_index', 'Actions'),
'class' => 'yii\grid\ActionColumn',
'options' => ['style' => 'width:80px; min-width:80px;'],
'buttons' => [
'view' => function($url, $model) {
return Html::a('<i class="fa fa-eye"></i>', $model->getUrl(), ['class' => 'btn btn-primary btn-xs tt']);
},
'update' => function($url, $model) {
return Html::a('<i class="fa fa-pencil"></i>', Url::toRoute(['edit', 'id' => $model->id]), ['class' => 'btn btn-primary btn-xs tt']);
},
'delete' => function($url, $model) {
return Html::a('<i class="fa fa-times"></i>', Url::toRoute(['delete', 'id' => $model->id]), ['class' => 'btn btn-danger btn-xs tt']);
}
],
],
],
]);
?>
</div>
</div>

View File

@ -0,0 +1,69 @@
<?php
use yii\helpers\Url;
use yii\helpers\Html;
use yii\widgets\ActiveForm;
use humhub\widgets\GridView;
use humhub\widgets\Button;
use humhub\modules\admin\models\UserSearch;
use humhub\modules\user\grid\ImageColumn;
use humhub\modules\user\grid\DisplayNameColumn;
use humhub\modules\admin\grid\UserActionColumn;
?>
<div class="panel-body">
<div class="pull-right">
<?= Html::a('<i class="fa fa-plus" aria-hidden="true"></i>&nbsp;&nbsp;' . Yii::t('AdminModule.user', 'Add new user'), ['/admin/user/add'], ['class' => 'btn btn-success btn-sm', 'data-ui-loader' => '']); ?>
</div>
<h4><?= Yii::t('AdminModule.user', 'Overview'); ?></h4>
<div class="help-block">
<?= Yii::t('AdminModule.user', 'This overview contains a list of each registered user with actions to view, edit and delete users.'); ?>
</div>
<br />
<?php $form = ActiveForm::begin(['method' => 'get']); ?>
<div class="row">
<div class="col-md-8">
<div class="input-group">
<?= Html::activeTextInput($searchModel, 'freeText', ['class' => 'form-control', 'placeholder' => Yii::t('AdminModule.user', 'Search by name, email or id.')]); ?>
<span class="input-group-btn">
<button class="btn btn-default" type="submit"><i class="fa fa-search"></i></button>
</span>
</div>
</div>
<div class="col-md-4">
<?= Html::activeDropDownList($searchModel, 'status', UserSearch::getStatusAttributes(), ['class' => 'form-control', 'onchange' => 'this.form.submit()']); ?>
</div>
</div>
<?php ActiveForm::end(); ?>
<div class="table-responsive">
<?=
GridView::widget([
'dataProvider' => $dataProvider,
'summary' => '',
'columns' => [
['class' => ImageColumn::class],
['class' => DisplayNameColumn::class],
'email',
[
'attribute' => 'last_login',
'label' => Yii::t('AdminModule.user', 'Last login'),
'options' => ['style' => 'width:120px;'],
'value' => function ($data) {
return ($data->last_login == NULL) ? Yii::t('AdminModule.user', 'never') : Yii::$app->formatter->asDate($data->last_login);
}
],
['class' => UserActionColumn::class],
],
]);
?>
</div>
<?php if ($showPendingRegistrations): ?>
<br />
<?= Button::defaultType(Yii::t('AdminModule.user', 'List pending registrations'))->link(Url::to(['/admin/pending-registrations']))->right()->sm(); ?>
<?php endif; ?>
</div>

View File

@ -2,7 +2,7 @@
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
@ -33,19 +33,19 @@ class GroupManagerMenu extends \humhub\widgets\BaseMenu
public function init()
{
$this->addItem([
'label' => Yii::t('AdminModule.views_user_index', 'Settings'),
'label' => Yii::t('AdminModule.user', 'Settings'),
'url' => Url::toRoute(['/admin/group/edit', 'id' => $this->group->id]),
'sortOrder' => 100,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'group' && Yii::$app->controller->action->id == 'edit'),
]);
$this->addItem([
'label' => Yii::t('AdminModule.views_groups_index', "Permissions"),
'label' => Yii::t('AdminModule.user', "Permissions"),
'url' => Url::toRoute(['/admin/group/manage-permissions', 'id' => $this->group->id]),
'sortOrder' => 200,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'group' && Yii::$app->controller->action->id == 'manage-permissions'),
]);
$this->addItem([
'label' => Yii::t('AdminModule.views_groups_index', "Members"),
'label' => Yii::t('AdminModule.user', "Members"),
'url' => Url::toRoute(['/admin/group/manage-group-users', 'id' => $this->group->id]),
'sortOrder' => 200,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'group' && Yii::$app->controller->action->id == 'manage-group-users'),

View File

@ -8,21 +8,25 @@
namespace humhub\modules\admin\widgets;
use humhub\modules\admin\models\UserApprovalSearch;
use humhub\modules\user\models\Invite;
use Yii;
use yii\helpers\Url;
use humhub\modules\admin\models\UserApprovalSearch;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\admin\permissions\ManageGroups;
use humhub\modules\admin\permissions\ManageSettings;
use humhub\modules\user\models\Invite;
use humhub\widgets\BaseMenu;
/**
* User Administration Menu
*
* @author Basti
*/
class UserMenu extends \humhub\widgets\BaseMenu
class UserMenu extends BaseMenu
{
public $template = "@humhub/widgets/views/tabMenu";
public $type = "adminUserSubNavigation";
public $template = '@humhub/widgets/views/tabMenu';
public $type = 'adminUserSubNavigation';
public function init()
{
@ -32,8 +36,8 @@ class UserMenu extends \humhub\widgets\BaseMenu
'sortOrder' => 100,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && (Yii::$app->controller->id == 'user' || Yii::$app->controller->id == 'pending-registrations')),
'isVisible' => Yii::$app->user->can([
new \humhub\modules\admin\permissions\ManageUsers(),
new \humhub\modules\admin\permissions\ManageGroups(),
new ManageUsers(),
new ManageGroups(),
])
]);
@ -43,7 +47,7 @@ class UserMenu extends \humhub\widgets\BaseMenu
'sortOrder' => 200,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'authentication'),
'isVisible' => Yii::$app->user->can([
new \humhub\modules\admin\permissions\ManageSettings()
new ManageSettings()
])
]);
@ -55,8 +59,8 @@ class UserMenu extends \humhub\widgets\BaseMenu
'sortOrder' => 300,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'approval'),
'isVisible' => Yii::$app->user->can([
new \humhub\modules\admin\permissions\ManageUsers(),
new \humhub\modules\admin\permissions\ManageGroups()
new ManageUsers(),
new ManageGroups()
])
]);
}
@ -67,7 +71,7 @@ class UserMenu extends \humhub\widgets\BaseMenu
'sortOrder' => 400,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'user-profile'),
'isVisible' => Yii::$app->user->can([
new \humhub\modules\admin\permissions\ManageUsers()
new ManageUsers()
])
]);
@ -77,7 +81,7 @@ class UserMenu extends \humhub\widgets\BaseMenu
'sortOrder' => 500,
'isActive' => (Yii::$app->controller->module && Yii::$app->controller->module->id == 'admin' && Yii::$app->controller->id == 'group'),
'isVisible' => Yii::$app->user->can(
new \humhub\modules\admin\permissions\ManageGroups()
new ManageGroups()
)
]);

View File

@ -2,7 +2,7 @@
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
@ -10,6 +10,7 @@ namespace humhub\modules\content;
use Yii;
use humhub\modules\content\models\Content;
use humhub\modules\user\events\UserEvent;
/**
* Events provides callbacks to handle events.
@ -19,23 +20,43 @@ use humhub\modules\content\models\Content;
class Events extends \yii\base\Object
{
/**
* Callback when a user is soft deleted.
*
* @param UserEvent $event
*/
public static function onUserSoftDelete(UserEvent $event)
{
// Delete user profile content on soft delete
foreach (Content::findAll(['contentcontainer_id' => $event->user->contentcontainer_id]) as $content) {
$content->delete();
}
}
/**
* Callback when a user is completely deleted.
*
* @param \yii\base\Event $event
*/
public static function onUserDelete($event)
{
$user = $event->sender;
foreach (Content::findAll(['created_by' => $user->id]) as $content) {
$content->delete();
}
return true;
}
/**
* Callback when a user is completely deleted.
*
* @param \yii\base\Event $event
*/
public static function onSpaceDelete($event)
{
$space = $event->sender;
foreach (Content::findAll(['contentcontainer_id' => $space->contentContainerRecord->id]) as $content) {
$content->delete();
}
return true;
}
/**

View File

@ -2,33 +2,35 @@
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2016 HumHub GmbH & Co. KG
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\components;
use Yii;
use yii\helpers\Url;
use humhub\components\ActiveRecord;
use humhub\libs\BasePermission;
use humhub\modules\content\models\Content;
use humhub\libs\ProfileBannerImage;
use humhub\libs\ProfileImage;
use humhub\modules\user\models\User;
use humhub\components\ActiveRecord;
use humhub\modules\content\models\Content;
use humhub\modules\content\models\ContentContainer;
use humhub\modules\content\components\ContentContainerModuleManager;
/**
* ContentContainerActiveRecord for ContentContainer Models e.g. Space or User.
*
* Required Methods:
* - getProfileImage()
* - getUrl()
*
* @property integer $id
* @property integer $visibility
* @property integer $contentcontainer_id
* @property ContentContainerPermissionManager $permissionManager
* @property ContentContainerSettingsManager $settings
* @property ContentContainerModuleManager $moduleManager
*
* @since 1.0
* @author Luke
@ -41,6 +43,25 @@ abstract class ContentContainerActiveRecord extends ActiveRecord
*/
protected $permissionManager = null;
/**
* @var ModuleManager
*/
protected $moduleManager = null;
/**
* The behavior which will be attached to the base controller.
*
* @since 1.3
* @see \humhub\modules\contentcontainer\components\Controller
* @var string class name of additional the controller behavior
*/
public $controllerBehavior = null;
/**
* @var string the default route
*/
public $defaultRoute = '/';
/**
* Returns the Profile Image Object for this Content Base
*
@ -74,15 +95,17 @@ abstract class ContentContainerActiveRecord extends ActiveRecord
/**
* Creates url in content container scope.
* E.g. add uguid or sguid parameter to parameters.
*
* @param string $route
* @param array $params
* @param boolean|string $scheme
*/
public function createUrl($route = null, $params = array(), $scheme = false)
public function createUrl($route = null, $params = [], $scheme = false)
{
return "";
array_unshift($params, ($route !== null) ? $route : $this->defaultRoute);
$params['contentContainer'] = $this;
return Url::to($params, $scheme);
}
/**
@ -117,7 +140,7 @@ abstract class ContentContainerActiveRecord extends ActiveRecord
{
return "Default Wall Output for Class " . get_class($this);
}
public static function findByGuid($token)
{
return static::findOne(['guid' => $token]);
@ -169,10 +192,14 @@ abstract class ContentContainerActiveRecord extends ActiveRecord
*/
public function getContentContainerRecord()
{
if ($this->hasAttribute('contentcontainer_id')) {
return $this->hasOne(ContentContainer::className(), ['id' => 'contentcontainer_id']);
}
return $this->hasOne(ContentContainer::className(), ['pk' => 'id'])
->andOnCondition(['class' => self::className()]);
->andOnCondition(['class' => $this->className()]);
}
/**
* Checks if the current user has the given Permission on this ContentContainerActiveRecord.
* This is a shortcut for `$this->getPermisisonManager()->can()`.
@ -204,7 +231,7 @@ abstract class ContentContainerActiveRecord extends ActiveRecord
*/
public function getPermissionManager(User $user = null)
{
if($user && !$user->is(Yii::$app->user->getIdentity())) {
if ($user && !$user->is(Yii::$app->user->getIdentity())) {
return new ContentContainerPermissionManager([
'contentContainer' => $this,
'subject' => $user
@ -220,6 +247,23 @@ abstract class ContentContainerActiveRecord extends ActiveRecord
]);
}
/**
* Returns a ModuleManager
*
* @since 1.3
* @param User $user
* @return ModuleManager
*/
public function getModuleManager()
{
if ($this->moduleManager !== null) {
return $this->moduleManager;
}
return $this->moduleManager = new ContentContainerModuleManager(['contentContainer' => $this]);
}
/**
* Returns user group for the given $user or current logged in user if no $user instance was provided.
*
@ -238,7 +282,7 @@ abstract class ContentContainerActiveRecord extends ActiveRecord
{
return [];
}
/**
* Returns weather or not the contentcontainer is archived. (Default false).
* @return boolean

View File

@ -2,108 +2,95 @@
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2015 HumHub GmbH & Co. KG
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\components;
use humhub\components\Controller;
use humhub\modules\space\behaviors\SpaceController;
use humhub\modules\space\models\Space;
use humhub\modules\space\widgets\Image;
use humhub\modules\user\behaviors\ProfileController;
use humhub\modules\user\models\User;
use Yii;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\HttpException;
use humhub\components\Controller;
use humhub\modules\content\models\ContentContainer;
use humhub\modules\content\components\ContentContainerModule;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\components\ContentContainerControllerAccess;
/**
* ContainerController is the base controller for all space or user profile controllers.
* It automatically detects the Container by request parameters.
* Use [[ContentContainerActiveCreated::createUrl]] method to generate URLs.
* e.g. $this->contentContainer->createUrl();
* Depends on the loaded the Container Type a Behavior with additional methods will be attached.
* - Space \humhub\modules\space\behaviors\SpaceController
* - User attached Behavior: \humhub\modules\user\behaviors\ProfileController
*
* @since 0.6
* Controller is the base class of web controllers which acts in scope of a ContentContainer (e.g. Space or User).
*
* To automatically load the current contentcontainer the containers guid must be passed as GET parameter 'cguid'.
* You can create URLs in the scope of an ContentContainer by passing the contentContainer instance as 'container' or 'contentContainer'
* as parameter to the URLManager.
*
* Example:
*
* ```
* $url = Url::to(['my/action', 'container' => $this->contentContainer');
* ```
*
* Based on the current ContentContainer a behavior (defined in ContentContainerActiveRecord::controllerBehavior) will be automatically
* attached to this controller instance.
* The attached behavior will perform basic access checks, adds the container sublayout and perform other tasks
* (e.g. the space behavior will update the last visit membership attribute).
*
* @see \humhub\modules\space\behaviors\SpaceController
* @see \humhub\modules\user\behaviors\ProfileController
*/
class ContentContainerController extends Controller
{
/**
* @var ContentContainerActiveRecord
* Specifies if a contentContainer (e.g. Space or User) is required to run this controller.
* Set this to false, if your controller should also act on global scope.
*
* @var boolean require cguid container parameter
*/
public $requireContainer = true;
/**
* @var ContentContainerActiveRecord the content container (e.g. Space or User record)
*/
public $contentContainer = null;
/**
* @var boolean automatic check user access permissions to this container
* Limit this controller only for usage on given contentcontainer types (e.g. Space).
*
* @since 1.3
* @var array|null an array of valid content container classes. if null all container types (User & Space) are allowed.
*/
public $autoCheckContainerAccess = true;
public $validContentContainerClasses = null;
/**
* @var boolean hides containers sidebar in layout
* @since 0.11
*/
public $hideSidebar = true;
/**
* Automatically loads the underlying contentContainer (User/Space) by using
* the uguid/sguid request parameter
*
* @return bool
* @throws HttpException
* @inheritdoc
*/
public function init()
{
$request = Yii::$app->request;
$spaceGuid = $request->get('sguid');
$userGuid = $request->get('uguid');
if ($spaceGuid !== null) {
$this->contentContainer = Space::findOne(['guid' => $spaceGuid]);
if ($this->contentContainer == null) {
throw new HttpException(404, Yii::t('base', 'Space not found!'));
}
$this->attachBehavior('SpaceControllerBehavior', [
'class' => SpaceController::className(),
'space' => $this->contentContainer,
]);
$this->subLayout = "@humhub/modules/space/views/space/_layout";
// Handle case, if a non-logged user tries to acccess a hidden space
// Redirect to Login instead of showing error
if ($this->contentContainer->visibility == Space::VISIBILITY_NONE && Yii::$app->user->isGuest) {
}
} elseif ($userGuid !== null) {
$this->contentContainer = User::findOne(['guid' => $userGuid]);
if ($this->contentContainer == null) {
throw new HttpException(404, Yii::t('base', 'User not found!'));
}
$this->attachBehavior('ProfileControllerBehavior', [
'class' => ProfileController::className(),
'user' => $this->contentContainer,
]);
$this->subLayout = "@humhub/modules/user/views/profile/_layout";
} else {
throw new HttpException(500, Yii::t('base', 'Could not determine content container!'));
}
if (!$this->checkModuleIsEnabled()) {
throw new HttpException(405, Yii::t('base', 'Module is not enabled on this content container!'));
}
parent::init();
// Load the ContentContainer
$guid = Yii::$app->request->get('cguid', Yii::$app->request->get('sguid', Yii::$app->request->get('uguid')));
if (!empty($guid)) {
$contentContainerModel = ContentContainer::findOne(['guid' => $guid]);
if ($contentContainerModel !== null) {
$this->contentContainer = $contentContainerModel->getPolymorphicRelation();
}
}
if ($this->requireContainer && $this->contentContainer === null) {
throw new HttpException(404, Yii::t('base', 'Could not find requested page.'));
}
if ($this->validContentContainerClasses !== null) {
if ($this->contentContainer === null || !in_array($this->contentContainer->className(), $this->validContentContainerClasses)) {
throw new HttpException(400);
}
}
if ($this->contentContainer !== null && $this->contentContainer->controllerBehavior) {
$this->attachBehavior('containerControllerBehavior', ['class' => $this->contentContainer->controllerBehavior]);
}
}
/**
@ -111,7 +98,7 @@ class ContentContainerController extends Controller
*/
public function beforeAction($action)
{
if (parent::beforeAction($action) === false) {
if (!parent::beforeAction($action)) {
return false;
}
@ -121,43 +108,11 @@ class ContentContainerController extends Controller
return false;
}
// Auto check access rights to this container
if ($this->contentContainer != null && $this->autoCheckContainerAccess) {
$this->checkContainerAccess();
}
if ($this->contentContainer instanceof Space && (Yii::$app->request->isPjax || !Yii::$app->request->isAjax)) {
$options = [
'guid' => $this->contentContainer->guid,
'name' => Html::encode($this->contentContainer->name),
'archived' => $this->contentContainer->isArchived(),
'image' => Image::widget([
'space' => $this->contentContainer,
'width' => 32,
'htmlOptions' => [
'class' => 'current-space-image',
],
]),
];
$this->view->registerJs('humhub.modules.space.setSpace(' . Json::encode($options) . ', ' .
Json::encode(Yii::$app->request->isPjax) . ')');
}
$this->checkModuleIsEnabled();
return true;
}
/**
* Checks if current user can access current ContentContainer by using
* underlying behavior ProfileControllerBehavior/SpaceControllerBehavior.
* If access check failed, an CHttpException is thrown.
*/
public function checkContainerAccess()
{
// Implemented by behavior
$this->checkAccess();
}
/**
* @inheritdoc
*/
@ -167,18 +122,16 @@ class ContentContainerController extends Controller
}
/**
* Checks if current module is enabled on this content container.
*
* @todo Also support submodules
* @return boolean is current module enabled
* Checks if the requested module is available in this contentContainer.
*
* @throws HttpException if the module is not enabled
*/
public function checkModuleIsEnabled()
protected function checkModuleIsEnabled()
{
if ($this->module instanceof ContentContainerModule && $this->contentContainer !== null) {
return $this->contentContainer->isModuleEnabled($this->module->id);
if ($this->module instanceof ContentContainerModule && $this->contentContainer !== null &&
!$this->contentContainer->moduleManager->isEnabled($this->module->id)) {
throw new HttpException(405, Yii::t('base', 'Module is not enabled on this content container!'));
}
return true;
}
}

View File

@ -8,8 +8,8 @@
namespace humhub\modules\content\components;
use humhub\components\Module;
use humhub\modules\content\models\ContentContainerModuleState;
/**
* Base Module with ContentContainer support.
@ -23,6 +23,25 @@ use humhub\components\Module;
class ContentContainerModule extends Module
{
/**
* @inheritdoc
*/
public function disable()
{
// disable in content containers
$contentContainerQuery = ContentContainerModuleManager::getContentContainerQueryByModule($this->id);
foreach ($contentContainerQuery->all() as $contentContainer) {
/* @var $contentContainer \humhub\modules\content\models\ContentContainer */
$this->disableContentContainer($contentContainer->getPolymorphicRelation());
}
foreach (ContentContainerModuleState::findAll(['module_id' => $this->id]) as $moduleState) {
$moduleState->delete();
}
parent::disable();
}
/**
* Returns the list of valid content container classes this module supports.
*

View File

@ -0,0 +1,267 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\components;
use ReflectionClass;
use Yii;
use yii\db\ActiveQuery;
use humhub\modules\content\components\ContentContainerModule;
use humhub\modules\content\models\ContentContainerModuleState;
use humhub\modules\content\models\ContentContainer;
/**
* ModuleManager handles modules of a content container.
*
* @since 1.3
* @author Luke
*/
class ContentContainerModuleManager extends \yii\base\Component
{
/**
* @var \humhub\modules\content\components\ContentContainerActiveRecord
*/
public $contentContainer;
/**
* @var array the available module ids
*/
private $_available;
/**
* Disables a module for the content container
*
* @param string $id the module id
* @return boolean
*/
public function disable($id)
{
if ($this->canDisable($id)) {
Yii::$app->moduleManager->getModule($id)->disableContentContainer($this->contentContainer);
$moduleState = $this->getModuleStateRecord($id);
$moduleState->module_state = ContentContainerModuleState::STATE_DISABLED;
$moduleState->save();
return true;
}
return false;
}
/**
* Enables a module for this content container
*
* @param string $id the module id
* @return boolean
*/
public function enable($id)
{
if ($this->canEnable($id)) {
Yii::$app->moduleManager->getModule($id)->enableContentContainer($this->contentContainer);
$moduleState = $this->getModuleStateRecord($id);
$moduleState->module_state = ContentContainerModuleState::STATE_ENABLED;
$moduleState->save();
return true;
}
return false;
}
/**
* Checks whether the module is activated or not
*
* @param string $id the module id
* @return boolean
*/
public function isEnabled($id)
{
return in_array($id, $this->getEnabled());
}
/**
* Checks whether the module can be enabled or not
*
* @param string $id the module id
* @return boolean
*/
public function canEnable($id)
{
$available = $this->getAvailable();
if (!$this->isEnabled($id) && array_key_exists($id, $available)) {
return true;
}
return false;
}
/**
* Checks whether the module can be disabled or not
*
* @param string $id the module id
* @return boolean
*/
public function canDisable($id)
{
if (!$this->isEnabled($id) || self::getDefaultState($this->contentContainer->className(), $id) === ContentContainerModuleState::STATE_FORCE_ENABLED) {
return false;
}
return true;
}
/**
* Returns an array of all enabled module ids
*
* @return array a list of enabled module ids
*/
public function getEnabled()
{
$enabled = [];
$available = $this->getAvailable();
foreach ($this->getStates() as $id => $state) {
if (array_key_exists($id, $available) && ($state === ContentContainerModuleState::STATE_ENABLED || $state === ContentContainerModuleState::STATE_FORCE_ENABLED)) {
$enabled[] = $id;
}
}
return $enabled;
}
/**
* Returns an array of all available modules
*
* @return ContentContainerModule[] a list of modules
*/
public function getAvailable()
{
if ($this->_available !== null) {
return $this->_available;
}
$this->_available = [];
foreach (Yii::$app->moduleManager->getModules() as $id => $module) {
if ($module instanceof ContentContainerModule && Yii::$app->hasModule($module->id) &&
$module->hasContentContainerType($this->contentContainer->className())) {
$this->_available[$module->id] = $module;
}
}
return $this->_available;
}
/**
* Returns an array of all module states.
*
* @see Module
* @return array a list of modules with the corresponding state
*/
protected function getStates()
{
$states = [];
// Get states for this contentcontainer from database
foreach (ContentContainerModuleState::findAll(['contentcontainer_id' => $this->contentContainer->contentcontainer_id]) as $module) {
$states[$module->module_id] = $module->module_state;
}
// Get default states, when no state is stored
foreach ($this->getAvailable() as $module) {
if (!isset($states[$module->id])) {
$states[$module->id] = self::getDefaultState($this->contentContainer->className(), $module->id);
}
}
return $states;
}
/**
* Sets the default state for a module based on the contentcontainer class
*
* @param string $class the class name (e.g. Space or User)
* @param string $id the module id
* @param int $state the state
*/
public static function setDefaultState($class, $id, $state)
{
$reflect = new ReflectionClass($class);
Yii::$app->getModule($id)->settings->set('moduleManager.defaultState.' . $reflect->getShortName(), $state);
}
/**
* Returns the default module state for a given contentcontainer class
*
* @param string $class the class name (e.g. Space or User)
* @param string $id the module id
* @return int|null the default state or null when no default state is defined
*/
public static function getDefaultState($class, $id)
{
$reflect = new ReflectionClass($class);
$state = Yii::$app->getModule($id)->settings->get('moduleManager.defaultState.' . $reflect->getShortName());
if ($state === null) {
return null;
} else {
return (int) $state;
}
}
/**
* Returns an Module record instance for the given module id
*
* @see Module
* @param string $id the module id
* @return Module the Module record instance
*/
protected function getModuleStateRecord($id)
{
$moduleState = ContentContainerModuleState::findOne(['module_id' => $id, 'contentcontainer_id' => $this->contentContainer->contentcontainer_id]);
if ($moduleState === null) {
$moduleState = new ContentContainerModuleState;
$moduleState->contentcontainer_id = $this->contentContainer->contentcontainer_id;
$moduleState->module_id = $id;
}
return $moduleState;
}
/**
* Returns a query for \humhub\modules\content\models\ContentContainer where the given module is enabled.
*
* @param string $id the module mid
* @return \yii\db\ActiveQuerythe list of content container
*/
public static function getContentContainerQueryByModule($id)
{
$query = ContentContainer::find();
$query->leftJoin('contentcontainer_module', 'contentcontainer_module.contentcontainer_id=contentcontainer.id AND contentcontainer_module.module_id=:moduleId', [':moduleId' => $id]);
$query->andWhere(['contentcontainer_module.module_state' => ContentContainerModuleState::STATE_ENABLED]);
$query->orWhere(['contentcontainer_module.module_state' => ContentContainerModuleState::STATE_FORCE_ENABLED]);
$moduleSettings = Yii::$app->getModule($id)->settings;
// Add default enabled modules
$contentContainerClasses = [\humhub\modules\user\models\User::class, \humhub\modules\space\models\Space::class];
foreach ($contentContainerClasses as $class) {
$reflect = new ReflectionClass($class);
$defaultState = (int) $moduleSettings->get('moduleManager.defaultState.' . $reflect->getShortName());
if ($defaultState === ContentContainerModuleState::STATE_ENABLED || $defaultState === ContentContainerModuleState::STATE_FORCE_ENABLED) {
$query->orWhere(['contentcontainer.class' => $class]);
}
}
return $query;
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\components\behaviors;
use yii\base\Behavior;
/**
* Compatiblity layer for old ContentContainer ModuleManager calls.
*
* @see \humhub\modules\contentcontainer\components\ModuleManager
* @see \humhub\modules\content\components\ContentContainerActiveRecord
* @since 1.3
* @author luke
*/
class CompatModuleManager extends Behavior
{
/**
* @var \humhub\modules\contentcontainer\components\ModuleManager
*/
public $moduleManager;
public function attach($owner)
{
parent::attach($owner);
$this->moduleManager = $this->owner->moduleManager;
}
public function getAvailableModules()
{
return $this->moduleManager->getAvailable();
}
public function getEnabledModules()
{
return $this->moduleManager->getEnabled();
}
public function isModuleEnabled($moduleId)
{
return $this->moduleManager->isEnabled($moduleId);
}
public function enableModule($moduleId)
{
return $this->moduleManager->enable($moduleId);
}
public function canDisableModule($id)
{
return $this->moduleManager->canDisable($id);
}
public function disableModule($moduleId)
{
return $this->moduleManager->disable($moduleId);
}
}

View File

@ -17,6 +17,7 @@ return [
['class' => IntegrityController::className(), 'event' => IntegrityController::EVENT_ON_RUN, 'callback' => array(Events::className(), 'onIntegrityCheck')],
['class' => WallEntryAddons::className(), 'event' => WallEntryAddons::EVENT_INIT, 'callback' => array(Events::className(), 'onWallEntryAddonInit')],
['class' => User::className(), 'event' => User::EVENT_BEFORE_DELETE, 'callback' => [Events::className(), 'onUserDelete']],
['class' => User::className(), 'event' => User::EVENT_BEFORE_SOFT_DELETE, 'callback' => [Events::className(), 'onUserSoftDelete']],
['class' => Space::className(), 'event' => User::EVENT_BEFORE_DELETE, 'callback' => [Events::className(), 'onSpaceDelete']],
['class' => Search::className(), 'event' => Search::EVENT_ON_REBUILD, 'callback' => [Events::className(), 'onSearchRebuild']],
['class' => ContentActiveRecord::className(), 'event' => ContentActiveRecord::EVENT_AFTER_INSERT, 'callback' => [Events::className(), 'onContentActiveRecordSave']],

View File

@ -16,6 +16,12 @@ class m150927_190830_create_contentcontainer extends Migration
'owner_user_id' => Schema::TYPE_INTEGER,
'wall_id' => Schema::TYPE_INTEGER,
), '');
# 1.3 - prepare utf8_mb4 support
$this->alterColumn('contentcontainer', 'guid', 'char(36) NOT NULL');
$this->alterColumn('contentcontainer', 'class', 'char(60) NOT NULL');
$this->createIndex('unique_target', 'contentcontainer', ['class', 'pk'], true);
$this->createIndex('unique_guid', 'contentcontainer', ['guid'], true);

View File

@ -0,0 +1,33 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\models;
/**
* This is the model class for table "contentcontainer_module".
*
* @property integer $contentcontainer_id
* @property string $module_id
* @property integer $module_state
*/
class ContentContainerModuleState extends \yii\db\ActiveRecord
{
const STATE_DISABLED = 0;
const STATE_ENABLED = 1;
const STATE_FORCE_ENABLED = 2;
/**
* @inheritdoc
*/
public static function tableName()
{
return 'contentcontainer_module';
}
}

View File

@ -1,9 +1,13 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\models;
/**
* This is the model class for table "contentcontainer_permission".
*
@ -16,6 +20,7 @@ namespace humhub\modules\content\models;
*/
class ContentContainerPermission extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
@ -50,4 +55,5 @@ class ContentContainerPermission extends \yii\db\ActiveRecord
'state' => 'State',
];
}
}

View File

@ -12,6 +12,7 @@ use humhub\modules\search\engine\Search;
use humhub\modules\file\models\File;
use yii\base\Event;
use humhub\modules\search\events\SearchAttributesEvent;
use humhub\modules\file\converter\TextConverter;
/**
* Events provides callbacks to handle events.
@ -113,8 +114,16 @@ class Events extends \yii\base\Object
foreach (File::findAll(['object_model' => $event->record->className(), 'object_id' => $event->record->id]) as $file) {
/* @var $file File */
$textContent = null;
$textConverter = new TextConverter();
if ($textConverter->applyFile($file)) {
$textContent = $textConverter->getContentAsText();
}
$event->attributes['files'][$file->id] = [
'name' => $file->file_name
'name' => $file->file_name,
'content' => $textContent
];
// Add comment related attributes

View File

@ -0,0 +1,141 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\file\converter;
use Yii;
use humhub\modules\file\models\File;
use humhub\modules\file\libs\FileHelper;
/**
* Text Converter
*
* @since 1.3
* @author Luke
*/
class TextConverter extends BaseConverter
{
/**
* List of installed text conversion converters
*
* Example:
*
* ```
* [
* 'cmd' => '/usr/bin/pdftotext -q -enc UTF-8 {fileName} {outputFileName}',
* 'only' => ['pdf']
* ],
* [
* 'cmd' => '/usr/bin/java -jar /path/to/tika-app-1.16.jar --text {fileName} 2>/dev/null',
* 'except' => ['image/']
* ]
* ```
*
* @var array
*/
public $converter = [];
/**
* @inheritdoc
*/
protected function canConvert(File $file)
{
$originalFile = $file->store->get();
if (!is_file($originalFile)) {
return false;
}
if ($this->getConverter() === null) {
// No text converter found for given file
return false;
}
return true;
}
/**
* @inheritdoc
*/
protected function convert($fileName)
{
$convertedFile = $this->file->store->get($fileName);
if (is_file($convertedFile)) {
return;
}
$textContent = '';
$converter = $this->getConverter();
if ($converter !== null) {
if (Yii::$app->request->isConsoleRequest) {
print "C";
}
$command = str_replace('{fileName}', $this->file->store->get(), $converter['cmd']);
if (strpos($command, "{outputFileName}") !== false) {
$command = str_replace('{outputFileName}', $convertedFile, $command);
shell_exec($command);
} else {
$textContent = shell_exec($command) . "\n";
file_put_contents($convertedFile, $textContent);
}
}
}
/**
* Returns the first matching converter for the file
*
* @return array the converter definitions
*/
public function getConverter()
{
foreach ($this->converter as $converter) {
// Check Exceptions
if (!empty($converter['except']) && is_array($converter['except'])) {
foreach ($converter['except'] as $except) {
if (strpos($this->file->mime_type, $except) !== false || FileHelper::getExtension($this->file) == $except) {
continue 2;
}
}
}
if (!empty($converter['only']) && is_array($converter['only'])) {
foreach ($converter['only'] as $only) {
if (strpos($this->file->mime_type, $only) !== false || FileHelper::getExtension($this->file) == $only) {
return $converter;
}
}
} else {
// Valid for all file types
return $converter;
}
}
return null;
}
/**
* Returns the file content as text
*
* @return string the text output
*/
public function getContentAsText()
{
$fileName = $this->getFilename();
$convertedFile = $this->file->store->get($fileName);
if (is_file($convertedFile)) {
return file_get_contents($convertedFile);
}
return null;
}
}

View File

@ -2,9 +2,15 @@
namespace humhub\modules\file\widgets;
use humhub\modules\file\libs\FileHelper;
use humhub\widgets\JsWidget;
use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use humhub\widgets\JsWidget;
use humhub\modules\file\models\File;
use humhub\modules\file\libs\FileHelper;
use humhub\modules\search\libs\SearchHelper;
use humhub\modules\search\controllers\SearchController;
use humhub\modules\file\converter\TextConverter;
/**
*
@ -14,13 +20,19 @@ use yii\helpers\Html;
class FilePreview extends JsWidget
{
/**
* @inheritdoc
*/
public $jsWidget = "file.Preview";
public $items;
public $model;
public $hideImageFileInfo = false;
public $edit = false;
/**
* @inheritdoc
*/
public $visible = false;
public $preventPopover = false;
public $popoverPosition = 'right';
@ -58,12 +70,12 @@ class FilePreview extends JsWidget
protected function getFileData()
{
$files = $this->getFiles();
$result = [];
foreach ($files as $file) {
if($file) {
$result[] = FileHelper::getFileInfos($file);
if ($file) {
$result[] = ArrayHelper::merge(FileHelper::getFileInfos($file), ['highlight' => $this->isHighlighed($file)]);
}
}
@ -76,14 +88,36 @@ class FilePreview extends JsWidget
return [];
}
if($this->items) {
if ($this->items) {
return $this->items;
}
if($this->showInStream === null) {
if ($this->showInStream === null) {
return $this->model->fileManager->findAll();
} else {
return $this->model->fileManager->findStreamFiles($this->showInStream);
}
}
/**
* Checks whether the file should be highlighed in the results or not.
*
* @param File $file
* @return boolean is highlighed
*/
protected function isHighlighed(File $file)
{
if (Yii::$app->controller instanceof SearchController) {
if (SearchController::$keyword !== null) {
$converter = new TextConverter();
if ($converter->applyFile($file) &&
SearchHelper::matchQuery(SearchController::$keyword, $converter->getContentAsText())) {
return true;
}
}
}
return false;
}
}

View File

@ -38,7 +38,7 @@ class Events extends \yii\base\Object
*/
public static function onMemberEvent(MemberEvent $event)
{
Yii::$app->cache->delete(Module::$legitimateCachePrefix . $event->user->id);
Yii::$app->getModule('live')->refreshLegitimateContentContainerIds($event->user);
}
/**
@ -47,8 +47,8 @@ class Events extends \yii\base\Object
*/
public static function onFriendshipEvent(FriendshipEvent $event)
{
Yii::$app->cache->delete(Module::$legitimateCachePrefix . $event->user1->id);
Yii::$app->cache->delete(Module::$legitimateCachePrefix . $event->user2->id);
Yii::$app->getModule('live')->refreshLegitimateContentContainerIds($event->user1);
Yii::$app->getModule('live')->refreshLegitimateContentContainerIds($event->user2);
}
/**
@ -58,7 +58,7 @@ class Events extends \yii\base\Object
public static function onFollowEvent(FollowEvent $event)
{
if ($event->target instanceof ContentContainerActiveRecord) {
Yii::$app->cache->delete(Module::$legitimateCachePrefix . $event->user->id);
Yii::$app->getModule('live')->refreshLegitimateContentContainerIds($event->user);
}
}

View File

@ -22,36 +22,11 @@ use humhub\modules\friendship\models\Friendship;
class Module extends \humhub\components\Module
{
/**
* Defines the minimum polling interval in seconds if the default polling client is active.
*/
public $minPollInterval = 15;
/**
* Defines the maximum polling interval in seconds if the default polling client is active.
*/
public $maxPollInterval = 45;
/**
* Factor used in the actual interval calculation in case of user idle.
*/
public $idleFactor = 0.1;
/**
* Interval for updating the update delay in case of user idle in seconds.
*/
public $idleInterval = 20;
/**
* @inheritdoc
*/
public $isCoreModule = true;
/**
* @var int seconds to delete old live events
*/
public $maxLiveEventAge = 600;
/**
* @var string cache prefix for legitimate content container ids by user
*/
@ -81,6 +56,12 @@ class Module extends \humhub\components\Module
Content::VISIBILITY_OWNER => [],
];
// When no content container record exists (yet)
// This may happen during the registration process
if ($user->contentContainerRecord === null) {
return $legitimation;
}
// Add users own content container (user == contentcontainer)
$legitimation[Content::VISIBILITY_OWNER][] = $user->contentContainerRecord->id;
@ -103,9 +84,16 @@ class Module extends \humhub\components\Module
}
Yii::$app->cache->set(self::$legitimateCachePrefix . $user->id, $legitimation);
Yii::$app->live->driver->onContentContainerLegitimationChanged($user, $legitimation);
};
return $legitimation;
}
public function refreshLegitimateContentContainerIds(User $user)
{
Yii::$app->cache->delete(self::$legitimateCachePrefix . $user->id);
$this->getLegitimateContentContainerIds($user);
}
}

View File

@ -16,6 +16,11 @@ class LiveAsset extends AssetBundle
public $css = [];
public $js = [
'js/humhub.live.js',
'js/humhub.live.poll.js'
'js/humhub.live.poll.js',
'js/humhub.live.push.js',
];
public $publishOptions = [
'forceCopy' => false,
];
}

View File

@ -54,7 +54,7 @@ class PollController extends Controller
}
if (parent::beforeAction($action)) {
if (!Yii::$app->live->driver instanceof \humhub\modules\live\driver\Database) {
if (!Yii::$app->live->driver instanceof \humhub\modules\live\driver\Poll) {
throw new Exception('Polling is only available when using the live database driver!');
}

View File

@ -10,6 +10,7 @@ namespace humhub\modules\live\driver;
use yii\base\Object;
use humhub\modules\live\components\LiveEvent;
use humhub\modules\user\models\User;
/**
* Base driver for live event storage and distribution
@ -27,4 +28,26 @@ abstract class BaseDriver extends Object
* @return boolean indicates the sent was successful
*/
abstract public function send(LiveEvent $liveEvent);
/**
* Returns the JavaScript Configuration for this driver
*
* @since 1.3
* @see \humhub\widgets\CoreJsConfig
* @return array the JS Configuratoin
*/
abstract public function getJsConfig();
/**
* This callback will be executed whenever the access rules for a
* contentcontainer is changed. e.g. user joined a new space as member.
*
* @since 1.3
* @see \humhub\modules\live\Module::getLegitimateContentContainerIds()
*/
public function onContentContainerLegitimationChanged(User $user, $legitimation = [])
{
}
}

View File

@ -1,38 +0,0 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\live\driver;
use humhub\modules\live\driver\BaseDriver;
use humhub\modules\live\components\LiveEvent;
use humhub\modules\live\models\Live;
/**
* Database driver for live events
*
* @since 1.2
* @author Luke
*/
class Database extends BaseDriver
{
/**
* @inheritdoc
*/
public function send(LiveEvent $liveEvent)
{
$model = new Live();
$model->serialized_data = serialize($liveEvent);
$model->created_at = time();
$model->visibility = $liveEvent->visibility;
$model->contentcontainer_id = $liveEvent->contentContainerId;
$model->created_at = time();
return $model->save();
}
}

View File

@ -0,0 +1,84 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\live\driver;
use Yii;
use yii\helpers\Json;
use yii\helpers\Url;
use humhub\modules\live\driver\BaseDriver;
use humhub\modules\live\components\LiveEvent;
use humhub\modules\live\models\Live;
/**
* Database driver for live events
*
* @since 1.2
* @author Luke
*/
class Poll extends BaseDriver
{
/**
* Defines the minimum polling interval in seconds if the default polling client is active.
*/
public $minPollInterval = 15;
/**
* Defines the maximum polling interval in seconds if the default polling client is active.
*/
public $maxPollInterval = 45;
/**
* Factor used in the actual interval calculation in case of user idle.
*/
public $idleFactor = 0.1;
/**
* Interval for updating the update delay in case of user idle in seconds.
*/
public $idleInterval = 20;
/**
* @var int seconds to delete old live events
*/
public $maxLiveEventAge = 600;
/**
* @inheritdoc
*/
public function send(LiveEvent $liveEvent)
{
$model = new Live();
$model->serialized_data = serialize($liveEvent);
$model->created_at = time();
$model->visibility = $liveEvent->visibility;
$model->contentcontainer_id = $liveEvent->contentContainerId;
$model->created_at = time();
return $model->save();
}
/**
* @inheritdoc
*/
public function getJsConfig()
{
return [
'type' => 'humhub.modules.live.poll.PollClient',
'options' => [
'url' => Url::to(['/live/poll']),
'initTime' => time(),
'minInterval' => $this->minPollInterval, // Minimal polling request interval in seconds.
'maxInterval' => $this->maxPollInterval, // Maximal polling request interval in seconds.
'idleFactor' => $this->idleFactor, // Factor used in the actual interval calculation in case of user idle.
'idleInterval' => $this->idleInterval // Interval for updating the update delay in case of user idle in seconds.
]
];
}
}

View File

@ -0,0 +1,124 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\live\driver;
use Firebase\JWT\JWT;
use Yii;
use yii\helpers\Json;
use yii\helpers\Url;
use yii\base\InvalidConfigException;
use yii\redis\Connection;
use yii\di\Instance;
use humhub\modules\user\models\User;
use humhub\modules\live\driver\BaseDriver;
use humhub\modules\live\components\LiveEvent;
use humhub\modules\live\live\LegitimationChanged;
/**
* Database driver for live events
*
* @since 1.3
* @author Luke
*/
class Push extends BaseDriver
{
/**
* @var string the used Redis push channel
*/
public $pushChannel = 'push';
/**
* @var string the URL to the push service
*/
public $pushServiceUrl = '';
/**
* @var string the JWT secret key
*/
public $jwtKey = '';
/**
* @var Connection|string|array the Redis [[Connection]] object or the application component ID of the Redis [[Connection]].
* This can also be an array that is used to create a redis [[Connection]] instance in case you do not want do configure
* redis connection as an application component.
* After the Cache object is created, if you want to change this property, you should only assign it
* with a Redis [[Connection]] object.
*/
public $redis = 'redis';
/**
* Initializes the live push component.
* This method will initialize the [[redis]] property to make sure it refers to a valid redis connection.
*
* @throws \yii\base\InvalidConfigException if [[redis]] is invalid.
*/
public function init()
{
parent::init();
$this->redis = Instance::ensure($this->redis, Connection::className());
if (empty($this->jwtKey)) {
throw new InvalidConfigException('Push driver JWT key is not specified.');
}
}
/**
* @inheritdoc
*/
public function send(LiveEvent $liveEvent)
{
$this->redis->publish($this->pushChannel, Json::encode($liveEvent->getData()));
}
/**
* @inheritdoc
*/
public function getJsConfig()
{
return [
'type' => 'humhub.modules.live.push.PushClient',
'options' => [
'url' => $this->pushServiceUrl,
'jwt' => $this->generateJwtAuthorization(),
]
];
}
/**
* Generates an JWT authorization of the current user including
* the contentContainer id legitmation.
*
* @return string the JWT string
*/
public function generateJwtAuthorization()
{
if (Yii::$app->user->isGuest) {
return '';
}
$user = Yii::$app->user->getIdentity();
$token = [
'iss' => Url::to(['/'], true),
'sub' => Yii::$app->user->id,
'legitmation' => Yii::$app->getModule('live')->getLegitimateContentContainerIds($user)
];
return JWT::encode($token, $this->jwtKey);
}
/**
* @inheritdoc
*/
public function onContentContainerLegitimationChanged(User $user, $legitimation = [])
{
$this->send(new LegitimationChanged(['contentContainerId' => $user->contentcontainer_id, 'userId' => $user->id, 'legitimation' => $legitimation]));
}
}

View File

@ -10,7 +10,8 @@ namespace humhub\modules\live\jobs;
use Yii;
use humhub\modules\live\models\Live;
use humhub\components\queue\ActiveJob;
use humhub\modules\queue\ActiveJob;
use humhub\modules\live\driver\Poll;
/**
* DatabaseCleanup removes old live events
@ -26,7 +27,9 @@ class DatabaseCleanup extends ActiveJob
*/
public function run()
{
Live::deleteAll('created_at +' . Yii::$app->getModule('live')->maxLiveEventAge . ' < ' . time());
if (Yii::$app->live->driver instanceof Poll) {
Live::deleteAll('created_at +' . Yii::$app->live->maxLiveEventAge . ' < ' . time());
}
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\live\live;
use humhub\modules\live\components\LiveEvent;
use humhub\modules\content\models\Content;
/**
* Live event for push driver when contentContainerId legitimation was changed
*
* @since 1.3
*/
class LegitimationChanged extends LiveEvent
{
/**
* @var array the legitimation array
*/
public $legitimation;
/**
* @var int the user id
*/
public $userId;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
$this->visibility = Content::VISIBILITY_OWNER;
}
}

View File

@ -0,0 +1,42 @@
humhub.module('live.push', function (module, require, $) {
var client = require('client');
var event = require('event');
var object = require('util').object;
var PushClient = function (options) {
if (!options) {
module.log.error('Could not initialize PushClient. No options given!');
return;
}
this.options = options;
this.init();
};
PushClient.prototype.init = function () {
if (!this.options.url) {
module.log.error('Could not initialize PushClient. No url option given!');
return;
}
var that = this;
var socket = io.connect(this.options.url);
socket.on('connect', function () {
socket.emit('authenticate', {token: that.options.jwt});
});
socket.on('error', function (err) {
module.log.error(err);
});
socket.on('message', function (data) {
message = JSON.parse(data);
event.trigger(message.type.replace(/\./g, ':'), [[message]]);
});
};
var _handleUpdateError = function (e) {
module.log.error(e);
};
module.export({
PushClient: PushClient
});
});

View File

@ -8,7 +8,7 @@
namespace humhub\modules\notification\jobs;
use Yii;
use humhub\components\queue\ActiveJob;
use humhub\modules\queue\ActiveJob;
/**
* Description of SendNotification
@ -22,17 +22,17 @@ class SendBulkNotification extends ActiveJob
* @var array Basenotification data as array.
*/
public $notification;
/**
* @var integer[] Recepient userids.
*/
public $recepients;
/**
* @inheritdoc
*/
public function run()
{
Yii::$app->notification->sendBulk($this->notification, $this->recepients);
{
Yii::$app->notification->sendBulk($this->notification, $this->recepients);
}
}

View File

@ -9,7 +9,7 @@
namespace humhub\modules\notification\jobs;
use Yii;
use humhub\components\queue\ActiveJob;
use humhub\modules\queue\ActiveJob;
/**
* Description of SendNotification
@ -23,17 +23,17 @@ class SendNotification extends ActiveJob
* @var humhub\modules\notification\components\BaseNotification notification instance
*/
public $notification;
/**
* @var \humhub\modules\user\models\User Recepient user id.
*/
public $recepient;
/**
* @inheritdoc
*/
public function run()
{
Yii::$app->notification->send($this->notification, $this->recepient);
Yii::$app->notification->send($this->notification, $this->recepient);
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue;
use yii\base\Object;
use humhub\modules\queue\interfaces\JobInterface;
/**
* ActiveJob
*
* @since 1.3
* @author Luke
*/
abstract class ActiveJob extends Object implements JobInterface
{
/**
* Runs this job
*/
abstract public function run();
/**
* @inheritdoc
*/
public function execute($queue)
{
return $this->run();
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue;
use Yii;
use yii\base\Object;
use yii\base\Event;
use yii\queue\ErrorEvent;
use yii\queue\PushEvent;
use humhub\modules\queue\jobs\CleanupExclusiveJobs;
use humhub\modules\queue\interfaces\ExclusiveJobInterface;
use humhub\modules\queue\helpers\QueueHelper;
/**
* Events provides callbacks to handle events.
*
* @since 1.3
* @author luke
*/
class Events extends Object
{
/**
* Cron call back
*
* @param Event $event
*/
public static function onCronRun(Event $event)
{
//Yii::$app->queue->push(new CleanupExclusiveJobs());
}
/**
* Callback for errors while queue execution
*
* @param ErrorEvent $event
*/
public static function onQueueError(ErrorEvent $event)
{
/* @var $exception \Expection */
$exception = $event->error;
Yii::error('Could not execute queued job! Message: ' . $exception->getMessage() . ' Trace:' . $exception->getTraceAsString(), 'queue');
}
/**
* Callback before new jobs in the queue.
* Handles exclusive jobs.
*
* @param PushEvent $event
*/
public static function onQueueBeforePush(PushEvent $event)
{
if ($event->job instanceof ExclusiveJobInterface) {
// Do not add exclusive jobs if already exists in queue
if (QueueHelper::isQueued($event->job)) {
$event->handled = true;
}
}
}
/**
* Callback after new jobs in the queue.
* Handles exclusive jobs.
*
* @param PushEvent $event
*/
public static function onQueueAfterPush(PushEvent $event)
{
if ($event->job instanceof ExclusiveJobInterface) {
QueueHelper::markAsQueued($event->id, $event->job);
}
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue;
use humhub\components\Module as BaseModule;
/**
* Queue base module
*
* @author Lucas Bartholemy <lucas@bartholemy.com>
* @since 1.3
*/
class Module extends BaseModule
{
}

View File

@ -0,0 +1,23 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
use humhub\commands\CronController;
use humhub\modules\queue\Events;
use humhub\modules\queue\Module;
use yii\queue\Queue;
return [
'id' => 'queue',
'class' => Module::class,
'isCoreModule' => true,
'events' => [
[CronController::class, CronController::EVENT_ON_DAILY_RUN, [Events::class, 'onCronRun']],
[Queue::class, Queue::EVENT_AFTER_ERROR, [Events::class, 'onQueueError']],
[Queue::class, Queue::EVENT_BEFORE_PUSH, [Events::class, 'onQueueBeforePush']],
[Queue::class, Queue::EVENT_AFTER_PUSH, [Events::class, 'onQueueAfterPush']]
],
];

View File

@ -0,0 +1,44 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue\driver;
use yii\queue\Queue;
/**
* Instant queue driver, mainly used for testing purposes
*
* @since 1.2
* @author buddha
*/
class Instant extends Queue
{
/**
* @var int the message counter
*/
protected $messageId = 1;
/**
* @inheritdoc
*/
protected function pushMessage($message, $ttr, $delay, $priority)
{
$this->handleMessage($this->messageId, $message, $ttr);
$this->messageId++;
}
/**
* @inheritdoc
*/
protected function status($id)
{
return Queue::STATUS_DONE;
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue\driver;
use yii\queue\db\Queue;
/**
* MySQL queue driver
*
* @since 1.2
* @author Luke
*/
class MySQL extends Queue
{
/**
* @inheritdoc
*/
public $mutex = 'yii\mutex\MysqlMutex';
}

View File

@ -0,0 +1,22 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue\driver;
use yii\queue\redis\Queue;
/**
* Redis queue driver
*
* @since 1.2
* @author Luke
*/
class Redis extends Queue
{
}

View File

@ -0,0 +1,27 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue\driver;
use yii\queue\sync\Queue;
/**
* Sync queue driver
*
* @since 1.2
* @author Luke
*/
class Sync extends Queue
{
/**
* @inheritdoc
*/
public $handle = true;
}

View File

@ -0,0 +1,62 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue\helpers;
use Yii;
use yii\base\Object;
use yii\base\InvalidParamException;
use yii\queue\Queue;
use humhub\modules\queue\interfaces\ExclusiveJobInterface;
use humhub\modules\queue\models\QueueExclusive;
/**
* Queue Helpers
*
* @author Luke
*/
class QueueHelper extends Object
{
public static function isQueued(ExclusiveJobInterface $job)
{
$queueExclusive = QueueExclusive::findOne(['id' => $job->getExclusiveJobId()]);
if ($queueExclusive === null || $queueExclusive->job_status == Queue::STATUS_DONE) {
return false;
}
$jobInQueue = true;
try {
if (Yii::$app->queue->isDone($queueExclusive->job_message_id)) {
$jobInQueue = false;
}
} catch (InvalidParamException $ex) {
// not exists
$jobInQueue = false;
}
if (!$jobInQueue) {
$queueExclusive->delete();
return false;
}
return true;
}
public static function markAsQueued($jobQueueId, ExclusiveJobInterface $job)
{
$queueExclusive = QueueExclusive::findOne(['id' => $job->getExclusiveJobId()]);
if ($queueExclusive === null) {
$queueExclusive = new QueueExclusive();
$queueExclusive->id = $job->getExclusiveJobId();
}
$queueExclusive->job_message_id = $jobQueueId;
$queueExclusive->save();
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue\interfaces;
/**
* ExclusiveJobInterface can be added to an ActiveJob to ensure this task is only
* queued once. As example this is useful for asynchronous jobs like search index rebuild.
*
* @see \humhub\modules\queue\ActiveJob
* @author Luke
*/
interface ExclusiveJobInterface
{
public function getExclusiveJobId();
}

View File

@ -0,0 +1,22 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\queue\interfaces;
use yii\queue\Job;
/**
* @inheritdoc
* @see Job
* @since 1.3
* @author Luke
*/
interface JobInterface extends Job
{
}

Some files were not shown because too many files have changed in this diff Show More