From 24a436ce168778d8cd372664f08e1bbab2e268d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Fri, 28 Jul 2023 16:14:34 +0200 Subject: [PATCH 01/19] [Enh] Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces (#6451) * Introduce Deletable, Editable, Readable, & Viewable Interfaces * Update docs --- CHANGELOG-DEV.md | 1 + MIGRATE-DEV.md | 12 ++ .../interfaces/ArchiveableInterface.php | 28 +++++ .../humhub/interfaces/DeletableInterface.php | 19 +++ .../humhub/interfaces/EditableInterface.php | 27 +++++ .../humhub/interfaces/ReadableInterface.php | 23 ++++ .../humhub/interfaces/ViewableInterface.php | 27 +++++ .../humhub/modules/comment/models/Comment.php | 2 +- .../components/ContentAddonActiveRecord.php | 33 ++++-- .../humhub/modules/content/models/Content.php | 108 +++++++++++------- protected/humhub/modules/file/models/File.php | 27 ++++- 11 files changed, 248 insertions(+), 59 deletions(-) create mode 100644 protected/humhub/interfaces/ArchiveableInterface.php create mode 100644 protected/humhub/interfaces/DeletableInterface.php create mode 100644 protected/humhub/interfaces/EditableInterface.php create mode 100644 protected/humhub/interfaces/ReadableInterface.php create mode 100644 protected/humhub/interfaces/ViewableInterface.php diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index aa27459075..335817f345 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -5,6 +5,7 @@ HumHub Changelog ------------------- - Fix #6461: Test server support to serve web module's `/manifest.json`, `/sw.js`, & `/offline.pwa.html` - Enh #6460: Test server output: print application requests +- Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces - Fix #6423: log.fata in frontend logging is redirected to log.fatal, which did not work - Fix #6220: User Soft Delete doesn't remove third party auth references - Enh #6270: Add tests for SettingsManager diff --git a/MIGRATE-DEV.md b/MIGRATE-DEV.md index 2a99be5597..413b329409 100644 --- a/MIGRATE-DEV.md +++ b/MIGRATE-DEV.md @@ -43,3 +43,15 @@ Version 1.15 (Unreleased) - `humhub\widgets\MarkdownFieldModals` - `humhub\modules\ui\form\widgets\Markdown` - + +Version 1.16 (Unreleased) +------------------------- + +### Deprecations +- `\humhub\modules\content\components\ContentAddonActiveRecord::canWrite()` + +### Type restrictions +- `\humhub\modules\comment\models\Comment` on `canDelete()` +- `\humhub\modules\content\components\ContentAddonActiveRecord` on `canDelete()`, `canRead()`, `canWrite()`, `canEdit()` +- `\humhub\modules\content\models\Content` on `canEdit()`, `canView()` +- `\humhub\modules\file\models\File` on `canRead()`, `canDelete()` diff --git a/protected/humhub/interfaces/ArchiveableInterface.php b/protected/humhub/interfaces/ArchiveableInterface.php new file mode 100644 index 0000000000..70ac8f0756 --- /dev/null +++ b/protected/humhub/interfaces/ArchiveableInterface.php @@ -0,0 +1,28 @@ +message; } - public function canDelete($userId = '') + public function canDelete($userId = ''): bool { if ($userId == '') { diff --git a/protected/humhub/modules/content/components/ContentAddonActiveRecord.php b/protected/humhub/modules/content/components/ContentAddonActiveRecord.php index bcbb6a9526..78f948a873 100644 --- a/protected/humhub/modules/content/components/ContentAddonActiveRecord.php +++ b/protected/humhub/modules/content/components/ContentAddonActiveRecord.php @@ -9,6 +9,9 @@ namespace humhub\modules\content\components; use humhub\components\ActiveRecord; +use humhub\interfaces\DeletableInterface; +use humhub\interfaces\EditableInterface; +use humhub\interfaces\ReadableInterface; use humhub\modules\content\interfaces\ContentOwner; use humhub\modules\content\models\Content; use humhub\modules\content\Module; @@ -36,9 +39,8 @@ use yii\base\Exception; * @package humhub.components * @since 0.5 */ -class ContentAddonActiveRecord extends ActiveRecord implements ContentOwner +class ContentAddonActiveRecord extends ActiveRecord implements ContentOwner, ReadableInterface, EditableInterface, DeletableInterface { - /** * @var boolean also update underlying contents last update stream sorting */ @@ -136,9 +138,10 @@ class ContentAddonActiveRecord extends ActiveRecord implements ContentOwner * Checks if the given / or current user can delete this content. * Currently only the creator can remove. * + * @param null $userId * @return boolean */ - public function canDelete() + public function canDelete($userId = null): bool { if ($this->created_by == Yii::$app->user->id) { return true; @@ -150,11 +153,12 @@ class ContentAddonActiveRecord extends ActiveRecord implements ContentOwner /** * Check if current user can read this object * + * @param string $userId * @return boolean */ - public function canRead() + public function canRead($userId = ""): bool { - return $this->content->canView(); + return $this->content->canView($userId); } /** @@ -162,24 +166,27 @@ class ContentAddonActiveRecord extends ActiveRecord implements ContentOwner * * @return boolean * @deprecated since 1.4 + * @see static::canEdit() */ - public function canWrite() + public function canWrite($userId = "") { - return $this->canEdit(); + return $this->canEdit($userId); } /** * Checks if this record can be edited * - * @param User|null $user the user + * @param User|int|null $user the user * @return boolean * @since 1.4 */ - public function canEdit(User $user = null) + public function canEdit($user = null): bool { if ($user === null && Yii::$app->user->isGuest) { return false; - } elseif ($user === null) { + } + + if ($user === null) { /** @var User $user */ try { $user = Yii::$app->user->getIdentity(); @@ -189,7 +196,11 @@ class ContentAddonActiveRecord extends ActiveRecord implements ContentOwner } } - if ($this->created_by == $user->id) { + if (!$user instanceof User && !($user = User::findOne(['id' => $user]))) { + return false; + } + + if ($this->created_by === $user->id) { return true; } diff --git a/protected/humhub/modules/content/models/Content.php b/protected/humhub/modules/content/models/Content.php index e420e09021..817e373564 100644 --- a/protected/humhub/modules/content/models/Content.php +++ b/protected/humhub/modules/content/models/Content.php @@ -12,8 +12,11 @@ use humhub\components\ActiveRecord; use humhub\components\behaviors\GUID; use humhub\components\behaviors\PolymorphicRelation; use humhub\components\Module; -use humhub\modules\activity\models\Activity; +use humhub\interfaces\ArchiveableInterface; +use humhub\interfaces\EditableInterface; +use humhub\interfaces\ViewableInterface; use humhub\modules\admin\permissions\ManageUsers; +use humhub\modules\content\activities\ContentCreated as ActivitiesContentCreated; use humhub\modules\content\components\ContentActiveRecord; use humhub\modules\content\components\ContentContainerActiveRecord; use humhub\modules\content\components\ContentContainerModule; @@ -22,21 +25,22 @@ use humhub\modules\content\events\ContentStateEvent; use humhub\modules\content\interfaces\ContentOwner; use humhub\modules\content\interfaces\SoftDeletable; use humhub\modules\content\live\NewContent; +use humhub\modules\content\notifications\ContentCreated as NotificationsContentCreated; use humhub\modules\content\permissions\CreatePrivateContent; use humhub\modules\content\permissions\CreatePublicContent; use humhub\modules\content\permissions\ManageContent; use humhub\modules\content\services\ContentStateService; use humhub\modules\content\services\ContentTagService; use humhub\modules\notification\models\Notification; -use humhub\modules\post\models\Post; use humhub\modules\search\libs\SearchHelper; use humhub\modules\space\models\Space; use humhub\modules\user\components\PermissionManager; use humhub\modules\user\helpers\AuthHelper; use humhub\modules\user\models\User; +use Throwable; use Yii; use yii\base\Exception; -use yii\base\InvalidArgumentException; +use yii\db\ActiveQuery; use yii\db\IntegrityException; use yii\helpers\Url; @@ -78,19 +82,24 @@ use yii\helpers\Url; * @property string $updated_at * @property integer $updated_by * @property ContentContainer $contentContainer + * @property-read mixed $contentName + * @property-read mixed $content + * @property-read ActiveQuery $tagRelations + * @property-read ContentActiveRecord $model + * @property-read mixed $contentDescription + * @property-read StateServiceInterface $stateService * @property ContentContainerActiveRecord $container * @mixin PolymorphicRelation * @mixin GUID * @since 0.5 */ -class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletable +class Content extends ActiveRecord implements Movable, ContentOwner, ArchiveableInterface, EditableInterface, ViewableInterface, SoftDeletable { /** * The default stream channel. * @since 1.6 */ - const STREAM_CHANNEL_DEFAULT = 'default'; - + public const STREAM_CHANNEL_DEFAULT = 'default'; /** * A array of user objects which should informed about this new content. @@ -102,25 +111,25 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab /** * @var int The private visibility mode (e.g. for space member content or user profile posts for friends) */ - const VISIBILITY_PRIVATE = 0; + public const VISIBILITY_PRIVATE = 0; /** * @var int Public visibility mode, e.g. content which are visibile for followers */ - const VISIBILITY_PUBLIC = 1; + public const VISIBILITY_PUBLIC = 1; /** * @var int Owner visibility mode, only visible for contentContainer + content owner */ - const VISIBILITY_OWNER = 2; + public 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; + public const STATE_PUBLISHED = 1; + public const STATE_DRAFT = 10; + public const STATE_SCHEDULED = 20; + public const STATE_DELETED = 100; /** * @var ContentContainerActiveRecord the Container (e.g. Space or User) where this content belongs to. @@ -136,7 +145,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab /** * @event Event is used when a Content state is changed. */ - const EVENT_STATE_CHANGED = 'changedState'; + public const EVENT_STATE_CHANGED = 'changedState'; /** * @inheritdoc @@ -340,12 +349,12 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab ); } - \humhub\modules\content\notifications\ContentCreated::instance() + NotificationsContentCreated::instance() ->from($this->createdBy) ->about($contentSource) ->sendBulk($userQuery); - \humhub\modules\content\activities\ContentCreated::instance() + ActivitiesContentCreated::instance() ->from($this->createdBy) ->about($contentSource)->save(); } @@ -367,7 +376,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab */ public function afterDelete() { - // Try delete the underlying object (Post, Question, Task, ...) + // Try to delete the underlying object (Post, Question, Task, ...) $this->resetPolymorphicRelation(); /** @var ContentActiveRecord $record */ @@ -552,19 +561,31 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab * @throws Exception * @throws \yii\base\InvalidConfigException */ - public function canArchive(): bool + public function canArchive($user = null): bool { - // Currently global content can not be archived - if (!$this->getContainer()) { - return $this->canEdit(); - } + $appUser = Yii::$app->user; - // No need to archive content on an archived container, content is marked as archived already - if ($this->getContainer()->isArchived()) { + if ($appUser->isGuest) { return false; } - return $this->getContainer()->permissionManager->can(new ManageContent()); + if ($user === null) { + $user = $appUser->getIdentity(); + } elseif (!($user instanceof User)) { + $user = User::findOne(['id' => $user]); + } + + // Currently global content can not be archived + if (!$container = $this->getContainer()) { + return $this->canEdit($user); + } + + // No need to archive content on an archived container, content is marked as archived already + if ($container->isArchived()) { + return false; + } + + return $container->getPermissionManager($user)->can(new ManageContent()); } /** @@ -584,7 +605,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab /** * {@inheritdoc} - * @throws \Throwable + * @throws Throwable */ public function move(ContentContainerActiveRecord $container = null, $force = false) { @@ -683,7 +704,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab * @param ContentContainerActiveRecord|null $container * @return bool determines if the current user is generally permitted to move content on the given container (or the related container if no container was provided) * @throws IntegrityException - * @throws \Throwable + * @throws Throwable * @throws \yii\base\InvalidConfigException */ public function checkMovePermission(ContentContainerActiveRecord $container = null) @@ -778,7 +799,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab * Relation to ContentContainer model * Note: this is not a Space or User instance! * - * @return \yii\db\ActiveQuery + * @return ActiveQuery * @since 1.1 */ public function getContentContainer() @@ -789,7 +810,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab /** * Returns the ContentTagRelation query. * - * @return \yii\db\ActiveQuery + * @return ActiveQuery * @since 1.2.2 */ public function getTagRelations() @@ -800,7 +821,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab /** * Returns all content related tags ContentTags related to this content. * - * @return \yii\db\ActiveQuery + * @return ActiveQuery * @since 1.2.2 */ public function getTags($tagClass = ContentTag::class) @@ -847,19 +868,21 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab * @return bool can edit/create this content * @throws Exception * @throws IntegrityException - * @throws \Throwable + * @throws Throwable * @throws \yii\base\InvalidConfigException * @since 1.1 */ - public function canEdit($user = null) + public function canEdit($user = null): bool { - if (Yii::$app->user->isGuest) { + $appUser = Yii::$app->user; + + if ($appUser->isGuest) { return false; } if ($user === null) { - $user = Yii::$app->user->getIdentity(); - } else if (!($user instanceof User)) { + $user = $appUser->getIdentity(); + } elseif (!($user instanceof User)) { $user = User::findOne(['id' => $user]); } @@ -877,17 +900,17 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab // Check additional manage permission for the given container if ($this->container) { - if ($model->isNewRecord && $model->hasCreatePermission() && $this->container->getPermissionManager($user)->can($model->getCreatePermission())) { + if (($isNewRecord = $model->isNewRecord) && $model->hasCreatePermission() && $this->container->getPermissionManager($user)->can($model->getCreatePermission())) { return true; } - if (!$model->isNewRecord && $model->hasManagePermission() && $this->container->getPermissionManager($user)->can($model->getManagePermission())) { + if (!$isNewRecord && $model->hasManagePermission() && $this->container->getPermissionManager($user)->can($model->getManagePermission())) { return true; } } // Check if underlying models canEdit implementation - // ToDo: Implement this as interface - if (method_exists($model, 'canEdit') && $model->canEdit($user)) { + // ToDo: Send deprecation waring when not implementing EditableInterface + if (($model instanceof EditableInterface || method_exists($model, 'canEdit')) && $model->canEdit($user)) { return true; } @@ -942,14 +965,14 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab * @param User|integer $user * @return boolean can view this content * @throws Exception - * @throws \Throwable + * @throws Throwable * @since 1.1 */ - public function canView($user = null) + public function canView($user = null): bool { if (!$user && !Yii::$app->user->isGuest) { $user = Yii::$app->user->getIdentity(); - } else if (!$user instanceof User) { + } elseif (!$user instanceof User) { $user = User::findOne(['id' => $user]); } @@ -1078,5 +1101,4 @@ class Content extends ActiveRecord implements Movable, ContentOwner, SoftDeletab { $this->getStateService()->set($state, $options); } - } diff --git a/protected/humhub/modules/file/models/File.php b/protected/humhub/modules/file/models/File.php index 80033b323b..1accfc936e 100644 --- a/protected/humhub/modules/file/models/File.php +++ b/protected/humhub/modules/file/models/File.php @@ -11,6 +11,7 @@ namespace humhub\modules\file\models; use humhub\components\behaviors\GUID; use humhub\components\behaviors\PolymorphicRelation; use humhub\components\ActiveRecord; +use humhub\interfaces\ViewableInterface; use humhub\modules\content\components\ContentActiveRecord; use humhub\modules\content\components\ContentAddonActiveRecord; use humhub\modules\user\models\User; @@ -52,7 +53,7 @@ use yii\web\UploadedFile; * * @since 0.5 */ -class File extends FileCompat +class File extends FileCompat implements ViewableInterface { /** * @event Event that is triggered after a new file content has been stored. @@ -191,10 +192,15 @@ class File extends FileCompat * * If the file is not an instance of HActiveRecordContent or HActiveRecordContentAddon * the file is readable for all. + * * @param string|User $userId + * * @return bool + * @throws IntegrityException + * @throws Throwable + * @throws \yii\base\Exception */ - public function canRead($userId = "") + public function canRead($userId = ""): bool { $object = $this->getPolymorphicRelation(); if ($object !== null && ($object instanceof ContentActiveRecord || $object instanceof ContentAddonActiveRecord)) { @@ -204,13 +210,26 @@ class File extends FileCompat return true; } + public function canView($user = null): bool + { + return $this->canRead($user); + } + /** - * Checks if given file can deleted. + * Checks if given file can be deleted. * * If the file is not an instance of ContentActiveRecord or ContentAddonActiveRecord * the file is readable for all unless there is method canEdit or canDelete implemented. + * + * @param null $userId + * + * @return bool + * @throws IntegrityException + * @throws InvalidConfigException + * @throws Throwable + * @throws \yii\base\Exception */ - public function canDelete($userId = null) + public function canDelete($userId = null): bool { $object = $this->getPolymorphicRelation(); From 582a2cac6c44be6b5b6e803a201f2f1fed78e619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Sat, 12 Aug 2023 14:40:38 +0200 Subject: [PATCH 02/19] Enh: Attempt to show better error messages when DB-Connection is invalid --- CHANGELOG-DEV.md | 4 + index.php | 10 +- .../humhub/commands/MigrateController.php | 15 +- .../humhub/components/console/Application.php | 2 +- protected/humhub/helpers/DatabaseHelper.php | 147 ++++++++++++++++++ protected/humhub/libs/BaseSettingsManager.php | 15 +- .../installer/commands/InstallController.php | 38 ++--- protected/yii | 13 +- 8 files changed, 211 insertions(+), 33 deletions(-) create mode 100644 protected/humhub/helpers/DatabaseHelper.php diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 8865c2f26c..21a275055b 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -1,6 +1,10 @@ HumHub Changelog ================ +1.16.0 (Unreleased) +-------------------------- +- Enh #6512: Show error messages when DB connection configuration is invalid + 1.15.0-beta.2 (Unreleased) -------------------------- - Enh #6478: Add pseudo test class to allow population of DB with standard test data diff --git a/index.php b/index.php index b36348a6d6..9de9a45159 100644 --- a/index.php +++ b/index.php @@ -7,6 +7,8 @@ */ // comment out the following two lines when deployed to production +use humhub\helpers\DatabaseHelper; + defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); @@ -22,4 +24,10 @@ $config = yii\helpers\ArrayHelper::merge( require(__DIR__ . '/protected/config/web.php') ); -(new humhub\components\Application($config))->run(); +try { + (new humhub\components\Application($config))->run(); +} catch (\Throwable $ex) { + if (null === DatabaseHelper::handleConnectionErrors($ex)) { + throw $ex; + } +} diff --git a/protected/humhub/commands/MigrateController.php b/protected/humhub/commands/MigrateController.php index 5e563f0a33..094fbb7707 100644 --- a/protected/humhub/commands/MigrateController.php +++ b/protected/humhub/commands/MigrateController.php @@ -9,6 +9,7 @@ namespace humhub\commands; use humhub\components\Module; +use humhub\helpers\DatabaseHelper; use Yii; use yii\console\Exception; use yii\web\Application; @@ -78,9 +79,17 @@ class MigrateController extends \yii\console\controllers\MigrateController */ public function beforeAction($action) { - // Make sure to define default table storage engine - if (in_array(Yii::$app->db->getDriverName(), ['mysql', 'mysqli'], true)) { - Yii::$app->db->pdo->exec('SET default_storage_engine=' . Yii::$app->params['databaseDefaultStorageEngine']); + // Make sure to define a default table storage engine + $db = Yii::$app->db; + + try { + $db->open(); + } catch (\Throwable $ex) { + DatabaseHelper::handleConnectionErrors($ex); + } + + if (in_array($db->getDriverName(), ['mysql', 'mysqli'], true)) { + $db->pdo->exec('SET default_storage_engine=' . Yii::$app->params['databaseDefaultStorageEngine']); } return parent::beforeAction($action); } diff --git a/protected/humhub/components/console/Application.php b/protected/humhub/components/console/Application.php index 6683daa95f..0c26ba00b8 100644 --- a/protected/humhub/components/console/Application.php +++ b/protected/humhub/components/console/Application.php @@ -59,7 +59,7 @@ class Application extends \yii\console\Application implements \humhub\interfaces )); } - if (BaseSettingsManager::isDatabaseInstalled()) { + if (BaseSettingsManager::isDatabaseInstalled(Yii::$app->params['databaseInstalled'] ?? false)) { $baseUrl = Yii::$app->settings->get('baseUrl'); if (!empty($baseUrl)) { if (Yii::getAlias('@web', false) === false) { diff --git a/protected/humhub/helpers/DatabaseHelper.php b/protected/humhub/helpers/DatabaseHelper.php new file mode 100644 index 0000000000..e4532cc4bd --- /dev/null +++ b/protected/humhub/helpers/DatabaseHelper.php @@ -0,0 +1,147 @@ +getCode()) { + case 2002: + $error = 'Hostname not found.'; + break; + + case 1044: + $error = 'Database not found or not accessible.'; + break; + + case 1049: + $error = 'Database not found.'; + break; + + default: + $error = $ex->getMessage(); + } + + /** + * @see https://www.php.net/manual/en/ref.pdo-odbc.connection.php + * @see https://www.php.net/manual/en/ref.pdo-ibm.connection.php + * @see https://www.php.net/manual/en/ref.pdo-pgsql.connection.php + */ + $dsn = preg_replace( + '@((?<=:|;)(?:user|uid|User ID|pwd|password)=)(.*?)(?=;(?:$|\w+=)|$)@i', + '$1****', + Yii::$app->db->dsn + ); + + try { + $additionalInfo = json_encode([get_class($ex), ...$ex->errorInfo], JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $additionalInfo = 'N/A'; + } + + while ($ex->getPrevious()) { + $ex = $ex->getPrevious(); + } + + $htmlMessage = defined('YII_DEBUG') && YII_DEBUG + ? sprintf(' +

Invalid database configuration

+

%s

+

The following connection string was used:
%s

+
+

Technical information

+

%s

+

%s

+', $error, $dsn, $additionalInfo, $ex) + : sprintf(' +

Invalid database configuration

+

%s

+', $error); + + $txtMessage = defined('YII_DEBUG') && YII_DEBUG + ? sprintf(' +Invalid database configuration +============================== + +%s + +The following connection string was used: +%s + + +Technical information +--------------------- +%s + +%s + +', $error, $dsn, $additionalInfo, $ex) + : sprintf(' +Invalid database configuration +============================== + +%s + +The following connection string was used: +%s + + +Technical information +--------------------- +%s + +', $error, $dsn, $additionalInfo); + + if ($print) { + if ($forcePlainText) { + echo $txtMessage; + } elseif (Yii::$app instanceof \yii\console\Application && Yii::$app->controller instanceof \yii\console\Controller) { + Yii::$app->controller->stderr($txtMessage); + } else { + header("HTTP/1.1 500 Internal Server Error"); + echo $htmlMessage; + } + } + + if (!$die) { + return $txtMessage; + } + + die(1); + } +} diff --git a/protected/humhub/libs/BaseSettingsManager.php b/protected/humhub/libs/BaseSettingsManager.php index 33dc82254e..38f04f48be 100644 --- a/protected/humhub/libs/BaseSettingsManager.php +++ b/protected/humhub/libs/BaseSettingsManager.php @@ -10,12 +10,12 @@ namespace humhub\libs; use humhub\components\SettingActiveRecord; use humhub\exceptions\InvalidArgumentTypeException; +use humhub\helpers\DatabaseHelper; use Stringable; use Yii; use yii\base\Component; use yii\base\InvalidArgumentException; use yii\base\InvalidConfigException; -use yii\db\conditions\LikeCondition; use yii\db\StaleObjectException; use yii\helpers\Json; @@ -303,19 +303,20 @@ abstract class BaseSettingsManager extends Component /** * Checks if settings table exists or application is not installed yet * - * @return bool * @since 1.3 */ - public static function isDatabaseInstalled() + public static function isDatabaseInstalled(bool $dieOnError = false): bool { try { - if (in_array('setting', Yii::$app->db->schema->getTableNames())) { - return true; - } + $db = Yii::$app->db; + $db->open(); } catch (\Exception $ex) { + if ($dieOnError) { + DatabaseHelper::handleConnectionErrors($ex); + } return false; } - return false; + return in_array('setting', $db->schema->getTableNames()); } } diff --git a/protected/humhub/modules/installer/commands/InstallController.php b/protected/humhub/modules/installer/commands/InstallController.php index c6bcca630f..580280eb3f 100644 --- a/protected/humhub/modules/installer/commands/InstallController.php +++ b/protected/humhub/modules/installer/commands/InstallController.php @@ -8,27 +8,28 @@ namespace humhub\modules\installer\commands; +use humhub\helpers\DatabaseHelper; +use humhub\libs\DynamicConfig; +use humhub\libs\UUID; +use humhub\modules\installer\libs\InitialData; +use humhub\modules\user\models\Group; +use humhub\modules\user\models\Password; +use humhub\modules\user\models\User; use Yii; +use yii\base\Exception; use yii\console\Controller; use yii\console\ExitCode; use yii\helpers\Console; -use yii\base\Exception; -use humhub\modules\user\models\User; -use humhub\modules\user\models\Password; -use humhub\modules\user\models\Group; -use humhub\modules\installer\libs\InitialData; -use humhub\libs\UUID; -use humhub\libs\DynamicConfig; /** * Console Install - * + * * Example usage: * php yii installer/write-db-config "$HUMHUB_DB_HOST" "$HUMHUB_DB_NAME" "$HUMHUB_DB_USER" "$HUMHUB_DB_PASSWORD" * php yii installer/install-db * php yii installer/write-site-config "$HUMHUB_NAME" "$HUMHUB_EMAIL" * php yii installer/create-admin-account - * + * */ class InstallController extends Controller { @@ -42,9 +43,9 @@ class InstallController extends Controller return ExitCode::OK; } - + /** - * Tries to open a connection to given db. + * Tries to open a connection to given db. * On success: Writes given settings to config-file and reloads it. * On failure: Throws exception */ @@ -80,12 +81,12 @@ class InstallController extends Controller $this->stdout("Install DB:\n\n", Console::FG_YELLOW); $this->stdout(" * Checking Database Connection\n", Console::FG_YELLOW); - if(!$this->checkDBConnection()){ - throw new Exception("Could not connect to DB!"); + if (true !== $message = $this->checkDBConnection()) { + throw new Exception($message ?? "Could not connect to DB!"); } $this->stdout(" * Installing Database\n", Console::FG_YELLOW); - + Yii::$app->cache->flush(); // Disable max execution time to avoid timeouts during migrations @ini_set('max_execution_time', 0); @@ -119,7 +120,7 @@ class InstallController extends Controller $user->profile->firstname = 'Sys'; $user->profile->lastname = 'Admin'; $user->profile->save(); - + $password = new Password(); $password->user_id = $user->id; $password->setPassword($admin_pass); @@ -179,6 +180,8 @@ class InstallController extends Controller /** * Tries to open global db connection and checks result. + * + * @return true|null|string */ private function checkDBConnection() { @@ -186,10 +189,9 @@ class InstallController extends Controller // call setActive with true to open connection. Yii::$app->db->open(); // return the current connection state. - return Yii::$app->db->getIsActive(); + return Yii::$app->db->getIsActive() ?: null; } catch (Exception $e) { - $this->stderr($e->getMessage()); + return DatabaseHelper::handleConnectionErrors($e, false, false, true); } - return false; } } diff --git a/protected/yii b/protected/yii index ab69eda696..c4378c4c3b 100755 --- a/protected/yii +++ b/protected/yii @@ -8,6 +8,8 @@ * @license http://www.yiiframework.com/license/ */ +use humhub\helpers\DatabaseHelper; + defined('YII_DEBUG') or define('YII_DEBUG', true); // fcgi doesn't have STDIN and STDOUT defined by default @@ -25,6 +27,11 @@ $config = yii\helpers\ArrayHelper::merge( require(__DIR__ . '/config/console.php') ); -$application = new humhub\components\console\Application($config); -$exitCode = $application->run(); -exit($exitCode); +try { + $exitCode = (new humhub\components\console\Application($config))->run(); + exit($exitCode); +} catch (\Throwable $ex) { + if (null === DatabaseHelper::handleConnectionErrors($ex)) { + throw $ex; + } +} From 16210539dab3f6e95adde3cadda0828eebb881ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Thu, 17 Aug 2023 19:06:38 +0200 Subject: [PATCH 03/19] Update CHANGELOG-DEV.md (#6522) --- CHANGELOG-DEV.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index ec0b9ef645..581d906cc2 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -1,6 +1,10 @@ HumHub Changelog ================ +1.16.0 (next) +------------------- +- Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces + 1.15.0-beta.2 (Unreleased) -------------------------- - Enh #6478: Add pseudo test class to allow population of DB with standard test data From 331cc35b8611c179164cd1ef119df2fa9c822cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Thu, 17 Aug 2023 19:19:02 +0200 Subject: [PATCH 04/19] Fix humhub test case would fail on skipped tests (#6516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix skipped test handling * Fix error while sending mails from the queue ``` - ReadableContentQueryTest: Global content guest non guest mode Destroying application Starting application [ConnectionWatcher] watching new connections [Fixtures] Loading fixtures [ConnectionWatcher] watching new connections [ConnectionWatcher] no longer watching new connections [ConnectionWatcher] closing all (0) connections [Fixtures] Done [yii\web\Session::open] 'Session started' [queue] 'Could not execute queued job! Message: Argument 2 passed to Symfony\\Component\\Mime\\Address::__construct() must be of the type string, null given, called in /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php on line 368 Trace:#0 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php(368): Symfony\\Component\\Mime\\Address->__construct() #1 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php(51): yii\\symfonymailer\\Message->convertStringsToAddresses() #2 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/targets/MailTarget.php(71): yii\\symfonymailer\\Message->setFrom() #3 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/targets/BaseTarget.php(161): humhub\\modules\\notification\\targets\\MailTarget->handle() #4 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/components/NotificationManager.php(101): humhub\\modules\\notification\\targets\\BaseTarget->send() #5 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/jobs/SendBulkNotification.php(38): humhub\\modules\\notification\\components\\NotificationManager->sendBulk() #6 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/queue/ActiveJob.php(33): humhub\\modules\\notification\\jobs\\SendBulkNotification->run() #7 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-queue/src/Queue.php(246): humhub\\modules\\queue\\ActiveJob->execute() #8 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/queue/driver/Instant.php(31): yii\\queue\\Queue->handleMessage() #9 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-queue/src/Queue.php(196): humhub\\modules\\queue\\driver\\Instant->pushMessage() #10 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/components/BaseNotification.php(210): yii\\queue\\Queue->push() #11 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(370): humhub\\modules\\notification\\components\\BaseNotification->sendBulk() #12 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(321): humhub\\modules\\content\\models\\Content->notifyContentCreated() #13 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(268): humhub\\modules\\content\\models\\Content->processNewContent() #14 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(615): humhub\\modules\\content\\models\\Content->afterSave() #15 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(570): yii\\db\\ActiveRecord->insertInternal() #16 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/BaseActiveRecord.php(676): yii\\db\\ActiveRecord->insert() #17 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/components/ContentActiveRecord.php(476): yii\\db\\BaseActiveRecord->save() #18 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/post/models/Post.php(97): humhub\\modules\\content\\components\\ContentActiveRecord->afterSave() #19 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(615): humhub\\modules\\post\\models\\Post->afterSave() #20 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(570): yii\\db\\ActiveRecord->insertInternal() #21 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/BaseActiveRecord.php(676): yii\\db\\ActiveRecord->insert() #22 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/tests/codeception/unit/ReadableContentQueryTest.php(70): yii\\db\\BaseActiveRecord->save() #23 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Test/Unit.php(57): tests\\codeception\\unit\\modules\\content\\ReadableContentQueryTest->_before() #24 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/phpunit-wrapper/src/TestCase.php(12): Codeception\\Test\\Unit->_setUp() #25 /home/mdr/projects/humhub/htdocs/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php(60): Codeception\\PHPUnit\\TestCase->setUp() #26 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestCase.php(1207): tests\\codeception\\_support\\HumHubDbTestCase->setUp() #27 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestResult.php(728): PHPUnit\\Framework\\TestCase->runBare() #28 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestCase.php(964): PHPUnit\\Framework\\TestResult->run() #29 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestCase->run() #30 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/phpunit-wrapper/src/Runner.php(117): PHPUnit\\Framework\\TestSuite->run() #31 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/SuiteManager.php(161): Codeception\\PHPUnit\\Runner->doEnhancedRun() #32 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Codecept.php(208): Codeception\\SuiteManager->run() #33 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Codecept.php(162): Codeception\\Codecept->runSuite() #34 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Command/Run.php(401): Codeception\\Codecept->run() #35 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Command/Command.php(298): Codeception\\Command\\Run->execute() #36 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run() #37 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(301): Symfony\\Component\\Console\\Application->doRunCommand() #38 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(171): Symfony\\Component\\Console\\Application->doRun() #39 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Application.php(117): Symfony\\Component\\Console\\Application->run() #40 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/app.php(46): Codeception\\Application->run() #41 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/app.php(47): {closure}() #42 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/codecept(6): require(\'/home/mdr/proje...\') #43 /home/mdr/projects/humhub/htdocs/protected/vendor/bin/codecept(112): include(\'/home/mdr/proje...\') #44 {main}' [queue] 'Could not execute queued job! Message: Argument 2 passed to Symfony\\Component\\Mime\\Address::__construct() must be of the type string, null given, called in /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php on line 368 Trace:#0 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php(368): Symfony\\Component\\Mime\\Address->__construct() #1 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php(51): yii\\symfonymailer\\Message->convertStringsToAddresses() #2 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/targets/MailTarget.php(71): yii\\symfonymailer\\Message->setFrom() #3 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/targets/BaseTarget.php(161): humhub\\modules\\notification\\targets\\MailTarget->handle() #4 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/components/NotificationManager.php(101): humhub\\modules\\notification\\targets\\BaseTarget->send() #5 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/jobs/SendBulkNotification.php(38): humhub\\modules\\notification\\components\\NotificationManager->sendBulk() #6 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/queue/ActiveJob.php(33): humhub\\modules\\notification\\jobs\\SendBulkNotification->run() #7 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-queue/src/Queue.php(246): humhub\\modules\\queue\\ActiveJob->execute() #8 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/queue/driver/Instant.php(31): yii\\queue\\Queue->handleMessage() #9 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-queue/src/Queue.php(196): humhub\\modules\\queue\\driver\\Instant->pushMessage() #10 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/components/BaseNotification.php(210): yii\\queue\\Queue->push() #11 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(370): humhub\\modules\\notification\\components\\BaseNotification->sendBulk() #12 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(321): humhub\\modules\\content\\models\\Content->notifyContentCreated() #13 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(268): humhub\\modules\\content\\models\\Content->processNewContent() #14 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(615): humhub\\modules\\content\\models\\Content->afterSave() #15 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(570): yii\\db\\ActiveRecord->insertInternal() #16 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/BaseActiveRecord.php(676): yii\\db\\ActiveRecord->insert() #17 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/components/ContentActiveRecord.php(476): yii\\db\\BaseActiveRecord->save() #18 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/post/models/Post.php(97): humhub\\modules\\content\\components\\ContentActiveRecord->afterSave() #19 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(615): humhub\\modules\\post\\models\\Post->afterSave() #20 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(570): yii\\db\\ActiveRecord->insertInternal() #21 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/BaseActiveRecord.php(676): yii\\db\\ActiveRecord->insert() #22 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/tests/codeception/unit/ReadableContentQueryTest.php(76): yii\\db\\BaseActiveRecord->save() #23 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Test/Unit.php(57): tests\\codeception\\unit\\modules\\content\\ReadableContentQueryTest->_before() #24 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/phpunit-wrapper/src/TestCase.php(12): Codeception\\Test\\Unit->_setUp() #25 /home/mdr/projects/humhub/htdocs/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php(60): Codeception\\PHPUnit\\TestCase->setUp() #26 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestCase.php(1207): tests\\codeception\\_support\\HumHubDbTestCase->setUp() #27 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestResult.php(728): PHPUnit\\Framework\\TestCase->runBare() #28 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestCase.php(964): PHPUnit\\Framework\\TestResult->run() #29 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestCase->run() #30 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/phpunit-wrapper/src/Runner.php(117): PHPUnit\\Framework\\TestSuite->run() #31 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/SuiteManager.php(161): Codeception\\PHPUnit\\Runner->doEnhancedRun() #32 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Codecept.php(208): Codeception\\SuiteManager->run() #33 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Codecept.php(162): Codeception\\Codecept->runSuite() #34 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Command/Run.php(401): Codeception\\Codecept->run() #35 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Command/Command.php(298): Codeception\\Command\\Run->execute() #36 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run() #37 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(301): Symfony\\Component\\Console\\Application->doRunCommand() #38 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(171): Symfony\\Component\\Console\\Application->doRun() #39 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Application.php(117): Symfony\\Component\\Console\\Application->run() #40 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/app.php(46): Codeception\\Application->run() #41 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/app.php(47): {closure}() #42 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/codecept(6): require(\'/home/mdr/proje...\') #43 /home/mdr/projects/humhub/htdocs/protected/vendor/bin/codecept(112): include(\'/home/mdr/proje...\') #44 {main}' [tests\codeception\_support\HumHubDbTestCase::setUp] Initializing Modules [tests\codeception\_support\HumHubDbTestCase::setUp] Reloading settings [tests\codeception\_support\HumHubDbTestCase::setUp] Flushing cache [tests\codeception\_support\HumHubDbTestCase::setUp] Deleting mails [yii\mail\BaseMailer::send] 'Sending email "Sara Tester just wrote post "Public Space Public Post" in space Space 3" to "user1@example.com"' [yii\mail\BaseMailer::send] 'Sending email "Sara Tester just wrote post "Public Space Private Post" in space Space 3" to "user1@example.com"' [yii\web\User::logout] 'User \'2\' logged out from .' ✔ ReadableContentQueryTest: Global content guest non guest mode (3.06s) ^ ``` rather than ``` - ReadableContentQueryTest: Global content guest non guest mode Destroying application Starting application [ConnectionWatcher] watching new connections [Fixtures] Loading fixtures [ConnectionWatcher] watching new connections [ConnectionWatcher] no longer watching new connections [ConnectionWatcher] closing all (0) connections [Fixtures] Done [tests\codeception\_support\HumHubDbTestCase::setUp] Initializing Modules [tests\codeception\_support\HumHubDbTestCase::setUp] Reloading settings [tests\codeception\_support\HumHubDbTestCase::setUp] Flushing cache [tests\codeception\_support\HumHubDbTestCase::setUp] Deleting mails [yii\web\Session::open] 'Session started' [yii\mail\BaseMailer::send] 'Sending email "Sara Tester just wrote post "Public Space Public Post" in space Space 3" to "user1@example.com"' [yii\mail\BaseMailer::send] 'Sending email "Sara Tester just wrote post "Public Space Private Post" in space Space 3" to "user1@example.com"' [yii\web\User::logout] 'User \'2\' logged out from .' ✔ ReadableContentQueryTest: Global content guest non guest mode (1.51s) Destroying application [ConnectionWatcher] no longer watching new connections [ConnectionWatcher] closing all (0) connections - ``` --- CHANGELOG-DEV.md | 1 + .../codeception/_support/HumHubDbTestCase.php | 55 ++++++------------ .../codeception/_support/HumHubHelper.php | 14 ++--- .../_support/HumHubHelperTrait.php | 58 +++++++++++++++++++ 4 files changed, 82 insertions(+), 46 deletions(-) create mode 100644 protected/humhub/tests/codeception/_support/HumHubHelperTrait.php diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 1f3bbe5af6..8ba8ca484d 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -8,6 +8,7 @@ HumHub Changelog - 1.15.0-beta.2 (Unreleased) -------------------------- +- Fix #6516: Humhub test case would fail on skipped tests - Enh #6478: Add pseudo test class to allow population of DB with standard test data - Enh #6480: Convert assert* and db* methods to static, in line with general usage pattern - Enh #6505: Introduce Application interface; now also fire the `onInit` event when the web application has initialized diff --git a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php index 010c5a9a3e..e241a06494 100644 --- a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php +++ b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php @@ -28,6 +28,7 @@ use humhub\modules\user\tests\codeception\fixtures\GroupPermissionFixture; use humhub\modules\user\tests\codeception\fixtures\UserFullFixture; use humhub\tests\codeception\fixtures\SettingFixture; use humhub\tests\codeception\fixtures\UrlOembedFixture; +use PHPUnit\Framework\SkippedTestError; use TypeError; use Yii; use yii\db\ActiveRecord; @@ -47,6 +48,8 @@ use yii\db\Query; */ class HumHubDbTestCase extends Unit { + use HumHubHelperTrait; + protected $fixtureConfig; public $appConfig = '@tests/codeception/config/unit.php'; @@ -56,53 +59,33 @@ class HumHubDbTestCase extends Unit protected function setUp(): void { - parent::setUp(); + if (Yii::$app === null) { + $c = new \ReflectionClass($this); + $m = $c->getMethod($this->getName(false)); + $doc = $m->getDocComment(); + if (preg_match('#@skip(.*?)\r?\n#s', $doc, $annotations)) { + throw new SkippedTestError("Test was skipped due to @skip annotation: " . (trim($annotations[1]) ?: "[No reason indicated!]"), 0); + } + return; + } $webRoot = dirname(__DIR__, 2) . '/../../..'; Yii::setAlias('@webroot', realpath($webRoot)); - $this->initModules(); - $this->reloadSettings(); - $this->flushCache(); - $this->deleteMails(); - } + $this->initModules(__METHOD__); + $this->reloadSettings(__METHOD__); + $this->flushCache(__METHOD__); + $this->deleteMails(__METHOD__); - protected function reloadSettings() - { - Yii::$app->settings->reload(); - - foreach (Yii::$app->modules as $module) { - if ($module instanceof \humhub\components\Module) { - $module->settings->reload(); - } - } - } - - protected function flushCache() - { - RichTextToShortTextConverter::flushCache(); - RichTextToHtmlConverter::flushCache(); - RichTextToPlainTextConverter::flushCache(); - RichTextToMarkdownConverter::flushCache(); - UrlOembed::flush(); - } - - protected function deleteMails() - { - $path = Yii::getAlias('@runtime/mail'); - $files = glob($path . '/*'); // get all file names - foreach ($files as $file) { // iterate files - if (is_file($file)) { - unlink($file); // delete file - } - } + parent::setUp(); } /** * Initializes modules defined in @tests/codeception/config/test.config.php * Note the config key in test.config.php is modules and not humhubModules! */ - protected function initModules() + protected function initModules(?string $caller = null) { + codecept_debug(sprintf('[%s] Initializing Modules', $caller ?? __METHOD__)); $cfg = Configuration::config(); if (!empty($cfg['humhub_modules'])) { diff --git a/protected/humhub/tests/codeception/_support/HumHubHelper.php b/protected/humhub/tests/codeception/_support/HumHubHelper.php index 44a3ade111..ad00d1370f 100644 --- a/protected/humhub/tests/codeception/_support/HumHubHelper.php +++ b/protected/humhub/tests/codeception/_support/HumHubHelper.php @@ -9,6 +9,7 @@ use humhub\modules\content\widgets\richtext\converter\RichTextToMarkdownConverte use humhub\modules\content\widgets\richtext\converter\RichTextToPlainTextConverter; use humhub\modules\content\widgets\richtext\converter\RichTextToShortTextConverter; use Yii; +use yii\helpers\FileHelper; use yii\symfonymailer\Message; /** @@ -19,22 +20,15 @@ use yii\symfonymailer\Message; */ class HumHubHelper extends Module { + use HumHubHelperTrait; protected $config = []; public function _before(\Codeception\TestInterface $test) { Yii::$app->getUrlManager()->setScriptUrl('/index-test.php'); - $this->flushCache(); - } - - protected function flushCache() - { - RichTextToShortTextConverter::flushCache(); - RichTextToHtmlConverter::flushCache(); - RichTextToPlainTextConverter::flushCache(); - RichTextToMarkdownConverter::flushCache(); - UrlOembed::flush(); + $this->reloadSettings(__METHOD__); + $this->flushCache(__METHOD__); } public function fetchInviteToken($mail) diff --git a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php new file mode 100644 index 0000000000..e051490ae3 --- /dev/null +++ b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php @@ -0,0 +1,58 @@ +cache->flush(); + RichTextToShortTextConverter::flushCache(); + RichTextToHtmlConverter::flushCache(); + RichTextToPlainTextConverter::flushCache(); + RichTextToMarkdownConverter::flushCache(); + UrlOembed::flush(); + } + + protected function reloadSettings(?string $caller = null) + { + codecept_debug(sprintf('[%s] Reloading settings', $caller ?? __METHOD__)); + Yii::$app->settings->reload(); + + foreach (Yii::$app->modules as $module) { + if ($module instanceof \humhub\components\Module) { + $module->settings->reload(); + } + } + } + + protected function deleteMails(?string $caller = null) + { + codecept_debug(sprintf('[%s] Deleting mails', $caller ?? __METHOD__)); + $path = Yii::getAlias('@runtime/mail'); + $files = glob($path . '/*'); // get all file names + foreach ($files as $file) { // iterate files + if (is_file($file)) { + unlink($file); // delete file + } + } + } +} From 11ed785335d313620fcd1d66fd2d9d3b0b48beef Mon Sep 17 00:00:00 2001 From: Lucas Bartholemy Date: Thu, 17 Aug 2023 19:19:38 +0200 Subject: [PATCH 05/19] Update CHANGELOG-DEV.md --- CHANGELOG-DEV.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 8ba8ca484d..234acb25df 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -5,10 +5,10 @@ HumHub Changelog ------------------- - Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces - Enh #6512: Show error messages when DB connection configuration is invalid -- +- Fix #6516: Humhub test case would fail on skipped tests + 1.15.0-beta.2 (Unreleased) -------------------------- -- Fix #6516: Humhub test case would fail on skipped tests - Enh #6478: Add pseudo test class to allow population of DB with standard test data - Enh #6480: Convert assert* and db* methods to static, in line with general usage pattern - Enh #6505: Introduce Application interface; now also fire the `onInit` event when the web application has initialized From 82450b0530da11e2bf6ced2124ad46cf5f878254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Thu, 17 Aug 2023 18:05:43 +0200 Subject: [PATCH 06/19] fixup! Enh: Add Application interface and make sure the `onInit` event is fired in the web application too (#6505) --- protected/humhub/components/Application.php | 59 ++--------------- .../humhub/components/ApplicationTrait.php | 64 +++++++++++++++++++ .../humhub/components/console/Application.php | 53 ++------------- protected/humhub/config/__autocomplete.php | 2 +- ...plication.php => ApplicationInterface.php} | 2 +- 5 files changed, 79 insertions(+), 101 deletions(-) create mode 100644 protected/humhub/components/ApplicationTrait.php rename protected/humhub/interfaces/{Application.php => ApplicationInterface.php} (92%) diff --git a/protected/humhub/components/Application.php b/protected/humhub/components/Application.php index 3848664a13..d6d5669d25 100644 --- a/protected/humhub/components/Application.php +++ b/protected/humhub/components/Application.php @@ -8,54 +8,31 @@ namespace humhub\components; +use humhub\interfaces\ApplicationInterface; use Yii; -use yii\helpers\Url; -use yii\base\Exception; +use Exception; /** + * Description of Application + * * @inheritdoc */ -class Application extends \yii\web\Application implements \humhub\interfaces\Application +class Application extends \yii\web\Application implements ApplicationInterface { + use ApplicationTrait; /** * @inheritdoc */ public $controllerNamespace = 'humhub\\controllers'; - /** - * @var string|array the homepage url - */ - private $_homeUrl = null; - - /** - * @var string Minimum PHP version that recommended to work without issues - */ - public $minRecommendedPhpVersion; - - /** - * @var string Minimum PHP version that may works but probably with small issues - */ - public $minSupportedPhpVersion; - - /** - * @inheritdoc - */ - public function __construct($config = []) - { - // Remove obsolete config params: - unset($config['components']['formatterApp']); - - parent::__construct($config); - } - /** * @inheritdoc */ public function init() { if (version_compare(phpversion(), $this->minSupportedPhpVersion, '<')) { - throw new \Exception(sprintf( + throw new Exception(sprintf( 'Installed PHP Version is too old! Required minimum version is PHP %s (Installed: %s)', $this->minSupportedPhpVersion, phpversion() @@ -84,28 +61,6 @@ class Application extends \yii\web\Application implements \humhub\interfaces\App parent::bootstrap(); } - /** - * @return string the homepage URL - */ - public function getHomeUrl() - { - if ($this->_homeUrl === null) { - return Url::to(['/dashboard/dashboard']); - } elseif (is_array($this->_homeUrl)) { - return Url::to($this->_homeUrl); - } else { - return $this->_homeUrl; - } - } - - /** - * @param string|array $value the homepage URL - */ - public function setHomeUrl($value) - { - $this->_homeUrl = $value; - } - /** * @inheritdoc */ diff --git a/protected/humhub/components/ApplicationTrait.php b/protected/humhub/components/ApplicationTrait.php new file mode 100644 index 0000000000..9de7b68010 --- /dev/null +++ b/protected/humhub/components/ApplicationTrait.php @@ -0,0 +1,64 @@ +_homeUrl === null) { + return Url::to(['/dashboard/dashboard']); + } + + if (is_array($this->_homeUrl)) { + return Url::to($this->_homeUrl); + } + + return $this->_homeUrl; + } + + /** + * @param string|array $value the homepage URL + */ + public function setHomeUrl($value) + { + $this->_homeUrl = $value; + } +} diff --git a/protected/humhub/components/console/Application.php b/protected/humhub/components/console/Application.php index 0c26ba00b8..1f4812258f 100644 --- a/protected/humhub/components/console/Application.php +++ b/protected/humhub/components/console/Application.php @@ -8,43 +8,25 @@ namespace humhub\components\console; +use humhub\components\ApplicationTrait; +use humhub\interfaces\ApplicationInterface; use humhub\libs\BaseSettingsManager; use Yii; use yii\console\Exception; -use yii\helpers\Url; /** * Description of Application * - * @author luke + * @inheritdoc */ -class Application extends \yii\console\Application implements \humhub\interfaces\Application +class Application extends \yii\console\Application implements ApplicationInterface { - /** - * @var string|array the homepage url - */ - private $_homeUrl = null; - - /** - * @var string Minimum PHP version that recommended to work without issues - */ - public $minRecommendedPhpVersion; - - /** - * @var string Minimum PHP version that may works but probably with small issues - */ - public $minSupportedPhpVersion; + use ApplicationTrait; /** * @inheritdoc */ - public function __construct($config = []) - { - // Remove obsolete config params: - unset($config['components']['formatterApp']); - - parent::__construct($config); - } + public $controllerNamespace = 'humhub\\controllers'; /** * @inheritdoc @@ -102,27 +84,4 @@ class Application extends \yii\console\Application implements \humhub\interfaces 'fixture' => 'yii\console\controllers\FixtureController', ]; } - - /** - * @return string the homepage URL - */ - public function getHomeUrl() - { - if ($this->_homeUrl === null) { - return Url::to(['/dashboard/dashboard']); - } elseif (is_array($this->_homeUrl)) { - return Url::to($this->_homeUrl); - } else { - return $this->_homeUrl; - } - } - - /** - * @param string|array $value the homepage URL - */ - public function setHomeUrl($value) - { - $this->_homeUrl = $value; - } - } diff --git a/protected/humhub/config/__autocomplete.php b/protected/humhub/config/__autocomplete.php index 77fb803dd1..862a70aae2 100644 --- a/protected/humhub/config/__autocomplete.php +++ b/protected/humhub/config/__autocomplete.php @@ -13,7 +13,7 @@ */ class Yii { /** - * @var \yii\web\Application|\yii\console\Application|\humhub\components\Application|\humhub\components\console\Application|\humhub\interfaces\Application|__Application|__WebApplication + * @var \yii\web\Application|\yii\console\Application|\humhub\components\Application|\humhub\components\console\Application|\humhub\interfaces\ApplicationInterface|__Application|__WebApplication */ public static $app; } diff --git a/protected/humhub/interfaces/Application.php b/protected/humhub/interfaces/ApplicationInterface.php similarity index 92% rename from protected/humhub/interfaces/Application.php rename to protected/humhub/interfaces/ApplicationInterface.php index 596eb8d0da..a56a8440b3 100644 --- a/protected/humhub/interfaces/Application.php +++ b/protected/humhub/interfaces/ApplicationInterface.php @@ -13,7 +13,7 @@ namespace humhub\interfaces; * * @since 1.15 */ -interface Application +interface ApplicationInterface { /** * @event ActionEvent an event raised on init of application. From 7b19fbeb5484903a53a885cbed52ebf24c3df7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Thu, 17 Aug 2023 18:05:43 +0200 Subject: [PATCH 07/19] Fix: Ensure e-mails would always have a sender address set --- CHANGELOG-DEV.md | 5 +-- .../humhub/components/ApplicationTrait.php | 5 +++ protected/humhub/components/mail/Mailer.php | 31 ++++++++++++++----- .../interfaces/ApplicationInterface.php | 2 ++ .../humhub/interfaces/MailerInterface.php | 13 ++++++++ .../notification/targets/MailTarget.php | 1 - .../tests/codeception/_support/TestMailer.php | 25 +++++++++++++++ .../tests/codeception/config/config.php | 8 +++++ 8 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 protected/humhub/interfaces/MailerInterface.php create mode 100644 protected/humhub/tests/codeception/_support/TestMailer.php diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 234acb25df..dfc9dc5362 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -3,9 +3,10 @@ HumHub Changelog 1.16.0 (Unreleased) ------------------- -- Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces -- Enh #6512: Show error messages when DB connection configuration is invalid +- Fix #6519: Ensure e-mails would always have a sender address set - Fix #6516: Humhub test case would fail on skipped tests +- Enh #6512: Show error messages when DB connection configuration is invalid +- Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces 1.15.0-beta.2 (Unreleased) -------------------------- diff --git a/protected/humhub/components/ApplicationTrait.php b/protected/humhub/components/ApplicationTrait.php index 9de7b68010..4d63a37bfa 100644 --- a/protected/humhub/components/ApplicationTrait.php +++ b/protected/humhub/components/ApplicationTrait.php @@ -61,4 +61,9 @@ trait ApplicationTrait { $this->_homeUrl = $value; } + + public function getMailer(): MailerInterface + { + return parent::getMailer(); + } } diff --git a/protected/humhub/components/mail/Mailer.php b/protected/humhub/components/mail/Mailer.php index 21b2fb506e..c9f86393aa 100644 --- a/protected/humhub/components/mail/Mailer.php +++ b/protected/humhub/components/mail/Mailer.php @@ -8,8 +8,10 @@ namespace humhub\components\mail; +use humhub\interfaces\MailerInterface; use Symfony\Component\Mime\Crypto\SMimeSigner; use Yii; +use yii\mail\MessageInterface; /** * Mailer implements a mailer based on SymfonyMailer. @@ -18,7 +20,7 @@ use Yii; * @since 1.2 * @author Luke */ -class Mailer extends \yii\symfonymailer\Mailer +class Mailer extends \yii\symfonymailer\Mailer implements MailerInterface { /** * @inheritdoc @@ -68,13 +70,7 @@ class Mailer extends \yii\symfonymailer\Mailer { $message = parent::compose($view, $params); - // Set HumHub default from values - if (empty($message->getFrom())) { - $message->setFrom([Yii::$app->settings->get('mailer.systemEmailAddress') => Yii::$app->settings->get('mailer.systemEmailName')]); - if ($replyTo = Yii::$app->settings->get('mailer.systemEmailReplyTo')) { - $message->setReplyTo($replyTo); - } - } + self::ensureHumHubDefaultFromValues($message); if ($this->signingCertificatePath !== null && $this->signingPrivateKeyPath !== null) { if ($this->signer === null) { @@ -92,6 +88,25 @@ class Mailer extends \yii\symfonymailer\Mailer return $message; } + /** + * @param MessageInterface $message + * + * @return void + */ + public static function ensureHumHubDefaultFromValues(MessageInterface $message): MessageInterface + { + // Set HumHub default from values + if ($message->getFrom()) { + return $message; + } + + $message->setFrom([Yii::$app->settings->get('mailer.systemEmailAddress') => Yii::$app->settings->get('mailer.systemEmailName')]); + if ($replyTo = Yii::$app->settings->get('mailer.systemEmailReplyTo')) { + $message->setReplyTo($replyTo); + } + + return $message; + } /** * @inheritdoc diff --git a/protected/humhub/interfaces/ApplicationInterface.php b/protected/humhub/interfaces/ApplicationInterface.php index a56a8440b3..82be964e89 100644 --- a/protected/humhub/interfaces/ApplicationInterface.php +++ b/protected/humhub/interfaces/ApplicationInterface.php @@ -19,4 +19,6 @@ interface ApplicationInterface * @event ActionEvent an event raised on init of application. */ public const EVENT_ON_INIT = 'onInit'; + + public function getMailer(): MailerInterface; } diff --git a/protected/humhub/interfaces/MailerInterface.php b/protected/humhub/interfaces/MailerInterface.php new file mode 100644 index 0000000000..3afd331214 --- /dev/null +++ b/protected/humhub/interfaces/MailerInterface.php @@ -0,0 +1,13 @@ +getViewParams()); $mail = Yii::$app->mailer->compose($this->view, $viewParams) - ->setFrom([Yii::$app->settings->get('mailer.systemEmailAddress') => Yii::$app->settings->get('mailer.systemEmailName')]) ->setTo($recipient->email) ->setSubject(str_replace("\n", " ", trim($notification->getMailSubject()))); if ($replyTo = Yii::$app->settings->get('mailer.systemEmailReplyTo')) { diff --git a/protected/humhub/tests/codeception/_support/TestMailer.php b/protected/humhub/tests/codeception/_support/TestMailer.php new file mode 100644 index 0000000000..919dd04c30 --- /dev/null +++ b/protected/humhub/tests/codeception/_support/TestMailer.php @@ -0,0 +1,25 @@ + 'HumHub Test', 'language' => 'en-US', @@ -32,6 +33,13 @@ $default = [ 'scriptUrl' => '/index-test.php', ], ], + 'container' => [ + 'definitions' => [ + \Codeception\Lib\Connector\Yii2\TestMailer::class => [ + 'class' => \tests\codeception\_support\TestMailer::class, + ] + ] + ], 'modules' => [ 'user' => [ 'passwordStrength' => [ From 89997efff703eff8f29bbdaf07fef45255388606 Mon Sep 17 00:00:00 2001 From: Lucas Bartholemy Date: Thu, 17 Aug 2023 20:25:25 +0200 Subject: [PATCH 08/19] Default stream sort by `created_at` instead of `id` (#5315) * Allow to initialise filters from current request * Expand filters area on page loading if they are requested from url * Update URL in address bar on change checkbox filters * Update URL in address bar on change radio filters * Update URL in address bar on change picker filters * Improve updating of URL in address bar on change filters * Optimized stream sorting idx * Migration fix * Test PR * Updated testfixtures * Fix `stream_sort_date` format * Update CHANGELOG-DEV.md * Fix stream query selection for records with same created date --------- Co-authored-by: yurabakhtin --- CHANGELOG-DEV.md | 1 + .../m210928_162609_stream_sort_idx.php | 43 +++++++++++++++++++ .../humhub/modules/content/models/Content.php | 4 +- .../modules/stream/models/StreamQuery.php | 24 +++++++++-- 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 protected/humhub/modules/content/migrations/m210928_162609_stream_sort_idx.php diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 234acb25df..d98d406887 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -26,6 +26,7 @@ HumHub Changelog - Enh #6498: Implement option "disabled" for picker fields - Enh #6506: Allow event data from module config - Fix #6510: Fix online status position on people page +- Enh #5315: Default stream sort by `created_at` instead of `id` 1.15.0-beta.1 (July 31, 2023) ----------------------------- diff --git a/protected/humhub/modules/content/migrations/m210928_162609_stream_sort_idx.php b/protected/humhub/modules/content/migrations/m210928_162609_stream_sort_idx.php new file mode 100644 index 0000000000..d2cff101ab --- /dev/null +++ b/protected/humhub/modules/content/migrations/m210928_162609_stream_sort_idx.php @@ -0,0 +1,43 @@ +createIndex('idx_stream_created', 'content', 'created_at', false); + $this->createIndex('idx_stream_updated', 'content', 'stream_sort_date', false); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + echo "m210928_162609_stream_sort_idx cannot be reverted.\n"; + + return false; + } + + /* + // Use up()/down() to run migration code without a transaction. + public function up() + { + + } + + public function down() + { + echo "m210928_162609_stream_sort_idx cannot be reverted.\n"; + + return false; + } + */ +} diff --git a/protected/humhub/modules/content/models/Content.php b/protected/humhub/modules/content/models/Content.php index 817e373564..9742d29e4f 100644 --- a/protected/humhub/modules/content/models/Content.php +++ b/protected/humhub/modules/content/models/Content.php @@ -233,7 +233,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, Archiveable $this->created_by ??= Yii::$app->user->id; } - $this->stream_sort_date = date('Y-m-d G:i:s'); + $this->stream_sort_date = date('Y-m-d H:i:s'); if ($this->created_by == "") { throw new Exception("Could not save content without created_by!"); @@ -1050,7 +1050,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, Archiveable */ public function updateStreamSortTime() { - $this->updateAttributes(['stream_sort_date' => date('Y-m-d G:i:s')]); + $this->updateAttributes(['stream_sort_date' => date('Y-m-d H:i:s')]); } /** diff --git a/protected/humhub/modules/stream/models/StreamQuery.php b/protected/humhub/modules/stream/models/StreamQuery.php index ad816d95cf..36a8159de5 100644 --- a/protected/humhub/modules/stream/models/StreamQuery.php +++ b/protected/humhub/modules/stream/models/StreamQuery.php @@ -525,6 +525,9 @@ class StreamQuery extends Model return; } + /** + * Setup Sorting + */ /** * Setup Sorting */ @@ -550,13 +553,28 @@ class StreamQuery extends Model ], [':to' => $this->to]); } } else { - $this->_query->orderBy('content.id DESC'); + $this->_query->orderBy('content.created_at DESC,content.id DESC'); if (!empty($this->from)) { - $this->_query->andWhere("content.id < :from", [':from' => $this->from]); + $this->_query->andWhere( + ['or', + "content.created_at < (SELECT created_at FROM content wd WHERE wd.id=:from)", + ['and', + "content.created_at = (SELECT created_at FROM content wd WHERE wd.id=:from)", + "content.id < :from" + ], + ], [':from' => $this->from]); } elseif (!empty($this->to)) { - $this->_query->andWhere("content.id > :to", [':to' => $this->to]); + $this->_query->andWhere( + ['or', + "content.created_at > (SELECT created_at FROM content wd WHERE wd.id=:to)", + ['and', + "content.created_at = (SELECT created_at FROM content wd WHERE wd.id=:to)", + "content.id > :to" + ], + ], [':to' => $this->to]); } } + } /** From 05930c1f97d7d54353eeeea76404ae2c3d11ce44 Mon Sep 17 00:00:00 2001 From: Lucas Bartholemy Date: Thu, 17 Aug 2023 20:25:52 +0200 Subject: [PATCH 09/19] Update CHANGELOG-DEV.md --- CHANGELOG-DEV.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index d98d406887..db74e40e76 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -6,6 +6,7 @@ HumHub Changelog - Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces - Enh #6512: Show error messages when DB connection configuration is invalid - Fix #6516: Humhub test case would fail on skipped tests +- Enh #5315: Default stream sort by `created_at` instead of `id` 1.15.0-beta.2 (Unreleased) -------------------------- @@ -26,7 +27,6 @@ HumHub Changelog - Enh #6498: Implement option "disabled" for picker fields - Enh #6506: Allow event data from module config - Fix #6510: Fix online status position on people page -- Enh #5315: Default stream sort by `created_at` instead of `id` 1.15.0-beta.1 (July 31, 2023) ----------------------------- From 48ba9def229ed92c8a64c4db048301000c1e9a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Thu, 17 Aug 2023 22:14:59 +0200 Subject: [PATCH 10/19] Enh: Add module manager test and add some improvements and bug fixes (#6511) * Add test for ModuleManager * Add Event tracking capabilities to HumHubDbTestCase * Make module's module.json keywords accessible and searchable * Verify module's event definition * Allow module paths for `enableModules()` * Allow an empty filter list to filter all registered modules * Fix: `canRemoveModule` should not throw an exception. That's the whole point! :-) * Enh: return backup folder from `removeModule()` * Cleanup * Fix: Only test compatible modules in `onMarketplaceAfterFilterModules()` * fixup! Verify module's event definition * Fix: Ensure e-mails would always have a sender address set --- CHANGELOG-DEV.md | 11 +- protected/humhub/components/Module.php | 23 +- protected/humhub/components/ModuleManager.php | 190 +++- .../humhub/modules/marketplace/Events.php | 3 +- .../_data/ModuleConfig/coreModule/config.php | 16 + .../ModuleConfig/installerModule/config.php | 16 + .../ModuleConfig/invalidModule1/config.php | 9 + .../ModuleConfig/invalidModule2/config.php | 13 + .../_data/ModuleConfig/module1/Module.php | 15 + .../_data/ModuleConfig/module1/config.php | 27 + .../_data/ModuleConfig/module1/module.json | 25 + .../_data/ModuleConfig/module2/Module.php | 15 + .../_data/ModuleConfig/module2/config.php | 27 + .../_data/ModuleConfig/module2/module.json | 25 + .../codeception/_support/HumHubDbTestCase.php | 22 + .../unit/components/ModuleManagerMock.php | 29 + .../unit/components/ModuleManagerTest.php | 935 ++++++++++++++++++ .../unit/components/SettingsManagerTest.php | 2 +- .../bootstrap/ModuleAutoLoaderTest.php | 48 +- 19 files changed, 1377 insertions(+), 74 deletions(-) create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/coreModule/config.php create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/installerModule/config.php create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/invalidModule1/config.php create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/invalidModule2/config.php create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/module1/Module.php create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/module1/config.php create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/module1/module.json create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/module2/Module.php create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/module2/config.php create mode 100644 protected/humhub/tests/codeception/_data/ModuleConfig/module2/module.json create mode 100644 protected/humhub/tests/codeception/unit/components/ModuleManagerMock.php create mode 100644 protected/humhub/tests/codeception/unit/components/ModuleManagerTest.php diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 35054b8e70..65700022e9 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -3,9 +3,16 @@ HumHub Changelog 1.16.0 (Unreleased) ------------------- +- Fix #6511: Only test compatible modules in `onMarketplaceAfterFilterModules()` +- Enh #6511: Backup folder path is now return from `removeModule()` +- Fix #6511: `canRemoveModule` no longer throws an Exception +- Enh #6511: Allow an empty filter list to filter all registered modules +- Enh #6511: Allow module paths for `enableModules()` +- Enh #6511: Verify module's event definition +- Enh #6511: Make module's module.json keywords accessible and searchable +- Enh #6511: Add Event tracking capabilities to HumHubDbTestCase +- Enh #6511: Add test for ModuleManager - Fix #6519: Ensure e-mails would always have a sender address set -- Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces -- Enh #6512: Show error messages when DB connection configuration is invalid - Fix #6516: Humhub test case would fail on skipped tests - Enh #6512: Show error messages when DB connection configuration is invalid - Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces diff --git a/protected/humhub/components/Module.php b/protected/humhub/components/Module.php index f39289fc88..4a10b878ac 100644 --- a/protected/humhub/components/Module.php +++ b/protected/humhub/components/Module.php @@ -24,6 +24,7 @@ use yii\web\AssetBundle; * * @property-read string $name * @property-read string $description + * @property-read array $keywords * @property-read bool $isActivated * @property SettingsManager $settings * @author luke @@ -56,7 +57,7 @@ class Module extends \yii\base\Module } /** - * Returns modules name provided by module.json file + * Returns the module's name provided by module.json file * * @return string Name */ @@ -72,7 +73,7 @@ class Module extends \yii\base\Module } /** - * Returns modules description provided by module.json file + * Returns the module's description provided by module.json file * * @return string Description */ @@ -88,7 +89,7 @@ class Module extends \yii\base\Module } /** - * Returns modules version number provided by module.json file + * Returns the module's version number provided by module.json file * * @return string Version Number */ @@ -120,6 +121,22 @@ class Module extends \yii\base\Module return $url; } + /** + * Returns module's keywords provided by module.json file + * + * @return array List of keywords + */ + public function getKeywords(): array + { + $info = $this->getModuleInfo(); + + if ($info['keywords']) { + return (array)$info['keywords']; + } + + return []; + } + /** * Returns the url of an asset file and publishes all module assets if * the file is not published yet. diff --git a/protected/humhub/components/ModuleManager.php b/protected/humhub/components/ModuleManager.php index dbedbcd242..9df2503928 100644 --- a/protected/humhub/components/ModuleManager.php +++ b/protected/humhub/components/ModuleManager.php @@ -1,15 +1,19 @@ enabledModules)) { - return; + return $config['id']; } // Handle Submodules @@ -213,23 +216,106 @@ class ModuleManager extends Component Yii::$app->setModule($config['id'], $moduleConfig); // Register Event Handlers - if (isset($config['events'])) { - foreach ($config['events'] as $event) { - $eventClass = $event['class'] ?? $event[0]; - $eventName = $event['event'] ?? $event[1]; - $eventHandler = $event['callback'] ?? $event[2]; - $eventData = $event['data'] ?? $event[3] ?? null; - $eventAppend = filter_var($event['append'] ?? $event[4] ?? true, FILTER_VALIDATE_BOOLEAN); - if (method_exists($eventHandler[0], $eventHandler[1])) { - Event::on($eventClass, $eventName, $eventHandler, $eventData, $eventAppend); - } - } - } + $this->registerEventHandlers($basePath, $config); // Register Console ControllerMap if (Yii::$app instanceof ConsoleApplication && !(empty($config['consoleControllerMap']))) { Yii::$app->controllerMap = ArrayHelper::merge(Yii::$app->controllerMap, $config['consoleControllerMap']); } + + return $config['id']; + } + + /** + * @throws InvalidConfigException + */ + protected function registerEventHandlers(string $basePath, array &$config): void + { + $events = $config['events'] ?? null; + $strict = $config['strict'] ?? false; + + if (empty($events)) { + return; + } + + $error = static function (string $message, bool $throw = false) use (&$config, $basePath) { + $message = sprintf("Configuration at %s has an invalid event configuration: %s", $basePath, $message); + + if ($throw) { + throw new InvalidConfigException($message); + } + + Yii::warning($message, $config['id']); + }; + + if (!ArrayHelper::isTraversable($events)) { + $error('events must be traversable', $strict); + return; + } + + $getProperty = static function ($event, &$var, string $property, int $index, bool $throw = false) use ($error): bool { + + $var = $event[$property] ?? $event[$index] ?? null; + + if (empty($var)) { + $error(sprintf("required property '%s' missing!", $property), $throw); + return false; + } + + return true; + }; + + foreach ($events as $event) { + if (empty($event)) { + continue; + } + + if (!is_array($event) && !$event instanceof ArrayAccess) { + $error('event configuration must be an array or implement \ArrayAccess', $strict); + break; + } + + if (!$getProperty($event, $eventClass, 'class', 0, $strict)) { + continue; + } + + if (!$getProperty($event, $eventName, 'event', 1, $strict)) { + continue; + } + + if (!$getProperty($event, $eventHandler, 'callback', 2, $strict)) { + continue; + } + + if (!is_array($eventHandler)) { + $error("property 'callback' must be a callable defined in the array-notation denoting a method of a class", $strict); + continue; + } + + if (!is_object($eventHandler[0] ?? null) && !class_exists($eventHandler[0] ?? null)) { + $error(sprintf("class '%s' does not exist.", $eventHandler[0] ?? ''), $strict); + continue; + } + + if (!method_exists($eventHandler[0], $eventHandler[1])) { + $error( + sprintf( + "class '%s' does not have a method called '%s", + is_object($eventHandler[0]) ? get_class($eventHandler[0]) : $eventHandler[0], + $eventHandler[1] + ), + $strict + ); + continue; + } + + $eventData = $event['data'] ?? $event[3] ?? null; + $eventAppend = filter_var($event['append'] ?? $event[4] ?? true, FILTER_VALIDATE_BOOLEAN); + + Event::on($eventClass, $eventName, $eventHandler, $eventData, $eventAppend); + } + + $events = null; } /** @@ -283,17 +369,18 @@ class ModuleManager extends Component /** * Filter modules by keyword and by additional filters from module event * - * @param Module[] $modules - * @param array $filters + * @param Module[]|null $modules + * @param array|ArrayAccess $filters = ['keyword' => 'search term'] + * * @return Module[] */ - public function filterModules(array $modules, $filters = []): array + public function filterModules(?array $modules, $filters = []): array { - $filters = array_merge([ - 'keyword' => null, - ], $filters); + if (!$filters instanceof ArrayAccess && !is_array($filters)) { + throw new InvalidArgumentTypeException(__METHOD__, [2 => '$filters'], ['array', ArrayAccess::class], $filters); + } - $modules = $this->filterModulesByKeyword($modules, $filters['keyword']); + $modules = $this->filterModulesByKeyword($modules, $filters['keyword'] ?? null); $modulesEvent = new ModulesEvent(['modules' => $modules]); $this->trigger(static::EVENT_AFTER_FILTER_MODULES, $modulesEvent); @@ -304,12 +391,14 @@ class ModuleManager extends Component /** * Filter modules by keyword * - * @param Module[] $modules + * @param Module[]|null $modules list of modules, defaulting to installed non-core modules * @param null|string $keyword * @return Module[] */ - public function filterModulesByKeyword(array $modules, $keyword = null): array + public function filterModulesByKeyword(?array $modules, $keyword = null): array { + $modules ??= $this->getModules(); + if ($keyword === null) { $keyword = Yii::$app->request->get('keyword', ''); } @@ -321,11 +410,16 @@ class ModuleManager extends Component foreach ($modules as $id => $module) { /* @var Module $module */ $searchFields = [$id]; - if (isset($module->name)) { - $searchFields[] = $module->name; + if ($searchField = $module->getName()) { + $searchFields[] = $searchField; } - if (isset($module->description)) { - $searchFields[] = $module->description; + + if ($searchField = $module->getDescription()) { + $searchFields[] = $searchField; + } + + if ($searchField = $module->getKeywords()) { + array_push($searchFields, ...$searchField); } $keywordFound = false; @@ -396,6 +490,10 @@ class ModuleManager extends Component */ public function getModule($id, $throwOnMissingModule = true) { + if ($id instanceof Module) { + return $id; + } + // Enabled Module if (Yii::$app->hasModule($id)) { return Yii::$app->getModule($id, true); @@ -407,6 +505,10 @@ class ModuleManager extends Component return Yii::createObject($class, [$id, Yii::$app]); } + if (is_dir($id) && is_file($id . '/config.php')) { + return $this->getModule($this->register($id)); + } + if ($throwOnMissingModule) { throw new Exception('Could not find/load requested module: ' . $id); } @@ -423,15 +525,16 @@ class ModuleManager extends Component } /** - * Checks the module can removed + * Checks if the module can be removed * * @param string $moduleId - * @return bool - * @throws Exception - */ - public function canRemoveModule($moduleId) + * + * @noinspection PhpDocMissingThrowsInspection + * */ + public function canRemoveModule($moduleId): bool { - $module = $this->getModule($moduleId); + /** @noinspection PhpUnhandledExceptionInspection */ + $module = $this->getModule($moduleId, false); if ($module === null) { return false; @@ -460,11 +563,11 @@ class ModuleManager extends Component * @throws Exception * @throws \yii\base\ErrorException */ - public function removeModule($moduleId, $disableBeforeRemove = true) + public function removeModule($moduleId, $disableBeforeRemove = true): ?string { $module = $this->getModule($moduleId); - if ($module == null) { + if ($module === null) { throw new Exception('Could not load module to remove!'); } @@ -487,10 +590,13 @@ class ModuleManager extends Component FileHelper::copyDirectory($moduleBasePath, $backupFolderName); FileHelper::removeDirectory($moduleBasePath); } else { + $backupFolderName = null; //TODO: Delete directory } $this->flushCache(); + + return $backupFolderName; } /** @@ -517,7 +623,7 @@ class ModuleManager extends Component public function enableModules($modules = []) { foreach ($modules as $module) { - $module = ($module instanceof Module) ? $module : $this->getModule($module); + $module = $this->getModule($module); if ($module != null) { $module->enable(); } @@ -537,11 +643,11 @@ class ModuleManager extends Component $this->trigger(static::EVENT_BEFORE_MODULE_DISABLE, new ModuleEvent(['module' => $module])); $moduleEnabled = ModuleEnabled::findOne(['module_id' => $module->id]); - if ($moduleEnabled != null) { + if ($moduleEnabled !== null) { $moduleEnabled->delete(); } - if (($key = array_search($module->id, $this->enabledModules)) !== false) { + if (($key = array_search($module->id, $this->enabledModules, true)) !== false) { unset($this->enabledModules[$key]); } @@ -557,8 +663,8 @@ class ModuleManager extends Component public function disableModules($modules = []) { foreach ($modules as $module) { - $module = ($module instanceof Module) ? $module : $this->getModule($module); - if ($module != null) { + $module = $this->getModule($module); + if ($module !== null) { $module->disable(); } } diff --git a/protected/humhub/modules/marketplace/Events.php b/protected/humhub/modules/marketplace/Events.php index b948c373d7..03b76e469f 100644 --- a/protected/humhub/modules/marketplace/Events.php +++ b/protected/humhub/modules/marketplace/Events.php @@ -56,8 +56,7 @@ class Events extends BaseObject } foreach ($event->modules as $m => $module) { - /* @var ModelModule $module */ - if (!$module->getFilterService()->isFiltered()) { + if ($module instanceof ModelModule && !$module->getFilterService()->isFiltered()) { unset($event->modules[$m]); } } diff --git a/protected/humhub/tests/codeception/_data/ModuleConfig/coreModule/config.php b/protected/humhub/tests/codeception/_data/ModuleConfig/coreModule/config.php new file mode 100644 index 0000000000..e9c6bfaa1e --- /dev/null +++ b/protected/humhub/tests/codeception/_data/ModuleConfig/coreModule/config.php @@ -0,0 +1,16 @@ + basename(__DIR__), + 'class' => \humhub\components\Module::class, + 'namespace' => "Some\\Name\\Space", + 'isCoreModule' => true, +]; diff --git a/protected/humhub/tests/codeception/_data/ModuleConfig/installerModule/config.php b/protected/humhub/tests/codeception/_data/ModuleConfig/installerModule/config.php new file mode 100644 index 0000000000..96c6251ff5 --- /dev/null +++ b/protected/humhub/tests/codeception/_data/ModuleConfig/installerModule/config.php @@ -0,0 +1,16 @@ + basename(__DIR__), + 'class' => \humhub\components\Module::class, + 'namespace' => "Some\\Name\\Space", + 'isInstallerModule' => true, +]; diff --git a/protected/humhub/tests/codeception/_data/ModuleConfig/invalidModule1/config.php b/protected/humhub/tests/codeception/_data/ModuleConfig/invalidModule1/config.php new file mode 100644 index 0000000000..30241b8c6c --- /dev/null +++ b/protected/humhub/tests/codeception/_data/ModuleConfig/invalidModule1/config.php @@ -0,0 +1,9 @@ + 'module1', + 'class' => \Some\Name\Space\module1\Module::class, + 'namespace' => "Some\\Name\\Space\\module1", + 'events' => [ + [ + 'class' => \humhub\tests\codeception\unit\components\ModuleManagerTest::class, + 'event' => 'valid', + 'callback' => [ + \humhub\tests\codeception\unit\components\ModuleManagerTest::class, + 'handleEvent', + ], + ], + ] +]; diff --git a/protected/humhub/tests/codeception/_data/ModuleConfig/module1/module.json b/protected/humhub/tests/codeception/_data/ModuleConfig/module1/module.json new file mode 100644 index 0000000000..7691bfc02c --- /dev/null +++ b/protected/humhub/tests/codeception/_data/ModuleConfig/module1/module.json @@ -0,0 +1,25 @@ +{ + "id": "example", + "version": "1.0", + "name": "My Example Module 1", + "description": "My testing module 1.", + "humhub": { + "minVersion": "1.2" + }, + "keywords": ["my", "cool", "one"], + "homepage": "https://www.example.com", + "authors": [ + { + "name": "Tom Coder", + "email": "tc@example.com", + "role": "Developer" + }, + { + "name": "Sarah Mustermann", + "email": "sm@example.com", + "homepage": "http://example.com", + "role": "Translator" + } + ], + "licence": "AGPL-3.0-or-later" +} diff --git a/protected/humhub/tests/codeception/_data/ModuleConfig/module2/Module.php b/protected/humhub/tests/codeception/_data/ModuleConfig/module2/Module.php new file mode 100644 index 0000000000..7c79e31435 --- /dev/null +++ b/protected/humhub/tests/codeception/_data/ModuleConfig/module2/Module.php @@ -0,0 +1,15 @@ + 'module2', + 'class' => \Some\Name\Space\module2\Module::class, + 'namespace' => "Some\\Name\\Space\\module2", + 'events' => [ + [ + 'class' => \humhub\tests\codeception\unit\components\ModuleManagerTest::class, + 'event' => 'valid', + 'callback' => [ + \humhub\tests\codeception\unit\components\ModuleManagerTest::class, + 'handleEvent', + ], + ], + ] +]; diff --git a/protected/humhub/tests/codeception/_data/ModuleConfig/module2/module.json b/protected/humhub/tests/codeception/_data/ModuleConfig/module2/module.json new file mode 100644 index 0000000000..d4fe7b9ec6 --- /dev/null +++ b/protected/humhub/tests/codeception/_data/ModuleConfig/module2/module.json @@ -0,0 +1,25 @@ +{ + "id": "example", + "version": "1.0", + "name": "My Example Module 2", + "description": "My testing module 2.", + "humhub": { + "minVersion": "1.2" + }, + "keywords": ["my", "cool", "two"], + "homepage": "https://www.example.com", + "authors": [ + { + "name": "Tom Coder", + "email": "tc@example.com", + "role": "Developer" + }, + { + "name": "Sarah Mustermann", + "email": "sm@example.com", + "homepage": "http://example.com", + "role": "Translator" + } + ], + "licence": "AGPL-3.0-or-later" +} diff --git a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php index e241a06494..14c89ec24d 100644 --- a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php +++ b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php @@ -31,6 +31,7 @@ use humhub\tests\codeception\fixtures\UrlOembedFixture; use PHPUnit\Framework\SkippedTestError; use TypeError; use Yii; +use yii\base\Event; use yii\db\ActiveRecord; use humhub\modules\activity\models\Activity; use humhub\modules\content\components\ContentContainerPermissionManager; @@ -56,6 +57,7 @@ class HumHubDbTestCase extends Unit public $time; + public array $firedEvents = []; protected function setUp(): void { @@ -256,6 +258,13 @@ class HumHubDbTestCase extends Unit $this->assertEquals($subject, str_replace(["\n", "\r"], '', $message->getSubject())); } + public function assertEvents(array $events, string $message = ''): void + { + static::assertEquals($events, $this->firedEvents, $message); + + $this->firedEvents = []; + } + /** * @param int|null $expected Number of records expected. Null for any number, but not none * @param string|array|ExpressionInterface $tables @@ -431,6 +440,19 @@ class HumHubDbTestCase extends Unit Yii::$app->user->logout(); } + public function handleEvent(Event $event, array $eventData = []) + { + $eventData += [ + 'class' => get_class($event), + 'event' => $event->name, + 'sender' => $event->sender, + 'data' => $event->data, + 'handled' => $event->handled, + ]; + + $this->firedEvents[] = $eventData; + } + /** * @see \yii\db\Connection::createCommand() * @since 1.15 diff --git a/protected/humhub/tests/codeception/unit/components/ModuleManagerMock.php b/protected/humhub/tests/codeception/unit/components/ModuleManagerMock.php new file mode 100644 index 0000000000..4bda92e9a9 --- /dev/null +++ b/protected/humhub/tests/codeception/unit/components/ModuleManagerMock.php @@ -0,0 +1,29 @@ +modules; + } + + public function &myEnabledModules(): array + { + return $this->enabledModules; + } + + public function &myCoreModules(): array + { + return $this->coreModules; + } +} diff --git a/protected/humhub/tests/codeception/unit/components/ModuleManagerTest.php b/protected/humhub/tests/codeception/unit/components/ModuleManagerTest.php new file mode 100644 index 0000000000..f61f97ee28 --- /dev/null +++ b/protected/humhub/tests/codeception/unit/components/ModuleManagerTest.php @@ -0,0 +1,935 @@ +reset(); + + try { + Yii::$app->set('moduleManager', static::$originalModuleManager); + } catch (InvalidConfigException $e) { + } + + parent::tearDown(); + } + + protected function setUp(): void + { + static::$aliases = Yii::$aliases; + static::$originalModuleManager = Yii::$app->moduleManager; + /** + * prevent calling ModuleEnabled::getEnabledIds() from @see ModuleManager::init() + */ + Yii::$app->params['databaseInstalled'] = false; + + $this->reset(); + + $this->config = null; + $this->moduleId = null; + $this->moduleClass = null; + $this->moduleNS = null; + + parent::setUp(); + } + + public function testBasics() + { + static::assertEquals([], $this->moduleManager->myModules()); + static::assertEquals([], $this->moduleManager->myCoreModules()); + static::assertEquals([], $this->moduleManager->myEnabledModules()); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testGetModule() + { + $this->moduleId = 'non-existing-module'; + + static::assertNull($this->moduleManager->getModule($this->moduleId, false)); + + $this->expectException(Exception::class); + $this->expectExceptionMessage( + 'Could not find/load requested module: ' . $this->moduleId + ); + + $this->moduleManager->getModule($this->moduleId); + } + + public function testRegisterNonExistingModulePath() + { + $basePath = static::$testModuleRoot . '/nonExistingModule'; + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage( + 'Module configuration requires an id and class attribute: ' . $basePath + ); + + $this->moduleManager->register($basePath); + } + + public function testRegisterInvalidModuleConfig() + { + $basePath = static::$testModuleRoot . '/nonExistingModule'; + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage( + 'Module configuration requires an id and class attribute: ' . $basePath + ); + + $this->moduleManager->register($basePath, ['foo' => 'bar']); + } + + public function testRegisterInvalidModuleConfigFromPath() + { + $basePath = static::$testModuleRoot . '/invalidModule1'; + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage( + 'Module configuration requires an id and class attribute: ' . $basePath + ); + + $this->moduleManager->register($basePath); + } + + public function testRegisterInvalidModuleConfigFromPathWithProvidedInvalidConfig() + { + $basePath = static::$testModuleRoot . '/invalidModule1'; + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage( + 'Module configuration requires an id and class attribute: ' . $basePath + ); + + $this->moduleManager->register($basePath, ['foo' => 'bar']); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testRegisterValidModuleConfig() + { + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + $this->registerModule($basePath, $config); + + static::assertEquals([], $this->moduleManager->getModules([ + 'includeCoreModules' => true, + 'enabled' => true, + 'returnClass' => true, + ])); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testRegisterValidModuleConfigFromPath() + { + [$basePath] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + $this->registerModule($basePath, null); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testRegisterEnabledModuleConfig() + { + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + $this->registerModuleAsEnabled($basePath, $config); + + $this->assertModuleActive(); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testRegisterIncompatibleEnabledModuleConfig() + { + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/invalidModule2'); + + $this->registerModuleAsEnabled($basePath, $config); + + $this->assertModuleNotActive(); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testRegisterInstallerModuleWhenInstalled() + { + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/installerModule'); + + static::assertTrue(Yii::$app->params['installed']); + + $this->registerModule($basePath, $config, false); + + $this->assertModuleNotActive(); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testRegisterInstallerModuleWhenNotInstalled() + { + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/installerModule'); + + Yii::$app->params['installed'] = false; + + $this->registerModule($basePath, $config, true); + + $this->assertModuleActive(); + + static::assertFalse($this->moduleManager->isCoreModule($this->moduleId)); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testRegisterCoreModule() + { + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/coreModule'); + + $this->registerModule($basePath, $config, true); + + $this->assertModuleActive(); + } + + public function testInvalidEventConfigEmpty() + { + $this->runEventExceptionTest(null, null); + $this->runEventExceptionTest('', null); + $this->runEventExceptionTest([], null); + } + + public function testInvalidEventConfigAsString() + { + $this->runEventExceptionTest('wrong', 'events must be traversable'); + } + + public function testInvalidEventConfigNotArrayAccess() + { + $this->runEventExceptionTest([ + 'invalid1', + ], "event configuration must be an array or implement \\ArrayAccess"); + } + + public function testInvalidEventConfigMissingClass() + { + $this->runEventExceptionTest([ + [ + 'event' => 'invalid1', + ], + ], "required property 'class' missing!"); + } + + public function testInvalidEventConfigMissingEvent() + { + $this->runEventExceptionTest([ + [ + 'class' => __CLASS__, + ], + ], "required property 'event' missing!"); + } + + public function testInvalidEventConfigMissingCallback() + { + $this->runEventExceptionTest([ + [ + 'class' => __CLASS__, + 'event' => 'invalid1', + ], + ], "required property 'callback' missing!"); + } + + public function testInvalidEventConfigInvalidCallbackAsClosure() + { + $this->runEventExceptionTest([ + [ + 'class' => __CLASS__, + 'event' => 'invalid1', + 'callback' => static function () { + }, + ], + ], "property 'callback' must be a callable defined in the array-notation denoting a method of a class"); + } + + public function testInvalidEventConfigInvalidCallbackWithEmptyClass() + { + $this->runEventExceptionTest([ + [ + 'class' => __CLASS__, + 'event' => 'invalid1', + 'callback' => [null, 'test'], + ], + ], "class '' does not exist."); + } + + public function testInvalidEventConfigInvalidCallbackWithNonExistingClass() + { + $this->runEventExceptionTest([ + [ + 'class' => __CLASS__, + 'event' => 'invalid1', + 'callback' => ['someClass'], + ], + ], "class 'someClass' does not exist."); + } + + public function testInvalidEventConfigInvalidCallbackWithNonExistingMethod() + { + $this->runEventExceptionTest([ + [ + 'class' => __CLASS__, + 'event' => 'invalid1', + 'callback' => [__CLASS__, 'someMethod'], + ], + ], "class 'humhub\\tests\\codeception\\unit\\components\\ModuleManagerTest' does not have a method called 'someMethod"); + } + + /** + * @throws Exception + */ + public function testGetEnabledModules() + { + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + $this->registerModule($basePath, $config); + + static::assertEquals([], $this->moduleManager->getEnabledModules([ + 'returnClass' => true, + ])); + + $this->reset(); + + $this->registerModuleAsEnabled($basePath, $config); + + static::assertEquals([$this->moduleId => $this->moduleClass], $this->moduleManager->getEnabledModules([ + 'returnClass' => true, + ])); + + $moduleManager = Yii::$app->moduleManager; + + static::assertIsArray($modules = $moduleManager->getEnabledModules([ + 'includeCoreModules' => true, + 'enabled' => false, + 'returnClass' => true, + ])); + static::assertEquals(array_flip(ModuleAutoLoaderTest::EXPECTED_CORE_MODULES), $modules); + + static::assertIsArray($modules = $moduleManager->getEnabledModules([ + 'enabled' => false, + 'returnClass' => true, + ])); + static::assertEquals([], $modules); + } + + /** + * @throws InvalidConfigException + * @throws Exception + */ + public function testCanRemoveModule() + { + $this->skipIfMarketplaceNotEnabled(); + + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + // cannot be removed, since it does not exist + static::assertFalse($this->moduleManager->canRemoveModule($this->moduleId)); + + $module = $this->registerModuleAsEnabled($basePath, $config); + + static::assertTrue($this->moduleManager->canRemoveModule($this->moduleId)); + + $module->setBasePath(static::$coreModuleRoot . "/admin"); + + // cannot be removed, since it does not exist within the marketplace dir + static::assertFalse($this->moduleManager->canRemoveModule($this->moduleId)); + } + + /** + * @throws Exception + * @throws ErrorException + * @throws InvalidConfigException + */ + public function testRemoveModule() + { + $this->skipIfMarketplaceNotEnabled(); + + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + $module = $this->registerModuleAsEnabled($basePath, $config); + + $tmp = $this->createTempDir(); + $module->setBasePath($tmp); + + static::assertTrue($this->moduleManager->createBackup); + $backup = $this->moduleManager->removeModule($this->moduleId, false); + + static::assertDirectoryDoesNotExist($tmp); + static::assertDirectoryExists($backup); + + FileHelper::removeDirectory($backup); + + $this->moduleManager->createBackup = false; + + $tmp = $this->createTempDir(); + $module->setBasePath($tmp); + + $backup = $this->moduleManager->removeModule($this->moduleId, false); + + static::assertNull($backup); + static::assertDirectoryExists($tmp); + + FileHelper::removeDirectory($tmp); + } + + /** + * @noinspection MissedFieldInspection + */ + /** + * @throws \Throwable + * @throws InvalidConfigException + * @throws StaleObjectException + * @throws Exception + */ + public function testEnableAndDisableModules() + { + $this->moduleManager->on(ModuleManager::EVENT_BEFORE_MODULE_ENABLE, [$this, 'handleEvent']); + $this->moduleManager->on(ModuleManager::EVENT_AFTER_MODULE_ENABLE, [$this, 'handleEvent']); + $this->moduleManager->on(ModuleManager::EVENT_BEFORE_MODULE_DISABLE, [$this, 'handleEvent']); + $this->moduleManager->on(ModuleManager::EVENT_AFTER_MODULE_DISABLE, [$this, 'handleEvent']); + + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + static::assertRecordCount(0, ModuleEnabled::tableName(), ['module_id' => $this->moduleId]); + + $module = $this->registerModule($basePath, $config); + + static::assertRecordCount(0, ModuleEnabled::tableName(), ['module_id' => $this->moduleId]); + + $this->moduleManager->enable($module); + + static::assertRecordCount(1, ModuleEnabled::tableName(), ['module_id' => $this->moduleId]); + + /** @noinspection MissedFieldInspection */ + $this->assertEvents([ + [ + 'class' => ModuleEvent::class, + 'event' => 'beforeModuleEnabled', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'module' => ['module1' => Module1::class], + ], + [ + 'class' => ModuleEvent::class, + 'event' => 'afterModuleEnabled', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'module' => ['module1' => Module1::class], + ] + ]); + + $this->moduleManager->disable($module); + + static::assertCount(0, $this->moduleManager->myEnabledModules()); + static::assertRecordCount(0, ModuleEnabled::tableName(), ['module_id' => $this->moduleId]); + static::assertNull(Yii::$app->getModule($this->moduleId)); + + /** @noinspection MissedFieldInspection */ + $this->assertEvents([ + [ + 'class' => ModuleEvent::class, + 'event' => 'beforeModuleDisabled', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'module' => ['module1' => Module1::class], + ], + [ + 'class' => ModuleEvent::class, + 'event' => 'afterModuleDisabled', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'module' => ['module1' => Module1::class], + ] + ]); + } + + /** + * @throws Exception + */ + public function testGetModules() + { + $moduleManager = Yii::$app->moduleManager; + static::assertIsArray($modules = $moduleManager->getModules(['returnClass' => true])); + static::assertCount(static::$moduleDirCount, $modules); + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function testEnableModules() + { + $this->moduleManager->on(ModuleManager::EVENT_AFTER_MODULE_ENABLE, [$this, 'handleEvent']); + + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + $module = $this->registerModule($basePath, $config); + + $oldMM = Yii::$app->cache; + Yii::$app->set('moduleManager', $this->moduleManager); + + $this->moduleManager->enableModules([$module, static::$testModuleRoot . '/module2']); + + Yii::$app->set('moduleManager', $oldMM); + + /** @noinspection MissedFieldInspection */ + $this->assertEvents([ + [ + 'class' => ModuleEvent::class, + 'event' => 'afterModuleEnabled', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'module' => ['module1' => Module1::class], + ], + [ + 'class' => ModuleEvent::class, + 'event' => 'afterModuleEnabled', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'module' => ['module2' => Module2::class], + ] + ]); + + $this->expectException(Exception::class); + $this->expectExceptionMessage( + 'Could not find/load requested module: ' . static::$testModuleRoot . '/non-existing-module' + ); + + $this->moduleManager->enableModules([static::$testModuleRoot . '/non-existing-module']); + } + + /** + * @throws InvalidConfigException + */ + public function testFilterModules() + { + Yii::$app->set('moduleManager', $this->moduleManager); + + $this->moduleManager->enableModules([static::$testModuleRoot . '/module1', static::$testModuleRoot . '/module2']); + + static::assertEquals(['module1' => Module1::class, 'module2' => Module2::class], $this->moduleManager->myModules()); + static::assertEquals([], $this->moduleManager->myCoreModules()); + static::assertEquals(['module1', 'module2'], $this->moduleManager->myEnabledModules()); + + $module1 = $this->moduleManager->getModule('module1'); + $module2 = $this->moduleManager->getModule('module2'); + + static::assertEquals([], $this->moduleManager->filterModulesByKeyword(null, 'foo')); + + // match keyword + static::assertEquals(['module1' => $module1], $this->moduleManager->filterModulesByKeyword(null, 'one')); + static::assertEquals(['module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'two')); + static::assertEquals(['module1' => $module1, 'module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'cool')); + + // match name + static::assertEquals(['module1' => $module1], $this->moduleManager->filterModulesByKeyword(null, 'Module 1')); + static::assertEquals(['module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'Module 2')); + static::assertEquals(['module1' => $module1, 'module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'Example')); + + // match description + static::assertEquals(['module1' => $module1], $this->moduleManager->filterModulesByKeyword(null, 'module 1.')); + static::assertEquals(['module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'module 2.')); + static::assertEquals(['module1' => $module1, 'module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'testing')); + + $this->moduleManager->on(ModuleManager::EVENT_AFTER_FILTER_MODULES, [$this, 'handleEvent']); + + static::assertEquals(['module1' => $module1, 'module2' => $module2], $this->moduleManager->filterModules(null, ['foo'])); + + /** @noinspection MissedFieldInspection */ + $this->assertEvents([ + [ + 'class' => ModulesEvent::class, + 'event' => 'afterFilterModules', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'modules' => ['module1' => 'My Example Module 1', 'module2' => 'My Example Module 2'], + ], + ]); + + static::assertEquals([], $this->moduleManager->filterModules(null, ['keyword' => 'foo'])); + + /** @noinspection MissedFieldInspection */ + $this->assertEvents([ + [ + 'class' => ModulesEvent::class, + 'event' => 'afterFilterModules', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'modules' => [], + ] + ]); + + static::assertEquals(['module2' => $module2], $this->moduleManager->filterModules(null, ['keyword' => 'Example Module 2'])); + + /** @noinspection MissedFieldInspection */ + $this->assertEvents([ + [ + 'class' => ModulesEvent::class, + 'event' => 'afterFilterModules', + 'sender' => $this->moduleManager, + 'data' => null, + 'handled' => false, + 'modules' => ['module2' => 'My Example Module 2'], + ] + ]); + + $this->expectException(InvalidArgumentTypeException::class); + $this->expectExceptionMessage( + 'Argument #2 $filters passed to humhub\components\ModuleManager::filterModules must be of type array, ArrayAccess, string given.' + ); + static::assertEquals([], $this->moduleManager->filterModules(null, 'foo')); + } + + /** + * @throws InvalidConfigException + */ + public function testFlushCache() + { + $oldCache = Yii::$app->cache; + + Yii::$app->set('cache', new ArrayCache()); + static::assertInstanceOf(ArrayCache::class, $cache = Yii::$app->cache); + + static::assertFalse($cache->get(ModuleAutoLoader::CACHE_ID)); + Yii::$app->cache->set(ModuleAutoLoader::CACHE_ID, ['foo' => 'bar']); + static::assertEquals(['foo' => 'bar'], $cache->get(ModuleAutoLoader::CACHE_ID)); + + $this->moduleManager->flushCache(); + + static::assertFalse($cache->get(ModuleAutoLoader::CACHE_ID)); + + Yii::$app->set('cache', $oldCache); + } + + /** + * @param string $basePath + * + * @return array + */ + public function getModuleConfig(string $basePath): array + { + $this->config = require "$basePath/config.php"; + $this->moduleId = $this->config['id']; + $this->moduleNS = str_replace('\\', '/', $this->config['namespace'] ?? ''); + $this->moduleClass = $this->config['class']; + + return [$basePath, $this->config]; + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function registerModule(string $basePath, $config, ?bool $isEnabled = null) + { + $isEnabled ??= in_array($this->moduleId, $this->moduleManager->myEnabledModules(), true); + $isCore = $this->config['isCoreModule'] ?? false; + + if ($isEnabled) { + // check url-manager + if (isset($this->config['urlManagerRules'])) { + $urlManager = Yii::$app->urlManager; + $rules = $urlManager->rules; + } + + // check module on app + if (!is_array($moduleConfig = Yii::$app->modules[$this->moduleId] ?? null)) { + $moduleConfig = [ + 'class' => $this->moduleClass, + 'modules' => $this->config['modules'] ?? [], + ]; + } + } + + if ($this->moduleNS) { + static::assertFalse(Yii::getAlias("@$this->moduleNS", false)); + } + + static::assertFalse(Yii::getAlias("@$this->moduleId", false)); + + $this->moduleManager->register($basePath, $config); + + static::assertEquals([$this->moduleId => $this->moduleClass], $this->moduleManager->myModules()); + static::assertEquals($isCore ? [$this->moduleClass] : [], $this->moduleManager->myCoreModules()); + static::assertEquals($isEnabled && !$isCore ? [$this->moduleId] : [], $this->moduleManager->myEnabledModules()); + + static::assertTrue($this->moduleManager->hasModule($this->moduleId)); + + + if ($isEnabled) { + // check module on app + static::assertEquals($moduleConfig, Yii::$app->getModules()[$this->moduleId]); + static::assertInstanceOf($this->moduleClass, Yii::$app->getModule($this->moduleId)); + } else { + static::assertNull(Yii::$app->getModule($this->moduleId, false)); + static::assertNull(Yii::$app->getModule($this->moduleId, true)); + } + + static::assertInstanceOf($this->moduleClass, $module = $this->moduleManager->getModule($this->moduleId)); +// $module->setBasePath($basePath); + + static::assertEquals($isCore, $this->moduleManager->isCoreModule($this->moduleId)); + + // check if alias has been set + if ($this->moduleNS) { + static::assertEquals("$basePath", Yii::getAlias("@$this->moduleNS")); + } + + // check if alias has been set + static::assertEquals("$basePath", Yii::getAlias("@$this->moduleId")); + + static::assertEquals([$this->moduleId => $this->moduleClass], $this->moduleManager->getModules([ + 'includeCoreModules' => true, + 'enabled' => false, + 'returnClass' => true, + ])); + + if ($config === null) { + return $module; + } + + // check url-manager + if ($isEnabled && isset($this->config['urlManagerRules'])) { + static::assertEquals($rules, $urlManager->rules); + } + + foreach ($this->config['events'] ?? [] as $event) { + $eventClass = $event['class'] ?? $event[0] ?? null; + $eventName = $event['event'] ?? $event[1] ?? null; + $eventHandler = $event['callback'] ?? $event[2] ?? null; + + if ($isEnabled && $eventClass && $eventName && is_array($eventHandler) && method_exists($eventHandler[0], $eventHandler[1])) { + static::assertTrue(Event::off($eventClass, $eventName, $eventHandler)); + } else { + static::assertFalse(Event::off($eventClass, $eventName, $eventHandler)); + } + } + + return $module; + } + + /** + * @throws Exception + * @throws InvalidConfigException + */ + public function registerModuleAsEnabled(string $basePath, $config) + { + // set module as enabled + $this->moduleManager->myEnabledModules()[] = $this->moduleId; + + $module = $this->registerModule($basePath, $config); + + static::assertEquals([$this->moduleId => $this->moduleClass], $this->moduleManager->getModules([ + 'includeCoreModules' => true, + 'enabled' => true, + 'returnClass' => true, + ])); + + return $module; + } + + public function handleEvent(Event $event, array $eventData = []) + { + $e = []; + + if ($event instanceof ModuleEvent) { + $e['module'] = [$event->moduleId => get_class($event->module)]; + } + + if ($event instanceof ModulesEvent) { + $e['modules'] = array_column($event->modules, 'name', 'id'); + } + + parent::handleEvent($event, $e); + } + + public function runEventExceptionTest($events, ?string $exceptionMessage): void + { + $this->moduleManager = new ModuleManagerMock(); + + [$basePath, $config] = $this->getModuleConfig(static::$testModuleRoot . '/module1'); + + // set module as enabled + $this->moduleManager->myEnabledModules()[] = $this->moduleId; + + unset($config['namespace']); + + $config['strict'] = true; + $config['events'] = $events; + + if ($exceptionMessage !== null) { + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage($exceptionMessage); + } + + $this->moduleManager->register($basePath, $config); + } + + /** + * @throws Exception + */ + public function assertModuleActive(): void + { + static::assertInstanceOf($this->moduleClass, $this->moduleManager->getModules([ + 'includeCoreModules' => true, + 'enabled' => true, + 'returnClass' => false, + ])[$this->moduleId]); + } + + /** + * @return void + * @throws Exception + */ + public function assertModuleNotActive(): void + { + static::assertEquals([], $this->moduleManager->getModules([ + 'includeCoreModules' => true, + 'enabled' => true, + 'returnClass' => false, + ])); + } + + /** + * @return void + */ + public function reset(): void + { + if ($this->moduleManager) { + foreach ($this->moduleManager->myEnabledModules() as $enableModule) { + Yii::$app->setModule($enableModule, null); + } + } + + Yii::$aliases = static::$aliases; + + $this->firedEvents = []; + + static::dbDelete(ModuleEnabled::tableName(), ['module_id' => ['module1', 'module2', 'coreModule', 'installerModule', 'invalidModule1', 'invalidModule2']]); + + $this->moduleManager = new ModuleManagerMock(); + } + + public function skipIfMarketplaceNotEnabled(): void + { + $marketplaceModule = Yii::$app->getModule('marketplace'); + + if ($marketplaceModule === null) { + $this->markTestSkipped(); + } + + Yii::setAlias($marketplaceModule->modulesPath, static::$testModuleRoot); + } + + /** + * @return bool|string + * @throws Exception + */ + public function createTempDir() + { + $tmp = tempnam(sys_get_temp_dir(), "humhub_" . time() . "_"); + unlink($tmp); + FileHelper::createDirectory($tmp); + + static::assertDirectoryExists($tmp); + + return $tmp; + } +} diff --git a/protected/humhub/tests/codeception/unit/components/SettingsManagerTest.php b/protected/humhub/tests/codeception/unit/components/SettingsManagerTest.php index 16cfdfc581..b0fd2a70dc 100644 --- a/protected/humhub/tests/codeception/unit/components/SettingsManagerTest.php +++ b/protected/humhub/tests/codeception/unit/components/SettingsManagerTest.php @@ -385,7 +385,7 @@ class SettingsManagerTest extends HumHubDbTestCase // changing the value behind the scenes $value2 = 'third value'; - $this->dbUpdate($table, ['value' => $value2], ['name' => $setting, 'module_id' => $module]); + self::dbUpdate($table, ['value' => $value2], ['name' => $setting, 'module_id' => $module]); $this->assertRecordValue($value2, 'value', $table, ['name' => $setting, 'module_id' => $module]); // getting the value now should still show tho "old" value diff --git a/protected/humhub/tests/codeception/unit/components/bootstrap/ModuleAutoLoaderTest.php b/protected/humhub/tests/codeception/unit/components/bootstrap/ModuleAutoLoaderTest.php index ff88bb7aa6..82aaaef5d7 100644 --- a/protected/humhub/tests/codeception/unit/components/bootstrap/ModuleAutoLoaderTest.php +++ b/protected/humhub/tests/codeception/unit/components/bootstrap/ModuleAutoLoaderTest.php @@ -17,30 +17,30 @@ use Yii; class ModuleAutoLoaderTest extends Unit { /** @var array list of expected core modules */ - const EXPECTED_CORE_MODULES = [ - 'activity', - 'admin', - 'comment', - 'content', - 'dashboard', - 'file', - 'friendship', - 'installer', - 'ldap', - 'like', - 'live', - 'marketplace', - 'notification', - 'post', - 'queue', - 'search', - 'space', - 'stream', - 'topic', - 'tour', - 'ui', - 'user', - 'web' + public const EXPECTED_CORE_MODULES = [ + 'humhub\modules\activity\Module' => 'activity', + 'humhub\modules\admin\Module' => 'admin', + 'humhub\modules\comment\Module' => 'comment', + 'humhub\modules\content\Module' => 'content', + 'humhub\modules\dashboard\Module' => 'dashboard', + 'humhub\modules\file\Module' => 'file', + 'humhub\modules\friendship\Module' => 'friendship', + 'humhub\modules\installer\Module' => 'installer', + 'humhub\modules\ldap\Module' => 'ldap', + 'humhub\modules\like\Module' => 'like', + 'humhub\modules\live\Module' => 'live', + 'humhub\modules\marketplace\Module' => 'marketplace', + 'humhub\modules\notification\Module' => 'notification', + 'humhub\modules\post\Module' => 'post', + 'humhub\modules\queue\Module' => 'queue', + 'humhub\modules\search\Module' => 'search', + 'humhub\modules\space\Module' => 'space', + 'humhub\modules\stream\Module' => 'stream', + 'humhub\modules\topic\Module' => 'topic', + 'humhub\modules\tour\Module' => 'tour', + 'humhub\modules\ui\Module' => 'ui', + 'humhub\modules\user\Module' => 'user', + 'humhub\modules\web\Module' => 'web', ]; /** From 36c1b8700a428d189a1f7d2bdfb82c509cbbeba6 Mon Sep 17 00:00:00 2001 From: Yuriy Bakhtin Date: Fri, 18 Aug 2023 11:05:32 +0200 Subject: [PATCH 11/19] Update `created_at` after first publishing of content record (#6525) --- CHANGELOG-DEV.md | 1 + protected/humhub/modules/content/models/Content.php | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 65700022e9..a45a720893 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -17,6 +17,7 @@ HumHub Changelog - Enh #6512: Show error messages when DB connection configuration is invalid - Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces - Enh #5315: Default stream sort by `created_at` instead of `id` +- Fix #6337: Update `created_at` after first publishing of content record 1.15.0-beta.2 (Unreleased) -------------------------- diff --git a/protected/humhub/modules/content/models/Content.php b/protected/humhub/modules/content/models/Content.php index 9742d29e4f..671e333ea0 100644 --- a/protected/humhub/modules/content/models/Content.php +++ b/protected/humhub/modules/content/models/Content.php @@ -249,7 +249,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, Archiveable { if (array_key_exists('state', $changedAttributes)) { // Run process for new content(Send notifications) only after changing state - $this->processNewContent(); + $this->processNewContent($insert); $model = $this->getModel(); if (!$insert && $model instanceof ContentActiveRecord && $this->getStateService()->isPublished()) { @@ -279,7 +279,7 @@ class Content extends ActiveRecord implements Movable, ContentOwner, Archiveable parent::afterSave($insert, $changedAttributes); } - private function processNewContent() + private function processNewContent($insert) { if (!$this->getStateService()->isPublished()) { // Don't notify about not published Content @@ -291,6 +291,11 @@ class Content extends ActiveRecord implements Movable, ContentOwner, Archiveable return; } + if (!$insert) { + // Update creation datetime after first publishing + $this->updateAttributes(['created_at' => date('Y-m-d H:i:s')]); + } + $record = $this->getModel(); Yii::debug('Process new content: ' . get_class($record) . ' ID: ' . $record->getPrimaryKey(), 'content'); From 006717e2f26603268642ad00025e688616d5e9ed Mon Sep 17 00:00:00 2001 From: Lucas Bartholemy Date: Mon, 21 Aug 2023 09:25:38 +0200 Subject: [PATCH 12/19] Enh: Improve Codeception Test Speed (#5869) * Improve Test Speed using Transactions * Fixed OEmbed Test Fixtures * Fix DB Event position * Fix test "visibility of soft deleted content in stream" --------- Co-authored-by: Yuriy Bakhtin --- .../modules/activity/tests/codeception/unit.suite.yml | 3 ++- .../modules/admin/tests/codeception/unit.suite.yml | 3 ++- .../modules/comment/tests/codeception/unit.suite.yml | 3 ++- .../modules/content/tests/codeception/unit.suite.yml | 3 ++- .../codeception/unit/ContentContainerStreamTest.php | 4 ++-- .../unit/widgets/RichTextPostProcessTest.php | 10 +++++++--- .../modules/dashboard/tests/codeception/unit.suite.yml | 3 ++- .../modules/file/tests/codeception/unit.suite.yml | 3 ++- .../friendship/tests/codeception/unit.suite.yml | 3 ++- .../modules/like/tests/codeception/unit.suite.yml | 3 ++- .../modules/live/tests/codeception/unit.suite.yml | 3 ++- .../notification/tests/codeception/unit.suite.yml | 3 ++- .../modules/post/tests/codeception/unit.suite.yml | 3 ++- .../modules/space/tests/codeception/unit.suite.yml | 3 ++- .../modules/stream/tests/codeception/unit.suite.yml | 3 ++- .../modules/topic/tests/codeception/unit.suite.yml | 3 ++- .../modules/tour/tests/codeception/unit.suite.yml | 3 ++- .../humhub/modules/ui/tests/codeception/unit.suite.yml | 3 ++- .../modules/user/tests/codeception/unit.suite.yml | 3 ++- .../modules/web/tests/codeception/unit.suite.yml | 3 ++- .../tests/codeception/_support/HumHubDbTestCase.php | 4 ++++ 21 files changed, 49 insertions(+), 23 deletions(-) diff --git a/protected/humhub/modules/activity/tests/codeception/unit.suite.yml b/protected/humhub/modules/activity/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/activity/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/activity/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/admin/tests/codeception/unit.suite.yml b/protected/humhub/modules/admin/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/admin/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/admin/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/comment/tests/codeception/unit.suite.yml b/protected/humhub/modules/comment/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/comment/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/comment/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/content/tests/codeception/unit.suite.yml b/protected/humhub/modules/content/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/content/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/content/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/content/tests/codeception/unit/ContentContainerStreamTest.php b/protected/humhub/modules/content/tests/codeception/unit/ContentContainerStreamTest.php index 8cbe2d4e7a..bcd7b8666a 100644 --- a/protected/humhub/modules/content/tests/codeception/unit/ContentContainerStreamTest.php +++ b/protected/humhub/modules/content/tests/codeception/unit/ContentContainerStreamTest.php @@ -145,8 +145,8 @@ class ContentContainerStreamTest extends HumHubDbTestCase $this->becomeUser('User2'); $deleteId = $this->createPost('Something to delete',['visibility' => Content::VISIBILITY_PRIVATE]); - $post = Post::findOne(['id' => $deleteId]); - $post->content->softDelete(); + $content = Content::findOne(['id' => $deleteId]); + $content->softDelete(); $ids = $this->getStreamActionIds($this->space, 3); diff --git a/protected/humhub/modules/content/tests/codeception/unit/widgets/RichTextPostProcessTest.php b/protected/humhub/modules/content/tests/codeception/unit/widgets/RichTextPostProcessTest.php index 55e519a986..f1abe8ea4f 100644 --- a/protected/humhub/modules/content/tests/codeception/unit/widgets/RichTextPostProcessTest.php +++ b/protected/humhub/modules/content/tests/codeception/unit/widgets/RichTextPostProcessTest.php @@ -19,7 +19,7 @@ use tests\codeception\_support\HumHubDbTestCase; class RichTextPostProcessTest extends HumHubDbTestCase { - public function _before() + private function addTestOEmbedRecords() { (new UrlOembed([ 'url' => 'https://www.youtube.com/watch?v=yt1', @@ -30,8 +30,6 @@ class RichTextPostProcessTest extends HumHubDbTestCase 'url' => 'https://www.youtube.com/watch?v=yt2', 'preview' => 'yt2' ]))->save(); - - parent::_before(); } /** @@ -39,6 +37,8 @@ class RichTextPostProcessTest extends HumHubDbTestCase */ public function testProcessSingleOembed() { + $this->addTestOEmbedRecords(); + $post = Post::findOne(['id' => 1]); $text = '[https://www.youtube.com/watch?v=yt1](oembed:https://www.youtube.com/watch?v=yt1)'; $result = RichText::postProcess($text, $post); @@ -52,6 +52,8 @@ class RichTextPostProcessTest extends HumHubDbTestCase */ public function testProcessNoneOembed() { + $this->addTestOEmbedRecords(); + $post = Post::findOne(['id' => 1]); $text = '[Normal link](https://www.youtube.com/watch?v=yt1)'; $result = RichText::postProcess($text, $post); @@ -63,6 +65,8 @@ class RichTextPostProcessTest extends HumHubDbTestCase */ public function testProcessMultipleOembed() { + $this->addTestOEmbedRecords(); + $post = Post::findOne(['id' => 1]); $text = '[https://www.youtube.com/watch?v=yt1](oembed:https://www.youtube.com/watch?v=yt1)\n\n[https://www.youtube.com/watch?v=yt2](oembed:https://www.youtube.com/watch?v=yt2)'; $result = RichText::postProcess($text, $post); diff --git a/protected/humhub/modules/dashboard/tests/codeception/unit.suite.yml b/protected/humhub/modules/dashboard/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/dashboard/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/dashboard/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/file/tests/codeception/unit.suite.yml b/protected/humhub/modules/file/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/file/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/file/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/friendship/tests/codeception/unit.suite.yml b/protected/humhub/modules/friendship/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/friendship/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/friendship/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/like/tests/codeception/unit.suite.yml b/protected/humhub/modules/like/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/like/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/like/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/live/tests/codeception/unit.suite.yml b/protected/humhub/modules/live/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/live/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/live/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/notification/tests/codeception/unit.suite.yml b/protected/humhub/modules/notification/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/notification/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/notification/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/post/tests/codeception/unit.suite.yml b/protected/humhub/modules/post/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/post/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/post/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/space/tests/codeception/unit.suite.yml b/protected/humhub/modules/space/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/space/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/space/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/stream/tests/codeception/unit.suite.yml b/protected/humhub/modules/stream/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/stream/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/stream/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/topic/tests/codeception/unit.suite.yml b/protected/humhub/modules/topic/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/topic/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/topic/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/tour/tests/codeception/unit.suite.yml b/protected/humhub/modules/tour/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/tour/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/tour/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/ui/tests/codeception/unit.suite.yml b/protected/humhub/modules/ui/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/ui/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/ui/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/user/tests/codeception/unit.suite.yml b/protected/humhub/modules/user/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/user/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/user/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/modules/web/tests/codeception/unit.suite.yml b/protected/humhub/modules/web/tests/codeception/unit.suite.yml index e11d43f816..00d384dc8c 100644 --- a/protected/humhub/modules/web/tests/codeception/unit.suite.yml +++ b/protected/humhub/modules/web/tests/codeception/unit.suite.yml @@ -11,4 +11,5 @@ modules: config: Yii2: configFile: 'codeception/config/unit.php' - transaction: false \ No newline at end of file + transaction: true + cleanup: false diff --git a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php index 14c89ec24d..ec448a401c 100644 --- a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php +++ b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php @@ -61,6 +61,10 @@ class HumHubDbTestCase extends Unit protected function setUp(): void { + if (\Yii::$app !== null) { + \Yii::$app->db->trigger('afterOpen'); + } + if (Yii::$app === null) { $c = new \ReflectionClass($this); $m = $c->getMethod($this->getName(false)); From 9c7d9362750cf74839d51abb856bb4c263076823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Tue, 12 Sep 2023 15:39:57 +0200 Subject: [PATCH 13/19] Enh: perfomance improvements (#6530) * ModuleEnabled::getEnabledIds(): Only retrieve required column and skip foreach * Module::disable(): Uss prepared statement and only change search parameter * Update CHANGELOG-DEV.md --- CHANGELOG-DEV.md | 1 + protected/humhub/components/Module.php | 7 ++++++- protected/humhub/models/ModuleEnabled.php | 11 ++++------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index a45a720893..c2fc3e0f94 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -3,6 +3,7 @@ HumHub Changelog 1.16.0 (Unreleased) ------------------- +- Enh #6530: Small performance improvements - Fix #6511: Only test compatible modules in `onMarketplaceAfterFilterModules()` - Enh #6511: Backup folder path is now return from `removeModule()` - Fix #6511: `canRemoveModule` no longer throws an Exception diff --git a/protected/humhub/components/Module.php b/protected/humhub/components/Module.php index 4a10b878ac..ce0bed208d 100644 --- a/protected/humhub/components/Module.php +++ b/protected/humhub/components/Module.php @@ -288,11 +288,16 @@ class Module extends \yii\base\Module * Delete all Migration Table Entries */ $migrations = opendir($migrationPath); + $params = []; while (false !== ($migration = readdir($migrations))) { if ($migration == '.' || $migration == '..' || $migration == 'uninstall.php') { continue; } - Yii::$app->db->createCommand()->delete('migration', ['version' => str_replace('.php', '', $migration)])->execute(); + + $command ??= Yii::$app->db->createCommand()->delete('migration', 'version = :version', $params); + + $version = str_replace('.php', '', $migration); + $command->bindValue(':version', $version)->execute(); } } diff --git a/protected/humhub/models/ModuleEnabled.php b/protected/humhub/models/ModuleEnabled.php index 20325f32cd..39fb034f44 100644 --- a/protected/humhub/models/ModuleEnabled.php +++ b/protected/humhub/models/ModuleEnabled.php @@ -65,16 +65,13 @@ class ModuleEnabled extends \yii\db\ActiveRecord public static function getEnabledIds() { - $enabledModules = Yii::$app->cache->get(self::CACHE_ID_ALL_IDS); + $cache = Yii::$app->cache; + $enabledModules = $cache->get(self::CACHE_ID_ALL_IDS); if ($enabledModules === false) { - $enabledModules = []; - foreach (\humhub\models\ModuleEnabled::find()->all() as $em) { - $enabledModules[] = $em->module_id; - } - Yii::$app->cache->set(self::CACHE_ID_ALL_IDS, $enabledModules); + $enabledModules = self::find()->select('module_id')->createCommand()->queryColumn(); + $cache->set(self::CACHE_ID_ALL_IDS, $enabledModules); } return $enabledModules; } - } From a58f302114d74305f12f42241c6963dcc3ebb190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Thu, 14 Sep 2023 10:54:16 +0200 Subject: [PATCH 14/19] Support log assertions (#6553) --- CHANGELOG-DEV.md | 1 + .../codeception/_support/ArrayTarget.php | 45 +++ .../codeception/_support/HumHubDbTestCase.php | 10 +- .../_support/HumHubHelperTrait.php | 305 ++++++++++++++++ .../tests/codeception/_support/Logger.php | 25 ++ .../_support/TraversableContainsRegex.php | 63 ++++ .../unit/LogAssertionsSelfTest.php | 340 ++++++++++++++++++ 7 files changed, 788 insertions(+), 1 deletion(-) create mode 100644 protected/humhub/tests/codeception/_support/ArrayTarget.php create mode 100644 protected/humhub/tests/codeception/_support/Logger.php create mode 100644 protected/humhub/tests/codeception/_support/TraversableContainsRegex.php create mode 100644 protected/humhub/tests/codeception/unit/LogAssertionsSelfTest.php diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 67f2285777..896bea4db0 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -3,6 +3,7 @@ HumHub Changelog 1.16.0 (Unreleased) ------------------- +- Enh #6553: Support log assertions - Enh #6530: Small performance improvements - Fix #6511: Only test compatible modules in `onMarketplaceAfterFilterModules()` - Enh #6511: Backup folder path is now return from `removeModule()` diff --git a/protected/humhub/tests/codeception/_support/ArrayTarget.php b/protected/humhub/tests/codeception/_support/ArrayTarget.php new file mode 100644 index 0000000000..29ee600db5 --- /dev/null +++ b/protected/humhub/tests/codeception/_support/ArrayTarget.php @@ -0,0 +1,45 @@ +messageStore = array_merge($this->messageStore, $this->messages); + $this->messages = []; + } + + public function flush() + { + $this->messageStore = []; + } + + + /** + * @param int|string[]|null $levels + * @param array|null $categories + * @param array|null $except + * + * @return array + */ + public function &getMessages($levels = 0, ?array $categories = null, ?array $except = null): array + { + $messages = static::filterMessages($this->messageStore, $levels ?? 0, $categories ?? [], $except ?? []); + + return $messages; + } +} diff --git a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php index ec448a401c..8566064a06 100644 --- a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php +++ b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php @@ -85,6 +85,14 @@ class HumHubDbTestCase extends Unit parent::setUp(); } + protected function tearDown(): void + { + static::logReset(); + + parent::tearDown(); + } + + /** * Initializes modules defined in @tests/codeception/config/test.config.php * Note the config key in test.config.php is modules and not humhubModules! @@ -262,7 +270,7 @@ class HumHubDbTestCase extends Unit $this->assertEquals($subject, str_replace(["\n", "\r"], '', $message->getSubject())); } - public function assertEvents(array $events, string $message = ''): void + public function assertEvents(array $events = [], string $message = ''): void { static::assertEquals($events, $this->firedEvents, $message); diff --git a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php index e051490ae3..fbf822ca22 100644 --- a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php +++ b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php @@ -1,4 +1,5 @@ getLog(); + + // create a new dispatcher target to store the log entries + self::$logTarget = Yii::createObject([ + 'class' => ArrayTarget::class, + 'levels' => [ + 'error', + 'warning', + 'info', + 'trace', // aka debug + // 'profile', + ], + 'except' => [], + 'logVars' => [], + ]); + + // create a new proxy logger that forwards log entries both to the current logger as well as to the above target + Yii::setLogger(new Logger(['proxy' => Yii::getLogger()])); + + /** + * will automagically connect to the logger set above + * + * @see Dispatcher::__construct + * @see Dispatcher::getLogger + */ + Yii::$app->set('log', Yii::createObject(['class' => Dispatcher::class, 'targets' => [static::class => self::$logTarget]])); + } + + /** + * Flush the captured log entries without stopping to capture them + * + * @since 1.16 + * @see static::logInitialize() + */ + protected static function logFlush() + { + if (self::$logTarget === null) { + return; + } + + Yii::getLogger()->flush(); + self::$logTarget->messages = []; + self::$logTarget->flush(); + } + + /** + * Delete any captured log entry and stop capturing new entries. Automatically called by `static::tearDown()` + * + * @throws InvalidConfigException + * @since 1.16 + * @see static::logInitialize() + * @see static::tearDown() + */ + protected static function logReset() + { + if (self::$logTarget === null) { + return; + } + + if (Yii::$app->getLog()->targets[static::class] ?? null === self::$logTarget) { + Yii::$app->set('log', self::$logOldDispatcher); + } + self::$logOldDispatcher = null; + + $logger = Yii::getLogger(); + + if ($logger instanceof Logger) { + Yii::setLogger($logger->proxy); + } else { + Yii::setLogger(null); + } + + self::$logTarget = null; + } + + /** + * Returns the array of captured log message arrays + * + * @param int|int[]|null $levels Array or bitmask of verbosity levels to be returned: + * Logger::LEVEL_ERROR, Logger::LEVEL_WARNING, Logger::LEVEL_INFO, Logger::LEVEL_TRACE + * @param string[]|null $categories Array of categories to be returned or null for any + * @param string[]|null $exceptCategories Array of categories NOT to be returned, or null for no exclusion + * + * @return array of message, following the format of `\yii\log\Logger::$messages` + * @throws ErrorException + * @since 1.16 + * @see static::logInitialize() + * @see \yii\log\Logger::$messages + */ + public static function &logFilterMessages($levels = null, ?array $categories = null, ?array $exceptCategories = null): array + { + if (self::$logTarget === null) { + throw new ErrorException("Log has not been initialized"); + } + + Yii::getLogger()->flush(); + self::$logTarget->export(); + + return self::$logTarget->getMessages($levels ?? 0, $categories, $exceptCategories); + } + + /** + * Returns an array of captured log messages as string (without the category or level) + * + * @throws ErrorException + * @since 1.16 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function logFilterMessageTexts($levels = null, ?array $categories = null, ?array $exceptCategories = null): array + { + return array_column(static::logFilterMessages($levels, $categories, $exceptCategories), 0); + } + + /** + * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` exactly + * + * @throws ErrorException + * @see static::logInitialize() + * @see static::logFilterMessages() + * @since 1.16 + */ + public static function assertLog(?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + if ($logMessage === null) { + static::assertNotEmpty($messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } else { + static::assertContains($logMessage, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + } + + /** + * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` exactly + * + * @throws ErrorException + * @see static::logInitialize() + * @see static::logFilterMessages() + * @since 1.16 + */ + public static function assertLogCount(int $expectedCount, ?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + if ($logMessage !== null) { + $messages = array_filter($messages, static fn($text) => $text === $logMessage); + } + + static::assertCount($expectedCount, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + + /** + * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` exactly + * + * @throws ErrorException + * @since 1.16 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function assertNotLog(?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + if ($logMessage === null) { + static::assertEmpty($messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } else { + static::assertNotContains($logMessage, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + } + + /** + * @param string $regex At least one of the filtered messages must match the given `$regex` pattern + * + * @throws ErrorException|Exception + * @since 1.16 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function assertLogRegex(string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + static::asserContainsRegex($regex, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + + /** + * @param string $regex At least one of the filtered messages must match the given `$regex` pattern + * + * @throws ErrorException|Exception + * @since 1.16 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function assertLogRegexCount(int $expectedCount, string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + if (count($messages)) { + try { + preg_match($regex, ''); + } catch (ErrorException $e) { + throw new Exception("Invalid regex given: '{$regex}'"); + } + + $messages = array_filter($messages, static fn($text) => preg_match($regex, $text)); + } + + static::assertCount($expectedCount, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + + /** + * @param string $regex None of the filtered messages may match the given `$regex` pattern + * + * @throws ErrorException|Exception + * @since 1.16 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function assertNotLogRegex(string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + static::assertNotContainsRegex($regex, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + + /** + * Asserts that `$haystack` contains an element that matches the `$regex` + * + * @since 1.16 + * @throws Exception + */ + public static function asserContainsRegex(string $regex, iterable $haystack, string $message = ''): void + { + $constraint = new TraversableContainsRegex($regex); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * Asserts that `$haystack` does not contain an element that matches the `$regex` + * + * @since 1.16 + * @throws Exception + */ + public static function assertNotContainsRegex(string $regex, iterable $haystack, string $message = ''): void + { + $constraint = new LogicalNot( + new TraversableContainsRegex($regex), + ); + + static::assertThat($haystack, $constraint, $message); + } } diff --git a/protected/humhub/tests/codeception/_support/Logger.php b/protected/humhub/tests/codeception/_support/Logger.php new file mode 100644 index 0000000000..76dab8e943 --- /dev/null +++ b/protected/humhub/tests/codeception/_support/Logger.php @@ -0,0 +1,25 @@ +proxy) { + $this->proxy->log($message, $level, $category); + } + } +} diff --git a/protected/humhub/tests/codeception/_support/TraversableContainsRegex.php b/protected/humhub/tests/codeception/_support/TraversableContainsRegex.php new file mode 100644 index 0000000000..ef6924e9b9 --- /dev/null +++ b/protected/humhub/tests/codeception/_support/TraversableContainsRegex.php @@ -0,0 +1,63 @@ +value(), + ); + } + + /** + * Evaluates the constraint for parameter $other. Returns true if the + * constraint is met, false otherwise. + * + * @param mixed $other value or object to evaluate + */ + protected function matches($other): bool + { + $value = $this->value(); + + foreach ($other as $element) { + while (!is_string($element)) { + if ($element instanceof \Stringable || (is_object($element) && !method_exists($element, '__toString'))) { + $element = $element->__toString(); + break; + } + + continue 2; + } + + $match = RegularExpressionUtil::safeMatch($value, $element); + + if ($match === false) { + throw new Exception( + "Invalid regex given: '{$value}'", + ); + } + + return $match === 1; + } + + return false; + } +} diff --git a/protected/humhub/tests/codeception/unit/LogAssertionsSelfTest.php b/protected/humhub/tests/codeception/unit/LogAssertionsSelfTest.php new file mode 100644 index 0000000000..8f35d9ae9e --- /dev/null +++ b/protected/humhub/tests/codeception/unit/LogAssertionsSelfTest.php @@ -0,0 +1,340 @@ + false, 'colors' => false]) extends Output { + public bool $forward = false; + + public function doWrite(string $message, bool $newline) + { + if ($this->forward) { + parent::doWrite($message, $newline); + } + + if ($newline) { + $message .= \PHP_EOL; + } + + LogAssertionsSelfTest::addData($message); + } + }; + + self::$output->waitForDebugOutput = false; + + if (Debug::isEnabled()) { + $rcDebug = new \ReflectionClass(Debug::class); + self::$originalOutput = $rcDebug->getStaticPropertyValue('output'); + self::$output->forward = true; + } else { + self::$originalOutput = new class ($config = ['interactive' => false, 'colors' => false]) extends Output { + public function debug($message) + { + // don't do anything + } + }; + } + + parent::setUpBeforeClass(); + } + + protected function setUp(): void + { + parent::setUp(); + + Debug::setOutput(self::$output); + + static::clean(); + } + + protected function tearDown(): void + { + // reset output to original + Debug::setOutput(self::$originalOutput); + + static::clean(); + + parent::tearDown(); + } + + public function testNormalDebugMessage() + { + Yii::error("Foo", "error"); + self::assertEquals(" [error] 'Foo'\n", static::getClean()); + + Yii::warning("Foo", "warning"); + self::assertEquals(" [warning] 'Foo'\n", static::getClean()); + + Yii::info("Foo", "info"); + self::assertEquals(" [info] 'Foo'\n", static::getClean()); + + /** + * debug messages are not prited + * @see \Codeception\Lib\Connector\Yii2\Logger::log() + */ + Yii::debug("Foo", "debug"); + self::assertEquals(null, static::getClean()); + } + + public function testInitializationRequired() + { + $this->expectException(yii\base\ErrorException::class); + $this->expectExceptionMessage('Log has not been initialized'); + + static::assertLogCount(1); + } + + /** + * @noinspection UnsetConstructsCanBeMergedInspection + */ + public function testAssertingDebugMessage() + { + static::logInitialize(); + + static::assertLogCount(0); + + Yii::error("Foo", "LogAssertionsSelfTest_error"); + self::assertEquals(" [LogAssertionsSelfTest_error] 'Foo'\n", static::getClean()); + static::assertLogCount(1); + static::assertLogCount(1, null, Logger::LEVEL_ERROR); + static::assertLogCount(0, null, Logger::LEVEL_WARNING | Logger::LEVEL_INFO | Logger::LEVEL_TRACE); + static::assertLogCount(1, 'Foo'); + static::assertLog('Foo'); + static::assertLog('Foo', Logger::LEVEL_ERROR); + static::assertLog('Foo', Logger::LEVEL_ERROR, ['LogAssertionsSelfTest_error']); + static::assertLog('Foo', null, ['LogAssertionsSelfTest_error']); + static::assertLog(null, Logger::LEVEL_ERROR); + static::assertLog(null, Logger::LEVEL_ERROR, ['LogAssertionsSelfTest_error']); + static::assertLog(null, null, ['LogAssertionsSelfTest_error']); + static::assertLog(null, null, null, ['LogAssertionsSelfTest_warning']); + static::assertLog(); + static::assertNotLog('Bar'); + static::assertNotLog(null, Logger::LEVEL_WARNING); + static::assertNotLog(null, null, ['LogAssertionsSelfTest_warning']); + static::assertNotLog('Bar', null, ['LogAssertionsSelfTest_error', 'LogAssertionsSelfTest_warning']); + static::assertNotLog('Foo', null, null, ['LogAssertionsSelfTest_error']); + + Yii::warning("Foo", "LogAssertionsSelfTest_warning"); + self::assertEquals(" [LogAssertionsSelfTest_warning] 'Foo'\n", static::getClean()); + static::assertLogCount(2); + static::assertLogCount(1, null, Logger::LEVEL_ERROR); + static::assertLogCount(1, null, Logger::LEVEL_WARNING); + static::assertLogCount(2, null, Logger::LEVEL_ERROR | Logger::LEVEL_WARNING); + static::assertLogCount(0, null, Logger::LEVEL_INFO | Logger::LEVEL_TRACE); + static::assertLogCount(2, 'Foo'); + static::assertLog('Foo', Logger::LEVEL_ERROR, ['LogAssertionsSelfTest_error']); + static::assertLog('Foo', Logger::LEVEL_WARNING, ['LogAssertionsSelfTest_warning']); + static::assertNotLog('Bar'); + + Yii::info("Bar", "LogAssertionsSelfTest"); + self::assertEquals(" [LogAssertionsSelfTest] 'Bar'\n", static::getClean()); + static::assertLogCount(3); + static::assertLogCount(1, null, Logger::LEVEL_ERROR); + static::assertLogCount(1, null, Logger::LEVEL_WARNING); + static::assertLogCount(1, null, Logger::LEVEL_INFO); + static::assertLogCount(2, null, Logger::LEVEL_ERROR | Logger::LEVEL_WARNING); + static::assertLogCount(2, null, Logger::LEVEL_ERROR | Logger::LEVEL_INFO); + static::assertLogCount(2, null, Logger::LEVEL_WARNING | Logger::LEVEL_INFO); + static::assertLogCount(1, null, Logger::LEVEL_INFO | Logger::LEVEL_TRACE); + static::assertLogCount(0, null, Logger::LEVEL_TRACE); + static::assertLogCount(2, 'Foo'); + static::assertLogCount(1, 'Bar'); + static::assertLog('Bar', Logger::LEVEL_INFO, ['LogAssertionsSelfTest']); + + /** + * debug messages are not printed + * @see \Codeception\Lib\Connector\Yii2\Logger::log() + */ + Yii::debug("Foo", "LogAssertionsSelfTest"); + self::assertEquals(null, static::getClean()); + static::assertLogCount(4); + static::assertLogCount(1, null, Logger::LEVEL_ERROR); + static::assertLogCount(1, null, Logger::LEVEL_WARNING); + static::assertLogCount(1, null, Logger::LEVEL_INFO); + static::assertLogCount(1, null, Logger::LEVEL_TRACE); + static::assertLogCount(2, null, Logger::LEVEL_ERROR | Logger::LEVEL_WARNING); + static::assertLogCount(2, null, Logger::LEVEL_ERROR | Logger::LEVEL_INFO); + static::assertLogCount(2, null, Logger::LEVEL_WARNING | Logger::LEVEL_INFO); + static::assertLogCount(2, null, Logger::LEVEL_INFO | Logger::LEVEL_TRACE); + static::assertLogCount(3, 'Foo'); + static::assertLogCount(1, 'Bar'); + static::assertLogCount(2, null, null, ['LogAssertionsSelfTest']); + static::assertLog('Bar', Logger::LEVEL_INFO, ['LogAssertionsSelfTest']); + static::assertLog('Foo', Logger::LEVEL_TRACE, ['LogAssertionsSelfTest']); + static::assertLog('Bar', null, ['LogAssertionsSelfTest']); + static::assertLog('Foo', null, ['LogAssertionsSelfTest']); + + static::assertNotLog('Fo'); + static::assertNotLog('FooBar'); + + static::assertLogRegex('@oo@'); + static::assertNotLogRegex('@Oo@'); + static::assertLogRegex('@Oo@i'); + static::assertLogRegexCount(1, '@foo|Ba@'); + + static::assertEquals( + [ + 'Foo', + 'Foo', + 'Bar', + 'Foo', + ], + static::logFilterMessageTexts() + ); + + $messages = static::logFilterMessages(); + + /** + * Cleanup + * + * @see \yii\log\Logger::$messages + * */ + foreach ($messages as &$message) { + // unset timestamp + unset($message[3]); + + // traces + //unset($message[4]); + + // memory usage + unset($message[5]); + } + + static::assertEquals( + [ + 0 => [ + 0 => 'Foo', + 1 => 1, + 2 => 'LogAssertionsSelfTest_error', + 4 => [], + ], + 1 => [ + 0 => 'Foo', + 1 => 2, + 2 => 'LogAssertionsSelfTest_warning', + 4 => [], + ], + 2 => [ + 0 => 'Bar', + 1 => 4, + 2 => 'LogAssertionsSelfTest', + 4 => [], + ], + 3 => [ + 0 => 'Foo', + 1 => 8, + 2 => 'LogAssertionsSelfTest', + 4 => [], + ] + ], + $messages + ); + + static::logFlush(); + static::assertLogCount(0); + + /** + * Note: this is also automatically done in @see HumHubDbTestCase::tearDown() + * As such, it is mainly useful to stop capturing within a test. + */ + static::logReset(); + } + + /** + * @noinspection UnsetConstructsCanBeMergedInspection + */ + public function testInvalidRegex1() + { + static::logInitialize(); + + Yii::debug("Foo", "LogAssertionsSelfTest"); + self::assertEquals(null, static::getClean()); + static::assertLogCount(1); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Invalid regex given: '@Oo'"); + + static::assertLogRegex('@Oo'); + } + + /** + * @noinspection UnsetConstructsCanBeMergedInspection + */ + public function testInvalidRegex2() + { + static::logInitialize(); + + Yii::debug("Foo", "LogAssertionsSelfTest"); + self::assertEquals(null, static::getClean()); + static::assertLogCount(1); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Invalid regex given: '@Oo'"); + + static::assertLogRegexCount(1, '@Oo'); + } + + public static function get(): ?string + { + return self::$data; + } + + public static function getClean(): ?string + { + $data = self::$data; + + self::clean(); + + return $data; + } + + public static function clean(): void + { + self::$data = null; + } + + public static function addData(string $data): void + { + static::$data .= $data; + } +} From 3f389542b6d5a3872d04ec76b4322fd8e67e380b Mon Sep 17 00:00:00 2001 From: Lucas Bartholemy Date: Thu, 14 Sep 2023 11:14:00 +0200 Subject: [PATCH 15/19] Changed ContentContainer based class to HumHub AR --- protected/humhub/modules/content/models/ContentContainer.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/protected/humhub/modules/content/models/ContentContainer.php b/protected/humhub/modules/content/models/ContentContainer.php index b325caf5d3..7caad43df8 100644 --- a/protected/humhub/modules/content/models/ContentContainer.php +++ b/protected/humhub/modules/content/models/ContentContainer.php @@ -8,10 +8,9 @@ namespace humhub\modules\content\models; +use humhub\components\ActiveRecord; use humhub\components\behaviors\PolymorphicRelation; use humhub\modules\content\components\ContentContainerActiveRecord; -use humhub\modules\space\models\Space; -use yii\db\ActiveRecord; /** * This is the model class for table "contentcontainer". From 6b2553017c1436a6deeb5d4d7311661e673f9c71 Mon Sep 17 00:00:00 2001 From: Lucas Bartholemy Date: Wed, 20 Sep 2023 23:10:07 +0200 Subject: [PATCH 16/19] Added RuntimeCache Flush --- .../humhub/tests/codeception/_support/HumHubHelperTrait.php | 1 + 1 file changed, 1 insertion(+) diff --git a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php index fbf822ca22..6dfe4b9596 100644 --- a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php +++ b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php @@ -42,6 +42,7 @@ trait HumHubHelperTrait FileHelper::removeDirectory($cachePath); } Yii::$app->cache->flush(); + Yii::$app->runtimeCache->flush(); RichTextToShortTextConverter::flushCache(); RichTextToHtmlConverter::flushCache(); RichTextToPlainTextConverter::flushCache(); From 75fec3364c40e9f3012b4fb66ed66e0c20ee0e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Wed, 27 Sep 2023 11:49:42 +0200 Subject: [PATCH 17/19] Merge develop into next (#6601) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix mimetype of converted preview svg file to png after uploading (#6599) * Enh: `file` fields: `sort_order` (#6593) * Add `File.sort_order` * Apply requested changes * Enh Testing: Backport #6553 (#6588) * Fix humhub test case would fail on skipped tests (#6516) * Fix skipped test handling * Fix error while sending mails from the queue ``` - ReadableContentQueryTest: Global content guest non guest mode Destroying application Starting application [ConnectionWatcher] watching new connections [Fixtures] Loading fixtures [ConnectionWatcher] watching new connections [ConnectionWatcher] no longer watching new connections [ConnectionWatcher] closing all (0) connections [Fixtures] Done [yii\web\Session::open] 'Session started' [queue] 'Could not execute queued job! Message: Argument 2 passed to Symfony\\Component\\Mime\\Address::__construct() must be of the type string, null given, called in /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php on line 368 Trace:#0 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php(368): Symfony\\Component\\Mime\\Address->__construct() #1 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php(51): yii\\symfonymailer\\Message->convertStringsToAddresses() #2 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/targets/MailTarget.php(71): yii\\symfonymailer\\Message->setFrom() #3 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/targets/BaseTarget.php(161): humhub\\modules\\notification\\targets\\MailTarget->handle() #4 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/components/NotificationManager.php(101): humhub\\modules\\notification\\targets\\BaseTarget->send() #5 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/jobs/SendBulkNotification.php(38): humhub\\modules\\notification\\components\\NotificationManager->sendBulk() #6 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/queue/ActiveJob.php(33): humhub\\modules\\notification\\jobs\\SendBulkNotification->run() #7 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-queue/src/Queue.php(246): humhub\\modules\\queue\\ActiveJob->execute() #8 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/queue/driver/Instant.php(31): yii\\queue\\Queue->handleMessage() #9 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-queue/src/Queue.php(196): humhub\\modules\\queue\\driver\\Instant->pushMessage() #10 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/components/BaseNotification.php(210): yii\\queue\\Queue->push() #11 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(370): humhub\\modules\\notification\\components\\BaseNotification->sendBulk() #12 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(321): humhub\\modules\\content\\models\\Content->notifyContentCreated() #13 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(268): humhub\\modules\\content\\models\\Content->processNewContent() #14 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(615): humhub\\modules\\content\\models\\Content->afterSave() #15 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(570): yii\\db\\ActiveRecord->insertInternal() #16 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/BaseActiveRecord.php(676): yii\\db\\ActiveRecord->insert() #17 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/components/ContentActiveRecord.php(476): yii\\db\\BaseActiveRecord->save() #18 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/post/models/Post.php(97): humhub\\modules\\content\\components\\ContentActiveRecord->afterSave() #19 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(615): humhub\\modules\\post\\models\\Post->afterSave() #20 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(570): yii\\db\\ActiveRecord->insertInternal() #21 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/BaseActiveRecord.php(676): yii\\db\\ActiveRecord->insert() #22 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/tests/codeception/unit/ReadableContentQueryTest.php(70): yii\\db\\BaseActiveRecord->save() #23 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Test/Unit.php(57): tests\\codeception\\unit\\modules\\content\\ReadableContentQueryTest->_before() #24 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/phpunit-wrapper/src/TestCase.php(12): Codeception\\Test\\Unit->_setUp() #25 /home/mdr/projects/humhub/htdocs/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php(60): Codeception\\PHPUnit\\TestCase->setUp() #26 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestCase.php(1207): tests\\codeception\\_support\\HumHubDbTestCase->setUp() #27 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestResult.php(728): PHPUnit\\Framework\\TestCase->runBare() #28 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestCase.php(964): PHPUnit\\Framework\\TestResult->run() #29 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestCase->run() #30 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/phpunit-wrapper/src/Runner.php(117): PHPUnit\\Framework\\TestSuite->run() #31 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/SuiteManager.php(161): Codeception\\PHPUnit\\Runner->doEnhancedRun() #32 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Codecept.php(208): Codeception\\SuiteManager->run() #33 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Codecept.php(162): Codeception\\Codecept->runSuite() #34 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Command/Run.php(401): Codeception\\Codecept->run() #35 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Command/Command.php(298): Codeception\\Command\\Run->execute() #36 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run() #37 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(301): Symfony\\Component\\Console\\Application->doRunCommand() #38 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(171): Symfony\\Component\\Console\\Application->doRun() #39 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Application.php(117): Symfony\\Component\\Console\\Application->run() #40 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/app.php(46): Codeception\\Application->run() #41 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/app.php(47): {closure}() #42 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/codecept(6): require(\'/home/mdr/proje...\') #43 /home/mdr/projects/humhub/htdocs/protected/vendor/bin/codecept(112): include(\'/home/mdr/proje...\') #44 {main}' [queue] 'Could not execute queued job! Message: Argument 2 passed to Symfony\\Component\\Mime\\Address::__construct() must be of the type string, null given, called in /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php on line 368 Trace:#0 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php(368): Symfony\\Component\\Mime\\Address->__construct() #1 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-symfonymailer/src/Message.php(51): yii\\symfonymailer\\Message->convertStringsToAddresses() #2 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/targets/MailTarget.php(71): yii\\symfonymailer\\Message->setFrom() #3 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/targets/BaseTarget.php(161): humhub\\modules\\notification\\targets\\MailTarget->handle() #4 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/components/NotificationManager.php(101): humhub\\modules\\notification\\targets\\BaseTarget->send() #5 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/jobs/SendBulkNotification.php(38): humhub\\modules\\notification\\components\\NotificationManager->sendBulk() #6 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/queue/ActiveJob.php(33): humhub\\modules\\notification\\jobs\\SendBulkNotification->run() #7 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-queue/src/Queue.php(246): humhub\\modules\\queue\\ActiveJob->execute() #8 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/queue/driver/Instant.php(31): yii\\queue\\Queue->handleMessage() #9 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2-queue/src/Queue.php(196): humhub\\modules\\queue\\driver\\Instant->pushMessage() #10 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/notification/components/BaseNotification.php(210): yii\\queue\\Queue->push() #11 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(370): humhub\\modules\\notification\\components\\BaseNotification->sendBulk() #12 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(321): humhub\\modules\\content\\models\\Content->notifyContentCreated() #13 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/models/Content.php(268): humhub\\modules\\content\\models\\Content->processNewContent() #14 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(615): humhub\\modules\\content\\models\\Content->afterSave() #15 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(570): yii\\db\\ActiveRecord->insertInternal() #16 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/BaseActiveRecord.php(676): yii\\db\\ActiveRecord->insert() #17 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/components/ContentActiveRecord.php(476): yii\\db\\BaseActiveRecord->save() #18 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/post/models/Post.php(97): humhub\\modules\\content\\components\\ContentActiveRecord->afterSave() #19 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(615): humhub\\modules\\post\\models\\Post->afterSave() #20 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/ActiveRecord.php(570): yii\\db\\ActiveRecord->insertInternal() #21 /home/mdr/projects/humhub/htdocs/protected/vendor/yiisoft/yii2/db/BaseActiveRecord.php(676): yii\\db\\ActiveRecord->insert() #22 /home/mdr/projects/humhub/htdocs/protected/humhub/modules/content/tests/codeception/unit/ReadableContentQueryTest.php(76): yii\\db\\BaseActiveRecord->save() #23 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Test/Unit.php(57): tests\\codeception\\unit\\modules\\content\\ReadableContentQueryTest->_before() #24 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/phpunit-wrapper/src/TestCase.php(12): Codeception\\Test\\Unit->_setUp() #25 /home/mdr/projects/humhub/htdocs/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php(60): Codeception\\PHPUnit\\TestCase->setUp() #26 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestCase.php(1207): tests\\codeception\\_support\\HumHubDbTestCase->setUp() #27 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestResult.php(728): PHPUnit\\Framework\\TestCase->runBare() #28 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestCase.php(964): PHPUnit\\Framework\\TestResult->run() #29 /home/mdr/projects/humhub/htdocs/protected/vendor/phpunit/phpunit/src/Framework/TestSuite.php(684): PHPUnit\\Framework\\TestCase->run() #30 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/phpunit-wrapper/src/Runner.php(117): PHPUnit\\Framework\\TestSuite->run() #31 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/SuiteManager.php(161): Codeception\\PHPUnit\\Runner->doEnhancedRun() #32 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Codecept.php(208): Codeception\\SuiteManager->run() #33 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Codecept.php(162): Codeception\\Codecept->runSuite() #34 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Command/Run.php(401): Codeception\\Codecept->run() #35 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Command/Command.php(298): Codeception\\Command\\Run->execute() #36 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(1040): Symfony\\Component\\Console\\Command\\Command->run() #37 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(301): Symfony\\Component\\Console\\Application->doRunCommand() #38 /home/mdr/projects/humhub/htdocs/protected/vendor/symfony/console/Application.php(171): Symfony\\Component\\Console\\Application->doRun() #39 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/src/Codeception/Application.php(117): Symfony\\Component\\Console\\Application->run() #40 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/app.php(46): Codeception\\Application->run() #41 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/app.php(47): {closure}() #42 /home/mdr/projects/humhub/htdocs/protected/vendor/codeception/codeception/codecept(6): require(\'/home/mdr/proje...\') #43 /home/mdr/projects/humhub/htdocs/protected/vendor/bin/codecept(112): include(\'/home/mdr/proje...\') #44 {main}' [tests\codeception\_support\HumHubDbTestCase::setUp] Initializing Modules [tests\codeception\_support\HumHubDbTestCase::setUp] Reloading settings [tests\codeception\_support\HumHubDbTestCase::setUp] Flushing cache [tests\codeception\_support\HumHubDbTestCase::setUp] Deleting mails [yii\mail\BaseMailer::send] 'Sending email "Sara Tester just wrote post "Public Space Public Post" in space Space 3" to "user1@example.com"' [yii\mail\BaseMailer::send] 'Sending email "Sara Tester just wrote post "Public Space Private Post" in space Space 3" to "user1@example.com"' [yii\web\User::logout] 'User \'2\' logged out from .' ✔ ReadableContentQueryTest: Global content guest non guest mode (3.06s) ^ ``` rather than ``` - ReadableContentQueryTest: Global content guest non guest mode Destroying application Starting application [ConnectionWatcher] watching new connections [Fixtures] Loading fixtures [ConnectionWatcher] watching new connections [ConnectionWatcher] no longer watching new connections [ConnectionWatcher] closing all (0) connections [Fixtures] Done [tests\codeception\_support\HumHubDbTestCase::setUp] Initializing Modules [tests\codeception\_support\HumHubDbTestCase::setUp] Reloading settings [tests\codeception\_support\HumHubDbTestCase::setUp] Flushing cache [tests\codeception\_support\HumHubDbTestCase::setUp] Deleting mails [yii\web\Session::open] 'Session started' [yii\mail\BaseMailer::send] 'Sending email "Sara Tester just wrote post "Public Space Public Post" in space Space 3" to "user1@example.com"' [yii\mail\BaseMailer::send] 'Sending email "Sara Tester just wrote post "Public Space Private Post" in space Space 3" to "user1@example.com"' [yii\web\User::logout] 'User \'2\' logged out from .' ✔ ReadableContentQueryTest: Global content guest non guest mode (1.51s) Destroying application [ConnectionWatcher] no longer watching new connections [ConnectionWatcher] closing all (0) connections - ``` * Support log assertions (#6553) * Make methods static where possible, so they can be used in class setup and teardown * Revert extracting DB helper functions to DbHelper.php * fixup! Enh UUID validator * fixup! Revert extracting DB helper functions to DbHelper.php --------- Co-authored-by: Yuriy Bakhtin Co-authored-by: Lucas Bartholemy --- CHANGELOG-DEV.md | 6 +- CHANGELOG.md | 1 + .../modules/file/actions/DownloadAction.php | 2 +- ...m230618_135508_file_add_sorting_column.php | 64 ++ protected/humhub/modules/file/models/File.php | 5 +- .../codeception/_support/ArrayTarget.php | 4 + .../codeception/_support/HumHubDbTestCase.php | 445 +---------- .../codeception/_support/HumHubHelper.php | 25 +- .../_support/HumHubHelperTrait.php | 707 +++++++++++++++--- .../tests/codeception/_support/Logger.php | 4 + .../_support/TraversableContainsRegex.php | 4 + .../tests/codeception/unit/LoadDbTest.php | 7 +- 12 files changed, 724 insertions(+), 550 deletions(-) create mode 100644 protected/humhub/modules/file/migrations/m230618_135508_file_add_sorting_column.php diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index c1458cece2..0016a74db0 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -3,7 +3,6 @@ HumHub Changelog 1.16.0 (Unreleased) ------------------- -- Enh #6553: Support log assertions - Enh #6530: Small performance improvements - Fix #6511: Only test compatible modules in `onMarketplaceAfterFilterModules()` - Enh #6511: Backup folder path is now return from `removeModule()` @@ -15,20 +14,21 @@ HumHub Changelog - Enh #6511: Add Event tracking capabilities to HumHubDbTestCase - Enh #6511: Add test for ModuleManager - Fix #6519: Ensure e-mails would always have a sender address set -- Fix #6516: Humhub test case would fail on skipped tests - Enh #6512: Show error messages when DB connection configuration is invalid -- Enh #6451: Introduce Archiveable, Deletable, Editable, Readable, & Viewable Interfaces - Enh #5315: Default stream sort by `created_at` instead of `id` - Fix #6337: Update `created_at` after first publishing of content record 1.15.0-beta.2 (Unreleased) -------------------------- +- Enh #6593: Add field `file.sort_order` - Enh #6592: Add field `file.state` - Enh #6591: Add field `file.category` - Enh #6568: improve UUID validation and add `UUIDValidater` +- Enh #6553: Support log assertions - Fix #6551: Migration's transaction with invalid savepoint - Fix #6549: Empty profile field type causing an unspecific error - Enh #6529: Add boolean return-type to `*safe*` methods in migrations +- Fix #6516: Humhub test case would fail on skipped tests - Enh #6478: Add pseudo test class to allow population of DB with standard test data - Enh #6480: Convert assert* and db* methods to static, in line with general usage pattern - Enh #6505: Introduce Application interface; now also fire the `onInit` event when the web application has initialized diff --git a/CHANGELOG.md b/CHANGELOG.md index fa4dc307dd..285be404f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ HumHub Changelog 1.14.5 (Unreleased) --------------------------- - Fix #6596: Fix duplicate error and hint for RichText editor with defined form +- Fix #6595: Fix mimetype of converted preview svg file to png after uploading 1.14.4 (September 20, 2023) --------------------------- diff --git a/protected/humhub/modules/file/actions/DownloadAction.php b/protected/humhub/modules/file/actions/DownloadAction.php index 45814dcd68..cc050e7bff 100644 --- a/protected/humhub/modules/file/actions/DownloadAction.php +++ b/protected/humhub/modules/file/actions/DownloadAction.php @@ -104,7 +104,7 @@ class DownloadAction extends Action public function run() { $fileName = $this->getFileName(); - $mimeType = FileHelper::getMimeTypeByExtension($fileName); + $mimeType = FileHelper::getMimeType($this->getStoredFilePath()); $options = [ 'inline' => (!$this->download && in_array($mimeType, $this->getModule()->inlineMimeTypes, true)), diff --git a/protected/humhub/modules/file/migrations/m230618_135508_file_add_sorting_column.php b/protected/humhub/modules/file/migrations/m230618_135508_file_add_sorting_column.php new file mode 100644 index 0000000000..d68651f6f1 --- /dev/null +++ b/protected/humhub/modules/file/migrations/m230618_135508_file_add_sorting_column.php @@ -0,0 +1,64 @@ +table = File::tableName(); + parent::__construct($config); + } + + /** + * {@inheritdoc} + * @throws \yii\db\Exception + */ + public function safeUp(): void + { + $command = Yii::$app->getDb() + ->createCommand(); + + $this->safeAddColumn( + $this->table, + 'sort_order', + $this->integer(11) + ->notNull() + ->defaultValue(100) + ->after('object_id') + ); + + $command->update( + $this->table, + ['object_model' => null], + ['object_model' => ''] + ) + ->execute() + ; + + $command->update( + $this->table, + ['object_id' => null], + ['object_id' => ''] + ) + ->execute() + ; + + $this->safeCreateIndex("ix-$this->table-object", $this->table, ['object_model', 'object_id', 'sort_order']); + + $this->safeDropIndex('index_object', $this->table); + } +} diff --git a/protected/humhub/modules/file/models/File.php b/protected/humhub/modules/file/models/File.php index 9ad1a24cb3..3be12162b7 100644 --- a/protected/humhub/modules/file/models/File.php +++ b/protected/humhub/modules/file/models/File.php @@ -44,6 +44,7 @@ use humhub\interfaces\ViewableInterface; * @property string|null $object_model * @property integer|null $object_id * @property integer|null $content_id + * @property integer $sort_order * @property string|null $created_at * @property integer|null $created_by * @property string|null $updated_at @@ -118,7 +119,7 @@ class File extends FileCompat implements ViewableInterface 'pattern' => '/[^a-zA-Z0-9\.ä\/\-\+]/', 'message' => Yii::t('FileModule.base', 'Invalid Mime-Type') ], - [['category', 'size', 'state'], 'integer'], + [['category', 'size', 'state', 'sort_order'], 'integer'], [['file_name', 'title'], 'string', 'max' => 255], ]; } @@ -178,6 +179,8 @@ class File extends FileCompat implements ViewableInterface $this->state ??= self::STATE_PUBLISHED; + $this->sort_order ??= 0; + return parent::beforeSave($insert); } diff --git a/protected/humhub/tests/codeception/_support/ArrayTarget.php b/protected/humhub/tests/codeception/_support/ArrayTarget.php index 29ee600db5..563e7b0926 100644 --- a/protected/humhub/tests/codeception/_support/ArrayTarget.php +++ b/protected/humhub/tests/codeception/_support/ArrayTarget.php @@ -6,6 +6,10 @@ * @license https://www.humhub.com/licences */ +/** + * @noinspection PhpIllegalPsrClassPathInspection + */ + namespace tests\codeception\_support; use yii\log\Target; diff --git a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php index f6802d1529..4a57270353 100644 --- a/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php +++ b/protected/humhub/tests/codeception/_support/HumHubDbTestCase.php @@ -1,49 +1,40 @@ initModules(__METHOD__); - $this->reloadSettings(__METHOD__); - $this->flushCache(__METHOD__); - $this->deleteMails(__METHOD__); + static::initModules(__METHOD__); + static::reloadSettings(__METHOD__); + static::flushCache(__METHOD__); + static::deleteMails(__METHOD__); parent::setUp(); } - protected function tearDown(): void - { - static::logReset(); - - parent::tearDown(); - } - - /** * Initializes modules defined in @tests/codeception/config/test.config.php * Note the config key in test.config.php is modules and not humhubModules! */ - protected function initModules(?string $caller = null) + protected static function initModules(?string $caller = null) { codecept_debug(sprintf('[%s] Initializing Modules', $caller ?? __METHOD__)); $cfg = Configuration::config(); @@ -122,7 +105,7 @@ class HumHubDbTestCase extends Unit if (!empty($this->fixtureConfig)) { foreach ($this->fixtureConfig as $fixtureTable => $fixtureClass) { if ($fixtureClass === 'default') { - $result = array_merge($result, $this->getDefaultFixtures()); + $result = array_merge($result, static::getDefaultFixtures()); } else { $result[$fixtureTable] = ['class' => $fixtureClass]; } @@ -132,7 +115,7 @@ class HumHubDbTestCase extends Unit return $result; } - protected function getDefaultFixtures(): array + protected static function getDefaultFixtures(): array { return [ 'user' => ['class' => UserFullFixture::class], @@ -152,263 +135,10 @@ class HumHubDbTestCase extends Unit ]; } - public static function assertHasNotification($class, ActiveRecord $source, $originator_id = null, $target_id = null, $msg = '') - { - $notificationQuery = Notification::find()->where([ - 'class' => $class, - 'source_class' => PolymorphicRelation::getObjectModel($source), - 'source_pk' => $source->getPrimaryKey(), - ]); - if (is_string($target_id)) { - $msg = $target_id; - $target_id = null; - } - - if ($originator_id != null) { - $notificationQuery->andWhere(['originator_user_id' => $originator_id]); - } - - if ($target_id != null) { - $notificationQuery->andWhere(['user_id' => $target_id]); - } - - static::assertNotEmpty($notificationQuery->all(), $msg); - } - - public static function assertEqualsNotificationCount($count, $class, ActiveRecord $source, $originator_id = null, $target_id = null, $msg = '') - { - $notificationQuery = Notification::find()->where(['class' => $class, 'source_class' => PolymorphicRelation::getObjectModel($source), 'source_pk' => $source->getPrimaryKey()]); - - if ($originator_id != null) { - $notificationQuery->andWhere(['originator_user_id' => $originator_id]); - } - - if ($target_id != null) { - $notificationQuery->andWhere(['user_id' => $target_id]); - } - - static::assertEquals($count, $notificationQuery->count(), $msg); - } - - public static function assertHasNoNotification($class, ActiveRecord $source, $originator_id = null, $target_id = null, $msg = '') - { - $notificationQuery = Notification::find()->where(['class' => $class, 'source_class' => PolymorphicRelation::getObjectModel($source), 'source_pk' => $source->getPrimaryKey()]); - - if ($originator_id != null) { - $notificationQuery->andWhere(['originator_user_id' => $originator_id]); - } - - if ($target_id != null) { - $notificationQuery->andWhere(['user_id' => $target_id]); - } - - static::assertEmpty($notificationQuery->all(), $msg); - } - - public static function assertHasActivity($class, ActiveRecord $source, $msg = '') - { - $activity = Activity::findOne([ - 'class' => $class, - 'object_model' => PolymorphicRelation::getObjectModel($source), - 'object_id' => $source->getPrimaryKey(), - ]); - static::assertNotNull($activity, $msg); - } - - /** - * @return Yii2|Module - * @throws ModuleException - */ - public function getYiiModule() - { - return $this->getModule('Yii2'); - } - - /** - * @see assertSentEmail - * @since 1.3 - */ - public function assertMailSent($count = 0) - { - /** @noinspection PhpUnhandledExceptionInspection */ - $this->getYiiModule()->seeEmailIsSent($count); - } - - /** - * @param int $count - * - * @throws ModuleException - * @since 1.3 - */ - public function assertSentEmail(int $count = 0) - { - $this->getYiiModule()->seeEmailIsSent($count); - } - - public function assertEqualsLastEmailTo($to, $strict = true) - { - if (is_string($to)) { - $to = [$to]; - } - - /** @noinspection PhpUnhandledExceptionInspection */ - $message = $this->getYiiModule()->grabLastSentEmail(); - $expected = $message->getTo(); - - foreach ($to as $email) { - $this->assertArrayHasKey($email, $expected); - } - - if ($strict) { - $this->assertCount(count($expected), $to); - } - } - - public function assertEqualsLastEmailSubject($subject) - { - /** @noinspection PhpUnhandledExceptionInspection */ - $message = $this->getYiiModule()->grabLastSentEmail(); - $this->assertEquals($subject, str_replace(["\n", "\r"], '', $message->getSubject())); - } - - public static function assertUUID($value, bool $allowNull = false, bool $strict = false, $message = '') - { - if ($allowNull && $value === null) { - return; - } - - // validate UUID without changing the input (other than trimming) - $uuid = UUID::validate($value, null, null, null); - - static::assertNotNull($uuid, $message); - - if ($strict) { - static::assertEquals($uuid, $value, $message); - } - } - - public static function assertNotUUID($value, $message = '') - { - // validate UUID without changing the input (other than trimming) - $uuid = UUID::validate($value, null, null, null); - - static::assertNull($uuid, $message); - } - - public function assertEvents(array $events = [], string $message = ''): void - { - static::assertEquals($events, $this->firedEvents, $message); - - $this->firedEvents = []; - } - - /** - * @param int|null $expected Number of records expected. Null for any number, but not none - * @param string|array|ExpressionInterface $tables - * @param string|array|ExpressionInterface|null $condition - * @param array|null $params - * @param string $message - * - * @return void - * @since 1.15 - */ - public static function assertRecordCount(?int $expected, $tables, $condition = null, ?array $params = [], string $message = ''): void - { - $count = static::dbCount($tables, $condition, $params ?? []); - - if ($expected === null) { - static::assertGreaterThan(0, $count, $message); - } else { - static::assertEquals($expected, $count, $message); - } - } - - /** - * @param string|array|ExpressionInterface $tables - * @param string|array|ExpressionInterface|null $condition - * @param array|null $params - * @param string $message - * - * @return void - * @since 1.15 - */ - public static function assertRecordExistsAny($tables, $condition = null, ?array $params = [], string $message = 'Record does not exist'): void - { - static::assertRecordCount(null, $tables, $condition, $params ?? [], $message); - } - - /** - * @param string|array|ExpressionInterface $tables - * @param string|array|ExpressionInterface|null $condition - * @param array|null $params - * @param string $message - * - * @return void - * @since 1.15 - */ - public static function assertRecordExists($tables, $condition = null, ?array $params = [], string $message = 'Record does not exist'): void - { - static::assertRecordCount(1, $tables, $condition, $params ?? [], $message); - } - - /** - * @param string|array|ExpressionInterface $tables - * @param string|array|ExpressionInterface|null $condition - * @param array|null $params - * @param string $message - * - * @return void - * @since 1.15 - */ - public static function assertRecordNotExists($tables, $condition = null, ?array $params = [], string $message = 'Record exists'): void - { - static::assertRecordCount(0, $tables, $condition, $params ?? [], $message); - } - - /** - * @param int|string|null $expected Number of records expected. Null for any number, but not none - * @param string $column - * @param string|array|ExpressionInterface $tables - * @param string|array|ExpressionInterface|null $condition - * @param array|null $params - * @param string $message - * - * @return void - * @since 1.15 - */ - public static function assertRecordValue($expected, string $column, $tables, $condition = null, ?array $params = [], string $message = ''): void - { - $value = static::dbQuery($tables, $condition, $params, 1)->select($column)->scalar(); - static::assertEquals($expected, $value, $message); - } - - public function expectExceptionTypeError(string $calledClass, string $method, int $argumentNumber, string $argumentName, string $expectedType, string $givenTye, string $exceptionClass = TypeError::class): void - { - $this->expectException($exceptionClass); - - $calledClass = str_replace('\\', '\\\\', $calledClass); - $argumentName = ltrim($argumentName, '$'); - - $this->expectExceptionMessageRegExp( - sprintf( - // Php < 8 uses: "Argument n passed to class::method() ..." - // PHP > 7 uses: "class::method(): Argument #n ($argument) ..." - '@^((Argument %d passed to )?%s::%s\\(\\)(?(2)|: Argument #%d \\(\\$%s\\))) must be of( the)? type %s, %s given, called in /.*@', - $argumentNumber, - $calledClass, - $method, - $argumentNumber, - $argumentName, - $expectedType, - $givenTye - ) - ); - } - /** * @param bool $allow */ - public function allowGuestAccess(bool $allow = true) + public static function allowGuestAccess(bool $allow = true) { Yii::$app ->getModule('user') @@ -416,7 +146,7 @@ class HumHubDbTestCase extends Unit ->set('auth.allowGuestAccess', (int)$allow); } - public function setProfileField($field, $value, $user) + public static function setProfileField($field, $value, $user) { if (is_int($user)) { $user = User::findOne($user); @@ -430,31 +160,31 @@ class HumHubDbTestCase extends Unit $user->profile->save(); } - public function becomeFriendWith($username) + public static function becomeFriendWith($username) { $user = User::findOne(['username' => $username]); Friendship::add($user, Yii::$app->user->identity); Friendship::add(Yii::$app->user->identity, $user); } - public function follow($username) + public static function follow($username) { User::findOne(['username' => $username])->follow(); } - public function enableFriendships($enable = true) + public static function enableFriendships($enable = true) { Yii::$app->getModule('friendship')->settings->set('enable', $enable); } - public function setGroupPermission($groupId, $permission, $state = BasePermission::STATE_ALLOW) + public static function setGroupPermission($groupId, $permission, $state = BasePermission::STATE_ALLOW) { $permissionManger = new PermissionManager(); $permissionManger->setGroupState($groupId, $permission, $state); Yii::$app->user->permissionManager->clear(); } - public function setContentContainerPermission( + public static function setContentContainerPermission( $contentContainer, $groupId, $permission, @@ -465,126 +195,15 @@ class HumHubDbTestCase extends Unit $contentContainer->permissionManager->clear(); } - public function becomeUser($userName): ?User + public static function becomeUser($userName): ?User { $user = User::findOne(['username' => $userName]); Yii::$app->user->switchIdentity($user); return $user; } - public function logout() + public static function logout() { Yii::$app->user->logout(); } - - public function handleEvent(Event $event, array $eventData = []) - { - $eventData += [ - 'class' => get_class($event), - 'event' => $event->name, - 'sender' => $event->sender, - 'data' => $event->data, - 'handled' => $event->handled, - ]; - - $this->firedEvents[] = $eventData; - } - - /** - * @see \yii\db\Connection::createCommand() - * @since 1.15 - */ - public static function dbCommand($sql = null, $params = []): Command - { - return Yii::$app->getDb()->createCommand($sql, $params); - } - - /** - * @param Command $cmd - * @param bool $execute - * - * @return Command - * @throws Exception - */ - protected static function dbCommandExecute(Command $cmd, bool $execute = true): Command - { - if ($execute) { - $cmd->execute(); - } - - return $cmd; - } - - /** - * @see Query - * @since 1.15 - */ - public static function dbQuery($tables, $condition, $params = [], $limit = 10): Query - { - return (new Query()) - ->from($tables) - ->where($condition, $params) - ->limit($limit); - } - - /** - * @see Command::insert - * @since 1.15 - */ - public static function dbInsert($table, $columns, bool $execute = true): Command - { - return static::dbCommandExecute(static::dbCommand()->insert($table, $columns), $execute); - } - - /** - * @see Command::update - * @since 1.15 - */ - public static function dbUpdate($table, $columns, $condition = '', $params = [], bool $execute = true): Command - { - return static::dbCommandExecute(static::dbCommand()->update($table, $columns, $condition, $params), $execute); - } - - /** - * @see Command::upsert - * @since 1.15 - */ - public static function dbUpsert($table, $insertColumns, $updateColumns = true, $params = [], bool $execute = true): Command - { - return static::dbCommandExecute(static::dbCommand()->upsert($table, $insertColumns, $updateColumns, $params), $execute); - } - - /** - * @see Command::delete() - * @since 1.15 - */ - public static function dbDelete($table, $condition = '', $params = [], bool $execute = true): Command - { - return static::dbCommandExecute(static::dbCommand()->delete($table, $condition, $params), $execute); - } - - /** - * @see Query::select - * @see Query::from - * @see Query::where - * @see \yii\db\QueryTrait::limit() - * @since 1.15 - */ - public static function dbSelect($tables, $columns, $condition = '', $params = [], $limit = 10, $selectOption = null): array - { - return static::dbQuery($tables, $condition, $params, $limit) - ->select($columns, $selectOption) - ->all(); - } - - /** - * @see Command::delete() - * @since 1.15 - */ - public static function dbCount($tables, $condition = '', $params = []) - { - return static::dbQuery($tables, $condition, $params) - ->select("count(*)") - ->scalar(); - } } diff --git a/protected/humhub/tests/codeception/_support/HumHubHelper.php b/protected/humhub/tests/codeception/_support/HumHubHelper.php index ad00d1370f..08e794eccc 100644 --- a/protected/humhub/tests/codeception/_support/HumHubHelper.php +++ b/protected/humhub/tests/codeception/_support/HumHubHelper.php @@ -1,5 +1,15 @@ getUrlManager()->setScriptUrl('/index-test.php'); - $this->reloadSettings(__METHOD__); - $this->flushCache(__METHOD__); + static::reloadSettings(__METHOD__); + static::flushCache(__METHOD__); } public function fetchInviteToken($mail) @@ -52,27 +63,28 @@ class HumHubHelper extends Module $this->getModule('Yii2')->_loadPage('POST', '/user/invite', ['Invite[emails]' => $email]); } + /** @noinspection PhpUnhandledExceptionInspection */ public function assertMailSent($count = 0, $msg = null) { - return $this->getModule('Yii2')->seeEmailIsSent($count); + $this->getYiiModule()->seeEmailIsSent($count); } public function assertEqualsLastEmailSubject($subject) { - $message = $this->getModule('Yii2')->grabLastSentEmail(); + $message = $this->getYiiModule()->grabLastSentEmail(); $this->assertEquals($subject, $message->getSubject()); } public function grapLastEmailText() { /** @var Message $message */ - $message = $this->getModule('Yii2')->grabLastSentEmail(); + $message = $this->getYiiModule()->grabLastSentEmail(); return $message->getTextBody(); } /*public function assertEqualsLastEmailSubject($subject) { - $message = $this->getModule('Yii2')->grabLastSentEmail(); + $message = $this->getYiiModule()->grabLastSentEmail(); $this->assertEquals($subject, $message->getSubject()); }*/ @@ -95,5 +107,4 @@ class HumHubHelper extends Module } } } - } diff --git a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php index 6dfe4b9596..7640c7b33d 100644 --- a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php +++ b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php @@ -6,23 +6,36 @@ * @license https://www.humhub.com/licences */ +/** + * @noinspection PhpIllegalPsrClassPathInspection + */ + namespace tests\codeception\_support; +use Codeception\Exception\ModuleException; +use Codeception\Module\Yii2; +use humhub\components\behaviors\PolymorphicRelation; +use humhub\libs\UUID; use humhub\models\UrlOembed; +use humhub\modules\activity\models\Activity; use humhub\modules\content\widgets\richtext\converter\RichTextToHtmlConverter; use humhub\modules\content\widgets\richtext\converter\RichTextToMarkdownConverter; use humhub\modules\content\widgets\richtext\converter\RichTextToPlainTextConverter; use humhub\modules\content\widgets\richtext\converter\RichTextToShortTextConverter; +use humhub\modules\notification\models\Notification; use PHPUnit\Framework\Constraint\LogicalNot; use PHPUnit\Framework\Exception; +use TypeError; use Yii; use yii\base\ErrorException; use yii\base\InvalidConfigException; +use yii\db\ActiveRecord; +use yii\db\Command; +use yii\db\ExpressionInterface; +use yii\db\Query; use yii\helpers\FileHelper; use yii\log\Dispatcher; -use function PHPUnit\Framework\assertEquals; - /** * Humhub Test Helper Functions * @@ -30,11 +43,18 @@ use function PHPUnit\Framework\assertEquals; */ trait HumHubHelperTrait { + public array $firedEvents = []; protected static ?ArrayTarget $logTarget = null; - protected static ?\yii\log\Logger $logOldLogger = null; private static $logOldDispatcher; - protected function flushCache(?string $caller = null) + protected function tearDown(): void + { + static::logReset(); + + parent::tearDown(); + } + + protected static function flushCache(?string $caller = null) { codecept_debug(sprintf('[%s] Flushing cache', $caller ?? __METHOD__)); $cachePath = Yii::getAlias('@runtime/cache'); @@ -50,7 +70,7 @@ trait HumHubHelperTrait UrlOembed::flush(); } - protected function reloadSettings(?string $caller = null) + protected static function reloadSettings(?string $caller = null) { codecept_debug(sprintf('[%s] Reloading settings', $caller ?? __METHOD__)); Yii::$app->settings->reload(); @@ -62,7 +82,7 @@ trait HumHubHelperTrait } } - protected function deleteMails(?string $caller = null) + protected static function deleteMails(?string $caller = null) { codecept_debug(sprintf('[%s] Deleting mails', $caller ?? __METHOD__)); $path = Yii::getAlias('@runtime/mail'); @@ -74,6 +94,463 @@ trait HumHubHelperTrait } } + /** + * @return Yii2 + * @throws ModuleException + */ + public function getYiiModule(): Yii2 + { + /** @noinspection PhpIncompatibleReturnTypeInspection */ + return $this->getModule('Yii2'); + } + + /** + * GENERAL + * ======= + */ + + /** + * Asserts that `$haystack` contains an element that matches the `$regex` + * + * @throws Exception + * @since 1.15 + */ + public static function assertContainsRegex(string $regex, iterable $haystack, string $message = ''): void + { + $constraint = new TraversableContainsRegex($regex); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * Asserts that `$haystack` does not contain an element that matches the `$regex` + * + * @throws Exception + * @since 1.15 + */ + public static function assertNotContainsRegex(string $regex, iterable $haystack, string $message = ''): void + { + $constraint = new LogicalNot( + new TraversableContainsRegex($regex), + ); + + static::assertThat($haystack, $constraint, $message); + } + + /** + * ACTIVITIES + * ========== + */ + + public static function assertHasActivity($class, ActiveRecord $source, $msg = '') + { + $activity = Activity::findOne([ + 'class' => $class, + 'object_model' => PolymorphicRelation::getObjectModel($source), + 'object_id' => $source->getPrimaryKey(), + ]); + + static::assertNotNull($activity, $msg); + } + + /** + * EVENTS (Yii) + * ============ + */ + + /** + * @since 1.15 + */ + public function assertEvents(array $events = [], string $message = ''): void + { + static::assertEquals($events, $this->firedEvents, $message); + + $this->firedEvents = []; + } + + /** + * LOGGING + * ======= + */ + + /** + * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` + * exactly + * + * @throws ErrorException + * @see static::logInitialize() + * @see static::logFilterMessages() + * @since 1.15 + */ + public static function assertLog(?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + if ($logMessage === null) { + static::assertNotEmpty($messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } else { + static::assertContains( + $logMessage, + $messages, + $errorMessage ?? print_r(static::logFilterMessageTexts(), true) + ); + } + } + + /** + * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` + * exactly + * + * @throws ErrorException + * @see static::logInitialize() + * @see static::logFilterMessages() + * @since 1.15 + */ + public static function assertLogCount(int $expectedCount, ?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + if ($logMessage !== null) { + $messages = array_filter($messages, static fn($text) => $text === $logMessage); + } + + static::assertCount($expectedCount, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + + /** + * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` + * exactly + * + * @throws ErrorException + * @since 1.15 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function assertNotLog(?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + if ($logMessage === null) { + static::assertEmpty($messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } else { + static::assertNotContains( + $logMessage, + $messages, + $errorMessage ?? print_r(static::logFilterMessageTexts(), true) + ); + } + } + + /** + * @param string $regex At least one of the filtered messages must match the given `$regex` pattern + * + * @throws ErrorException|Exception + * @since 1.15 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function assertLogRegex(string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + static::assertContainsRegex($regex, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + + /** + * @param string $regex At least one of the filtered messages must match the given `$regex` pattern + * + * @throws ErrorException|Exception + * @since 1.15 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function assertLogRegexCount(int $expectedCount, string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + if (count($messages)) { + try { + preg_match($regex, ''); + } catch (ErrorException $e) { + throw new Exception("Invalid regex given: '{$regex}'"); + } + + $messages = array_filter($messages, static fn($text) => preg_match($regex, $text)); + } + + static::assertCount($expectedCount, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + } + + /** + * @param string $regex None of the filtered messages may match the given `$regex` pattern + * + * @throws ErrorException|Exception + * @since 1.15 + * @see static::logInitialize() + * @see static::logFilterMessages() + */ + public static function assertNotLogRegex(string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + { + $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); + + static::assertNotContainsRegex( + $regex, + $messages, + $errorMessage ?? print_r(static::logFilterMessageTexts(), true) + ); + } + + /** + * MAILS + * ===== + */ + + /** + * @see assertSentEmail + * @since 1.3 + */ + public function assertMailSent($count = 0) + { + /** @noinspection PhpUnhandledExceptionInspection */ + $this->getYiiModule()->seeEmailIsSent($count); + } + + /** + * @param int $count + * + * @throws ModuleException + * @since 1.3 + */ + public function assertSentEmail(int $count = 0) + { + $this->getYiiModule()->seeEmailIsSent($count); + } + + public function assertEqualsLastEmailTo($to, $strict = true) + { + if (is_string($to)) { + $to = [$to]; + } + + /** @noinspection PhpUnhandledExceptionInspection */ + $message = $this->getYiiModule()->grabLastSentEmail(); + $expected = $message->getTo(); + + foreach ($to as $email) { + $this->assertArrayHasKey($email, $expected); + } + + if ($strict) { + $this->assertCount(count($expected), $to); + } + } + + public function assertEqualsLastEmailSubject($subject) + { + /** @noinspection PhpUnhandledExceptionInspection */ + $message = $this->getYiiModule()->grabLastSentEmail(); + $this->assertEquals($subject, str_replace(["\n", "\r"], '', $message->getSubject())); + } + + /** + * NOTIFICATIONS + * ============= + */ + + public static function assertHasNotification($class, ActiveRecord $source, $originator_id = null, $target_id = null, $msg = '') + { + $notificationQuery = Notification::find()->where([ + 'class' => $class, + 'source_class' => PolymorphicRelation::getObjectModel($source), + 'source_pk' => $source->getPrimaryKey(), + ]); + + if (is_string($target_id)) { + $msg = $target_id; + $target_id = null; + } + + if ($originator_id != null) { + $notificationQuery->andWhere(['originator_user_id' => $originator_id]); + } + + if ($target_id != null) { + $notificationQuery->andWhere(['user_id' => $target_id]); + } + + static::assertNotEmpty($notificationQuery->all(), $msg); + } + + public static function assertHasNoNotification($class, ActiveRecord $source, $originator_id = null, $target_id = null, $msg = '') + { + $notificationQuery = Notification::find()->where([ + 'class' => $class, + 'source_class' => PolymorphicRelation::getObjectModel($source), + 'source_pk' => $source->getPrimaryKey(), + ]); + + if ($originator_id != null) { + $notificationQuery->andWhere(['originator_user_id' => $originator_id]); + } + + if ($target_id != null) { + $notificationQuery->andWhere(['user_id' => $target_id]); + } + + static::assertEmpty($notificationQuery->all(), $msg); + } + + public static function assertEqualsNotificationCount($count, $class, ActiveRecord $source, $originator_id = null, $target_id = null, $msg = '') + { + $notificationQuery = Notification::find()->where([ + 'class' => $class, + 'source_class' => PolymorphicRelation::getObjectModel($source), + 'source_pk' => $source->getPrimaryKey() + ]); + + if ($originator_id != null) { + $notificationQuery->andWhere(['originator_user_id' => $originator_id]); + } + + if ($target_id != null) { + $notificationQuery->andWhere(['user_id' => $target_id]); + } + + static::assertEquals($count, $notificationQuery->count(), $msg); + } + + /** + * RECORDS + * ======= + */ + + /** + * @param int|null $expected Number of records expected. Null for any number, but not none + * @param string|array|ExpressionInterface $tables + * @param string|array|ExpressionInterface|null $condition + * @param array|null $params + * @param string $message + * + * @return void + * @since 1.15 + */ + public static function assertRecordCount(?int $expected, $tables, $condition = null, ?array $params = [], string $message = ''): void + { + $count = static::dbCount($tables, $condition, $params ?? []); + + if ($expected === null) { + static::assertGreaterThan(0, $count, $message); + } else { + static::assertEquals($expected, $count, $message); + } + } + + /** + * @param string|array|ExpressionInterface $tables + * @param string|array|ExpressionInterface|null $condition + * @param array|null $params + * @param string $message + * + * @return void + * @since 1.15 + */ + public static function assertRecordExists($tables, $condition = null, ?array $params = [], string $message = 'Record does not exist'): void + { + static::assertRecordCount(1, $tables, $condition, $params ?? [], $message); + } + + /** + * @param string|array|ExpressionInterface $tables + * @param string|array|ExpressionInterface|null $condition + * @param array|null $params + * @param string $message + * + * @return void + * @since 1.15 + */ + public static function assertRecordNotExists($tables, $condition = null, ?array $params = [], string $message = 'Record exists'): void + { + static::assertRecordCount(0, $tables, $condition, $params ?? [], $message); + } + + /** + * @param string|array|ExpressionInterface $tables + * @param string|array|ExpressionInterface|null $condition + * @param array|null $params + * @param string $message + * + * @return void + * @since 1.15 + */ + public static function assertRecordExistsAny($tables, $condition = null, ?array $params = [], string $message = 'Record does not exist'): void + { + static::assertRecordCount(null, $tables, $condition, $params ?? [], $message); + } + + /** + * @param int|string|null $expected Number of records expected. Null for any number, but not none + * @param string $column + * @param string|array|ExpressionInterface $tables + * @param string|array|ExpressionInterface|null $condition + * @param array|null $params + * @param string $message + * + * @return void + * @since 1.15 + */ + public static function assertRecordValue($expected, string $column, $tables, $condition = null, ?array $params = [], string $message = ''): void + { + $value = self::dbQuery($tables, $condition, $params, 1)->select($column)->scalar(); + static::assertEquals($expected, $value, $message); + } + + public static function assertUUID($value, bool $allowNull = false, bool $strict = false, $message = '') + { + if ($allowNull && $value === null) { + return; + } + + // validate UUID without changing the input (other than trimming) + $uuid = UUID::validate($value, null, null, null); + + static::assertNotNull($uuid, $message); + + if ($strict) { + static::assertEquals($uuid, $value, $message); + } + } + + public static function assertNotUUID($value, $message = '') + { + // validate UUID without changing the input (other than trimming) + $uuid = UUID::validate($value, null, null, null); + + static::assertNull($uuid, $message); + } + + public function expectExceptionTypeError(string $calledClass, string $method, int $argumentNumber, string $argumentName, string $expectedType, string $givenTye, string $exceptionClass = TypeError::class): void + { + $this->expectException($exceptionClass); + + $calledClass = str_replace('\\', '\\\\', $calledClass); + $argumentName = ltrim($argumentName, '$'); + + $this->expectExceptionMessageRegExp( + sprintf( + // Php < 8 uses: "Argument n passed to class::method() ..." + // PHP > 7 uses: "class::method(): Argument #n ($argument) ..." + '@^((Argument %d passed to )?%s::%s\\(\\)(?(2)|: Argument #%d \\(\\$%s\\))) must be of( the)? type %s, %s given, called in /.*@', + $argumentNumber, + $calledClass, + $method, + $argumentNumber, + $argumentName, + $expectedType, + $givenTye + ) + ); + } + /** * Start capturing log entries, in order to later check them with `assertLog*` and `assertNotLog*` functions * @@ -86,18 +563,18 @@ trait HumHubHelperTrait * - run the code that should generate the log entries * - call any of the `assertLog*` or `assertNotLog*` functions to test for the (un)expected log message * - optionally call `static::logFlush();` if you want to run more log-generating code - * - optionally call `static::logReset();` when you no longer need the log to be captured within the test. Though, this - * will automatically be called during `tearDown()`. + * - optionally call `static::logReset();` when you no longer need the log to be captured within the test. Though, + * this will automatically be called during `tearDown()`. * - * If you need all log entries, call `static::logFilterMessageTexts()` with no arguments, or if you want the complete - * log information, use `static::logFilterMessages()`. For the format of the returned array elements to the latter, - * please see `\yii\log\Logger::$messages`. + * If you need all log entries, call `static::logFilterMessageTexts()` with no arguments, or if you want the + * complete log information, use `static::logFilterMessages()`. For the format of the returned array elements to + * the latter, please see `\yii\log\Logger::$messages`. * * For some kind of sample implementation, please see `\humhub\tests\codeception\unit\LogAssertionsSelfTest` * ********************************************************************************************************************** * * @throws InvalidConfigException - * @since 1.16 + * @since 1.15 * @see \yii\log\Logger::$messages * @see \humhub\tests\codeception\unit\LogAssertionsSelfTest */ @@ -133,13 +610,16 @@ trait HumHubHelperTrait * @see Dispatcher::__construct * @see Dispatcher::getLogger */ - Yii::$app->set('log', Yii::createObject(['class' => Dispatcher::class, 'targets' => [static::class => self::$logTarget]])); + Yii::$app->set( + 'log', + Yii::createObject(['class' => Dispatcher::class, 'targets' => [static::class => self::$logTarget]]) + ); } /** * Flush the captured log entries without stopping to capture them * - * @since 1.16 + * @since 1.15 * @see static::logInitialize() */ protected static function logFlush() @@ -157,7 +637,7 @@ trait HumHubHelperTrait * Delete any captured log entry and stop capturing new entries. Automatically called by `static::tearDown()` * * @throws InvalidConfigException - * @since 1.16 + * @since 1.15 * @see static::logInitialize() * @see static::tearDown() */ @@ -187,13 +667,14 @@ trait HumHubHelperTrait * Returns the array of captured log message arrays * * @param int|int[]|null $levels Array or bitmask of verbosity levels to be returned: - * Logger::LEVEL_ERROR, Logger::LEVEL_WARNING, Logger::LEVEL_INFO, Logger::LEVEL_TRACE + * Logger::LEVEL_ERROR, Logger::LEVEL_WARNING, Logger::LEVEL_INFO, + * Logger::LEVEL_TRACE * @param string[]|null $categories Array of categories to be returned or null for any * @param string[]|null $exceptCategories Array of categories NOT to be returned, or null for no exclusion * * @return array of message, following the format of `\yii\log\Logger::$messages` * @throws ErrorException - * @since 1.16 + * @since 1.15 * @see static::logInitialize() * @see \yii\log\Logger::$messages */ @@ -213,7 +694,7 @@ trait HumHubHelperTrait * Returns an array of captured log messages as string (without the category or level) * * @throws ErrorException - * @since 1.16 + * @since 1.15 * @see static::logInitialize() * @see static::logFilterMessages() */ @@ -223,142 +704,122 @@ trait HumHubHelperTrait } /** - * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` exactly - * - * @throws ErrorException - * @see static::logInitialize() - * @see static::logFilterMessages() - * @since 1.16 + * @see \yii\db\Connection::createCommand() + * @since 1.15 + * @deprecated since 1.15 + * @internal */ - public static function assertLog(?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + public static function dbCommand($sql = null, $params = []): Command { - $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); - - if ($logMessage === null) { - static::assertNotEmpty($messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); - } else { - static::assertContains($logMessage, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); - } + return Yii::$app->getDb()->createCommand($sql, $params); } /** - * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` exactly + * @param Command $cmd + * @param bool $execute * - * @throws ErrorException - * @see static::logInitialize() - * @see static::logFilterMessages() - * @since 1.16 + * @return Command + * @throws \yii\db\Exception + * @since 1.15 + * @deprecated since 1.15 + * @internal */ - public static function assertLogCount(int $expectedCount, ?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + protected static function dbCommandExecute(Command $cmd, bool $execute = true): Command { - $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); - - if ($logMessage !== null) { - $messages = array_filter($messages, static fn($text) => $text === $logMessage); + if ($execute) { + $cmd->execute(); } - static::assertCount($expectedCount, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + return $cmd; } /** - * @param string|null $logMessage If not null, at least one of the filtered messages must match `$logMessage` exactly - * - * @throws ErrorException - * @since 1.16 - * @see static::logInitialize() - * @see static::logFilterMessages() + * @see Query + * @since 1.15 + * @deprecated since 1.15 + * @internal */ - public static function assertNotLog(?string $logMessage = null, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + public static function dbQuery($tables, $condition, $params = [], $limit = 10): Query { - $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); - - if ($logMessage === null) { - static::assertEmpty($messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); - } else { - static::assertNotContains($logMessage, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); - } + return (new Query()) + ->from($tables) + ->where($condition, $params) + ->limit($limit); } /** - * @param string $regex At least one of the filtered messages must match the given `$regex` pattern - * - * @throws ErrorException|Exception - * @since 1.16 - * @see static::logInitialize() - * @see static::logFilterMessages() + * @see Command::insert + * @since 1.15 + * @deprecated since 1.15 + * @internal */ - public static function assertLogRegex(string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + public static function dbInsert($table, $columns, bool $execute = true): Command { - $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); - - static::asserContainsRegex($regex, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + return static::dbCommandExecute(static::dbCommand()->insert($table, $columns), $execute); } /** - * @param string $regex At least one of the filtered messages must match the given `$regex` pattern - * - * @throws ErrorException|Exception - * @since 1.16 - * @see static::logInitialize() - * @see static::logFilterMessages() + * @see Command::update + * @since 1.15 + * @deprecated since 1.15 + * @internal */ - public static function assertLogRegexCount(int $expectedCount, string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + public static function dbUpdate($table, $columns, $condition = '', $params = [], bool $execute = true): Command { - $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); - - if (count($messages)) { - try { - preg_match($regex, ''); - } catch (ErrorException $e) { - throw new Exception("Invalid regex given: '{$regex}'"); - } - - $messages = array_filter($messages, static fn($text) => preg_match($regex, $text)); - } - - static::assertCount($expectedCount, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); + return static::dbCommandExecute(static::dbCommand()->update($table, $columns, $condition, $params), $execute); } /** - * @param string $regex None of the filtered messages may match the given `$regex` pattern - * - * @throws ErrorException|Exception - * @since 1.16 - * @see static::logInitialize() - * @see static::logFilterMessages() + * @see Command::upsert + * @since 1.15 + * @deprecated since 1.15 + * @internal */ - public static function assertNotLogRegex(string $regex, $levels = null, ?array $categories = null, ?array $exceptCategories = null, $errorMessage = null) + public static function dbUpsert($table, $insertColumns, $updateColumns = true, $params = [], bool $execute = true): Command { - $messages = static::logFilterMessageTexts($levels, $categories, $exceptCategories); - - static::assertNotContainsRegex($regex, $messages, $errorMessage ?? print_r(static::logFilterMessageTexts(), true)); - } - - /** - * Asserts that `$haystack` contains an element that matches the `$regex` - * - * @since 1.16 - * @throws Exception - */ - public static function asserContainsRegex(string $regex, iterable $haystack, string $message = ''): void - { - $constraint = new TraversableContainsRegex($regex); - - static::assertThat($haystack, $constraint, $message); - } - - /** - * Asserts that `$haystack` does not contain an element that matches the `$regex` - * - * @since 1.16 - * @throws Exception - */ - public static function assertNotContainsRegex(string $regex, iterable $haystack, string $message = ''): void - { - $constraint = new LogicalNot( - new TraversableContainsRegex($regex), + return static::dbCommandExecute( + static::dbCommand()->upsert($table, $insertColumns, $updateColumns, $params), + $execute ); + } - static::assertThat($haystack, $constraint, $message); + /** + * @see Command::delete() + * @since 1.15 + * @deprecated since 1.15 + * @internal + */ + public static function dbDelete($table, $condition = '', $params = [], bool $execute = true): Command + { + return static::dbCommandExecute(static::dbCommand()->delete($table, $condition, $params), $execute); + } + + /** + * @see Query::select + * @see Query::from + * @see Query::where + * @see \yii\db\QueryTrait::limit() + * @since 1.15 + * @deprecated since 1.15 + * @internal + */ + public static function dbSelect($tables, $columns, $condition = '', $params = [], $limit = 10, $selectOption = null): array + { + return static::dbQuery($tables, $condition, $params, $limit) + ->select($columns, $selectOption) + ->all(); + } + + /** + * @see Command::delete() + * @since 1.15 + * @deprecated since 1.15 + * @internal + */ + public static function dbCount($tables, $condition = '', $params = []) + { + return static::dbQuery($tables, $condition, $params) + ->select("count(*)") + ->scalar(); } } diff --git a/protected/humhub/tests/codeception/_support/Logger.php b/protected/humhub/tests/codeception/_support/Logger.php index 76dab8e943..c3267491cf 100644 --- a/protected/humhub/tests/codeception/_support/Logger.php +++ b/protected/humhub/tests/codeception/_support/Logger.php @@ -6,6 +6,10 @@ * @license https://www.humhub.com/licences */ +/** + * @noinspection PhpIllegalPsrClassPathInspection + */ + namespace tests\codeception\_support; use yii\log\Logger as YiiLogger; diff --git a/protected/humhub/tests/codeception/_support/TraversableContainsRegex.php b/protected/humhub/tests/codeception/_support/TraversableContainsRegex.php index ef6924e9b9..bad34ee37f 100644 --- a/protected/humhub/tests/codeception/_support/TraversableContainsRegex.php +++ b/protected/humhub/tests/codeception/_support/TraversableContainsRegex.php @@ -6,6 +6,10 @@ * @license https://www.humhub.com/licences */ +/** + * @noinspection PhpIllegalPsrClassPathInspection + */ + declare(strict_types=1); namespace tests\codeception\_support; diff --git a/protected/humhub/tests/codeception/unit/LoadDbTest.php b/protected/humhub/tests/codeception/unit/LoadDbTest.php index 60083d7b48..52af2cd5ff 100644 --- a/protected/humhub/tests/codeception/unit/LoadDbTest.php +++ b/protected/humhub/tests/codeception/unit/LoadDbTest.php @@ -6,6 +6,10 @@ * @license https://www.humhub.com/licences */ +/** + * @noinspection PhpIllegalPsrClassPathInspection + */ + namespace humhub\tests\codeception\unit; use Codeception\Exception\InjectionException; @@ -14,9 +18,8 @@ use Codeception\Lib\Console\Output; use Codeception\Lib\ModuleContainer; use Codeception\Module\Yii2; use Codeception\Test\Metadata; -use tests\codeception\_support\HumHubDbTestCase; -use Yii; use humhub\modules\queue\models\QueueExclusive; +use tests\codeception\_support\HumHubDbTestCase; /** * Test Debugging DbLoad Helper From 6d2b1881896adc33f6d5b6b4eb0def5656476995 Mon Sep 17 00:00:00 2001 From: Yuriy Bakhtin Date: Wed, 25 Oct 2023 17:08:23 +0200 Subject: [PATCH 18/19] Fix visibility of the method `Controller::getAccessRules()` (#6631) --- CHANGELOG-DEV.md | 1 + protected/humhub/components/behaviors/AccessControl.php | 8 ++++---- protected/humhub/controllers/OembedController.php | 2 +- .../modules/activity/controllers/AdminController.php | 2 +- protected/humhub/modules/admin/components/Controller.php | 7 ++----- .../modules/admin/controllers/ApprovalController.php | 4 ++-- .../admin/controllers/AuthenticationController.php | 4 ++-- .../humhub/modules/admin/controllers/GroupController.php | 4 ++-- .../modules/admin/controllers/InformationController.php | 7 +++++-- .../modules/admin/controllers/LoggingController.php | 4 ++-- .../humhub/modules/admin/controllers/ModuleController.php | 5 ++--- .../admin/controllers/PendingRegistrationsController.php | 2 +- .../modules/admin/controllers/SettingController.php | 5 ++--- .../humhub/modules/admin/controllers/SpaceController.php | 4 ++-- .../humhub/modules/admin/controllers/UserController.php | 2 +- .../modules/admin/controllers/UserPeopleController.php | 2 +- .../admin/controllers/UserPermissionsController.php | 5 +---- .../modules/admin/controllers/UserProfileController.php | 4 ++-- .../modules/comment/controllers/CommentController.php | 4 ++-- .../humhub/modules/file/controllers/FileController.php | 2 +- .../humhub/modules/ldap/controllers/AdminController.php | 4 ++-- .../modules/marketplace/controllers/BrowseController.php | 2 +- .../modules/marketplace/controllers/LicenceController.php | 2 +- .../marketplace/controllers/PurchaseController.php | 2 +- .../modules/marketplace/controllers/UpdateController.php | 2 +- .../modules/notification/controllers/AdminController.php | 2 +- .../modules/notification/controllers/EntryController.php | 2 +- .../modules/notification/controllers/ListController.php | 4 ++-- .../notification/controllers/OverviewController.php | 2 +- .../modules/search/controllers/MentioningController.php | 4 ++-- .../modules/search/controllers/SearchController.php | 2 +- .../modules/space/controllers/MembershipController.php | 5 ++++- .../space/modules/manage/controllers/ImageController.php | 5 ++++- .../humhub/modules/topic/controllers/ManageController.php | 5 ++++- .../humhub/modules/topic/controllers/TopicController.php | 7 +------ .../humhub/modules/tour/controllers/TourController.php | 7 +++++-- .../modules/user/components/BaseAccountController.php | 4 ++-- .../humhub/modules/user/controllers/ImageController.php | 5 ++++- .../humhub/modules/user/controllers/PeopleController.php | 2 +- 39 files changed, 76 insertions(+), 70 deletions(-) diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 0016a74db0..3c905c1123 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -17,6 +17,7 @@ HumHub Changelog - Enh #6512: Show error messages when DB connection configuration is invalid - Enh #5315: Default stream sort by `created_at` instead of `id` - Fix #6337: Update `created_at` after first publishing of content record +- Fix #6631: Fix visibility of the method `Controller::getAccessRules()` 1.15.0-beta.2 (Unreleased) -------------------------- diff --git a/protected/humhub/components/behaviors/AccessControl.php b/protected/humhub/components/behaviors/AccessControl.php index 954f6fe86b..c9c28b23bb 100644 --- a/protected/humhub/components/behaviors/AccessControl.php +++ b/protected/humhub/components/behaviors/AccessControl.php @@ -24,7 +24,7 @@ use yii\web\HttpException; * Disable guest access for all controller actions: * * ```php - * public function getAccessRules() + * protected function getAccessRules() * { * return [ * ['login'] @@ -35,7 +35,7 @@ use yii\web\HttpException; * Disable guest access for specific controller actions: * * ```php - * public function getAccessRules() + * protected function getAccessRules() * { * return [ * ['login' => ['action1', 'action2']] @@ -46,7 +46,7 @@ use yii\web\HttpException; * All users have to be logged in + additional permission check for 'action1' and 'action2': * * ```php - * public function getAccessRules() + * protected function getAccessRules() * { * return [ * ['login'], @@ -58,7 +58,7 @@ use yii\web\HttpException; * Custom inline validator for action 'action1': * * ```php - * public function getAccessRules() + * protected function getAccessRules() * { * return [ * ['validateMyCustomRule', 'someParameter' => 'someValue', 'actions' => ['action1']] diff --git a/protected/humhub/controllers/OembedController.php b/protected/humhub/controllers/OembedController.php index 0a1f4c9c8b..7a9b32157f 100644 --- a/protected/humhub/controllers/OembedController.php +++ b/protected/humhub/controllers/OembedController.php @@ -22,7 +22,7 @@ class OembedController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [['login']]; } diff --git a/protected/humhub/modules/activity/controllers/AdminController.php b/protected/humhub/modules/activity/controllers/AdminController.php index 2cd14b8dfb..36bc70dfdb 100644 --- a/protected/humhub/modules/activity/controllers/AdminController.php +++ b/protected/humhub/modules/activity/controllers/AdminController.php @@ -25,7 +25,7 @@ class AdminController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageSettings::class], diff --git a/protected/humhub/modules/admin/components/Controller.php b/protected/humhub/modules/admin/components/Controller.php index 20a82730e2..5c1a173e3b 100644 --- a/protected/humhub/modules/admin/components/Controller.php +++ b/protected/humhub/modules/admin/components/Controller.php @@ -62,12 +62,9 @@ class Controller extends \humhub\components\Controller } /** - * Returns access rules for the standard access control behavior - * - * @return array the access permissions - * @see AccessControl + * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { // Use by default ManageSettings permission if method is not overwritten by custom module if ($this->module->id !== 'admin') { diff --git a/protected/humhub/modules/admin/controllers/ApprovalController.php b/protected/humhub/modules/admin/controllers/ApprovalController.php index 067f71b25c..541a77a493 100644 --- a/protected/humhub/modules/admin/controllers/ApprovalController.php +++ b/protected/humhub/modules/admin/controllers/ApprovalController.php @@ -39,13 +39,13 @@ class ApprovalController extends Controller { $this->subLayout = '@admin/views/layouts/user'; $this->appendPageTitle(Yii::t('AdminModule.base', 'Approval')); - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ControllerAccess::RULE_LOGGED_IN_ONLY], diff --git a/protected/humhub/modules/admin/controllers/AuthenticationController.php b/protected/humhub/modules/admin/controllers/AuthenticationController.php index 6173007766..ed972e5281 100644 --- a/protected/humhub/modules/admin/controllers/AuthenticationController.php +++ b/protected/humhub/modules/admin/controllers/AuthenticationController.php @@ -38,13 +38,13 @@ class AuthenticationController extends Controller $this->subLayout = '@admin/views/layouts/user'; - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageSettings::class] diff --git a/protected/humhub/modules/admin/controllers/GroupController.php b/protected/humhub/modules/admin/controllers/GroupController.php index a9214c2d9a..bf6c715cd6 100644 --- a/protected/humhub/modules/admin/controllers/GroupController.php +++ b/protected/humhub/modules/admin/controllers/GroupController.php @@ -42,13 +42,13 @@ class GroupController extends Controller $this->subLayout = '@admin/views/layouts/user'; $this->appendPageTitle(Yii::t('AdminModule.base', 'Groups')); - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageGroups::class], diff --git a/protected/humhub/modules/admin/controllers/InformationController.php b/protected/humhub/modules/admin/controllers/InformationController.php index 445374de82..4b76364a1d 100644 --- a/protected/humhub/modules/admin/controllers/InformationController.php +++ b/protected/humhub/modules/admin/controllers/InformationController.php @@ -37,16 +37,19 @@ class InformationController extends Controller */ public $defaultAction = 'about'; + /** + * @inheritdoc + */ public function init() { $this->subLayout = '@admin/views/layouts/information'; - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => \humhub\modules\admin\permissions\SeeAdminInformation::class], diff --git a/protected/humhub/modules/admin/controllers/LoggingController.php b/protected/humhub/modules/admin/controllers/LoggingController.php index 4fbcee3817..8eb58ca3ce 100644 --- a/protected/humhub/modules/admin/controllers/LoggingController.php +++ b/protected/humhub/modules/admin/controllers/LoggingController.php @@ -32,13 +32,13 @@ class LoggingController extends Controller $this->appendPageTitle(Yii::t('AdminModule.base', 'Logging')); $this->subLayout = '@admin/views/layouts/information'; - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => SeeAdminInformation::class] diff --git a/protected/humhub/modules/admin/controllers/ModuleController.php b/protected/humhub/modules/admin/controllers/ModuleController.php index ee81cd17f5..258c6f80e2 100644 --- a/protected/humhub/modules/admin/controllers/ModuleController.php +++ b/protected/humhub/modules/admin/controllers/ModuleController.php @@ -39,15 +39,14 @@ class ModuleController extends Controller { $this->appendPageTitle(Yii::t('AdminModule.base', 'Modules')); - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { - return [ ['permissions' => [ManageModules::class]], ['permissions' => [ManageSettings::class], 'actions' => ['index', 'list']] diff --git a/protected/humhub/modules/admin/controllers/PendingRegistrationsController.php b/protected/humhub/modules/admin/controllers/PendingRegistrationsController.php index 7a1bdde2a9..a962b98a11 100644 --- a/protected/humhub/modules/admin/controllers/PendingRegistrationsController.php +++ b/protected/humhub/modules/admin/controllers/PendingRegistrationsController.php @@ -34,7 +34,7 @@ class PendingRegistrationsController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ diff --git a/protected/humhub/modules/admin/controllers/SettingController.php b/protected/humhub/modules/admin/controllers/SettingController.php index 7675403b41..e168cc0c41 100644 --- a/protected/humhub/modules/admin/controllers/SettingController.php +++ b/protected/humhub/modules/admin/controllers/SettingController.php @@ -28,7 +28,6 @@ use humhub\models\UrlOembed; use humhub\modules\admin\components\Controller; use humhub\modules\admin\models\Log; use humhub\modules\notification\models\forms\NotificationSettings; -use yii\base\BaseObject; /** * SettingController @@ -65,13 +64,13 @@ class SettingController extends Controller ]); $this->subLayout = '@admin/views/layouts/setting'; - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageSettings::class] diff --git a/protected/humhub/modules/admin/controllers/SpaceController.php b/protected/humhub/modules/admin/controllers/SpaceController.php index 4685fc0531..87b6be86d1 100644 --- a/protected/humhub/modules/admin/controllers/SpaceController.php +++ b/protected/humhub/modules/admin/controllers/SpaceController.php @@ -41,13 +41,13 @@ class SpaceController extends Controller $this->subLayout = '@admin/views/layouts/space'; $this->appendPageTitle(Yii::t('AdminModule.base', 'Spaces')); - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => [ManageSpaces::class, ManageSettings::class]], diff --git a/protected/humhub/modules/admin/controllers/UserController.php b/protected/humhub/modules/admin/controllers/UserController.php index cb4631f88c..7790312164 100644 --- a/protected/humhub/modules/admin/controllers/UserController.php +++ b/protected/humhub/modules/admin/controllers/UserController.php @@ -55,7 +55,7 @@ class UserController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => [ManageUsers::class, ManageGroups::class]], diff --git a/protected/humhub/modules/admin/controllers/UserPeopleController.php b/protected/humhub/modules/admin/controllers/UserPeopleController.php index bab1a58d56..1070c2e21a 100644 --- a/protected/humhub/modules/admin/controllers/UserPeopleController.php +++ b/protected/humhub/modules/admin/controllers/UserPeopleController.php @@ -40,7 +40,7 @@ class UserPeopleController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => [ManageSettings::class]] diff --git a/protected/humhub/modules/admin/controllers/UserPermissionsController.php b/protected/humhub/modules/admin/controllers/UserPermissionsController.php index 4957b2909e..9ba55c94ce 100644 --- a/protected/humhub/modules/admin/controllers/UserPermissionsController.php +++ b/protected/humhub/modules/admin/controllers/UserPermissionsController.php @@ -11,13 +11,10 @@ use humhub\modules\admin\components\Controller; use humhub\modules\admin\permissions\ManageSettings; use humhub\modules\admin\permissions\ManageUsers; use humhub\modules\content\components\ContentContainerDefaultPermissionManager; -use humhub\modules\content\models\ContentContainerDefaultPermission; use humhub\modules\content\models\ContentContainerPermission; -use humhub\modules\content\models\ContentContainerSetting; use humhub\modules\user\models\User; use humhub\modules\user\Module; use Yii; -use yii\db\Expression; use yii\web\HttpException; /** @@ -47,7 +44,7 @@ class UserPermissionsController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => [ManageUsers::class]], diff --git a/protected/humhub/modules/admin/controllers/UserProfileController.php b/protected/humhub/modules/admin/controllers/UserProfileController.php index c8f5ad59a7..b99bf9d21c 100644 --- a/protected/humhub/modules/admin/controllers/UserProfileController.php +++ b/protected/humhub/modules/admin/controllers/UserProfileController.php @@ -39,13 +39,13 @@ class UserProfileController extends Controller $this->appendPageTitle(Yii::t('AdminModule.base', 'Userprofiles')); $this->subLayout = '@admin/views/layouts/user'; - return parent::init(); + parent::init(); } /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageUsers::class] diff --git a/protected/humhub/modules/comment/controllers/CommentController.php b/protected/humhub/modules/comment/controllers/CommentController.php index 2e05d82749..a6c154aebe 100644 --- a/protected/humhub/modules/comment/controllers/CommentController.php +++ b/protected/humhub/modules/comment/controllers/CommentController.php @@ -39,9 +39,9 @@ use yii\web\NotFoundHttpException; class CommentController extends Controller { /** - * @return array + * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ControllerAccess::RULE_LOGGED_IN_ONLY => ['post', 'edit', 'delete']], diff --git a/protected/humhub/modules/file/controllers/FileController.php b/protected/humhub/modules/file/controllers/FileController.php index af5929550b..364e9730a4 100644 --- a/protected/humhub/modules/file/controllers/FileController.php +++ b/protected/humhub/modules/file/controllers/FileController.php @@ -31,7 +31,7 @@ class FileController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ControllerAccess::RULE_LOGGED_IN_ONLY => ['upload', 'delete']] diff --git a/protected/humhub/modules/ldap/controllers/AdminController.php b/protected/humhub/modules/ldap/controllers/AdminController.php index ac1c802093..7fb3673698 100644 --- a/protected/humhub/modules/ldap/controllers/AdminController.php +++ b/protected/humhub/modules/ldap/controllers/AdminController.php @@ -25,9 +25,9 @@ use Yii; class AdminController extends Controller { /** - * @inerhitdoc + * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => [ManageSettings::class]], diff --git a/protected/humhub/modules/marketplace/controllers/BrowseController.php b/protected/humhub/modules/marketplace/controllers/BrowseController.php index 48ac96f05c..d96350a3e6 100644 --- a/protected/humhub/modules/marketplace/controllers/BrowseController.php +++ b/protected/humhub/modules/marketplace/controllers/BrowseController.php @@ -26,7 +26,7 @@ class BrowseController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageModules::class] diff --git a/protected/humhub/modules/marketplace/controllers/LicenceController.php b/protected/humhub/modules/marketplace/controllers/LicenceController.php index 42a427e1f7..91e1e1b90f 100644 --- a/protected/humhub/modules/marketplace/controllers/LicenceController.php +++ b/protected/humhub/modules/marketplace/controllers/LicenceController.php @@ -24,7 +24,7 @@ class LicenceController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageModules::class] diff --git a/protected/humhub/modules/marketplace/controllers/PurchaseController.php b/protected/humhub/modules/marketplace/controllers/PurchaseController.php index 61e2e97dd1..9dea63f9ae 100644 --- a/protected/humhub/modules/marketplace/controllers/PurchaseController.php +++ b/protected/humhub/modules/marketplace/controllers/PurchaseController.php @@ -32,7 +32,7 @@ class PurchaseController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageModules::class] diff --git a/protected/humhub/modules/marketplace/controllers/UpdateController.php b/protected/humhub/modules/marketplace/controllers/UpdateController.php index cdd96594f9..467c1a04cb 100644 --- a/protected/humhub/modules/marketplace/controllers/UpdateController.php +++ b/protected/humhub/modules/marketplace/controllers/UpdateController.php @@ -23,7 +23,7 @@ class UpdateController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageModules::class] diff --git a/protected/humhub/modules/notification/controllers/AdminController.php b/protected/humhub/modules/notification/controllers/AdminController.php index b8967dd94d..bacf493332 100644 --- a/protected/humhub/modules/notification/controllers/AdminController.php +++ b/protected/humhub/modules/notification/controllers/AdminController.php @@ -25,7 +25,7 @@ class AdminController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['permissions' => ManageSettings::class], diff --git a/protected/humhub/modules/notification/controllers/EntryController.php b/protected/humhub/modules/notification/controllers/EntryController.php index c8ad870820..3c9a46664e 100644 --- a/protected/humhub/modules/notification/controllers/EntryController.php +++ b/protected/humhub/modules/notification/controllers/EntryController.php @@ -30,7 +30,7 @@ class EntryController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ControllerAccess::RULE_LOGGED_IN_ONLY] diff --git a/protected/humhub/modules/notification/controllers/ListController.php b/protected/humhub/modules/notification/controllers/ListController.php index 91c7f14358..c19a7f659f 100644 --- a/protected/humhub/modules/notification/controllers/ListController.php +++ b/protected/humhub/modules/notification/controllers/ListController.php @@ -22,9 +22,9 @@ use yii\db\IntegrityException; class ListController extends Controller { /** - * @inheritDoc + * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ControllerAccess::RULE_LOGGED_IN_ONLY] diff --git a/protected/humhub/modules/notification/controllers/OverviewController.php b/protected/humhub/modules/notification/controllers/OverviewController.php index 90336d3cd7..d231397b8d 100644 --- a/protected/humhub/modules/notification/controllers/OverviewController.php +++ b/protected/humhub/modules/notification/controllers/OverviewController.php @@ -29,7 +29,7 @@ class OverviewController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ControllerAccess::RULE_LOGGED_IN_ONLY] diff --git a/protected/humhub/modules/search/controllers/MentioningController.php b/protected/humhub/modules/search/controllers/MentioningController.php index 98efba676f..06e1b040a4 100644 --- a/protected/humhub/modules/search/controllers/MentioningController.php +++ b/protected/humhub/modules/search/controllers/MentioningController.php @@ -10,7 +10,7 @@ namespace humhub\modules\search\controllers; use humhub\components\Controller; use humhub\libs\ParameterEvent; -use \humhub\modules\comment\Module as CommentModule; +use humhub\modules\comment\Module as CommentModule; use humhub\modules\content\components\ContentContainerActiveRecord; use humhub\modules\content\models\Content; use humhub\modules\post\models\Post; @@ -45,7 +45,7 @@ class MentioningController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['login'] diff --git a/protected/humhub/modules/search/controllers/SearchController.php b/protected/humhub/modules/search/controllers/SearchController.php index 29de599fea..ea80a769c0 100644 --- a/protected/humhub/modules/search/controllers/SearchController.php +++ b/protected/humhub/modules/search/controllers/SearchController.php @@ -48,7 +48,7 @@ class SearchController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['login'] diff --git a/protected/humhub/modules/space/controllers/MembershipController.php b/protected/humhub/modules/space/controllers/MembershipController.php index 6c464a3c42..7939075fc2 100644 --- a/protected/humhub/modules/space/controllers/MembershipController.php +++ b/protected/humhub/modules/space/controllers/MembershipController.php @@ -36,7 +36,10 @@ use yii\web\HttpException; */ class MembershipController extends ContentContainerController { - public function getAccessRules() + /** + * @inheritdoc + */ + protected function getAccessRules() { return [ ['permission' => [InviteUsers::class], 'actions' => ['invite']], diff --git a/protected/humhub/modules/space/modules/manage/controllers/ImageController.php b/protected/humhub/modules/space/modules/manage/controllers/ImageController.php index f3c38b9585..1df3da9aeb 100644 --- a/protected/humhub/modules/space/modules/manage/controllers/ImageController.php +++ b/protected/humhub/modules/space/modules/manage/controllers/ImageController.php @@ -21,7 +21,10 @@ class ImageController extends ContainerImageController { public $validContentContainerClasses = [Space::class]; - public function getAccessRules() + /** + * @inheritdoc + */ + protected function getAccessRules() { return [ [ContentContainerControllerAccess::RULE_USER_GROUP_ONLY => [Space::USERGROUP_ADMIN]], diff --git a/protected/humhub/modules/topic/controllers/ManageController.php b/protected/humhub/modules/topic/controllers/ManageController.php index 9fb67ed9f5..3892dd01df 100644 --- a/protected/humhub/modules/topic/controllers/ManageController.php +++ b/protected/humhub/modules/topic/controllers/ManageController.php @@ -19,7 +19,10 @@ use yii\web\HttpException; class ManageController extends ContentContainerController { - public function getAccessRules() + /** + * @inheritdoc + */ + protected function getAccessRules() { return [ ['login'], diff --git a/protected/humhub/modules/topic/controllers/TopicController.php b/protected/humhub/modules/topic/controllers/TopicController.php index ccc5c18fcf..19fb2956f2 100644 --- a/protected/humhub/modules/topic/controllers/TopicController.php +++ b/protected/humhub/modules/topic/controllers/TopicController.php @@ -8,13 +8,8 @@ namespace humhub\modules\topic\controllers; - use humhub\modules\content\components\ContentContainerController; -use humhub\modules\content\models\Content; -use humhub\modules\topic\models\forms\ContentTopicsForm; use humhub\modules\topic\widgets\TopicPicker; -use Yii; -use yii\web\HttpException; class TopicController extends ContentContainerController { @@ -26,7 +21,7 @@ class TopicController extends ContentContainerController /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ ['json'] diff --git a/protected/humhub/modules/tour/controllers/TourController.php b/protected/humhub/modules/tour/controllers/TourController.php index 27f6f381a3..3be8e1c7ee 100644 --- a/protected/humhub/modules/tour/controllers/TourController.php +++ b/protected/humhub/modules/tour/controllers/TourController.php @@ -9,10 +9,10 @@ namespace humhub\modules\tour\controllers; use humhub\modules\space\models\Membership; +use humhub\modules\space\models\Space; use humhub\modules\tour\Module; use Yii; use yii\web\HttpException; -use humhub\modules\space\models\Space; /** * TourController @@ -23,7 +23,10 @@ use humhub\modules\space\models\Space; */ class TourController extends \humhub\components\Controller { - public function getAccessRules() + /** + * @inheritdoc + */ + protected function getAccessRules() { return [ ['login'] diff --git a/protected/humhub/modules/user/components/BaseAccountController.php b/protected/humhub/modules/user/components/BaseAccountController.php index f9ba953442..9f8998e4b3 100644 --- a/protected/humhub/modules/user/components/BaseAccountController.php +++ b/protected/humhub/modules/user/components/BaseAccountController.php @@ -28,7 +28,7 @@ class BaseAccountController extends \humhub\components\Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ControllerAccess::RULE_LOGGED_IN_ONLY] @@ -46,7 +46,7 @@ class BaseAccountController extends \humhub\components\Controller public function init() { $this->appendPageTitle(\Yii::t('UserModule.base', 'My Account')); - return parent::init(); + parent::init(); } /** diff --git a/protected/humhub/modules/user/controllers/ImageController.php b/protected/humhub/modules/user/controllers/ImageController.php index 846dd9893d..7535331c3b 100644 --- a/protected/humhub/modules/user/controllers/ImageController.php +++ b/protected/humhub/modules/user/controllers/ImageController.php @@ -49,7 +49,10 @@ class ImageController extends ContainerImageController } } - public function getAccessRules() + /** + * @inheritdoc + */ + protected function getAccessRules() { return [ ['validateAccess'], diff --git a/protected/humhub/modules/user/controllers/PeopleController.php b/protected/humhub/modules/user/controllers/PeopleController.php index a23d429857..9da5b39ca0 100644 --- a/protected/humhub/modules/user/controllers/PeopleController.php +++ b/protected/humhub/modules/user/controllers/PeopleController.php @@ -44,7 +44,7 @@ class PeopleController extends Controller /** * @inheritdoc */ - public function getAccessRules() + protected function getAccessRules() { return [ [ControllerAccess::RULE_LOGGED_IN_ONLY], From ecd9c28d83b208bad9322fa42b1ecee0b889dd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20R=C3=BCegg?= Date: Sun, 29 Oct 2023 09:58:46 +0100 Subject: [PATCH 19/19] Fix: Module Manager test (#6636) * Adapt InvalidArgumentTypeException instantiation @see f2ef4c4d16a33b7dccaf7fe4c0b83e2cd07450f0 * Use event data (from child classes) * Adapt test to potentially installed and enabled modules * Cleanup some whitespace and linebreaks * Update CHANGELOG-DEV.md --- CHANGELOG-DEV.md | 1 + protected/humhub/components/ModuleManager.php | 2 +- .../_support/HumHubHelperTrait.php | 7 +- .../unit/components/ModuleManagerTest.php | 206 ++++++++++++++---- 4 files changed, 170 insertions(+), 46 deletions(-) diff --git a/CHANGELOG-DEV.md b/CHANGELOG-DEV.md index 9fc31fd6a2..4268796561 100644 --- a/CHANGELOG-DEV.md +++ b/CHANGELOG-DEV.md @@ -3,6 +3,7 @@ HumHub Changelog 1.16.0 (Unreleased) ------------------- +- Fix #6636: Module Manager test - Enh #6530: Small performance improvements - Fix #6511: Only test compatible modules in `onMarketplaceAfterFilterModules()` - Enh #6511: Backup folder path is now return from `removeModule()` diff --git a/protected/humhub/components/ModuleManager.php b/protected/humhub/components/ModuleManager.php index 9df2503928..4ed76b494c 100644 --- a/protected/humhub/components/ModuleManager.php +++ b/protected/humhub/components/ModuleManager.php @@ -377,7 +377,7 @@ class ModuleManager extends Component public function filterModules(?array $modules, $filters = []): array { if (!$filters instanceof ArrayAccess && !is_array($filters)) { - throw new InvalidArgumentTypeException(__METHOD__, [2 => '$filters'], ['array', ArrayAccess::class], $filters); + throw new InvalidArgumentTypeException('$filters', ['array', ArrayAccess::class], $filters); } $modules = $this->filterModulesByKeyword($modules, $filters['keyword'] ?? null); diff --git a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php index b30298b376..23825009df 100644 --- a/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php +++ b/protected/humhub/tests/codeception/_support/HumHubHelperTrait.php @@ -34,6 +34,7 @@ use yii\db\ActiveRecord; use yii\db\Command; use yii\db\ExpressionInterface; use yii\db\Query; +use yii\helpers\ArrayHelper; use yii\helpers\FileHelper; use yii\log\Dispatcher; @@ -619,15 +620,15 @@ trait HumHubHelperTrait * @since 1.15 * @see static::assertEvents() */ - public function handleEvent(Event $event) + public function handleEvent(Event $event, array $eventData = []) { - $eventData = [ + $eventData = ArrayHelper::merge([ 'class' => get_class($event), 'event' => $event->name, 'sender' => $event->sender, 'data' => $event->data, 'handled' => $event->handled, - ]; + ], $eventData); $this->firedEvents[] = $eventData; } diff --git a/protected/humhub/tests/codeception/unit/components/ModuleManagerTest.php b/protected/humhub/tests/codeception/unit/components/ModuleManagerTest.php index f61f97ee28..8506bb199d 100644 --- a/protected/humhub/tests/codeception/unit/components/ModuleManagerTest.php +++ b/protected/humhub/tests/codeception/unit/components/ModuleManagerTest.php @@ -11,6 +11,7 @@ namespace humhub\tests\codeception\unit\components; use humhub\components\bootstrap\ModuleAutoLoader; +use humhub\components\ModuleEvent; use humhub\components\ModuleManager; use humhub\exceptions\InvalidArgumentTypeException; use humhub\models\ModuleEnabled; @@ -27,7 +28,6 @@ use yii\base\InvalidConfigException; use yii\caching\ArrayCache; use yii\db\StaleObjectException; use yii\helpers\FileHelper; -use humhub\components\ModuleEvent; require_once __DIR__ . '/bootstrap/ModuleAutoLoaderTest.php'; @@ -37,7 +37,8 @@ require_once __DIR__ . '/bootstrap/ModuleAutoLoaderTest.php'; class ModuleManagerTest extends HumHubDbTestCase { private static int $moduleDirCount; - + private static array $moduleDirList; + private static ?array $moduleEnabledList = null; private static string $appModuleRoot; private static string $coreModuleRoot; private static string $testModuleRoot; @@ -47,7 +48,6 @@ class ModuleManagerTest extends HumHubDbTestCase private ?string $moduleClass = null; private ?string $moduleNS = null; private static array $aliases; - private static ModuleManager $originalModuleManager; public static function setUpBeforeClass(): void @@ -59,12 +59,28 @@ class ModuleManagerTest extends HumHubDbTestCase static::$appModuleRoot = dirname(__DIR__, 5) . '/modules'; static::$moduleDirCount = 0; + static::$moduleDirList = []; - $dh = opendir($appModuleRoot = static::$appModuleRoot); + $dh = opendir($appModuleRoot = static::$appModuleRoot); while (false !== ($filename = readdir($dh))) { if ($filename !== '.' && $filename !== '..' && is_dir("$appModuleRoot/$filename")) { static::$moduleDirCount++; + + $config_file = new \SplFileInfo("$appModuleRoot/$filename/config.php"); + $config_file = new \SplFileInfo($config_file->getRealPath()); + + if (!$config_file->isFile() || !$config_file->isReadable()) { + continue; + } + + $config = include $config_file->getPathname(); + + if (!is_array($config)) { + continue; + } + + static::$moduleDirList[$config['id']] = $config['class']; } } } @@ -102,9 +118,21 @@ class ModuleManagerTest extends HumHubDbTestCase public function testBasics() { - static::assertEquals([], $this->moduleManager->myModules()); - static::assertEquals([], $this->moduleManager->myCoreModules()); - static::assertEquals([], $this->moduleManager->myEnabledModules()); + static::assertEquals( + [], + $this->moduleManager->myModules(), + __FUNCTION__ . '.' . 'myModules()' + ); + static::assertEquals( + [], + $this->moduleManager->myCoreModules(), + __FUNCTION__ . '.' . 'myCoreModules()' + ); + static::assertEquals( + static::$moduleEnabledList, + $this->moduleManager->myEnabledModules(), + __FUNCTION__ . '.' . 'myEnabledModules()' + ); } /** @@ -183,11 +211,15 @@ class ModuleManagerTest extends HumHubDbTestCase $this->registerModule($basePath, $config); - static::assertEquals([], $this->moduleManager->getModules([ - 'includeCoreModules' => true, - 'enabled' => true, - 'returnClass' => true, - ])); + static::assertEquals( + [], + $this->moduleManager->getModules([ + 'includeCoreModules' => true, + 'enabled' => true, + 'returnClass' => true, + ]), + __FUNCTION__ . '.' . 'getModules()' + ); } /** @@ -287,7 +319,7 @@ class ModuleManagerTest extends HumHubDbTestCase public function testInvalidEventConfigNotArrayAccess() { $this->runEventExceptionTest([ - 'invalid1', + 'invalid1', ], "event configuration must be an array or implement \\ArrayAccess"); } @@ -355,13 +387,16 @@ class ModuleManagerTest extends HumHubDbTestCase public function testInvalidEventConfigInvalidCallbackWithNonExistingMethod() { - $this->runEventExceptionTest([ + $this->runEventExceptionTest( [ - 'class' => __CLASS__, - 'event' => 'invalid1', - 'callback' => [__CLASS__, 'someMethod'], + [ + 'class' => __CLASS__, + 'event' => 'invalid1', + 'callback' => [__CLASS__, 'someMethod'], + ], ], - ], "class 'humhub\\tests\\codeception\\unit\\components\\ModuleManagerTest' does not have a method called 'someMethod"); + "class 'humhub\\tests\\codeception\\unit\\components\\ModuleManagerTest' does not have a method called 'someMethod" + ); } /** @@ -392,13 +427,18 @@ class ModuleManagerTest extends HumHubDbTestCase 'enabled' => false, 'returnClass' => true, ])); - static::assertEquals(array_flip(ModuleAutoLoaderTest::EXPECTED_CORE_MODULES), $modules); + $expected = array_merge( + [], + array_flip(ModuleAutoLoaderTest::EXPECTED_CORE_MODULES), + static::$moduleDirList + ); + static::assertEquals($expected, $modules); static::assertIsArray($modules = $moduleManager->getEnabledModules([ 'enabled' => false, 'returnClass' => true, ])); - static::assertEquals([], $modules); + static::assertEquals(static::$moduleDirList, $modules); } /** @@ -511,7 +551,7 @@ class ModuleManagerTest extends HumHubDbTestCase $this->moduleManager->disable($module); - static::assertCount(0, $this->moduleManager->myEnabledModules()); + static::assertCount(count(static::$moduleEnabledList), $this->moduleManager->myEnabledModules()); static::assertRecordCount(0, ModuleEnabled::tableName(), ['module_id' => $this->moduleId]); static::assertNull(Yii::$app->getModule($this->moduleId)); @@ -600,11 +640,23 @@ class ModuleManagerTest extends HumHubDbTestCase { Yii::$app->set('moduleManager', $this->moduleManager); - $this->moduleManager->enableModules([static::$testModuleRoot . '/module1', static::$testModuleRoot . '/module2']); + $this->moduleManager->enableModules([ + static::$testModuleRoot . '/module1', + static::$testModuleRoot . '/module2' + ]); - static::assertEquals(['module1' => Module1::class, 'module2' => Module2::class], $this->moduleManager->myModules()); - static::assertEquals([], $this->moduleManager->myCoreModules()); - static::assertEquals(['module1', 'module2'], $this->moduleManager->myEnabledModules()); + static::assertEquals( + ['module1' => Module1::class, 'module2' => Module2::class], + $this->moduleManager->myModules() + ); + static::assertEquals( + [], + $this->moduleManager->myCoreModules() + ); + static::assertEquals( + [...static::$moduleEnabledList, 'module1', 'module2'], + $this->moduleManager->myEnabledModules() + ); $module1 = $this->moduleManager->getModule('module1'); $module2 = $this->moduleManager->getModule('module2'); @@ -612,23 +664,53 @@ class ModuleManagerTest extends HumHubDbTestCase static::assertEquals([], $this->moduleManager->filterModulesByKeyword(null, 'foo')); // match keyword - static::assertEquals(['module1' => $module1], $this->moduleManager->filterModulesByKeyword(null, 'one')); - static::assertEquals(['module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'two')); - static::assertEquals(['module1' => $module1, 'module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'cool')); + static::assertEquals( + ['module1' => $module1], + $this->moduleManager->filterModulesByKeyword(null, 'one') + ); + static::assertEquals( + ['module2' => $module2], + $this->moduleManager->filterModulesByKeyword(null, 'two') + ); + static::assertEquals( + ['module1' => $module1, 'module2' => $module2], + $this->moduleManager->filterModulesByKeyword(null, 'cool') + ); // match name - static::assertEquals(['module1' => $module1], $this->moduleManager->filterModulesByKeyword(null, 'Module 1')); - static::assertEquals(['module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'Module 2')); - static::assertEquals(['module1' => $module1, 'module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'Example')); + static::assertEquals( + ['module1' => $module1], + $this->moduleManager->filterModulesByKeyword(null, 'Module 1') + ); + static::assertEquals( + ['module2' => $module2], + $this->moduleManager->filterModulesByKeyword(null, 'Module 2') + ); + static::assertEquals( + ['module1' => $module1, 'module2' => $module2], + $this->moduleManager->filterModulesByKeyword(null, 'Example') + ); // match description - static::assertEquals(['module1' => $module1], $this->moduleManager->filterModulesByKeyword(null, 'module 1.')); - static::assertEquals(['module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'module 2.')); - static::assertEquals(['module1' => $module1, 'module2' => $module2], $this->moduleManager->filterModulesByKeyword(null, 'testing')); + static::assertEquals( + ['module1' => $module1], + $this->moduleManager->filterModulesByKeyword(null, 'module 1.') + ); + static::assertEquals( + ['module2' => $module2], + $this->moduleManager->filterModulesByKeyword(null, 'module 2.') + ); + static::assertEquals( + ['module1' => $module1, 'module2' => $module2], + $this->moduleManager->filterModulesByKeyword(null, 'testing') + ); $this->moduleManager->on(ModuleManager::EVENT_AFTER_FILTER_MODULES, [$this, 'handleEvent']); - static::assertEquals(['module1' => $module1, 'module2' => $module2], $this->moduleManager->filterModules(null, ['foo'])); + static::assertEquals( + ['module1' => $module1, 'module2' => $module2], + $this->moduleManager->filterModules(null, ['foo']) + ); /** @noinspection MissedFieldInspection */ $this->assertEvents([ @@ -656,7 +738,10 @@ class ModuleManagerTest extends HumHubDbTestCase ] ]); - static::assertEquals(['module2' => $module2], $this->moduleManager->filterModules(null, ['keyword' => 'Example Module 2'])); + static::assertEquals( + ['module2' => $module2], + $this->moduleManager->filterModules(null, ['keyword' => 'Example Module 2']) + ); /** @noinspection MissedFieldInspection */ $this->assertEvents([ @@ -672,7 +757,7 @@ class ModuleManagerTest extends HumHubDbTestCase $this->expectException(InvalidArgumentTypeException::class); $this->expectExceptionMessage( - 'Argument #2 $filters passed to humhub\components\ModuleManager::filterModules must be of type array, ArrayAccess, string given.' + 'Argument $filters passed to humhub\components\ModuleManager::filterModules must be one of the following types: array, ArrayAccess - string given.' ); static::assertEquals([], $this->moduleManager->filterModules(null, 'foo')); } @@ -746,9 +831,27 @@ class ModuleManagerTest extends HumHubDbTestCase $this->moduleManager->register($basePath, $config); - static::assertEquals([$this->moduleId => $this->moduleClass], $this->moduleManager->myModules()); - static::assertEquals($isCore ? [$this->moduleClass] : [], $this->moduleManager->myCoreModules()); - static::assertEquals($isEnabled && !$isCore ? [$this->moduleId] : [], $this->moduleManager->myEnabledModules()); + static::assertEquals( + [$this->moduleId => $this->moduleClass], + $this->moduleManager->myModules(), + __FUNCTION__ . '.' . 'myModules()' + ); + + static::assertEquals( + $isCore ? [$this->moduleClass] : [], + $this->moduleManager->myCoreModules(), + __FUNCTION__ . '.' . 'myCoreModules()' + ); + + $expected = static::$moduleEnabledList; + if ($isEnabled && !$isCore) { + $expected[] = $this->moduleId; + } + static::assertEquals( + $expected, + $this->moduleManager->myEnabledModules(), + __FUNCTION__ . '.' . 'myEnabledModules()' + ); static::assertTrue($this->moduleManager->hasModule($this->moduleId)); @@ -795,7 +898,12 @@ class ModuleManagerTest extends HumHubDbTestCase $eventName = $event['event'] ?? $event[1] ?? null; $eventHandler = $event['callback'] ?? $event[2] ?? null; - if ($isEnabled && $eventClass && $eventName && is_array($eventHandler) && method_exists($eventHandler[0], $eventHandler[1])) { + if ( + $isEnabled && $eventClass && $eventName && is_array($eventHandler) && method_exists( + $eventHandler[0], + $eventHandler[1] + ) + ) { static::assertTrue(Event::off($eventClass, $eventName, $eventHandler)); } else { static::assertFalse(Event::off($eventClass, $eventName, $eventHandler)); @@ -902,7 +1010,21 @@ class ModuleManagerTest extends HumHubDbTestCase $this->firedEvents = []; - static::dbDelete(ModuleEnabled::tableName(), ['module_id' => ['module1', 'module2', 'coreModule', 'installerModule', 'invalidModule1', 'invalidModule2']]); + static::dbDelete(ModuleEnabled::tableName(), [ + 'module_id' => [ + 'module1', + 'module2', + 'coreModule', + 'installerModule', + 'invalidModule1', + 'invalidModule2' + ] + ]); + + static::$moduleEnabledList ??= array_column( + static::dbSelect('module_enabled', 'module_id'), + 'module_id' + ); $this->moduleManager = new ModuleManagerMock(); }