%PDF- %PDF-
| Direktori : /home/vacivi36/intranet.vacivitta.com.br/protected/humhub/modules/content/models/ |
| Current File : /home/vacivi36/intranet.vacivitta.com.br/protected/humhub/modules/content/models/Content.php |
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\models;
use humhub\components\ActiveRecord;
use humhub\components\behaviors\GUID;
use humhub\components\behaviors\PolymorphicRelation;
use humhub\components\Module;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\components\ContentContainerModule;
use humhub\modules\content\interfaces\ContentOwner;
use humhub\modules\content\live\NewContent;
use humhub\modules\content\permissions\CreatePrivateContent;
use humhub\modules\content\permissions\CreatePublicContent;
use humhub\modules\content\permissions\ManageContent;
use humhub\modules\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 Yii;
use yii\base\Exception;
use yii\base\InvalidArgumentException;
use yii\db\Expression;
use yii\db\IntegrityException;
use yii\helpers\Url;
/**
* This is the model class for table "content". The content model serves as relation between a [[ContentContainer]] and
* [[ContentActiveRecord]] entries and contains shared content features as
*
* - read/write permission checks
* - move content
* - pin content
* - archive content
* - content author and update information
* - stream relation by `stream_channel` field
*
* The relation to [[ContentActiveRecord]] models is defined by a polymorphic relation
* `object_model` and `object_id` content table fields.
*
* The relation to [[ContentContainer]] is defined by `contentContainer_id` field.
*
* Note: Instances of this class are automatically created and saved by the [[ContentActiveRecord]] model and should not
* manually be created or deleted to maintain data integrity.
*
* @property integer $id
* @property string $guid
* @property string $object_model
* @property integer $object_id
* @property integer $visibility
* @property integer $pinned
* @property integer $archived
* @property integer $locked_comments
* @property string $created_at
* @property integer $created_by
* @property string $updated_at
* @property integer $updated_by
* @property string $stream_sort_date
* @property string $stream_channel
* @property integer $contentcontainer_id;
* @property ContentContainer $contentContainer
* @property ContentContainerActiveRecord $container
* @mixin PolymorphicRelation
* @mixin GUID
* @since 0.5
*/
class Content extends ActiveRecord implements Movable, ContentOwner
{
/**
* The default stream channel.
* @since 1.6
*/
const STREAM_CHANNEL_DEFAULT = 'default';
/**
* A array of user objects which should informed about this new content.
*
* @var array User
*/
public $notifyUsersOfNewContent = [];
/**
* @var int The private visibility mode (e.g. for space member content or user profile posts for friends)
*/
const VISIBILITY_PRIVATE = 0;
/**
* @var int Public visibility mode, e.g. content which are visibile for followers
*/
const VISIBILITY_PUBLIC = 1;
/**
* @var int Owner visibility mode, only visible for contentContainer + content owner
*/
const VISIBILITY_OWNER = 2;
/**
* @var ContentContainerActiveRecord the Container (e.g. Space or User) where this content belongs to.
*/
protected $_container = null;
/**
* @var bool flag to disable the creation of default social activities like activity and notifications in afterSave() at content creation.
* @deprecated since v1.2.3 use ContentActiveRecord::silentContentCreation instead.
*/
public $muteDefaultSocialActivities = false;
/**
* @inheritdoc
*/
public function behaviors()
{
return [
[
'class' => PolymorphicRelation::class,
'mustBeInstanceOf' => [ContentActiveRecord::class],
],
[
'class' => GUID::class,
],
];
}
/**
* @inheritdoc
*/
public static function tableName()
{
return 'content';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['object_id', 'visibility', 'pinned'], 'integer'],
[['archived'], 'safe'],
[['guid'], 'string', 'max' => 45],
[['object_model'], 'string', 'max' => 100],
[['object_model', 'object_id'], 'unique', 'targetAttribute' => ['object_model', 'object_id'], 'message' => 'The combination of Object Model and Object ID has already been taken.'],
[['guid'], 'unique']
];
}
/**
* Returns a [[ContentActiveRecord]] model by given polymorphic relation class and id.
* If there is no existing content relation with this model instance, null is returned.
*
* @param string $className Class Name of the Content
* @param int $id Primary Key
* @return ContentActiveRecord|null
* @throws IntegrityException
*/
public static function Get($className, $id)
{
$content = self::findOne(['object_model' => $className, 'object_id' => $id]);
if ($content) {
return $content->getModel();
}
return null;
}
/**
* @return ContentActiveRecord
* @throws IntegrityException
* @since 1.3
*/
public function getModel()
{
return $this->getPolymorphicRelation();
}
/**
* @inheritdoc
*/
public function beforeSave($insert)
{
if ($this->object_model == "" || $this->object_id == "") {
throw new Exception("Could not save content with object_model or object_id!");
}
// Set some default values
if (!$this->archived) {
$this->archived = 0;
}
if (!$this->visibility) {
$this->visibility = self::VISIBILITY_PRIVATE;
}
if (!$this->pinned) {
$this->pinned = 0;
}
if ($insert) {
if ($this->created_by == "") {
$this->created_by = Yii::$app->user->id;
}
}
$this->stream_sort_date = date('Y-m-d G:i:s');
if ($this->created_by == "") {
throw new Exception("Could not save content without created_by!");
}
return parent::beforeSave($insert);
}
/**
* @inheritdoc
*/
public function afterSave($insert, $changedAttributes)
{
/* @var $contentSource ContentActiveRecord */
$contentSource = $this->getModel();
foreach ($this->notifyUsersOfNewContent as $user) {
$contentSource->follow($user->id);
}
// TODO: handle ContentCreated notifications and live events for global content
if ($insert && !$this->isMuted()) {
$this->notifyContentCreated();
}
if ($this->container) {
Yii::$app->live->send(new NewContent([
'sguid' => ($this->container instanceof Space) ? $this->container->guid : null,
'uguid' => ($this->container instanceof User) ? $this->container->guid : null,
'originator' => $this->createdBy->guid,
'contentContainerId' => $this->container->contentContainerRecord->id,
'visibility' => $this->visibility,
'sourceClass' => get_class($contentSource),
'sourceId' => $contentSource->getPrimaryKey(),
'silent' => $this->isMuted(),
'streamChannel' => $this->stream_channel,
'contentId' => $this->id,
'insert' => $insert
]));
}
SearchHelper::queueUpdate($contentSource);
parent::afterSave($insert, $changedAttributes);
}
/**
* @return bool checks if the given content allows content creation notifications and activities
* @throws IntegrityException
*/
private function isMuted()
{
return $this->getPolymorphicRelation()->silentContentCreation || $this->getModel()->silentContentCreation || !$this->container;
}
/**
* Notifies all followers and manually set $notifyUsersOfNewContent of the creation of this content and creates an activity.
*/
private function notifyContentCreated()
{
$contentSource = $this->getPolymorphicRelation();
$userQuery = Yii::$app->notification->getFollowers($this);
if (count($this->notifyUsersOfNewContent) != 0) {
// Add manually notified users
$userQuery->union(
User::find()->active()->where(['IN', 'user.id', array_map(function (User $user) {
return $user->id;
}, $this->notifyUsersOfNewContent)])
);
}
\humhub\modules\content\notifications\ContentCreated::instance()
->from($this->createdBy)
->about($contentSource)
->sendBulk($userQuery);
\humhub\modules\content\activities\ContentCreated::instance()
->from($this->createdBy)
->about($contentSource)->save();
}
/**
* @inheritdoc
*/
public function afterDelete()
{
// Try delete the underlying object (Post, Question, Task, ...)
$this->resetPolymorphicRelation();
if ($this->getPolymorphicRelation() !== null) {
$this->getPolymorphicRelation()->delete();
}
parent::afterDelete();
}
/**
* Returns the visibility of the content object
*
* @return Integer
*/
public function getVisibility()
{
return $this->visibility;
}
/**
* Checks if the content visiblity is set to public.
*
* @return boolean
*/
public function isPublic()
{
return $this->visibility == self::VISIBILITY_PUBLIC;
}
/**
* Checks if the content visibility is set to private.
*
* @return boolean
*/
public function isPrivate()
{
return $this->visibility == self::VISIBILITY_PRIVATE;
}
/**
* Checks if comments are locked for the content.
*
* @return bool
*/
public function isLockedComments(): bool
{
return (bool)$this->locked_comments;
}
/**
* Checks if the content object is pinned
*
* @return Boolean
*/
public function isPinned()
{
return ($this->pinned);
}
/**
* Pins the content object
*/
public function pin()
{
$this->pinned = 1;
//This prevents the call of beforeSave, and the setting of update_at
$this->updateAttributes(['pinned']);
}
/**
* Unpins the content object
*/
public function unpin()
{
$this->pinned = 0;
$this->updateAttributes(['pinned']);
}
/**
* Checks if the user can pin this content.
* This is only allowed for workspace owner.
*
* @return boolean
* @throws Exception
* @throws \yii\base\InvalidConfigException
*/
public function canPin()
{
// Currently global content can not be pinned
if (!$this->getContainer()) {
return false;
}
if ($this->isArchived()) {
return false;
}
return $this->getContainer()->permissionManager->can(ManageContent::class);
}
/**
* Creates a list of pinned content objects of the wall
*
* @return Int
*/
public function countPinnedItems()
{
return Content::find()->where(['content.contentcontainer_id' => $this->contentcontainer_id, 'content.pinned' => 1])->count();
}
/**
* Checks if current content object is archived
*
* @return boolean
* @throws Exception
*/
public function isArchived()
{
return $this->archived || ($this->getContainer() !== null && $this->getContainer()->isArchived());
}
/**
* Checks if the current user can archive this content.
* The content owner and the workspace admin can archive contents.
*
* @return boolean
* @throws Exception
* @throws \yii\base\InvalidConfigException
*/
public function canArchive()
{
// Currently global content can not be archived
if (!$this->getContainer()) {
return $this->canEdit();
}
// No need to archive content on an archived container, content is marked as archived already
if ($this->getContainer()->isArchived()) {
return false;
}
return $this->getContainer()->permissionManager->can(new ManageContent());
}
/**
* Archives the content object
*/
public function archive()
{
if ($this->canArchive()) {
if ($this->isPinned()) {
$this->unpin();
}
$this->archived = 1;
if (!$this->save()) {
throw new Exception("Could not archive content!" . print_r($this->getErrors(), 1));
}
}
}
/**
* {@inheritdoc}
* @throws \Throwable
*/
public function move(ContentContainerActiveRecord $container = null, $force = false)
{
$move = ($force) ? true : $this->canMove($container);
if ($move === true) {
static::getDb()->transaction(function ($db) use ($container) {
$this->setContainer($container);
if ($this->save()) {
ContentTag::deleteContentRelations($this, false);
$model = $this->getModel();
$model->populateRelation('content', $this);
$model->afterMove($container);
}
});
}
return $move;
}
/**
* {@inheritdoc}
*/
public function canMove(ContentContainerActiveRecord $container = null)
{
$model = $this->getModel();
$canModelBeMoved = $this->isModelMovable($container);
if ($canModelBeMoved !== true) {
return $canModelBeMoved;
}
if (!$container) {
return $this->checkMovePermission() ? true : Yii::t('ContentModule.base', 'You do not have the permission to move this content.');
}
if ($container->contentcontainer_id === $this->contentcontainer_id) {
return Yii::t('ContentModule.base', 'The content can\'t be moved to its current space.');
}
// Check if the related module is installed on the target space
if (!$container->moduleManager->isEnabled($model->getModuleId())) {
/* @var $module Module */
$module = Yii::$app->getModule($model->getModuleId());
$moduleName = ($module instanceof ContentContainerModule) ? $module->getContentContainerName($container) : $module->getName();
return Yii::t('ContentModule.base', 'The module {moduleName} is not enabled on the selected target space.', ['moduleName' => $moduleName]);
}
// Check if the current user is allowed to move this content at all
if (!$this->checkMovePermission()) {
return Yii::t('ContentModule.base', 'You do not have the permission to move this content.');
}
// Check if the current user is allowed to move this content to the given target space
if (!$this->checkMovePermission($container)) {
return Yii::t('ContentModule.base', 'You do not have the permission to move this content to the given space.');
}
// Check if the content owner is allowed to create content on the target space
$ownerPermissions = $container->getPermissionManager($this->createdBy);
if ($this->isPrivate() && !$ownerPermissions->can(CreatePrivateContent::class)) {
return Yii::t('ContentModule.base', 'The author of this content is not allowed to create private content within the selected space.');
}
if ($this->isPublic() && !$ownerPermissions->can(CreatePublicContent::class)) {
return Yii::t('ContentModule.base', 'The author of this content is not allowed to create public content within the selected space.');
}
return true;
}
public function isModelMovable(ContentContainerActiveRecord $container = null)
{
$model = $this->getModel();
$canModelBeMoved = $model->canMove($container);
if ($canModelBeMoved !== true) {
return $canModelBeMoved;
}
// Check for legacy modules
if (!$model->getModuleId()) {
return Yii::t('ContentModule.base', 'This content type can\'t be moved due to a missing module-id setting.');
}
return true;
}
/**
* Checks if the current user has generally the permission to move this content on the given container or the current container if no container was provided.
*
* Note this function is only used for a general permission check use [[canMove()]] for a
*
* This is the case if:
*
* - The current user is the owner of this content
* @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 \yii\base\InvalidConfigException
*/
public function checkMovePermission(ContentContainerActiveRecord $container = null)
{
if (!$container) {
$container = $this->container;
}
return $this->getModel()->isOwner() || Yii::$app->user->can(ManageUsers::class) || $container->can(ManageContent::class);
}
/**
* {@inheritdoc}
*/
public function afterMove(ContentContainerActiveRecord $container = null)
{
// Nothing to do
}
/**
* Unarchives the content object
*/
public function unarchive()
{
if ($this->canArchive()) {
$this->archived = 0;
$this->save();
}
}
/**
* Returns the url of this content.
*
* By default is returns the url of the wall entry.
*
* Optionally it's possible to create an own getUrl method in the underlying
* HActiveRecordContent (e.g. Post) to overwrite this behavior.
* e.g. in case there is no wall entry available for this content.
*
* @param boolean $scheme
* @return string the URL
* @since 0.11.1
*/
public function getUrl($scheme = false)
{
try {
if (method_exists($this->getPolymorphicRelation(), 'getUrl')) {
return $this->getPolymorphicRelation()->getUrl($scheme);
}
} catch (IntegrityException $e) {
Yii::error($e->getMessage(), 'content');
}
return Url::toRoute(['/content/perma', 'id' => $this->id], $scheme);
}
/**
* Sets container (e.g. space or user record) for this content.
*
* @param ContentContainerActiveRecord $container
* @throws Exception
*/
public function setContainer(ContentContainerActiveRecord $container)
{
$this->contentcontainer_id = $container->contentContainerRecord->id;
$this->_container = $container;
if ($container instanceof Space && $this->visibility === null) {
$this->visibility = $container->getDefaultContentVisibility();
}
}
/**
* Returns the content container (e.g. space or user record) of this content
*
* @return ContentContainerActiveRecord
* @throws Exception
*/
public function getContainer()
{
if ($this->_container != null) {
return $this->_container;
}
if ($this->contentContainer !== null) {
$this->_container = $this->contentContainer->getPolymorphicRelation();
}
return $this->_container;
}
/**
* Relation to ContentContainer model
* Note: this is not a Space or User instance!
*
* @return \yii\db\ActiveQuery
* @since 1.1
*/
public function getContentContainer()
{
return $this->hasOne(ContentContainer::class, ['id' => 'contentcontainer_id']);
}
/**
* Returns the ContentTagRelation query.
*
* @return \yii\db\ActiveQuery
* @since 1.2.2
*/
public function getTagRelations()
{
return $this->hasMany(ContentTagRelation::class, ['content_id' => 'id']);
}
/**
* Returns all content related tags ContentTags related to this content.
*
* @return \yii\db\ActiveQuery
* @since 1.2.2
*/
public function getTags($tagClass = ContentTag::class)
{
return $this->hasMany($tagClass, ['id' => 'tag_id'])->via('tagRelations')->orderBy('sort_order');
}
/**
* Adds a new ContentTagRelation for this content and the given $tag instance.
*
* @param ContentTag $tag
* @return bool if the provided tag is part of another ContentContainer
* @since 1.2.2
*/
public function addTag(ContentTag $tag)
{
if (!empty($tag->contentcontainer_id) && $tag->contentcontainer_id != $this->contentcontainer_id) {
throw new InvalidArgumentException(Yii::t('ContentModule.base', 'Content Tag with invalid contentcontainer_id assigned.'));
}
if (ContentTagRelation::findBy($this, $tag)->count()) {
return true;
}
$this->refresh();
SearchHelper::queueUpdate($this->getPolymorphicRelation());
$contentRelation = new ContentTagRelation($this, $tag);
return $contentRelation->save();
}
/**
* Adds the given ContentTag array to this content.
*
* @param $tags ContentTag[]
* @since 1.3
*/
public function addTags($tags)
{
foreach ($tags as $tag) {
$this->addTag($tag);
}
}
/**
* Checks if the given user can edit/create this content.
*
* A user can edit a content if one of the following conditions are met:
*
* - User is the owner of the content
* - User is system administrator and the content module setting `adminCanEditAllContent` is set to true (default)
* - The user is granted the managePermission set by the model record class
* - The user meets the additional condition implemented by the model records class own `canEdit()` function.
*
* @param User|integer $user user instance or user id
* @return bool can edit/create this content
* @throws Exception
* @throws IntegrityException
* @throws \Throwable
* @throws \yii\base\InvalidConfigException
* @since 1.1
*/
public function canEdit($user = null)
{
if (Yii::$app->user->isGuest) {
return false;
}
if ($user === null) {
$user = Yii::$app->user->getIdentity();
} else if (!($user instanceof User)) {
$user = User::findOne(['id' => $user]);
}
// Only owner can edit his content
if ($user !== null && $this->created_by == $user->id) {
return true;
}
// Global Admin can edit/delete arbitrarily content
if (Yii::$app->getModule('content')->adminCanEditAllContent && $user->isSystemAdmin()) {
return true;
}
$model = $this->getModel();
// Check additional manage permission for the given container
if ($this->container) {
if ($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())) {
return true;
}
}
// Check if underlying models canEdit implementation
// ToDo: Implement this as interface
if (method_exists($model, 'canEdit') && $model->canEdit($user)) {
return true;
}
return false;
}
/**
* Checks if the user can lock comments for this content.
*
* @return bool
* @throws Exception
*/
public function canLockComments(): bool
{
if (!$this->getContainer()) {
return $this->canEdit();
}
return $this->getContainer()->permissionManager->can(ManageContent::class);
}
/**
* Checks the given $permission of the current user in the contents content container.
* This is short for `$this->getContainer()->getPermissionManager()->can()`.
*
* @param $permission
* @param array $params
* @param bool $allowCaching
* @return bool
* @throws Exception
* @throws \yii\base\InvalidConfigException
* @see PermissionManager::can()
* @since 1.2.1
*/
public function can($permission, $params = [], $allowCaching = true)
{
return $this->getContainer()->getPermissionManager()->can($permission, $params, $allowCaching);
}
/**
* Checks if user can view this content.
*
* @param User|integer $user
* @return boolean can view this content
* @throws Exception
* @throws \Throwable
* @since 1.1
*/
public function canView($user = null)
{
if (!$user && !Yii::$app->user->isGuest) {
$user = Yii::$app->user->getIdentity();
} else if (!$user instanceof User) {
$user = User::findOne(['id' => $user]);
}
// Check global content visibility, private global content is visible for all users
if (empty($this->contentcontainer_id) && !Yii::$app->user->isGuest) {
return true;
}
// User can access own content
if ($user !== null && $this->created_by == $user->id) {
return true;
}
// Check Guest Visibility
if (!$user) {
return $this->checkGuestAccess();
}
// Public visible content
if ($this->isPublic()) {
return true;
}
// Check system admin can see all content module configuration
if ($user->canViewAllContent()) {
return true;
}
if ($this->isPrivate() && $this->getContainer() !== null && $this->getContainer()->canAccessPrivateContent($user)) {
return true;
}
return false;
}
/**
* Determines if a guest user is able to read this content.
* This is the case if all of the following conditions are met:
*
* - The content is public
* - The `auth.allowGuestAccess` setting is enabled
* - The space or profile visibility is set to VISIBILITY_ALL
*
* @return bool
*/
public function checkGuestAccess()
{
if (!$this->isPublic() || !AuthHelper::isGuestAccessEnabled()) {
return false;
}
// GLobal content
if (!$this->container) {
return $this->isPublic();
}
if ($this->container instanceof Space) {
return $this->isPublic() && $this->container->visibility == Space::VISIBILITY_ALL;
}
if ($this->container instanceof User) {
return $this->isPublic() && $this->container->visibility == User::VISIBILITY_ALL;
}
return false;
}
/**
* Updates the wall/stream sorting time of this content for "updated at" sorting
*/
public function updateStreamSortTime()
{
$this->updateAttributes(['stream_sort_date' => date('Y-m-d G:i:s')]);
}
/**
* @returns \humhub\modules\content\models\Content content instance of this content owner
*/
public function getContent()
{
return $this;
}
/**
* @returns string name of the content like 'comment', 'post'
*/
public function getContentName()
{
return $this->getModel()->getContentName();
}
/**
* @returns string short content description
*/
public function getContentDescription()
{
return $this->getModel()->getContentDescription();
}
/**
* @returns boolean true if this content has been updated, otherwise false
* @since 1.7
*/
public function isUpdated()
{
return $this->created_at !== $this->updated_at && !empty($this->updated_at) && is_string($this->updated_at);
}
}