mirror of
https://github.com/humhub/humhub.git
synced 2025-01-17 22:28:51 +01:00
Merge branch 'develop' of github.com:humhub/humhub into develop
This commit is contained in:
commit
17cd8bbff0
4
.github/workflows/php-test.yml
vendored
4
.github/workflows/php-test.yml
vendored
@ -5,10 +5,14 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
@ -10,9 +10,17 @@ HumHub Changelog (DEVELOP)
|
||||
- Fix #6018: Disable profile fields textarea and checkbox list when they are not editable
|
||||
- Ehn #6017: Hide Password Tab in administration for LDAP users
|
||||
- Enh #6031: On users list grid, show Auth mode badge only for sys admins
|
||||
- Enh #6033: Moved more logic into `AbstractQueryContentContainer`
|
||||
- Enh #6035: Added Estonian language
|
||||
- Fix #5956: Display all newer comments after current comment
|
||||
- Enh #6061: Administration: Add a confirmation on profile field delete button
|
||||
- Enh #5699: Allow users to invite by link
|
||||
- Enh #6081: Added corresponding CSS variables for LESS color variables
|
||||
- Fix #6022: Fix Changelog Link with new Marketplace URL
|
||||
- Enh #5973: Stylesheet Prefix Cleanup and removed temporary style
|
||||
- Enh #6077: Always display content tabs
|
||||
- Enh #5263: Allow members of groups other than system admin to view all content (groups that can manage users for profile content and groups that can manage spaces for space content)
|
||||
- Enh #6102: Also allow Messages module to inject new message count into page title
|
||||
- Enh #6109: Added enabled Pretty URL as self test
|
||||
- Enh #6119: Added UserInfoWidget for User Notification Settings
|
||||
- Enh #6116: Scheduled content publishing
|
||||
|
@ -4,7 +4,12 @@ HumHub Changelog
|
||||
1.13.2 (Unreleased)
|
||||
-------------------------
|
||||
- Fix #5965: Suppress log warning 'Invalid session auth key attempted for user'
|
||||
|
||||
- Fix #6084: Automatic LDAP user registration broken when not all req. attributes provided
|
||||
- Fix #6104: Fix update user with not existing group
|
||||
- Fix #6103: Fix null passing to parse_str()
|
||||
- Fix #6108: Fix log time in the `date()` function
|
||||
- Fix #6122: Fix deleting a content with empty reason
|
||||
- Fix #6128: Reset backuped content after submit form
|
||||
|
||||
1.13.1 (January 25, 2023)
|
||||
-------------------------
|
||||
|
@ -73,7 +73,7 @@
|
||||
"web-token/jwt-signature-algorithm-hmac": ">=1.0 <3.0",
|
||||
"web-token/jwt-signature-algorithm-rsa": ">=1.0 <3.0",
|
||||
"xj/yii2-jplayer-widget": "*",
|
||||
"yiisoft/yii2": "2.0.47",
|
||||
"yiisoft/yii2": "dev-master#e2b40a2",
|
||||
"yiisoft/yii2-authclient": "~2.2.0",
|
||||
"yiisoft/yii2-bootstrap": "~2.0.0",
|
||||
"yiisoft/yii2-httpclient": "~2.0.0",
|
||||
|
67
composer.lock
generated
67
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9b5f7b0d900013a6baefdd0afd4fd998",
|
||||
"content-hash": "74fc5c69dbbd413e42d9b63e83706576",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@ -4241,16 +4241,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpspreadsheet",
|
||||
"version": "1.27.0",
|
||||
"version": "1.27.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||
"reference": "eeb8582f9cabf5a7f4ef78015691163233a1834f"
|
||||
"reference": "ef4e6ef74990239946d3983451a9bbed5ef1be5d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/eeb8582f9cabf5a7f4ef78015691163233a1834f",
|
||||
"reference": "eeb8582f9cabf5a7f4ef78015691163233a1834f",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/ef4e6ef74990239946d3983451a9bbed5ef1be5d",
|
||||
"reference": "ef4e6ef74990239946d3983451a9bbed5ef1be5d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4277,7 +4277,7 @@
|
||||
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||
"dompdf/dompdf": "^1.0 || ^2.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"mitoteam/jpgraph": "^10.2.4",
|
||||
@ -4340,9 +4340,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.27.0"
|
||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.27.1"
|
||||
},
|
||||
"time": "2023-01-24T20:07:45+00:00"
|
||||
"time": "2023-02-08T07:02:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@ -7956,16 +7956,16 @@
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.5.0",
|
||||
"version": "v3.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "3ffcf4b7d890770466da3b2666f82ac054e7ec72"
|
||||
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/3ffcf4b7d890770466da3b2666f82ac054e7ec72",
|
||||
"reference": "3ffcf4b7d890770466da3b2666f82ac054e7ec72",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15",
|
||||
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -8016,7 +8016,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.5.0"
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.5.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -8028,7 +8028,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-12-27T12:28:18+00:00"
|
||||
"time": "2023-02-08T07:49:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "voku/portable-ascii",
|
||||
@ -8624,16 +8624,16 @@
|
||||
},
|
||||
{
|
||||
"name": "yiisoft/yii2",
|
||||
"version": "2.0.47",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yiisoft/yii2-framework.git",
|
||||
"reference": "8ecf57895d9c4b29cf9658ffe57af5f3d0e25254"
|
||||
"reference": "e2b40a2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/8ecf57895d9c4b29cf9658ffe57af5f3d0e25254",
|
||||
"reference": "8ecf57895d9c4b29cf9658ffe57af5f3d0e25254",
|
||||
"url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/e2b40a2",
|
||||
"reference": "e2b40a2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -8650,6 +8650,7 @@
|
||||
"php": ">=5.4.0",
|
||||
"yiisoft/yii2-composer": "~2.0.4"
|
||||
},
|
||||
"default-branch": true,
|
||||
"bin": [
|
||||
"yii"
|
||||
],
|
||||
@ -8742,7 +8743,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-11-18T16:21:58+00:00"
|
||||
"time": "2023-02-11T16:21:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "yiisoft/yii2-authclient",
|
||||
@ -10387,37 +10388,38 @@
|
||||
},
|
||||
{
|
||||
"name": "php-webdriver/webdriver",
|
||||
"version": "1.13.1",
|
||||
"version": "1.14.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-webdriver/php-webdriver.git",
|
||||
"reference": "6dfe5f814b796c1b5748850aa19f781b9274c36c"
|
||||
"reference": "3ea4f924afb43056bf9c630509e657d951608563"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/6dfe5f814b796c1b5748850aa19f781b9274c36c",
|
||||
"reference": "6dfe5f814b796c1b5748850aa19f781b9274c36c",
|
||||
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3ea4f924afb43056bf9c630509e657d951608563",
|
||||
"reference": "3ea4f924afb43056bf9c630509e657d951608563",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"php": "^5.6 || ~7.0 || ^8.0",
|
||||
"php": "^7.3 || ^8.0",
|
||||
"symfony/polyfill-mbstring": "^1.12",
|
||||
"symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0"
|
||||
"symfony/process": "^5.0 || ^6.0"
|
||||
},
|
||||
"replace": {
|
||||
"facebook/webdriver": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"ondram/ci-detector": "^2.1 || ^3.5 || ^4.0",
|
||||
"ergebnis/composer-normalize": "^2.20.0",
|
||||
"ondram/ci-detector": "^4.0",
|
||||
"php-coveralls/php-coveralls": "^2.4",
|
||||
"php-mock/php-mock-phpunit": "^1.1 || ^2.0",
|
||||
"php-mock/php-mock-phpunit": "^2.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0"
|
||||
"symfony/var-dumper": "^5.0 || ^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-SimpleXML": "For Firefox profile creation"
|
||||
@ -10446,9 +10448,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
|
||||
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.13.1"
|
||||
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.14.0"
|
||||
},
|
||||
"time": "2022-10-11T11:49:44+00:00"
|
||||
"time": "2023-02-09T12:12:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpcompatibility/php-compatibility",
|
||||
@ -11585,7 +11587,8 @@
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"npm-asset/select2-bootstrap-theme": 10
|
||||
"npm-asset/select2-bootstrap-theme": 10,
|
||||
"yiisoft/yii2": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
|
@ -2,7 +2,7 @@
|
||||
/**
|
||||
* This file is generated by the "yii asset" command.
|
||||
* DO NOT MODIFY THIS FILE DIRECTLY.
|
||||
* @version 2022-11-29 14:04:39
|
||||
* @version 2023-02-27 09:01:41
|
||||
*/
|
||||
return [
|
||||
'app' => [
|
||||
|
@ -11,6 +11,7 @@ namespace humhub\libs;
|
||||
use Yii;
|
||||
use yii\base\Component;
|
||||
use yii\base\Exception;
|
||||
use yii\base\InvalidArgumentException;
|
||||
use yii\db\conditions\LikeCondition;
|
||||
use yii\db\StaleObjectException;
|
||||
use yii\helpers\Json;
|
||||
@ -112,7 +113,11 @@ abstract class BaseSettingsManager extends Component
|
||||
{
|
||||
$value = $this->get($name, $default);
|
||||
if (is_string($value)) {
|
||||
$value = Json::decode($value);
|
||||
try {
|
||||
$value = Json::decode($value);
|
||||
} catch (InvalidArgumentException $ex) {
|
||||
Yii::error($ex->getMessage());
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
namespace humhub\libs;
|
||||
|
||||
use humhub\models\Setting;
|
||||
use humhub\modules\admin\libs\HumHubAPI;
|
||||
use humhub\modules\ldap\helpers\LdapHelper;
|
||||
use humhub\modules\marketplace\Module;
|
||||
@ -434,13 +435,29 @@ class SelfTest
|
||||
];
|
||||
}
|
||||
|
||||
if (Setting::isInstalled()) {
|
||||
$title = Yii::t('AdminModule.information', 'Settings') . ' - ' . Yii::t('AdminModule.information', 'Pretty URLs');
|
||||
if (Yii::$app->urlManager->enablePrettyUrl) {
|
||||
$checks[] = [
|
||||
'title' => $title,
|
||||
'state' => 'OK'
|
||||
];
|
||||
} else {
|
||||
$checks[] = [
|
||||
'title' => $title,
|
||||
'state' => 'WARNING',
|
||||
'hint' => Html::a(Yii::t('AdminModule.information', 'HumHub Documentation'), 'https://docs.humhub.org/docs/admin/installation#pretty-urls'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$title = Yii::t('AdminModule.information', 'Settings') . ' - ' . Yii::t('AdminModule.information', 'Base URL');
|
||||
$sslPort = 443;
|
||||
$httpPort = 80;
|
||||
$scheme = $_SERVER['REQUEST_SCHEME'] ?? (
|
||||
isset($_SERVER['HTTPS'])
|
||||
? ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] === 1 || $_SERVER['SERVER_PORT'] == $sslPort ? 'https' : 'http')
|
||||
: ($_SERVER['SERVER_PORT'] == $sslPort ? 'https' : 'http'));
|
||||
isset($_SERVER['HTTPS'])
|
||||
? ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] === 1 || $_SERVER['SERVER_PORT'] == $sslPort ? 'https' : 'http')
|
||||
: ($_SERVER['SERVER_PORT'] == $sslPort ? 'https' : 'http'));
|
||||
$currentBaseUrl = $scheme . '://' . $_SERVER['HTTP_HOST']
|
||||
. (($scheme === 'https' && $_SERVER['SERVER_PORT'] == $sslPort) ||
|
||||
($scheme === 'http' && $_SERVER['SERVER_PORT'] == $httpPort) ? '' : ':' . $_SERVER['SERVER_PORT'])
|
||||
@ -610,7 +627,7 @@ class SelfTest
|
||||
];
|
||||
} else {
|
||||
$allowedDriverTitles = [];
|
||||
foreach(self::getSupportedDatabaseDrivers() as $allowedDriver) {
|
||||
foreach (self::getSupportedDatabaseDrivers() as $allowedDriver) {
|
||||
$allowedDriverTitles[] = $allowedDriver['title'];
|
||||
}
|
||||
$checks[] = [
|
||||
@ -745,7 +762,7 @@ class SelfTest
|
||||
$supportedDrivers = self::getSupportedDatabaseDrivers();
|
||||
|
||||
// Firstly parse driver name from version:
|
||||
if (preg_match('/(' . implode('|', array_keys($supportedDrivers)). ')/i', $driver['version'], $verMatch)) {
|
||||
if (preg_match('/(' . implode('|', array_keys($supportedDrivers)) . ')/i', $driver['version'], $verMatch)) {
|
||||
$driver['name'] = strtolower($verMatch[1]);
|
||||
} else {
|
||||
$driver['name'] = Yii::$app->getDb()->getDriverName();
|
||||
|
@ -8,7 +8,9 @@
|
||||
|
||||
namespace humhub\modules\activity;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\activity\components\MailSummary;
|
||||
use humhub\modules\activity\helpers\ActivityHelper;
|
||||
use humhub\modules\activity\jobs\SendMailSummary;
|
||||
use humhub\modules\activity\models\Activity;
|
||||
use humhub\modules\admin\permissions\ManageSettings;
|
||||
@ -19,7 +21,7 @@ use Yii;
|
||||
use yii\base\ActionEvent;
|
||||
use yii\base\BaseObject;
|
||||
use yii\base\Event;
|
||||
use yii\db\ActiveRecord;
|
||||
use yii\base\InvalidArgumentException;
|
||||
use yii\db\IntegrityException;
|
||||
|
||||
/**
|
||||
@ -66,23 +68,10 @@ class Events extends BaseObject
|
||||
public static function onActiveRecordDelete(Event $event)
|
||||
{
|
||||
if (!($event->sender instanceof ActiveRecord)) {
|
||||
throw new \LogicException('The handler can be applied only to the \yii\db\ActiveRecord.');
|
||||
throw new InvalidArgumentException('The handler can be applied only to the \humhub\components\ActiveRecord.');
|
||||
}
|
||||
|
||||
/** @var \yii\db\ActiveRecord $activeRecordModel */
|
||||
$activeRecordModel = $event->sender;
|
||||
$pk = $activeRecordModel->getPrimaryKey();
|
||||
|
||||
// Check if primary key exists and is not array (multiple pk)
|
||||
if ($pk !== null && !is_array($pk)) {
|
||||
$modelsActivity = Activity::find()->where([
|
||||
'object_id' => $pk,
|
||||
'object_model' => get_class($activeRecordModel)
|
||||
])->each();
|
||||
foreach ($modelsActivity as $activity) {
|
||||
$activity->delete();
|
||||
}
|
||||
}
|
||||
ActivityHelper::deleteActivitiesForRecord($event->sender);
|
||||
}
|
||||
|
||||
public static function onAccountMenuInit($event)
|
||||
|
32
protected/humhub/modules/activity/helpers/ActivityHelper.php
Normal file
32
protected/humhub/modules/activity/helpers/ActivityHelper.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\activity\helpers;
|
||||
|
||||
use humhub\components\ActiveRecord;
|
||||
use humhub\modules\activity\models\Activity;
|
||||
use Yii;
|
||||
|
||||
class ActivityHelper
|
||||
{
|
||||
|
||||
public static function deleteActivitiesForRecord(ActiveRecord $record)
|
||||
{
|
||||
$pk = $record->getPrimaryKey();
|
||||
|
||||
// Check if primary key exists and is not array (multiple pk)
|
||||
if ($pk !== null && !is_array($pk)) {
|
||||
|
||||
$modelsActivity = Activity::find()->where([
|
||||
'object_id' => $pk,
|
||||
'object_model' => get_class($record)
|
||||
])->each();
|
||||
|
||||
foreach ($modelsActivity as $activity) {
|
||||
$activity->delete();
|
||||
}
|
||||
|
||||
Yii::debug('Deleted activities for ' . get_class($record) . " with PK " . $pk, 'activity');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,6 @@ use yii\base\Exception;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\db\ActiveRecord;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use humhub\modules\activity\components\ActivityWebRenderer;
|
||||
use humhub\components\behaviors\PolymorphicRelation;
|
||||
use yii\db\IntegrityException;
|
||||
use humhub\modules\activity\widgets\Activity as ActivityStreamEntryWidget;
|
||||
@ -124,4 +123,13 @@ class Activity extends ContentActiveRecord
|
||||
{
|
||||
return $this->getPolymorphicRelation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|int
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
// Always hard delete activities
|
||||
return $this->hardDelete();
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ use yii\helpers\Html;
|
||||
<span
|
||||
style="text-decoration: none; color: <?= $this->theme->variable('primary') ?>;"> - <a
|
||||
href="<?= $url ?>"
|
||||
style="text-decoration: none; color: <?= $this->theme->variable('primary') ?>; "><?= Yii::t('ActivityModule.base', 'see online') ?></a></span>
|
||||
style="text-decoration: none; color: <?= $this->theme->variable('primary') ?>; font-weight: bold;"><?= Yii::t('ActivityModule.base', 'see online') ?></a></span>
|
||||
<!-- END: CONTENT LINK -->
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
@ -59,7 +59,8 @@ class PendingRegistrationsController extends Controller
|
||||
'searchModel' => $searchModel,
|
||||
'types' => [
|
||||
null => null,
|
||||
PendingRegistrationSearch::SOURCE_INVITE => Yii::t('AdminModule.base', 'Invite'),
|
||||
PendingRegistrationSearch::SOURCE_INVITE => Yii::t('AdminModule.base', 'Invite by email'),
|
||||
PendingRegistrationSearch::SOURCE_INVITE_BY_LINK => Yii::t('AdminModule.base', 'Invite by link'),
|
||||
PendingRegistrationSearch::SOURCE_SELF => Yii::t('AdminModule.base', 'Sign up'),
|
||||
]
|
||||
]);
|
||||
|
@ -314,7 +314,7 @@ class SettingController extends Controller
|
||||
|
||||
// I wish..
|
||||
if ($dating) {
|
||||
$dating = date('Y-m-d H:i:s', $dating->log_time);
|
||||
$dating = date('Y-m-d H:i:s', (int) $dating->log_time);
|
||||
} else {
|
||||
$dating = "the begining of time";
|
||||
}
|
||||
|
@ -149,7 +149,8 @@ class UserController extends Controller
|
||||
'items' => UserEditForm::getGroupItems(),
|
||||
'options' => [
|
||||
'data-placeholder' => Yii::t('AdminModule.user', 'Select Groups'),
|
||||
'data-placeholder-more' => Yii::t('AdminModule.user', 'Add Groups...')
|
||||
'data-placeholder-more' => Yii::t('AdminModule.user', 'Add Groups...'),
|
||||
'data-tags' => 'false'
|
||||
],
|
||||
'maxSelection' => 250,
|
||||
'isVisible' => Yii::$app->user->can(new ManageGroups())
|
||||
|
@ -76,7 +76,7 @@ class PendingRegistrationSearch extends Invite
|
||||
$query->andFilterWhere(['like', 'username', $this->getAttribute('originator.username')]);
|
||||
$query->andFilterWhere(['like', 'user_invite.email', $this->email]);
|
||||
$query->andFilterWhere(['like', 'user_invite.language', $this->language]);
|
||||
$query->andFilterWhere(['like', 'source', $this->source]);
|
||||
$query->andFilterWhere(['source' => $this->source]);
|
||||
|
||||
return $dataProvider;
|
||||
}
|
||||
|
@ -2,12 +2,11 @@
|
||||
|
||||
namespace humhub\modules\admin\models\forms;
|
||||
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\user\models\GroupUser;
|
||||
use Yii;
|
||||
use humhub\modules\user\models\User;
|
||||
use humhub\modules\user\models\Group;
|
||||
use humhub\modules\admin\permissions\ManageGroups;
|
||||
use humhub\modules\user\models\Group;
|
||||
use humhub\modules\user\models\GroupUser;
|
||||
use humhub\modules\user\models\User;
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* Description of UserEditForm
|
||||
@ -18,14 +17,13 @@ class UserEditForm extends User
|
||||
{
|
||||
/**
|
||||
* GroupId selection array of the form.
|
||||
* @var type
|
||||
* @var array
|
||||
*/
|
||||
public $groupSelection;
|
||||
|
||||
/**
|
||||
* Current member groups (models) of the given $user
|
||||
* @var type
|
||||
*
|
||||
* @var Group[]
|
||||
*/
|
||||
public $currentGroups;
|
||||
|
||||
@ -103,19 +101,19 @@ class UserEditForm extends User
|
||||
if (!$this->isCurrentlyMemberOf($groupId)) {
|
||||
/* @var $group Group */
|
||||
$group = Group::findOne(['id' => $groupId]);
|
||||
if(!$group->is_admin_group || Yii::$app->user->isAdmin()) {
|
||||
if ($group && (!$group->is_admin_group || Yii::$app->user->isAdmin())) {
|
||||
$group->addUser($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::afterSave($insert, $changedAttributes);
|
||||
parent::afterSave($insert, $changedAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given group (id or model object) is contained in the form selection
|
||||
* @param integer $groupId groupId or Group model object
|
||||
* @param int|Group $groupId groupId or Group model object
|
||||
* @return boolean true if contained in selection else false
|
||||
*/
|
||||
private function isInGroupSelection($groupId)
|
||||
|
@ -9,7 +9,7 @@ use yii\web\View;
|
||||
/* @var $name string */
|
||||
/** @var OEmbedProviderForm $model */
|
||||
|
||||
parse_str($model->endpoint, $query);
|
||||
parse_str($model->endpoint ?? '', $query);
|
||||
|
||||
$this->registerJs(<<<JS
|
||||
function initEndpointInputs() {
|
||||
|
@ -67,7 +67,7 @@ AdminPendingRegistrationsAsset::register($this);
|
||||
'options' => ['width' => '40px'],
|
||||
'format' => 'raw',
|
||||
'value' => function ($data) use ($types) {
|
||||
return isset($types[$data->source]) ?: Html::encode($data->source);
|
||||
return $types[$data->source] ?? Html::encode($data->source);
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -65,7 +65,7 @@ class CommentTest extends HumHubDbTestCase
|
||||
$comment->save();
|
||||
|
||||
$post = Post::findOne(['id' => 11]);
|
||||
$post->delete();
|
||||
$post->hardDelete();
|
||||
|
||||
$this->assertNull(Comment::findOne(['id' => $comment->id]));
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
namespace humhub\modules\content;
|
||||
|
||||
use humhub\commands\IntegrityController;
|
||||
use humhub\components\Event;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\search\interfaces\Searchable;
|
||||
@ -92,7 +93,7 @@ class Events extends BaseObject
|
||||
/**
|
||||
* On init of the WallEntryAddonWidget, attach the wall entry links widget.
|
||||
*
|
||||
* @param CEvent $event
|
||||
* @param Event $event
|
||||
*/
|
||||
public static function onWallEntryAddonInit($event)
|
||||
{
|
||||
@ -105,13 +106,13 @@ class Events extends BaseObject
|
||||
/**
|
||||
* On rebuild of the search index, rebuild all user records
|
||||
*
|
||||
* @param type $event
|
||||
* @param Event $event
|
||||
*/
|
||||
public static function onSearchRebuild($event)
|
||||
{
|
||||
foreach (Content::find()->each() as $content) {
|
||||
$contentObject = $content->getPolymorphicRelation();
|
||||
if ($contentObject instanceof Searchable) {
|
||||
if ($contentObject instanceof Searchable && $content->state === Content::STATE_PUBLISHED) {
|
||||
Yii::$app->search->add($contentObject);
|
||||
}
|
||||
}
|
||||
@ -126,7 +127,10 @@ class Events extends BaseObject
|
||||
{
|
||||
/** @var ContentActiveRecord $record */
|
||||
$record = $event->sender;
|
||||
SearchHelper::queueUpdate($record);
|
||||
|
||||
if ($record->content->state === Content::STATE_PUBLISHED) {
|
||||
SearchHelper::queueUpdate($record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,4 +145,21 @@ class Events extends BaseObject
|
||||
SearchHelper::queueDelete($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback on daily cron job run
|
||||
*/
|
||||
public static function onCronDailyRun(): void
|
||||
{
|
||||
Yii::$app->queue->push(new jobs\PurgeDeletedContents());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback on hourly cron job run
|
||||
*/
|
||||
public static function onCronHourlyRun(): void
|
||||
{
|
||||
Yii::$app->queue->push(new jobs\PublishScheduledContents());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,13 @@ abstract class AbstractActiveQueryContentContainer extends ActiveQuery
|
||||
*/
|
||||
const MAX_SEARCH_NEEDLES = 5;
|
||||
|
||||
/**
|
||||
* During search, keyword will be walked through and each character of the set will be changed to another
|
||||
* of the same set to create variants to maximise search.
|
||||
* @var array
|
||||
*/
|
||||
protected $multiCharacterSearchVariants = [['\'', '’', '`'], ['"', '”', '“']];
|
||||
|
||||
/**
|
||||
* Filter query by visible records for the given or current user
|
||||
*
|
||||
@ -29,36 +36,40 @@ abstract class AbstractActiveQueryContentContainer extends ActiveQuery
|
||||
*/
|
||||
abstract public function visible(?User $user = null): ActiveQuery;
|
||||
|
||||
/**
|
||||
* Performs a container text search
|
||||
*
|
||||
* @param string|array $keywords
|
||||
* @param array|null $fields if empty the fields will be used from the method getSearchableFields()
|
||||
* @return ActiveQuery
|
||||
*/
|
||||
abstract public function search($keywords, ?array $fields = null): ActiveQuery;
|
||||
|
||||
/**
|
||||
* Returns a list of fields to be included in a container search.
|
||||
* If additional tables are needed, they must be added via `joinWith`.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function getSearchableFields(): array;
|
||||
|
||||
|
||||
/**
|
||||
* Filter this query by keyword
|
||||
* Performs a container text search
|
||||
*
|
||||
* @param string $keyword
|
||||
* @param string $keywords
|
||||
* @param array|null $fields if empty the fields will be used from the method getSearchableFields()
|
||||
* @return ActiveQuery
|
||||
* @return self
|
||||
*/
|
||||
abstract public function searchKeyword(string $keyword, ?array $fields = null): ActiveQuery;
|
||||
public function search(string $keywords, ?array $fields = null): ActiveQuery
|
||||
{
|
||||
if (empty($keywords)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
foreach ($this->setUpKeywords($keywords) as $keyword) {
|
||||
$this->searchKeyword($keyword, $fields);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $keywords
|
||||
* @return array
|
||||
*/
|
||||
protected function setUpKeywords($keywords): array
|
||||
private function setUpKeywords($keywords): array
|
||||
{
|
||||
if (!is_array($keywords)) {
|
||||
$keywords = explode(' ', $keywords);
|
||||
@ -66,4 +77,58 @@ abstract class AbstractActiveQueryContentContainer extends ActiveQuery
|
||||
|
||||
return array_slice($keywords, 0, static::MAX_SEARCH_NEEDLES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
private function searchKeyword(string $keyword, ?array $fields = null): ActiveQuery
|
||||
{
|
||||
if (empty($fields)) {
|
||||
$fields = $this->getSearchableFields();
|
||||
}
|
||||
|
||||
$conditions = [];
|
||||
foreach ($this->prepareKeywordVariants($keyword) as $variant) {
|
||||
$subConditions = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$subConditions[] = ['LIKE', $field, $variant];
|
||||
}
|
||||
|
||||
$conditions[] = array_merge(['OR'], $subConditions);
|
||||
}
|
||||
|
||||
return $this->andWhere(array_merge(['OR'], $conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will look through keyword and prepare other variants of the words according to config
|
||||
* This is used to search for different apostrophes and quotes characters as for now.
|
||||
* Example: word "o'Surname", will create array ["o'Surname", "o’Surname", "o`Surname"]
|
||||
*
|
||||
* @param $keyword
|
||||
* @return array
|
||||
*/
|
||||
private function prepareKeywordVariants($keyword): array
|
||||
{
|
||||
$variants = [$keyword];
|
||||
|
||||
foreach ($this->multiCharacterSearchVariants as $set) {
|
||||
foreach ($set as $character) {
|
||||
if (strpos($keyword, $character) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($set as $replaceWithCharacter) {
|
||||
if ($character === $replaceWithCharacter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variants[] = str_replace($character, $replaceWithCharacter, $keyword);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $variants;
|
||||
}
|
||||
}
|
@ -8,12 +8,14 @@
|
||||
|
||||
namespace humhub\modules\content\components;
|
||||
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\content\models\ContentTag;
|
||||
use humhub\modules\content\models\ContentTagRelation;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\modules\user\helpers\AuthHelper;
|
||||
use humhub\modules\user\models\User;
|
||||
use Yii;
|
||||
use yii\db\ActiveQuery;
|
||||
use yii\db\Expression;
|
||||
|
||||
/**
|
||||
@ -23,9 +25,8 @@ use yii\db\Expression;
|
||||
*
|
||||
* @author luke
|
||||
*/
|
||||
class ActiveQueryContent extends \yii\db\ActiveQuery
|
||||
class ActiveQueryContent extends ActiveQuery
|
||||
{
|
||||
|
||||
/**
|
||||
* Own content scope for userRelated
|
||||
* @see ActiveQueryContent::userRelated
|
||||
@ -36,6 +37,21 @@ class ActiveQueryContent extends \yii\db\ActiveQuery
|
||||
const USER_RELATED_SCOPE_FOLLOWED_USERS = 4;
|
||||
const USER_RELATED_SCOPE_OWN_PROFILE = 5;
|
||||
|
||||
/**
|
||||
* State filter that is used for queries. By default, only Published content is returned.
|
||||
*
|
||||
* Example to include drafts:
|
||||
* ```
|
||||
* $query = Post::find();
|
||||
* $query->stateFilterCondition[] = ['content.state' => Content::STATE_DRAFT];
|
||||
* $posts = $query->readable()->all();
|
||||
* ```
|
||||
*
|
||||
* @since 1.14
|
||||
* @var array
|
||||
*/
|
||||
public $stateFilterCondition = ['OR', ['content.state' => Content::STATE_PUBLISHED]];
|
||||
|
||||
/**
|
||||
* Only returns user readable records
|
||||
*
|
||||
@ -49,8 +65,9 @@ class ActiveQueryContent extends \yii\db\ActiveQuery
|
||||
$user = Yii::$app->user->getIdentity();
|
||||
}
|
||||
|
||||
$this->joinWith(['content', 'content.contentContainer', 'content.createdBy']);
|
||||
$this->andWhere($this->stateFilterCondition);
|
||||
|
||||
$this->joinWith(['content', 'content.contentContainer', 'content.createdBy']);
|
||||
$this->leftJoin('space', 'contentcontainer.pk=space.id AND contentcontainer.class=:spaceClass', [':spaceClass' => Space::class]);
|
||||
$this->leftJoin('user cuser', 'contentcontainer.pk=cuser.id AND contentcontainer.class=:userClass', [':userClass' => User::class]);
|
||||
$conditionSpace = '';
|
||||
@ -60,19 +77,23 @@ class ActiveQueryContent extends \yii\db\ActiveQuery
|
||||
if ($user !== null) {
|
||||
$this->leftJoin('space_membership', 'contentcontainer.pk=space_membership.space_id AND contentcontainer.class=:spaceClass AND space_membership.user_id=:userId', [':userId' => $user->id, ':spaceClass' => Space::class]);
|
||||
|
||||
if ($user->canViewAllContent()) {
|
||||
if ($user->canViewAllContent(Space::class)) {
|
||||
// Don't restrict if user can view all content:
|
||||
$conditionSpaceMembershipRestriction = '';
|
||||
$conditionUserPrivateRestriction = '';
|
||||
} else {
|
||||
// User must be a space's member OR Space and Content are public
|
||||
$conditionSpaceMembershipRestriction = ' AND ( space_membership.status=3 OR (content.visibility=1 AND space.visibility != 0) )';
|
||||
}
|
||||
if ($user->canViewAllContent(User::class)) {
|
||||
// Don't restrict if user can view all content:
|
||||
$conditionUserPrivateRestriction = '';
|
||||
} else {
|
||||
// User can view only content of own profile
|
||||
$conditionUserPrivateRestriction = ' AND content.contentcontainer_id=' . $user->contentcontainer_id;
|
||||
}
|
||||
|
||||
// Build Access Check based on Space Content Container
|
||||
$conditionSpace = 'space.id IS NOT NULL' . $conditionSpaceMembershipRestriction; // space content
|
||||
$conditionSpace = 'space.id IS NOT NULL' . $conditionSpaceMembershipRestriction;
|
||||
|
||||
// Build Access Check based on User Content Container
|
||||
$conditionUser = 'cuser.id IS NOT NULL AND ('; // user content
|
||||
@ -139,7 +160,7 @@ class ActiveQueryContent extends \yii\db\ActiveQuery
|
||||
$contentTagQuery = ContentTagRelation::find()->select('content_id');
|
||||
$contentTagQuery->andWhere(['content_tag_relation.tag_id' => $contentTag->id]);
|
||||
$contentTagQuery->andWhere('content_tag_relation.content_id=content.id');
|
||||
$this->andWhere(['content.id' =>$contentTagQuery]);
|
||||
$this->andWhere(['content.id' => $contentTagQuery]);
|
||||
}
|
||||
} else if ($mode == 'OR') {
|
||||
$names = array_map(function ($v) {
|
||||
@ -182,6 +203,10 @@ class ActiveQueryContent extends \yii\db\ActiveQuery
|
||||
public function userRelated($scopes = [], $user = null)
|
||||
{
|
||||
if ($user === null) {
|
||||
if ( Yii::$app->user->isGuest) {
|
||||
return $this->andWhere('false');
|
||||
}
|
||||
|
||||
$user = Yii::$app->user->getIdentity();
|
||||
}
|
||||
|
||||
|
@ -158,13 +158,13 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
*/
|
||||
public function __construct($contentContainer = [], $visibility = null, $config = [])
|
||||
{
|
||||
if(is_array($contentContainer)) {
|
||||
if (is_array($contentContainer)) {
|
||||
parent::__construct($contentContainer);
|
||||
} elseif($contentContainer instanceof ContentContainerActiveRecord) {
|
||||
} elseif ($contentContainer instanceof ContentContainerActiveRecord) {
|
||||
$this->content->setContainer($contentContainer);
|
||||
if(is_array($visibility)) {
|
||||
if (is_array($visibility)) {
|
||||
$config = $visibility;
|
||||
} elseif($visibility !== null) {
|
||||
} elseif ($visibility !== null) {
|
||||
$this->content->visibility = $visibility;
|
||||
}
|
||||
parent::__construct($config);
|
||||
@ -194,7 +194,7 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
if ($name === 'content') {
|
||||
$content = parent::__get('content');
|
||||
|
||||
if(!$content) {
|
||||
if (!$content) {
|
||||
$content = new Content();
|
||||
$content->setPolymorphicRelation($this);
|
||||
$this->populateRelation('content', $content);
|
||||
@ -248,7 +248,7 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
$labels[] = Label::danger(Yii::t('ContentModule.base', 'Pinned'))->icon('fa-map-pin')->sortOrder(100);
|
||||
}
|
||||
|
||||
if($this->content->isArchived()) {
|
||||
if ($this->content->isArchived()) {
|
||||
$labels[] = Label::warning(Yii::t('ContentModule.base', 'Archived'))->icon('fa-archive')->sortOrder(200);
|
||||
}
|
||||
|
||||
@ -285,9 +285,9 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
/**
|
||||
* Returns the $createPermission settings interpretable by an PermissionManager instance.
|
||||
*
|
||||
* @since 1.13
|
||||
* @see ContentActiveRecord::$createPermission
|
||||
* @return null|object|string
|
||||
* @see ContentActiveRecord::$createPermission
|
||||
* @since 1.13
|
||||
*/
|
||||
public function getCreatePermission()
|
||||
{
|
||||
@ -299,8 +299,8 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
/**
|
||||
* Determines whether or not the record has an additional createPermission set.
|
||||
*
|
||||
* @since 1.13
|
||||
* @return boolean
|
||||
* @since 1.13
|
||||
*/
|
||||
public function hasCreatePermission()
|
||||
{
|
||||
@ -310,9 +310,9 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
/**
|
||||
* Returns the $managePermission settings interpretable by an PermissionManager instance.
|
||||
*
|
||||
* @since 1.2.1
|
||||
* @see ContentActiveRecord::$managePermission
|
||||
* @return null|object|string
|
||||
* @see ContentActiveRecord::$managePermission
|
||||
* @since 1.2.1
|
||||
*/
|
||||
public function getManagePermission()
|
||||
{
|
||||
@ -324,10 +324,10 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
/**
|
||||
* Returns the permission value interpretable by an PermissionManager instance.
|
||||
*
|
||||
* @since 1.13
|
||||
* @see ContentActiveRecord::$managePermission, ContentActiveRecord::$createPermission
|
||||
* @param string|array|null
|
||||
* @return null|object|string
|
||||
* @since 1.13
|
||||
* @see ContentActiveRecord::$managePermission, ContentActiveRecord::$createPermission
|
||||
*/
|
||||
private function getPermissionValue($perm)
|
||||
{
|
||||
@ -337,7 +337,7 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
|
||||
if (is_array($perm)) {
|
||||
if (isset($perm['class'])) { // ['class' => '...', 'callback' => '...']
|
||||
$handler = $perm['class'].'::'.$perm['callback'];
|
||||
$handler = $perm['class'] . '::' . $perm['callback'];
|
||||
return call_user_func($handler, $this);
|
||||
}
|
||||
// Simple Permission array specification
|
||||
@ -358,8 +358,8 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
/**
|
||||
* Determines weather or not this records has an additional managePermission set.
|
||||
*
|
||||
* @since 1.2.1
|
||||
* @return boolean
|
||||
* @since 1.2.1
|
||||
*/
|
||||
public function hasManagePermission()
|
||||
{
|
||||
@ -370,18 +370,18 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
* Returns the wall output widget of this content.
|
||||
*
|
||||
* @param array $params optional parameters for WallEntryWidget
|
||||
* @deprecated since 1.7 use StreamEntryWidget::renderStreamEntry()
|
||||
* @return string
|
||||
* @deprecated since 1.7 use StreamEntryWidget::renderStreamEntry()
|
||||
*/
|
||||
public function getWallOut($params = [])
|
||||
{
|
||||
if(is_subclass_of($this->wallEntryClass, StreamEntryWidget::class, true)) {
|
||||
if (is_subclass_of($this->wallEntryClass, StreamEntryWidget::class, true)) {
|
||||
$params['model'] = $this;
|
||||
} else if(!empty($this->wallEntryClass)) {
|
||||
} else if (!empty($this->wallEntryClass)) {
|
||||
$params['contentObject'] = $this; // legacy WallEntry widget
|
||||
}
|
||||
|
||||
return call_user_func($this->wallEntryClass.'::widget', $params);
|
||||
return call_user_func($this->wallEntryClass . '::widget', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,18 +394,18 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
*/
|
||||
public function getWallEntryWidget()
|
||||
{
|
||||
if(empty($this->wallEntryClass)) {
|
||||
if (empty($this->wallEntryClass)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_subclass_of($this->wallEntryClass, WallEntry::class) ) {
|
||||
if (is_subclass_of($this->wallEntryClass, WallEntry::class)) {
|
||||
$class = $this->wallEntryClass;
|
||||
$widget = new $class;
|
||||
$widget->contentObject = $this;
|
||||
return $widget;
|
||||
}
|
||||
|
||||
if($this->wallEntryClass) {
|
||||
if ($this->wallEntryClass) {
|
||||
$class = $this->wallEntryClass;
|
||||
$widget = new $class(['model' => $this]);
|
||||
return $widget;
|
||||
@ -470,10 +470,33 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectModel() {
|
||||
public static function getObjectModel()
|
||||
{
|
||||
return static::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this content for deletion (soft delete).
|
||||
* Use `hardDelete()` method to delete record immediately.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
return $this->content->softDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this content record immediately and permanently
|
||||
*
|
||||
* @return bool
|
||||
* @since 1.14
|
||||
*/
|
||||
public function hardDelete(): bool
|
||||
{
|
||||
return (parent::delete() !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
@ -499,8 +522,8 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
* Checks if the given user or the current logged in user if no user was given, is the owner of this content
|
||||
* @param null $user
|
||||
* @return bool
|
||||
* @since 1.3
|
||||
* @throws \Throwable
|
||||
* @since 1.3
|
||||
*/
|
||||
public function isOwner($user = null)
|
||||
{
|
||||
@ -569,13 +592,13 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
*/
|
||||
public function canMove(ContentContainerActiveRecord $container = null)
|
||||
{
|
||||
if(!$this->canMove) {
|
||||
if (!$this->canMove) {
|
||||
return Yii::t('ContentModule.base', 'This content type can\'t be moved.');
|
||||
}
|
||||
|
||||
if($container && is_string($this->canMove) && is_subclass_of($this->canMove, BasePermission::class)) {
|
||||
if ($container && is_string($this->canMove) && is_subclass_of($this->canMove, BasePermission::class)) {
|
||||
$ownerPermissions = $container->getPermissionManager($this->content->createdBy);
|
||||
if(!$ownerPermissions->can($this->canMove)) {
|
||||
if (!$ownerPermissions->can($this->canMove)) {
|
||||
return Yii::t('ContentModule.base', 'The author of this content is not allowed to create this type of content within this space.');
|
||||
}
|
||||
}
|
||||
@ -596,5 +619,7 @@ class ContentActiveRecord extends ActiveRecord implements ContentOwner, Movable
|
||||
* in order to define model specific logic as moving sub-content or other related.
|
||||
* @param ContentContainerActiveRecord|null $container
|
||||
*/
|
||||
public function afterMove(ContentContainerActiveRecord $container = null) {}
|
||||
public function afterMove(ContentContainerActiveRecord $container = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
use humhub\commands\CronController;
|
||||
use humhub\modules\content\Events;
|
||||
use humhub\commands\IntegrityController;
|
||||
use humhub\modules\content\widgets\WallEntryControls;
|
||||
use humhub\modules\content\widgets\WallEntryAddons;
|
||||
use humhub\modules\user\models\User;
|
||||
use humhub\modules\space\models\Space;
|
||||
@ -23,6 +23,7 @@ return [
|
||||
['class' => ContentActiveRecord::class, 'event' => ContentActiveRecord::EVENT_AFTER_INSERT, 'callback' => [Events::class, 'onContentActiveRecordSave']],
|
||||
['class' => ContentActiveRecord::class, 'event' => ContentActiveRecord::EVENT_AFTER_UPDATE, 'callback' => [Events::class, 'onContentActiveRecordSave']],
|
||||
['class' => ContentActiveRecord::class, 'event' => ContentActiveRecord::EVENT_AFTER_DELETE, 'callback' => [Events::class, 'onContentActiveRecordDelete']],
|
||||
['class' => CronController::class, 'event' => CronController::EVENT_ON_DAILY_RUN, 'callback' => [Events::class, 'onCronDailyRun']],
|
||||
['class' => CronController::class, 'event' => CronController::EVENT_ON_HOURLY_RUN, 'callback' => [Events::class, 'onCronHourlyRun']]
|
||||
],
|
||||
];
|
||||
?>
|
@ -12,19 +12,16 @@ use humhub\components\behaviors\AccessControl;
|
||||
use humhub\components\Controller;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\content\models\forms\AdminDeleteContentForm;
|
||||
use humhub\modules\content\models\forms\ScheduleOptionsForm;
|
||||
use humhub\modules\content\Module;
|
||||
use humhub\modules\content\notifications\ContentDeleted;
|
||||
use humhub\modules\content\permissions\CreatePublicContent;
|
||||
use humhub\modules\content\widgets\AdminDeleteModal;
|
||||
use humhub\modules\stream\actions\StreamEntryResponse;
|
||||
use Yii;
|
||||
use yii\base\BaseObject;
|
||||
use yii\base\Exception;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\web\BadRequestHttpException;
|
||||
use yii\web\ForbiddenHttpException;
|
||||
use yii\web\HttpException;
|
||||
use yii\web\NotAcceptableHttpException;
|
||||
use yii\web\NotFoundHttpException;
|
||||
use yii\web\Response;
|
||||
|
||||
@ -57,17 +54,15 @@ class ContentController extends Controller
|
||||
*/
|
||||
public function actionDelete()
|
||||
{
|
||||
Yii::$app->response->format = 'json';
|
||||
|
||||
$this->forcePostRequest();
|
||||
|
||||
$model = Yii::$app->request->get('model');
|
||||
|
||||
// Due to backward compatibility we use the old delete mechanism in case a model parameter is provided
|
||||
$id = (int)($model != null) ? Yii::$app->request->get('id') : Yii::$app->request->post('id');
|
||||
$id = $model ? Yii::$app->request->get('id') : Yii::$app->request->post('id');
|
||||
|
||||
/* @var $contentObjs Content */
|
||||
$contentObj = ($model != null) ? Content::Get($model, $id) : Content::findOne(['id' => $id]);
|
||||
/* @var $contentObj Content */
|
||||
$contentObj = $model ? Content::Get($model, $id) : Content::findOne(['id' => $id]);
|
||||
|
||||
if (!$contentObj) {
|
||||
throw new NotFoundHttpException();
|
||||
@ -77,37 +72,19 @@ class ContentController extends Controller
|
||||
throw new ForbiddenHttpException();
|
||||
}
|
||||
|
||||
if ($contentObj !== null) {
|
||||
$form = new AdminDeleteContentForm();
|
||||
$form = new AdminDeleteContentForm(['content' => $contentObj]);
|
||||
$form->load(Yii::$app->request->post());
|
||||
|
||||
if ($form->load(Yii::$app->request->post())) {
|
||||
if (!$form->validate()) {
|
||||
throw new BadRequestHttpException();
|
||||
}
|
||||
|
||||
if ($form->notify) {
|
||||
$contentDeleted = ContentDeleted::instance()
|
||||
->from(Yii::$app->user->getIdentity())
|
||||
->payload(['contentTitle' => (new ContentDeleted)->getContentPlainTextInfo($contentObj), 'reason' => $form->message]);
|
||||
$contentDeleted->saveRecord($contentObj->createdBy);
|
||||
|
||||
$contentDeleted->record->updateAttributes([
|
||||
'send_web_notifications' => 1
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$json = [
|
||||
'success' => $contentObj->delete(),
|
||||
'uniqueId' => $contentObj->getUniqueId(),
|
||||
'model' => $model,
|
||||
'pk' => $id
|
||||
];
|
||||
} else {
|
||||
throw new HttpException(500, Yii::t('ContentModule.base', 'Could not delete content!'));
|
||||
if (!$form->delete()) {
|
||||
return $this->asJson(['error' => $form->getErrorsAsString()]);
|
||||
}
|
||||
|
||||
return $json;
|
||||
return $this->asJson([
|
||||
'success' => true,
|
||||
'uniqueId' => $contentObj->getUniqueId(),
|
||||
'model' => $model,
|
||||
'pk' => $id
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,7 +103,7 @@ class ContentController extends Controller
|
||||
}
|
||||
|
||||
if (!$contentObj->canEdit()) {
|
||||
throw new HttpException(400);
|
||||
throw new ForbiddenHttpException();
|
||||
}
|
||||
|
||||
return [
|
||||
@ -192,9 +169,7 @@ class ContentController extends Controller
|
||||
{
|
||||
$this->forcePostRequest();
|
||||
|
||||
$post = Yii::$app->request->post();
|
||||
|
||||
$content = Content::findOne(['id' => $post['id']]);
|
||||
$content = Content::findOne(['id' => Yii::$app->request->post('id')]);
|
||||
|
||||
if (!$content) {
|
||||
throw new NotFoundHttpException();
|
||||
@ -204,26 +179,14 @@ class ContentController extends Controller
|
||||
throw new ForbiddenHttpException();
|
||||
}
|
||||
|
||||
$form = new AdminDeleteContentForm();
|
||||
$form = new AdminDeleteContentForm(['content' => $content]);
|
||||
$form->load(Yii::$app->request->post());
|
||||
|
||||
if ($form->load($post)) {
|
||||
if (!$form->validate()) {
|
||||
throw new BadRequestHttpException();
|
||||
}
|
||||
|
||||
if($form->notify) {
|
||||
$contentDeleted = ContentDeleted::instance()
|
||||
->from(Yii::$app->user->getIdentity())
|
||||
->payload(['contentTitle' => (new ContentDeleted)->getContentPlainTextInfo($content), 'reason' => $form->message]);
|
||||
$contentDeleted->saveRecord($content->createdBy);
|
||||
|
||||
$contentDeleted->record->updateAttributes([
|
||||
'send_web_notifications' => 1
|
||||
]);
|
||||
}
|
||||
if (!$form->delete()) {
|
||||
return $this->asJson(['error' => $form->getErrorsAsString()]);
|
||||
}
|
||||
|
||||
return $this->asJson(['success' => $content->delete()]);
|
||||
return $this->asJson(['success' => true]);
|
||||
}
|
||||
|
||||
public function actionReload($id)
|
||||
@ -231,11 +194,11 @@ class ContentController extends Controller
|
||||
$content = Content::findOne(['id' => $id]);
|
||||
|
||||
if (!$content) {
|
||||
throw new HttpException(400, Yii::t('ContentModule.base', 'Invalid content id given!'));
|
||||
throw new NotFoundHttpException(Yii::t('ContentModule.base', 'Invalid content id given!'));
|
||||
}
|
||||
|
||||
if (!$content->canView()) {
|
||||
throw new HttpException(403);
|
||||
throw new ForbiddenHttpException();
|
||||
}
|
||||
|
||||
return StreamEntryResponse::getAsJson($content);
|
||||
@ -244,10 +207,9 @@ class ContentController extends Controller
|
||||
/**
|
||||
* Switches the content visibility for the given content.
|
||||
*
|
||||
* @param type $id content id
|
||||
* @param int $id content id
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
* @throws HttpException
|
||||
* @throws InvalidConfigException
|
||||
* @throws \Throwable
|
||||
* @throws \yii\db\IntegrityException
|
||||
@ -258,11 +220,11 @@ class ContentController extends Controller
|
||||
$content = Content::findOne(['id' => $id]);
|
||||
|
||||
if (!$content) {
|
||||
throw new HttpException(400, Yii::t('ContentModule.base', 'Invalid content id given!'));
|
||||
throw new NotFoundHttpException(Yii::t('ContentModule.base', 'Invalid content id given!'));
|
||||
} elseif (!$content->canEdit()) {
|
||||
throw new HttpException(403);
|
||||
throw new ForbiddenHttpException();
|
||||
} elseif ($content->isPrivate() && !$content->container->permissionManager->can(new CreatePublicContent())) {
|
||||
throw new HttpException(403);
|
||||
throw new ForbiddenHttpException();
|
||||
}
|
||||
|
||||
if ($content->isPrivate()) {
|
||||
@ -284,7 +246,6 @@ class ContentController extends Controller
|
||||
* @param bool $lockComments True to lock comments, False to unlock
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
* @throws HttpException
|
||||
* @throws InvalidConfigException
|
||||
* @throws \Throwable
|
||||
* @throws \yii\db\IntegrityException
|
||||
@ -295,9 +256,9 @@ class ContentController extends Controller
|
||||
$content = Content::findOne(['id' => $id]);
|
||||
|
||||
if (!$content) {
|
||||
throw new HttpException(400, Yii::t('ContentModule.base', 'Invalid content id given!'));
|
||||
throw new NotFoundHttpException(Yii::t('ContentModule.base', 'Invalid content id given!'));
|
||||
} elseif (!$content->canLockComments()) {
|
||||
throw new HttpException(403);
|
||||
throw new ForbiddenHttpException();
|
||||
}
|
||||
|
||||
$content->locked_comments = $lockComments;
|
||||
@ -313,7 +274,6 @@ class ContentController extends Controller
|
||||
* @param int $id Content id
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
* @throws HttpException
|
||||
* @throws InvalidConfigException
|
||||
* @throws \Throwable
|
||||
* @throws \yii\db\IntegrityException
|
||||
@ -329,7 +289,6 @@ class ContentController extends Controller
|
||||
* @param int $id Content id
|
||||
* @return Response
|
||||
* @throws Exception
|
||||
* @throws HttpException
|
||||
* @throws InvalidConfigException
|
||||
* @throws \Throwable
|
||||
* @throws \yii\db\IntegrityException
|
||||
@ -345,7 +304,6 @@ class ContentController extends Controller
|
||||
* Returns JSON Output.
|
||||
* @return Response
|
||||
* @throws ForbiddenHttpException
|
||||
* @throws HttpException
|
||||
* @throws NotFoundHttpException
|
||||
* @throws Exception
|
||||
* @throws InvalidConfigException
|
||||
@ -402,6 +360,25 @@ class ContentController extends Controller
|
||||
return $this->asJson($json);
|
||||
}
|
||||
|
||||
public function actionPublishDraft()
|
||||
{
|
||||
$this->forcePostRequest();
|
||||
|
||||
$json = [];
|
||||
$json['success'] = false;
|
||||
|
||||
$content = Content::findOne(['id' => Yii::$app->request->get('id', '')]);
|
||||
if ($content !== null && $content->canEdit() && $content->state === Content::STATE_DRAFT) {
|
||||
$content->state = Content::STATE_PUBLISHED;
|
||||
$content->save();
|
||||
$json['message'] = Yii::t('ContentModule.base', 'The content has been successfully published.');
|
||||
$json['success'] = true;
|
||||
|
||||
}
|
||||
|
||||
return $this->asJson($json);
|
||||
}
|
||||
|
||||
public function actionNotificationSwitch()
|
||||
{
|
||||
$this->forcePostRequest();
|
||||
@ -419,4 +396,29 @@ class ContentController extends Controller
|
||||
|
||||
return $this->asJson($json);
|
||||
}
|
||||
|
||||
public function actionScheduleOptions($id = null)
|
||||
{
|
||||
$this->forcePostRequest();
|
||||
|
||||
$content = $id ? Content::findOne($id) : null;
|
||||
|
||||
if ($content instanceof Content && !$content->canEdit()) {
|
||||
throw new ForbiddenHttpException();
|
||||
}
|
||||
|
||||
$scheduleOptions = new ScheduleOptionsForm(['content' => $content]);
|
||||
|
||||
if ($scheduleOptions->load(Yii::$app->request->post())) {
|
||||
// Disable in order to don't focus the date field because modal window will be closed anyway
|
||||
$disableInputs = $scheduleOptions->save();
|
||||
} else {
|
||||
$disableInputs = !$scheduleOptions->enabled;
|
||||
}
|
||||
|
||||
return $this->renderAjax('scheduleOptions', [
|
||||
'scheduleOptions' => $scheduleOptions,
|
||||
'disableInputs' => $disableInputs
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @link https://www.humhub.org/
|
||||
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
|
||||
* @license https://www.humhub.com/licences
|
||||
*/
|
||||
|
||||
namespace humhub\modules\content\jobs;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\queue\ActiveJob;
|
||||
|
||||
class PublishScheduledContents extends ActiveJob
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$now = new DateTime('now', new DateTimeZone('UTC'));
|
||||
|
||||
/* @var Content[] $contents*/
|
||||
$contents = Content::find()
|
||||
->where(['state' => Content::STATE_SCHEDULED])
|
||||
->andWhere(['<=', 'scheduled_at', $now->format('Y-m-d H:i:s')])
|
||||
->all();
|
||||
|
||||
foreach ($contents as $content) {
|
||||
$content->setState(Content::STATE_PUBLISHED);
|
||||
$content->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @link https://www.humhub.org/
|
||||
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
|
||||
* @license https://www.humhub.com/licences
|
||||
*/
|
||||
|
||||
namespace humhub\modules\content\jobs;
|
||||
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\queue\ActiveJob;
|
||||
|
||||
class PurgeDeletedContents extends ActiveJob
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
foreach (Content::findAll(['content.state' => Content::STATE_DELETED]) as $content) {
|
||||
$content->delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'አገልግሎት ይሰጣል',
|
||||
'Configure' => 'አዋቅር',
|
||||
'Enable' => 'እንዲጠቀሙ አድርግ',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'تم التفعيل',
|
||||
'Configure' => 'إعدادات',
|
||||
'Enable' => 'تفعيل',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Активиран',
|
||||
'Configure' => 'Настройване',
|
||||
'Enable' => 'Активиране',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Activat',
|
||||
'Configure' => 'Configura',
|
||||
'Enable' => 'Habilita',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktivováno',
|
||||
'Configure' => 'Nastavit',
|
||||
'Enable' => 'Dostupné',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktiveret',
|
||||
'Configure' => 'Konfigurer',
|
||||
'Enable' => 'Aktiver',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktiviert',
|
||||
'Configure' => 'Konfigurieren',
|
||||
'Enable' => 'Aktivieren',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Ενεργοποιήθηκε',
|
||||
'Configure' => 'Ρύθμιση',
|
||||
'Enable' => 'Ενεργοποίηση',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Activado',
|
||||
'Configure' => 'Configurar',
|
||||
'Enable' => 'Habilitar',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'فعال شد',
|
||||
'Configure' => 'تنظیم',
|
||||
'Enable' => 'فعال',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktivoitu',
|
||||
'Configure' => 'Hallitse',
|
||||
'Enable' => 'Ota käyttöön',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Activé',
|
||||
'Configure' => 'Configurer',
|
||||
'Enable' => 'Activer',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'הופעל',
|
||||
'Configure' => '',
|
||||
'Enable' => 'פעיל',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktivirano',
|
||||
'Configure' => 'Konfiguriraj',
|
||||
'Enable' => 'Omogući',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktiválva',
|
||||
'Configure' => 'Konfiguráció',
|
||||
'Enable' => 'Engedélyezés',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => 'Aktifkan',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Attivato',
|
||||
'Configure' => 'Configura',
|
||||
'Enable' => 'Abilita',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => '作動中',
|
||||
'Configure' => '設定',
|
||||
'Enable' => '有効化',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktyvuota',
|
||||
'Configure' => 'Konfigūruoti',
|
||||
'Enable' => 'Įgalinti',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktivizēti',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktivert',
|
||||
'Configure' => 'Konfigurer',
|
||||
'Enable' => 'Aktiver',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Geactiveerd',
|
||||
'Configure' => 'Configureren',
|
||||
'Enable' => 'Inschakelen',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktywowane',
|
||||
'Configure' => 'Konfiguruj',
|
||||
'Enable' => 'Włącz',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Ativado',
|
||||
'Configure' => 'Configurar',
|
||||
'Enable' => 'Habilitar',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Ativado',
|
||||
'Configure' => 'Configurar',
|
||||
'Enable' => 'Ativar',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Включен',
|
||||
'Configure' => 'Настройки',
|
||||
'Enable' => 'Включить',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktiverad',
|
||||
'Configure' => 'Konfigurera',
|
||||
'Enable' => 'Aktivera',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'เปิดใช้งานแล้ว',
|
||||
'Configure' => 'กำหนดค่า',
|
||||
'Enable' => 'เปิดใช้งาน',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Aktif',
|
||||
'Configure' => 'Yapılandırma',
|
||||
'Enable' => 'Etkinleştirme',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => 'Đã được kích hoạt',
|
||||
'Configure' => 'Cấu hình',
|
||||
'Enable' => 'Kích hoạt',
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
<?php
|
||||
return array (
|
||||
'Activated' => '启用',
|
||||
'Configure' => '配置',
|
||||
'Enable' => '生效',
|
||||
);
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'Activated' => '',
|
||||
'Configure' => '',
|
||||
'Enable' => '',
|
||||
];
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use yii\db\Migration;
|
||||
|
||||
/**
|
||||
* Class m230127_195245_content_state
|
||||
*/
|
||||
class m230127_195245_content_state extends Migration
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeUp()
|
||||
{
|
||||
$this->addColumn(
|
||||
'content',
|
||||
'state',
|
||||
$this->tinyInteger()->defaultValue(1)->notNull()->after('visibility')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeDown()
|
||||
{
|
||||
echo "m230127_195245_content_state cannot be reverted.\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
// Use up()/down() to run migration code without a transaction.
|
||||
public function up()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
echo "m230127_195245_content_state cannot be reverted.\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use humhub\components\Migration;
|
||||
use humhub\modules\content\models\Content;
|
||||
|
||||
/**
|
||||
* Class m230217_112400_content_scheduled_at
|
||||
*/
|
||||
class m230217_112400_content_scheduled_at extends Migration
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeUp()
|
||||
{
|
||||
$this->safeAddColumn(Content::tableName(), 'scheduled_at', $this
|
||||
->dateTime()
|
||||
->null()
|
||||
->after('locked_comments'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function safeDown()
|
||||
{
|
||||
$this->safeDropColumn(Content::tableName(), 'scheduled_at');
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@ use humhub\components\ActiveRecord;
|
||||
use humhub\components\behaviors\GUID;
|
||||
use humhub\components\behaviors\PolymorphicRelation;
|
||||
use humhub\components\Module;
|
||||
use humhub\libs\DbDateValidator;
|
||||
use humhub\modules\activity\helpers\ActivityHelper;
|
||||
use humhub\modules\admin\permissions\ManageUsers;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
@ -21,6 +23,7 @@ use humhub\modules\content\live\NewContent;
|
||||
use humhub\modules\content\permissions\CreatePrivateContent;
|
||||
use humhub\modules\content\permissions\CreatePublicContent;
|
||||
use humhub\modules\content\permissions\ManageContent;
|
||||
use humhub\modules\notification\models\Notification;
|
||||
use humhub\modules\search\libs\SearchHelper;
|
||||
use humhub\modules\space\models\Space;
|
||||
use humhub\modules\user\components\PermissionManager;
|
||||
@ -29,7 +32,6 @@ use humhub\modules\user\models\User;
|
||||
use Yii;
|
||||
use yii\base\Exception;
|
||||
use yii\base\InvalidArgumentException;
|
||||
use yii\db\Expression;
|
||||
use yii\db\IntegrityException;
|
||||
use yii\helpers\Url;
|
||||
|
||||
@ -59,11 +61,13 @@ use yii\helpers\Url;
|
||||
* @property integer $visibility
|
||||
* @property integer $pinned
|
||||
* @property integer $archived
|
||||
* @property integer $state
|
||||
* @property integer $locked_comments
|
||||
* @property string $created_at
|
||||
* @property integer $created_by
|
||||
* @property string $updated_at
|
||||
* @property integer $updated_by
|
||||
* @property string $scheduled_at
|
||||
* @property string $stream_sort_date
|
||||
* @property string $stream_channel
|
||||
* @property integer $contentcontainer_id;
|
||||
@ -103,6 +107,14 @@ class Content extends ActiveRecord implements Movable, ContentOwner
|
||||
*/
|
||||
const VISIBILITY_OWNER = 2;
|
||||
|
||||
/**
|
||||
* Content States - By default, only content with the "Published" state is returned.
|
||||
*/
|
||||
const STATE_PUBLISHED = 1;
|
||||
const STATE_DRAFT = 10;
|
||||
const STATE_SCHEDULED = 20;
|
||||
const STATE_DELETED = 100;
|
||||
|
||||
/**
|
||||
* @var ContentContainerActiveRecord the Container (e.g. Space or User) where this content belongs to.
|
||||
*/
|
||||
@ -191,21 +203,13 @@ class Content extends ActiveRecord implements Movable, ContentOwner
|
||||
throw new Exception("Could not save content with object_model or object_id!");
|
||||
}
|
||||
|
||||
// Set some default values
|
||||
if (!$this->archived) {
|
||||
$this->archived = 0;
|
||||
}
|
||||
if (!$this->visibility) {
|
||||
$this->visibility = self::VISIBILITY_PRIVATE;
|
||||
}
|
||||
if (!$this->pinned) {
|
||||
$this->pinned = 0;
|
||||
}
|
||||
$this->archived ??= 0;
|
||||
$this->visibility ??= self::VISIBILITY_PRIVATE;
|
||||
$this->pinned ??= 0;
|
||||
$this->state ??= Content::STATE_PUBLISHED;
|
||||
|
||||
if ($insert) {
|
||||
if ($this->created_by == "") {
|
||||
$this->created_by = Yii::$app->user->id;
|
||||
}
|
||||
$this->created_by ??= Yii::$app->user->id;
|
||||
}
|
||||
|
||||
$this->stream_sort_date = date('Y-m-d G:i:s');
|
||||
@ -222,15 +226,38 @@ class Content extends ActiveRecord implements Movable, ContentOwner
|
||||
*/
|
||||
public function afterSave($insert, $changedAttributes)
|
||||
{
|
||||
/* @var $contentSource ContentActiveRecord */
|
||||
$contentSource = $this->getModel();
|
||||
if (// New Content with State Published:
|
||||
($insert && $this->state == Content::STATE_PUBLISHED) ||
|
||||
// Content Updated from Draft to Published
|
||||
(array_key_exists('state', $changedAttributes) &&
|
||||
$this->state == Content::STATE_PUBLISHED &&
|
||||
$changedAttributes['state'] == Content::STATE_DRAFT
|
||||
)) {
|
||||
$this->processNewContent();
|
||||
}
|
||||
|
||||
if ($this->state === static::STATE_PUBLISHED) {
|
||||
SearchHelper::queueUpdate($this->getModel());
|
||||
} else {
|
||||
SearchHelper::queueDelete($this->getModel());
|
||||
}
|
||||
|
||||
parent::afterSave($insert, $changedAttributes);
|
||||
}
|
||||
|
||||
private function processNewContent()
|
||||
{
|
||||
$record = $this->getModel();
|
||||
|
||||
Yii::debug('Process new content: ' . get_class($record) . ' ID: ' . $record->getPrimaryKey(), 'content');
|
||||
|
||||
foreach ($this->notifyUsersOfNewContent as $user) {
|
||||
$contentSource->follow($user->id);
|
||||
$record->follow($user->id);
|
||||
}
|
||||
|
||||
// TODO: handle ContentCreated notifications and live events for global content
|
||||
if ($insert && !$this->isMuted()) {
|
||||
|
||||
if (!$this->isMuted()) {
|
||||
$this->notifyContentCreated();
|
||||
}
|
||||
|
||||
@ -241,20 +268,17 @@ class Content extends ActiveRecord implements Movable, ContentOwner
|
||||
'originator' => $this->createdBy->guid,
|
||||
'contentContainerId' => $this->container->contentContainerRecord->id,
|
||||
'visibility' => $this->visibility,
|
||||
'sourceClass' => get_class($contentSource),
|
||||
'sourceId' => $contentSource->getPrimaryKey(),
|
||||
'sourceClass' => get_class($record),
|
||||
'sourceId' => $record->getPrimaryKey(),
|
||||
'silent' => $this->isMuted(),
|
||||
'streamChannel' => $this->stream_channel,
|
||||
'contentId' => $this->id,
|
||||
'insert' => $insert
|
||||
'insert' => true
|
||||
]));
|
||||
}
|
||||
|
||||
SearchHelper::queueUpdate($contentSource);
|
||||
|
||||
parent::afterSave($insert, $changedAttributes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool checks if the given content allows content creation notifications and activities
|
||||
* @throws IntegrityException
|
||||
@ -298,8 +322,12 @@ class Content extends ActiveRecord implements Movable, ContentOwner
|
||||
{
|
||||
// Try delete the underlying object (Post, Question, Task, ...)
|
||||
$this->resetPolymorphicRelation();
|
||||
if ($this->getPolymorphicRelation() !== null) {
|
||||
$this->getPolymorphicRelation()->delete();
|
||||
|
||||
/** @var ContentActiveRecord $record */
|
||||
$record = $this->getPolymorphicRelation();
|
||||
|
||||
if ($record) {
|
||||
$record->hardDelete();
|
||||
}
|
||||
|
||||
parent::afterDelete();
|
||||
@ -859,13 +887,18 @@ class Content extends ActiveRecord implements Movable, ContentOwner
|
||||
return $this->checkGuestAccess();
|
||||
}
|
||||
|
||||
// If content is draft, in trash, unapproved - restrict view access to editors
|
||||
if ($this->state !== static::STATE_PUBLISHED) {
|
||||
return $this->canEdit();
|
||||
}
|
||||
|
||||
// Public visible content
|
||||
if ($this->isPublic()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check system admin can see all content module configuration
|
||||
if ($user->canViewAllContent()) {
|
||||
if ($user->canViewAllContent(get_class($this->container))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -948,4 +981,74 @@ class Content extends ActiveRecord implements Movable, ContentOwner
|
||||
{
|
||||
return $this->created_at !== $this->updated_at && !empty($this->updated_at) && is_string($this->updated_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the content as deleted.
|
||||
*
|
||||
* Content which are marked as deleted will not longer returned in queries/stream/search.
|
||||
* A cron job will remove these content permanently.
|
||||
* If installed, such content can also be restored using the `recycle-bin` module.
|
||||
*
|
||||
* @return bool
|
||||
* @since 1.14
|
||||
*/
|
||||
public function softDelete(): bool
|
||||
{
|
||||
ActivityHelper::deleteActivitiesForRecord($this->getModel());
|
||||
|
||||
Notification::deleteAll([
|
||||
'source_class' => get_class($this),
|
||||
'source_pk' => $this->getPrimaryKey(),
|
||||
]);
|
||||
|
||||
$this->setState(self::STATE_DELETED);
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public static function getAllowedStates(): array
|
||||
{
|
||||
return [
|
||||
self::STATE_PUBLISHED,
|
||||
self::STATE_DRAFT,
|
||||
self::STATE_SCHEDULED,
|
||||
self::STATE_DELETED
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string|null $state
|
||||
* @return bool
|
||||
* @since 1.14
|
||||
*/
|
||||
public function canChangeState($state): bool
|
||||
{
|
||||
return in_array($state, self::getAllowedStates());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string|null $state
|
||||
* @param array $options Additional options depending on state
|
||||
* @since 1.14
|
||||
*/
|
||||
public function setState($state, array $options = [])
|
||||
{
|
||||
if (!$this->canChangeState($state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int) $state === self::STATE_SCHEDULED) {
|
||||
if (empty($options['scheduled_at'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->scheduled_at = $options['scheduled_at'];
|
||||
(new DbDateValidator())->validateAttribute($this, 'scheduled_at');
|
||||
if ($this->hasErrors('scheduled_at')) {
|
||||
$this->scheduled_at = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->state = $state;
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace humhub\modules\content\models\forms;
|
||||
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\content\notifications\ContentDeleted;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
@ -12,6 +12,11 @@ use yii\base\Model;
|
||||
*/
|
||||
class AdminDeleteContentForm extends Model
|
||||
{
|
||||
/**
|
||||
* @var Content
|
||||
*/
|
||||
public $content;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -43,4 +48,46 @@ class AdminDeleteContentForm extends Model
|
||||
'notify' => Yii::t('CommentModule.base', 'Send a notification to author')
|
||||
];
|
||||
}
|
||||
|
||||
public function delete(): bool
|
||||
{
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->notify()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->content->softDelete();
|
||||
}
|
||||
|
||||
public function notify(): bool
|
||||
{
|
||||
if (!$this->notify) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$contentDeleted = ContentDeleted::instance()
|
||||
->from(Yii::$app->user->getIdentity())
|
||||
->payload([
|
||||
'contentTitle' => (new ContentDeleted)->getContentPlainTextInfo($this->content),
|
||||
'reason' => $this->message
|
||||
]);
|
||||
if (!$contentDeleted->saveRecord($this->content->createdBy)) {
|
||||
$this->addError('message', Yii::t('ContentModule.base', 'Cannot notify the author.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$contentDeleted->record->updateAttributes([
|
||||
'send_web_notifications' => 1
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getErrorsAsString(): string
|
||||
{
|
||||
return implode(' ', $this->getErrorSummary(true));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.humhub.org/
|
||||
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
|
||||
* @license https://www.humhub.com/licences
|
||||
*/
|
||||
|
||||
namespace humhub\modules\content\models\forms;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use humhub\libs\DbDateValidator;
|
||||
use humhub\modules\content\models\Content;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
|
||||
class ScheduleOptionsForm extends Model
|
||||
{
|
||||
public ?Content $content = null;
|
||||
public bool $enabled = false;
|
||||
public ?string $date = null;
|
||||
public ?string $time = null;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
if ($this->hasContent() && $this->content->scheduled_at !== null) {
|
||||
$this->enabled = $this->content->state == Content::STATE_SCHEDULED;
|
||||
$this->date = $this->content->scheduled_at;
|
||||
}
|
||||
|
||||
if ($this->date === null) {
|
||||
$this->date = (new DateTime('tomorrow'))->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
if ($this->time === null) {
|
||||
$this->initTime();
|
||||
}
|
||||
}
|
||||
|
||||
private function initTime()
|
||||
{
|
||||
if ($this->date === null) {
|
||||
$this->time = '';
|
||||
} else {
|
||||
$scheduledDateTime = new DateTime($this->date, new DateTimeZone('UTC'));
|
||||
$this->time = Yii::$app->formatter->asTime($scheduledDateTime, 'short');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
['enabled', 'boolean'],
|
||||
['date', DbDateValidator::class, 'timeAttribute' => 'time'],
|
||||
['time', 'date', 'type' => 'time', 'format' => Yii::$app->formatter->isShowMeridiem() ? 'h:mm a' : 'php:H:i']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function attributeLabels()
|
||||
{
|
||||
return [
|
||||
'enabled' => Yii::t('ContentModule.base', 'Activate scheduling')
|
||||
];
|
||||
}
|
||||
|
||||
public function load($data, $formName = null)
|
||||
{
|
||||
if (!parent::load($data, $formName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->isSubmitted() && !$this->hasContent()) {
|
||||
$this->normalizeDate();
|
||||
$this->initTime();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->validate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasContent()) {
|
||||
if ($this->enabled) {
|
||||
$this->content->setState(Content::STATE_SCHEDULED, ['scheduled_at' => $this->date]);
|
||||
} else {
|
||||
$this->content->setState(Content::STATE_DRAFT);
|
||||
}
|
||||
return $this->content->save();
|
||||
}
|
||||
|
||||
return $this->isSubmitted();
|
||||
}
|
||||
|
||||
public function getStateTitle(): string
|
||||
{
|
||||
return Yii::t('ContentModule.base', 'Scheduled at {dateTime}', [
|
||||
'dateTime' => Yii::$app->formatter->asDatetime($this->date, 'short')
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasContent(): bool
|
||||
{
|
||||
return $this->content instanceof Content && !$this->content->isNewRecord;
|
||||
}
|
||||
|
||||
public function isSubmitted(): bool
|
||||
{
|
||||
return Yii::$app->request->post('state') == Content::STATE_SCHEDULED;
|
||||
}
|
||||
|
||||
private function normalizeDate()
|
||||
{
|
||||
if ($this->date === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$datetime = new DateTime('now', new DateTimeZone('UTC'));
|
||||
$datetime->setTimestamp(strtotime($this->date));
|
||||
$this->date = $datetime->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ humhub.module('content.form', function(module, require, $) {
|
||||
var event = require('event');
|
||||
var Widget = require('ui.widget').Widget;
|
||||
var loader = require('ui.loader');
|
||||
var modal = require('ui.modal');
|
||||
|
||||
var instance;
|
||||
|
||||
@ -28,16 +29,15 @@ humhub.module('content.form', function(module, require, $) {
|
||||
|
||||
this.setDefaultVisibility();
|
||||
this.$.fadeIn('fast');
|
||||
this.showMenu();
|
||||
|
||||
if(!module.config['disabled']) {
|
||||
var that = this;
|
||||
$('#contentFormBody').on('click.humhub:content:form dragover.humhub:content:form', function(evt) {
|
||||
// Prevent fading in for topic remove button clicks
|
||||
if($(evt.target).closest('.topic-remove-label').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
that.showMenu();
|
||||
$('.contentForm_options').fadeIn();
|
||||
});
|
||||
} else {
|
||||
@ -96,6 +96,7 @@ humhub.module('content.form', function(module, require, $) {
|
||||
this.setDefaultVisibility();
|
||||
this.resetFilePreview();
|
||||
this.resetFileUpload();
|
||||
this.resetState();
|
||||
|
||||
$('#public').attr('checked', false);
|
||||
$('#contentFormBody').find('.humhub-ui-richtext').trigger('clear');
|
||||
@ -186,6 +187,93 @@ humhub.module('content.form', function(module, require, $) {
|
||||
}
|
||||
};
|
||||
|
||||
CreateForm.prototype.changeState = function(state, title) {
|
||||
const stateInput = this.$.find('input[name=state]');
|
||||
let stateLabel = this.$.find('.label-content-state');
|
||||
|
||||
if (!stateLabel.length) {
|
||||
stateLabel = $('<span>').addClass('label label-warning label-content-state');
|
||||
this.$.find('.label-container').append(stateLabel);
|
||||
}
|
||||
|
||||
if (stateInput.data('initial') === undefined) {
|
||||
stateInput.data('initial', stateInput.val());
|
||||
}
|
||||
|
||||
if (typeof(state) === 'object') {
|
||||
title = state.$target.data('state-title');
|
||||
state = state.$target.data('state');
|
||||
if (stateInput.val() == state) {
|
||||
return this.resetState();
|
||||
}
|
||||
}
|
||||
|
||||
stateInput.val(state);
|
||||
stateLabel.show().html(title);
|
||||
}
|
||||
|
||||
CreateForm.prototype.resetState = function() {
|
||||
const stateInput = this.$.find('input[name=state]');
|
||||
if (stateInput.data('initial') !== undefined) {
|
||||
stateInput.val(stateInput.data('initial'));
|
||||
}
|
||||
this.$.find('input[name^=scheduled]').remove();
|
||||
this.$.find('.label-content-state').hide();
|
||||
}
|
||||
|
||||
CreateForm.prototype.scheduleOptions = function(evt) {
|
||||
const that = this;
|
||||
const modalGlobal = modal.global.$;
|
||||
const scheduledDate = that.$.find('input[name=scheduledDate]');
|
||||
const data = {};
|
||||
|
||||
if (scheduledDate.length) {
|
||||
data.ScheduleOptionsForm = {
|
||||
enabled: 1,
|
||||
date: scheduledDate.val()
|
||||
};
|
||||
}
|
||||
|
||||
modal.post(evt, {data}).then(function () {
|
||||
modalGlobal.one('submitted', function () {
|
||||
if (modalGlobal.find('.has-error').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modalGlobal.find('#scheduleoptionsform-enabled').is(':checked')) {
|
||||
that.changeState(modalGlobal.find('input[name=state]').val(), modalGlobal.find('input[name=stateTitle]').val());
|
||||
that.setScheduleOption('scheduledDate', modalGlobal.find('input[name=scheduledDate]').val());
|
||||
} else {
|
||||
that.resetState();
|
||||
that.resetScheduleOption('scheduledDate');
|
||||
}
|
||||
|
||||
modal.global.close(true);
|
||||
});
|
||||
}).catch(function (e) {
|
||||
module.log.error(e, true);
|
||||
});
|
||||
}
|
||||
|
||||
CreateForm.prototype.setScheduleOption = function(name, value) {
|
||||
let input = this.$.find('input[name=' + name + ']');
|
||||
|
||||
if (value === undefined) {
|
||||
input.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!input.length) {
|
||||
input = $('<input name="' + name + '" type="hidden">');
|
||||
this.$.find('input[name=state]').after(input);
|
||||
}
|
||||
input.val(value);
|
||||
}
|
||||
|
||||
CreateForm.prototype.resetScheduleOption = function(name) {
|
||||
this.setScheduleOption(name);
|
||||
}
|
||||
|
||||
const CreateFormMenu = Widget.extend();
|
||||
|
||||
CreateFormMenu.prototype.init = function() {
|
||||
|
@ -7,12 +7,12 @@
|
||||
humhub.module('content', function (module, require, $) {
|
||||
var client = require('client');
|
||||
var util = require('util');
|
||||
var object = util.object;
|
||||
var string = util.string;
|
||||
var actions = require('action');
|
||||
var Component = actions.Component;
|
||||
var event = require('event');
|
||||
var modal = require('ui.modal');
|
||||
var status = require('ui.status');
|
||||
|
||||
var DATA_CONTENT_KEY = "content-key";
|
||||
var DATA_CONTENT_DELETE_URL = "content-delete-url";
|
||||
@ -130,9 +130,13 @@ humhub.module('content', function (module, require, $) {
|
||||
client.post(deleteUrl, {
|
||||
data: postData
|
||||
}).then(function (response) {
|
||||
that.remove().then(function () {
|
||||
resolve(true);
|
||||
});
|
||||
if (response.response.success) {
|
||||
that.remove().then(function () {
|
||||
resolve(true);
|
||||
});
|
||||
} else {
|
||||
status.error(response.response.error);
|
||||
}
|
||||
}).catch(function (err) {
|
||||
reject(err);
|
||||
}).finally(function () {
|
||||
|
@ -10,6 +10,7 @@ humhub.module('ui.richtext.prosemirror', function(module, require, $) {
|
||||
var client = require('client');
|
||||
var Widget = require('ui.widget').Widget;
|
||||
var additions = require('ui.additions');
|
||||
var event = require('event');
|
||||
|
||||
var MarkdownEditor = prosemirror.MarkdownEditor;
|
||||
var MentionProvider = prosemirror.MentionProvider;
|
||||
@ -74,9 +75,8 @@ humhub.module('ui.richtext.prosemirror', function(module, require, $) {
|
||||
})
|
||||
|
||||
if (this.options.backupInterval) {
|
||||
setInterval(function () {
|
||||
that.backup();
|
||||
}, this.options.backupInterval * 1000);
|
||||
setInterval(() => this.backup(), this.options.backupInterval * 1000);
|
||||
event.on('humhub:content:afterSubmit', () => this.resetBackup());
|
||||
}
|
||||
};
|
||||
|
||||
@ -101,11 +101,14 @@ humhub.module('ui.richtext.prosemirror', function(module, require, $) {
|
||||
return {};
|
||||
}
|
||||
|
||||
RichTextEditor.prototype.backup = function() {
|
||||
RichTextEditor.prototype.backup = function(currentValue) {
|
||||
var inputId = this.getInput().attr('id');
|
||||
var currentValue = this.editor.serialize();
|
||||
var isBackuped = typeof this.backupedValue !== 'undefined';
|
||||
|
||||
if (typeof currentValue === 'undefined') {
|
||||
currentValue = this.editor.serialize();
|
||||
}
|
||||
|
||||
if (!isBackuped && currentValue === '') {
|
||||
// Don't back up first empty value
|
||||
return;
|
||||
@ -132,6 +135,10 @@ humhub.module('ui.richtext.prosemirror', function(module, require, $) {
|
||||
}
|
||||
};
|
||||
|
||||
RichTextEditor.prototype.resetBackup = function() {
|
||||
this.backup('');
|
||||
}
|
||||
|
||||
RichTextEditor.prototype.focus = function() {
|
||||
this.editor.view.focus();
|
||||
};
|
||||
|
@ -19,3 +19,6 @@ modules:
|
||||
url: 'http://localhost:8080/'
|
||||
browser: chrome
|
||||
port: 4444
|
||||
capabilities:
|
||||
chromeOptions:
|
||||
args: [ "--lang=en-US" ]
|
||||
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use content\AcceptanceTester;
|
||||
|
||||
class DraftCest
|
||||
{
|
||||
public function testCreateDraftPost(AcceptanceTester $I)
|
||||
{
|
||||
$I->amSpaceAdmin(false, 3);
|
||||
|
||||
$I->wantTo('create a draft post.');
|
||||
$I->waitForText('What\'s on your mind?');
|
||||
$I->click('#contentFormBody .humhub-ui-richtext[contenteditable]');
|
||||
$I->fillField('#contentFormBody .humhub-ui-richtext[contenteditable]', 'Some Schabernack');
|
||||
$I->click('#contentFormBody ul.preferences');
|
||||
$I->waitForText('Create as draft');
|
||||
$I->click('Create as draft');
|
||||
$I->waitForText('DRAFT', '10', '.label-container');
|
||||
$I->click('#post_submit_button', '#contentFormBody');
|
||||
|
||||
$I->wantTo('ensure draft has a draft badge.');
|
||||
$I->waitForText('DRAFT', '5', '//div[@class="wall-entry"][1]');
|
||||
|
||||
$I->wantTo('ensure draft is not visible for other users.');
|
||||
$I->amUser2(true);
|
||||
$I->amOnSpace3();
|
||||
$I->dontSee('Schabernack');
|
||||
|
||||
$I->wantTo('publish draft');
|
||||
$I->amSpaceAdmin(true, 3);
|
||||
$I->waitForText('Schabernack');
|
||||
$I->click('//div[@class="wall-entry"][1]//ul[contains(@class, "preferences")]');
|
||||
$I->waitForText('Publish draft', '5');
|
||||
$I->click('Publish draft');
|
||||
$I->waitForText('The content has been successfully published.');
|
||||
$I->dontSee('DRAFT');
|
||||
|
||||
$I->wantTo('ensure published draft is now visible for other users.');
|
||||
$I->amUser2(true);
|
||||
$I->amOnSpace3();
|
||||
$I->waitForText('Schabernack');
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
use content\AcceptanceTester;
|
||||
|
||||
class ScheduledCest
|
||||
{
|
||||
const DATE_FORMAT = 'short';
|
||||
|
||||
public function testCreateDraftPost(AcceptanceTester $I)
|
||||
{
|
||||
$I->amSpaceAdmin(false, 3);
|
||||
|
||||
$I->wantTo('create a scheduled post.');
|
||||
$I->waitForText('What\'s on your mind?');
|
||||
$I->click('#contentFormBody .humhub-ui-richtext[contenteditable]');
|
||||
$postContent = 'Sample text for a scheduled post';
|
||||
$I->fillField('#contentFormBody .humhub-ui-richtext[contenteditable]', $postContent);
|
||||
$I->click('#contentFormBody ul.preferences');
|
||||
$datetime = (new Datetime('tomorrow'))->setTime(19, 15);
|
||||
$this->updateSchedulingOptions($I, $datetime);
|
||||
$I->click('#post_submit_button', '#contentFormBody');
|
||||
|
||||
$I->wantTo('ensure the scheduled content has a proper badge.');
|
||||
$I->waitForText($postContent, null, '.wall-entry');
|
||||
$I->see($this->getLabelText($datetime), '//div[@class="wall-entry"][1]');
|
||||
|
||||
$I->wantTo('ensure draft is not visible for other users.');
|
||||
$I->amUser2(true);
|
||||
$I->amOnSpace3();
|
||||
$I->dontSee($postContent);
|
||||
|
||||
$I->wantTo('update scheduled options of the existing content');
|
||||
$I->amSpaceAdmin(true, 3);
|
||||
$I->waitForText($postContent);
|
||||
$I->jsClick('.wall-entry:first .dropdown-toggle');
|
||||
$datetime = (new Datetime('today'))->setTime(7, 45);
|
||||
$this->updateSchedulingOptions($I, $datetime, '.label-state-scheduled');
|
||||
|
||||
$I->wantTo('ensure the scheduled content can be modified to draft');
|
||||
$I->jsClick('.wall-entry:first .dropdown-toggle');
|
||||
$this->disableSchedulingOptions($I);
|
||||
}
|
||||
|
||||
private function getLabelText(?Datetime $datetime = null): string
|
||||
{
|
||||
return $datetime instanceof DateTime
|
||||
? 'SCHEDULED AT ' . Yii::$app->formatter->asDatetime($datetime, self::DATE_FORMAT)
|
||||
: 'DRAFT';
|
||||
}
|
||||
|
||||
private function updateSchedulingOptions(AcceptanceTester $I, ?Datetime $datetime = null, $labelSelector = '.label-content-state')
|
||||
{
|
||||
$I->waitForText('Schedule publication');
|
||||
$I->jsClick('.dropdown.open [data-action-click=scheduleOptions]');
|
||||
$I->waitForText('Scheduling Options', null, '#globalModal');
|
||||
if ($datetime instanceof DateTime) {
|
||||
$I->checkOption('#scheduleoptionsform-enabled');
|
||||
$I->fillField('ScheduleOptionsForm[date]', Yii::$app->formatter->asDate($datetime, self::DATE_FORMAT));
|
||||
$I->fillField('ScheduleOptionsForm[time]', Yii::$app->formatter->asTime($datetime, self::DATE_FORMAT));
|
||||
$I->click('.field-scheduleoptionsform-time');// to unfocus a datepicker in order to make the "Save" button visible
|
||||
} else {
|
||||
$I->uncheckOption('#scheduleoptionsform-enabled');
|
||||
}
|
||||
$I->click('Save');
|
||||
$I->waitForText($this->getLabelText($datetime), 5, $labelSelector);
|
||||
}
|
||||
|
||||
private function disableSchedulingOptions(AcceptanceTester $I)
|
||||
{
|
||||
$this->updateSchedulingOptions($I, null, '.label-state-draft');
|
||||
}
|
||||
}
|
@ -95,6 +95,40 @@ class ContentContainerStreamTest extends HumHubDbTestCase
|
||||
$this->assertTrue(in_array($w2, $ids));
|
||||
}
|
||||
|
||||
public function testDraftContent()
|
||||
{
|
||||
$this->becomeUser('User2');
|
||||
$draft1Id = $this->createPost('Some Draft', Content::VISIBILITY_PRIVATE, Content::STATE_DRAFT);
|
||||
$regular1Id = $this->createPost('Regular 1 by U2', Content::VISIBILITY_PRIVATE,);
|
||||
$this->becomeUser('Admin');
|
||||
$regular2Id = $this->createPost('Regular 2 by Admin', Content::VISIBILITY_PRIVATE);
|
||||
|
||||
$this->becomeUser('User2');
|
||||
$ids = $this->getStreamActionIds($this->space, 2);
|
||||
|
||||
// Check draft is first for Author
|
||||
$this->assertTrue($ids[0] === $draft1Id);
|
||||
|
||||
// Check draft is not visible for other users
|
||||
$this->becomeUser('Admin');
|
||||
$ids = $this->getStreamActionIds($this->space, 5);
|
||||
$this->assertTrue(!in_array($draft1Id, $ids));
|
||||
}
|
||||
|
||||
public function testDeletedContent()
|
||||
{
|
||||
$this->becomeUser('User2');
|
||||
$deleteId = $this->createPost('Something to delete', Content::VISIBILITY_PRIVATE);
|
||||
|
||||
$post = Post::findOne(['id' => $deleteId]);
|
||||
$post->content->softDelete();
|
||||
|
||||
$ids = $this->getStreamActionIds($this->space, 3);
|
||||
|
||||
// Deleted Content should not appear in stream
|
||||
$this->assertTrue(!in_array($deleteId, $ids));
|
||||
}
|
||||
|
||||
private function getStreamActionIds($container, $limit = 4)
|
||||
{
|
||||
$action = new ContentContainerStream('stream', Yii::$app->controller, [
|
||||
@ -107,7 +141,9 @@ class ContentContainerStreamTest extends HumHubDbTestCase
|
||||
|
||||
$wallEntries = $action->getStreamQuery()->all();
|
||||
|
||||
$wallEntryIds = array_map(static function($entry) {return $entry->id; }, $wallEntries);
|
||||
$wallEntryIds = array_map(static function ($entry) {
|
||||
return $entry->id;
|
||||
}, $wallEntries);
|
||||
|
||||
return $wallEntryIds;
|
||||
}
|
||||
@ -122,12 +158,13 @@ class ContentContainerStreamTest extends HumHubDbTestCase
|
||||
return $this->createPost('Public Post', Content::VISIBILITY_PUBLIC);
|
||||
}
|
||||
|
||||
private function createPost($message, $visibility)
|
||||
private function createPost($message, $visibility, $state = Content::STATE_PUBLISHED)
|
||||
{
|
||||
$post = new Post;
|
||||
$post->message = $message;
|
||||
$post->content->setContainer($this->space);
|
||||
$post->content->visibility = $visibility;
|
||||
$post->content->state = $state;
|
||||
$post->save();
|
||||
|
||||
return $post->content->id;
|
||||
|
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.humhub.org/
|
||||
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
|
||||
* @license https://www.humhub.com/licences
|
||||
*/
|
||||
|
||||
namespace tests\codeception\unit\modules\content;
|
||||
|
||||
use DateTime;
|
||||
use humhub\modules\content\jobs\PublishScheduledContents;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\content\widgets\WallCreateContentForm;
|
||||
use humhub\modules\post\models\Post;
|
||||
use humhub\modules\space\models\Space;
|
||||
use tests\codeception\_support\HumHubDbTestCase;
|
||||
use Yii;
|
||||
|
||||
class PublishScheduledContentTest extends HumHubDbTestCase
|
||||
{
|
||||
public function testPublishScheduledContent()
|
||||
{
|
||||
$this->becomeUser('Admin');
|
||||
|
||||
$postA = $this->createScheduledPost('-1 hour');
|
||||
$postB = $this->createScheduledPost('now');
|
||||
$postC = $this->createScheduledPost('1 hour');
|
||||
$postD = $this->createScheduledPost('tomorrow');
|
||||
|
||||
(new PublishScheduledContents())->run();
|
||||
|
||||
$postA = Post::findOne($postA->id);
|
||||
$this->assertEquals(Content::STATE_PUBLISHED, $postA->content->state);
|
||||
|
||||
$postB = Post::findOne($postB->id);
|
||||
$this->assertEquals(Content::STATE_PUBLISHED, $postB->content->state);
|
||||
|
||||
$postC = Post::findOne($postC->id);
|
||||
$this->assertEquals(Content::STATE_SCHEDULED, $postC->content->state);
|
||||
|
||||
$postD = Post::findOne($postD->id);
|
||||
$this->assertEquals(Content::STATE_SCHEDULED, $postD->content->state);
|
||||
}
|
||||
|
||||
private function createScheduledPost($date): Post
|
||||
{
|
||||
$datetime = (new DateTime($date))->format('Y-m-d H:i:s');
|
||||
|
||||
$space = Space::findOne(1);
|
||||
$post = new Post($space, ['message' => 'Post for test scheduling']);
|
||||
Yii::$app->request->setBodyParams([
|
||||
'state' => Content::STATE_SCHEDULED,
|
||||
'scheduledDate' => $datetime
|
||||
]);
|
||||
|
||||
$result = WallCreateContentForm::create($post, $space);
|
||||
$this->assertArrayHasKey('id', $result);
|
||||
$this->assertEquals(Content::STATE_SCHEDULED, $post->content->state);
|
||||
$this->assertEquals($datetime, $post->content->scheduled_at);
|
||||
|
||||
return $post;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.humhub.org/
|
||||
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
|
||||
* @license https://www.humhub.com/licences
|
||||
*/
|
||||
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\content\models\forms\ScheduleOptionsForm;
|
||||
use humhub\modules\ui\form\widgets\ActiveForm;
|
||||
use humhub\modules\ui\form\widgets\DatePicker;
|
||||
use humhub\modules\ui\form\widgets\TimePicker;
|
||||
use humhub\widgets\ModalButton;
|
||||
use humhub\widgets\ModalDialog;
|
||||
|
||||
/* @var ScheduleOptionsForm $scheduleOptions */
|
||||
/* @var bool $disableInputs */
|
||||
?>
|
||||
<?php ModalDialog::begin(['header' => Yii::t('ContentModule.base', '<strong>Scheduling </strong> Options')]) ?>
|
||||
|
||||
<?php $form = ActiveForm::begin() ?>
|
||||
<?= Html::hiddenInput('state', Content::STATE_SCHEDULED) ?>
|
||||
<?= Html::hiddenInput('stateTitle', $scheduleOptions->getStateTitle()) ?>
|
||||
<?= Html::hiddenInput('scheduledDate', $scheduleOptions->date) ?>
|
||||
|
||||
<div class="modal-body">
|
||||
<?= $form->field($scheduleOptions, 'enabled')->checkbox() ?>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-6">
|
||||
<?= $form->field($scheduleOptions, 'date')
|
||||
->widget(DatePicker::class, ['options' => ['disabled' => $disableInputs]])
|
||||
->label(false) ?>
|
||||
</div>
|
||||
<div class="col-sm-3 col-xs-6" style="padding-left:0">
|
||||
<?= $form->field($scheduleOptions, 'time')
|
||||
->widget(TimePicker::class, ['disabled' => $disableInputs])
|
||||
->label(false) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<?= ModalButton::submitModal() ?>
|
||||
<?= ModalButton::cancel() ?>
|
||||
</div>
|
||||
|
||||
<?php ActiveForm::end() ?>
|
||||
|
||||
<?php ModalDialog::end() ?>
|
||||
|
||||
<script <?= Html::nonce() ?>>
|
||||
$('#scheduleoptionsform-enabled').click(function () {
|
||||
$(this).closest('form').find('input[type=text]').prop('disabled', !$(this).is(':checked'));
|
||||
});
|
||||
</script>
|
@ -47,12 +47,12 @@ class ModuleActionButtons extends Widget
|
||||
|
||||
if ($this->module->getContentContainerConfigUrl($this->contentContainer) &&
|
||||
$this->contentContainer->moduleManager->isEnabled($this->module->id)) {
|
||||
$html .= Button::asLink(Yii::t('ContentModule.modules', 'Configure'), $this->module->getContentContainerConfigUrl($this->contentContainer))
|
||||
$html .= Button::asLink(Yii::t('ContentModule.base', 'Configure'), $this->module->getContentContainerConfigUrl($this->contentContainer))
|
||||
->cssClass('btn btn-sm btn-info configure-module-' . $this->module->id);
|
||||
}
|
||||
|
||||
if ($this->contentContainer->moduleManager->canDisable($this->module->id)) {
|
||||
$html .= Button::asLink('<span class="glyphicon glyphicon-ok"></span> ' . Yii::t('ContentModule.modules', 'Activated'), '#')
|
||||
$html .= Button::asLink('<span class="glyphicon glyphicon-ok"></span> ' . Yii::t('ContentModule.base', 'Activated'), '#')
|
||||
->cssClass('btn btn-sm btn-info active disable disable-module-' . $this->module->id)
|
||||
->style($this->contentContainer->moduleManager->isEnabled($this->module->id) ? '' : 'display:none')
|
||||
->options([
|
||||
@ -64,7 +64,7 @@ class ModuleActionButtons extends Widget
|
||||
]);
|
||||
}
|
||||
|
||||
$html .= Button::asLink(Yii::t('ContentModule.modules', 'Enable'), '#')
|
||||
$html .= Button::asLink(Yii::t('ContentModule.base', 'Enable'), '#')
|
||||
->cssClass('btn btn-sm btn-info enable enable-module-' . $this->module->id)
|
||||
->style($this->contentContainer->moduleManager->isEnabled($this->module->id) ? 'display:none' : '')
|
||||
->options([
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace humhub\modules\content\widgets;
|
||||
|
||||
use humhub\components\Widget;
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\content\models\Content;
|
||||
use Yii;
|
||||
use yii\helpers\Url;
|
||||
|
||||
class PublishDraftLink extends Widget
|
||||
{
|
||||
/**
|
||||
* @var \humhub\modules\content\components\ContentActiveRecord
|
||||
*/
|
||||
public $content;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
if ($this->content->content->state !== Content::STATE_DRAFT ||
|
||||
!$this->content->content->canEdit()) {
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$publishUrl = Url::to(['/content/content/publish-draft', 'id' => $this->content->content->id]);
|
||||
|
||||
return Html::tag('li',
|
||||
Html::a(
|
||||
'<i class="fa fa-mail-reply-all"></i> '
|
||||
. Yii::t('ContentModule.base', 'Publish draft'),
|
||||
'#', ['data-action-click' => 'publishDraft', 'data-action-url' => $publishUrl])
|
||||
);
|
||||
}
|
||||
|
||||
}
|
54
protected/humhub/modules/content/widgets/ScheduleLink.php
Normal file
54
protected/humhub/modules/content/widgets/ScheduleLink.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.humhub.org/
|
||||
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
|
||||
* @license https://www.humhub.com/licences
|
||||
*/
|
||||
|
||||
namespace humhub\modules\content\widgets;
|
||||
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\widgets\Link;
|
||||
use Yii;
|
||||
use yii\base\Widget;
|
||||
|
||||
/**
|
||||
* Schedule link for updating the schedule options of Wall Entries.
|
||||
*
|
||||
* @package humhub.modules_core.wall.widgets
|
||||
* @since 1.14
|
||||
*/
|
||||
class ScheduleLink extends Widget
|
||||
{
|
||||
public ContentActiveRecord $contentRecord;
|
||||
public array $allowedStates = [Content::STATE_DRAFT, Content::STATE_SCHEDULED];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$content = $this->contentRecord->content;
|
||||
|
||||
if (!in_array($content->state, $this->allowedStates)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$contentContainer = $content->container;
|
||||
if (!$contentContainer instanceof ContentContainerActiveRecord) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$content->canEdit()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Html::tag('li', Link::withAction(Yii::t('ContentModule.base', 'Schedule publication'),
|
||||
'scheduleOptions',
|
||||
$contentContainer->createUrl('/content/content/schedule-options', ['id' => $content->id]))
|
||||
->icon('clock-o'));
|
||||
}
|
||||
}
|
57
protected/humhub/modules/content/widgets/StateBadge.php
Normal file
57
protected/humhub/modules/content/widgets/StateBadge.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* @link https://www.humhub.org/
|
||||
* @copyright Copyright (c) 2023 HumHub GmbH & Co. KG
|
||||
* @license https://www.humhub.com/licences
|
||||
*/
|
||||
|
||||
namespace humhub\modules\content\widgets;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use humhub\components\Widget;
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use humhub\modules\content\models\Content;
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* Can be used to render an archive icon for archived content.
|
||||
* @package humhub\modules\content\widgets
|
||||
* @since 1.14
|
||||
*/
|
||||
class StateBadge extends Widget
|
||||
{
|
||||
public ?ContentActiveRecord $model;
|
||||
|
||||
/**
|
||||
* @throws \yii\base\InvalidConfigException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
if ($this->model === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch ($this->model->content->state) {
|
||||
case Content::STATE_DRAFT:
|
||||
return Html::tag('span', Yii::t('ContentModule.base', 'Draft'),
|
||||
['class' => 'label label-danger label-state-draft']
|
||||
);
|
||||
case Content::STATE_SCHEDULED:
|
||||
$scheduledDateTime = new DateTime($this->model->content->scheduled_at, new DateTimeZone('UTC'));
|
||||
return Html::tag('span', Yii::t('ContentModule.base', 'Scheduled at {dateTime}', [
|
||||
'dateTime' => Yii::$app->formatter->asDatetime($scheduledDateTime, 'short')
|
||||
]),
|
||||
['class' => 'label label-warning label-state-scheduled']
|
||||
);
|
||||
case Content::STATE_DELETED:
|
||||
return Html::tag('span', Yii::t('ContentModule.base', 'Deleted'),
|
||||
['class' => 'label label-danger label-state-deleted']
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
@ -114,6 +114,9 @@ abstract class WallCreateContentForm extends Widget
|
||||
|
||||
$record->content->visibility = $visibility;
|
||||
$record->content->container = $contentContainer;
|
||||
$record->content->setState(Yii::$app->request->post('state'), [
|
||||
'scheduled_at' => Yii::$app->request->post('scheduledDate')
|
||||
]);
|
||||
|
||||
// Handle Notify User Features of ContentFormWidget
|
||||
// ToDo: Check permissions of user guids
|
||||
@ -129,7 +132,7 @@ abstract class WallCreateContentForm extends Widget
|
||||
|
||||
if ($record->save()) {
|
||||
$topics = Yii::$app->request->post('postTopicInput');
|
||||
if(!empty($topics)) {
|
||||
if (!empty($topics)) {
|
||||
Topic::attach($record->content, $topics);
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,7 @@ class WallCreateContentFormFooter extends Widget
|
||||
'canSwitchVisibility' => $this->contentContainer->visibility !== Space::VISIBILITY_NONE && $this->contentContainer->can(CreatePublicContent::class),
|
||||
'fileHandlers' => FileHandlerCollection::getByType([FileHandlerCollection::TYPE_IMPORT, FileHandlerCollection::TYPE_CREATE]),
|
||||
'pickerUrl' => $this->contentContainer instanceof Space ? $this->contentContainer->createUrl('/space/membership/search') : null,
|
||||
'scheduleUrl' => $this->contentContainer->createUrl('/content/content/schedule-options')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ namespace humhub\modules\content\widgets\stream;
|
||||
use Exception;
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\content\widgets\ArchiveLink;
|
||||
use humhub\modules\content\widgets\DeleteLink;
|
||||
use humhub\modules\content\widgets\LockCommentsLink;
|
||||
@ -14,6 +15,8 @@ use humhub\modules\content\widgets\MoveContentLink;
|
||||
use humhub\modules\content\widgets\NotificationSwitchLink;
|
||||
use humhub\modules\content\widgets\PermaLink;
|
||||
use humhub\modules\content\widgets\PinLink;
|
||||
use humhub\modules\content\widgets\PublishDraftLink;
|
||||
use humhub\modules\content\widgets\ScheduleLink;
|
||||
use humhub\modules\content\widgets\VisibilityLink;
|
||||
use humhub\modules\dashboard\controllers\DashboardController;
|
||||
use humhub\modules\space\models\Space;
|
||||
@ -324,6 +327,10 @@ abstract class WallStreamEntryWidget extends StreamEntryWidget
|
||||
*/
|
||||
public function getControlsMenuEntries()
|
||||
{
|
||||
if ($this->model->content->state === Content::STATE_DELETED) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if($this->renderOptions->isViewContext([WallStreamEntryOptions::VIEW_CONTEXT_SEARCH])) {
|
||||
return [
|
||||
[PermaLink::class, ['content' => $this->model], ['sortOrder' => 200]]
|
||||
@ -331,10 +338,12 @@ abstract class WallStreamEntryWidget extends StreamEntryWidget
|
||||
}
|
||||
|
||||
$result = [
|
||||
[PublishDraftLink::class, ['content' => $this->model], ['sortOrder' => 100]],
|
||||
[PermaLink::class, ['content' => $this->model], ['sortOrder' => 200]],
|
||||
[DeleteLink::class, ['content' => $this->model], ['sortOrder' => 300]],
|
||||
new DropdownDivider(['sortOrder' => 350]),
|
||||
[VisibilityLink::class, ['contentRecord' => $this->model], ['sortOrder' => 400]],
|
||||
[ScheduleLink::class, ['contentRecord' => $this->model], ['sortOrder' => 420]],
|
||||
[LockCommentsLink::class, ['contentRecord' => $this->model], ['sortOrder' => 450]],
|
||||
[NotificationSwitchLink::class, ['content' => $this->model], ['sortOrder' => 500]],
|
||||
[MoveContentLink::class, ['model' => $this->model], ['sortOrder' => 700]],
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
use humhub\libs\Html;
|
||||
use humhub\modules\content\components\ContentActiveRecord;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\content\widgets\ArchivedIcon;
|
||||
use humhub\modules\content\widgets\LockCommentsIcon;
|
||||
use humhub\modules\content\widgets\StateBadge;
|
||||
use humhub\modules\content\widgets\stream\WallStreamEntryOptions;
|
||||
use humhub\modules\content\widgets\UpdatedIcon;
|
||||
use humhub\modules\content\widgets\VisibilityIcon;
|
||||
@ -29,6 +31,7 @@ $container = $model->content->container;
|
||||
<?php elseif ($renderOptions->isPinned($model)) : ?>
|
||||
<?= Icon::get('map-pin', ['htmlOptions' => ['class' => 'icon-pin tt', 'title' => Yii::t('ContentModule.base', 'Pinned')]]) ?>
|
||||
<?php endif; ?>
|
||||
<?= StateBadge::widget(['model' => $model]); ?>
|
||||
</div>
|
||||
|
||||
<!-- since v1.2 -->
|
||||
|
@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
use humhub\modules\content\components\ContentContainerActiveRecord;
|
||||
use humhub\modules\content\models\Content;
|
||||
use humhub\modules\file\handler\BaseFileHandler;
|
||||
use humhub\modules\file\widgets\FilePreview;
|
||||
use humhub\modules\topic\widgets\TopicPicker;
|
||||
@ -24,6 +25,7 @@ use yii\helpers\Html;
|
||||
/* @var $canSwitchVisibility boolean */
|
||||
/* @var $contentContainer ContentContainerActiveRecord */
|
||||
/* @var $pickerUrl string */
|
||||
/* @var $scheduleUrl string */
|
||||
?>
|
||||
|
||||
<div id="notifyUserContainer" class="form-group" style="margin-top:15px;display:none">
|
||||
@ -64,25 +66,30 @@ use yii\helpers\Html;
|
||||
<?= FileHandlerButtonDropdown::widget(['primaryButton' => $uploadButton, 'handlers' => $fileHandlers, 'cssButtonClass' => 'btn-default']); ?>
|
||||
|
||||
<!-- public checkbox -->
|
||||
<?= Html::checkbox('visibility', '', ['id' => 'contentForm_visibility', 'class' => 'contentForm hidden', 'aria-hidden' => 'true', 'title' => Yii::t('ContentModule.base', 'Content visibility') ]); ?>
|
||||
<?= Html::checkbox('visibility', '', ['id' => 'contentForm_visibility', 'class' => 'contentForm hidden', 'aria-hidden' => 'true']); ?>
|
||||
|
||||
<!-- state data -->
|
||||
<?= Html::hiddenInput('state', Content::STATE_PUBLISHED) ?>
|
||||
|
||||
<!-- content sharing -->
|
||||
<div class="pull-right">
|
||||
|
||||
<span class="label label-info label-public hidden"><?= Yii::t('ContentModule.base', 'Public'); ?></span>
|
||||
<span class="label-container">
|
||||
<span class="label label-info label-public hidden"><?= Yii::t('ContentModule.base', 'Public'); ?></span>
|
||||
</span>
|
||||
|
||||
<ul class="nav nav-pills preferences" style="right:0;top:5px">
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle" style="padding:5px 10px" data-toggle="dropdown" href="#" aria-label="<?= Yii::t('base', 'Toggle post menu'); ?>" aria-haspopup="true">
|
||||
<?= Icon::get('cogs')?>
|
||||
<a class="dropdown-toggle" style="padding:5px 10px" data-toggle="dropdown" href="#"
|
||||
aria-label="<?= Yii::t('base', 'Toggle post menu'); ?>" aria-haspopup="true">
|
||||
<?= Icon::get('cogs') ?>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li>
|
||||
<?= Link::withAction(Yii::t('ContentModule.base', 'Notify members'), 'notifyUser')->icon('bell')?>
|
||||
<?= Link::withAction(Yii::t('ContentModule.base', 'Notify members'), 'notifyUser')->icon('bell') ?>
|
||||
</li>
|
||||
<?php if (TopicPicker::showTopicPicker($contentContainer)) : ?>
|
||||
<li>
|
||||
<?= Link::withAction(Yii::t('ContentModule.base', 'Topics'), 'setTopics')->icon(Yii::$app->getModule('topic')->icon) ?>
|
||||
<?= Link::withAction(Yii::t('ContentModule.base', 'Topics'), 'setTopics')->icon(Yii::$app->getModule('topic')->icon) ?>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if ($canSwitchVisibility): ?>
|
||||
@ -91,6 +98,18 @@ use yii\helpers\Html;
|
||||
->id('contentForm_visibility_entry')->icon('unlock') ?>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li>
|
||||
<?= Link::withAction(Yii::t('ContentModule.base', 'Create as draft'), 'changeState')
|
||||
->icon('edit')
|
||||
->options([
|
||||
'data-state' => Content::STATE_DRAFT,
|
||||
'data-state-title' => Yii::t('ContentModule.base', 'Draft')
|
||||
]) ?>
|
||||
</li>
|
||||
<li>
|
||||
<?= Link::withAction(Yii::t('ContentModule.base', 'Schedule publication'), 'scheduleOptions', $scheduleUrl)
|
||||
->icon('clock-o') ?>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -173,7 +173,7 @@ class Module extends \humhub\components\Module
|
||||
];
|
||||
|
||||
/**
|
||||
* Step: Setup Admin User
|
||||
* Step: Setup Admin User
|
||||
*/
|
||||
$this->configSteps['admin'] = [
|
||||
'sort' => 400,
|
||||
@ -183,7 +183,6 @@ class Module extends \humhub\components\Module
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Step: Sample Data
|
||||
*/
|
||||
|
@ -232,6 +232,7 @@ class ConfigController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sample Data
|
||||
*/
|
||||
@ -541,7 +542,7 @@ class ConfigController extends Controller
|
||||
|
||||
try {
|
||||
Yii::$app->user->logout();
|
||||
} catch (Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
;
|
||||
}
|
||||
return $this->render('finished');
|
||||
|
@ -9,11 +9,12 @@
|
||||
namespace humhub\modules\installer\controllers;
|
||||
|
||||
use humhub\components\access\ControllerAccess;
|
||||
use Yii;
|
||||
use humhub\components\Controller;
|
||||
use humhub\modules\installer\forms\DatabaseForm;
|
||||
use humhub\libs\DynamicConfig;
|
||||
use humhub\modules\admin\widgets\PrerequisitesList;
|
||||
use humhub\modules\installer\forms\CronForm;
|
||||
use humhub\modules\installer\forms\DatabaseForm;
|
||||
use Yii;
|
||||
|
||||
/**
|
||||
* SetupController checks prerequisites and is responsible for database
|
||||
@ -130,7 +131,7 @@ class SetupController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* The init action imports the database structure & inital data
|
||||
* The init action imports the database structure & initial data
|
||||
*/
|
||||
public function actionInit()
|
||||
{
|
||||
@ -152,7 +153,22 @@ class SetupController extends Controller
|
||||
|
||||
$this->module->setDatabaseInstalled();
|
||||
|
||||
return $this->redirect(['/installer/config/index']);
|
||||
return $this->redirect(['/installer/setup/cron']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crontab
|
||||
*/
|
||||
public function actionCron()
|
||||
{
|
||||
return $this->render('cron', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty URLs
|
||||
*/
|
||||
public function actionPrettyUrls()
|
||||
{
|
||||
return $this->render('pretty-urls');
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user