Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions code_samples/collaboration/config/mysql/ibexa_share.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
CREATE TABLE ibexa_collaboration
(
id INT AUTO_INCREMENT NOT NULL,
owner_id INT NOT NULL,
token VARCHAR(160) NOT NULL,
discriminator VARCHAR(190) NOT NULL,
is_active TINYINT(1) NOT NULL,
has_public_link TINYINT(1) NOT NULL,
created_at DATETIME NOT NULL COMMENT '(DC2Type:datetimetz_immutable)',
updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetimetz_immutable)',
UNIQUE INDEX ibexa_collaboration_token_idx (token),
INDEX ibexa_collaboration_owner_idx (owner_id),
UNIQUE INDEX ibexa_collaboration_token_uc (token),
PRIMARY KEY (id)
) DEFAULT CHARACTER SET utf8mb4
COLLATE `utf8mb4_unicode_520_ci`
ENGINE = InnoDB;
CREATE TABLE ibexa_collaboration_participant
(
id INT AUTO_INCREMENT NOT NULL,
session_id INT NOT NULL,
discriminator VARCHAR(190) NOT NULL,
scope VARCHAR(255) DEFAULT NULL,
token VARCHAR(255) DEFAULT NULL,
created_at DATETIME NOT NULL COMMENT '(DC2Type:datetimetz_immutable)',
updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetimetz_immutable)',
INDEX IDX_9C5C6401613FECDF (session_id),
UNIQUE INDEX ibexa_collaboration_participant_token_idx (token),
PRIMARY KEY (id)
) DEFAULT CHARACTER SET utf8mb4
COLLATE `utf8mb4_unicode_520_ci`
ENGINE = InnoDB;
CREATE TABLE ibexa_collaboration_participant_internal
(
id INT NOT NULL,
user_id INT NOT NULL,
INDEX IDX_E838B79AA76ED395 (user_id),
PRIMARY KEY (id)
) DEFAULT CHARACTER SET utf8mb4
COLLATE `utf8mb4_unicode_520_ci`
ENGINE = InnoDB;
CREATE TABLE ibexa_collaboration_participant_external
(
id INT NOT NULL,
email VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) DEFAULT CHARACTER SET utf8mb4
COLLATE `utf8mb4_unicode_520_ci`
ENGINE = InnoDB;
CREATE TABLE ibexa_collaboration_invitation
(
id INT AUTO_INCREMENT NOT NULL,
session_id INT NOT NULL,
participant_id INT NOT NULL,
sender_id INT NOT NULL,
status VARCHAR(64) NOT NULL,
context LONGTEXT DEFAULT NULL COMMENT '(DC2Type:json)',
created_at DATETIME NOT NULL COMMENT '(DC2Type:datetimetz_immutable)',
updated_at DATETIME NOT NULL COMMENT '(DC2Type:datetimetz_immutable)',
INDEX IDX_36C63687613FECDF (session_id),
INDEX IDX_36C636879D1C3019 (participant_id),
INDEX IDX_36C63687F624B39D (sender_id),
PRIMARY KEY (id)
) DEFAULT CHARACTER SET utf8mb4
COLLATE `utf8mb4_unicode_520_ci`
ENGINE = InnoDB;
ALTER TABLE ibexa_collaboration
ADD CONSTRAINT ibexa_collaboration_owner_id_fk FOREIGN KEY (owner_id) REFERENCES ezuser (contentobject_id) ON DELETE RESTRICT;
ALTER TABLE ibexa_collaboration_participant
ADD CONSTRAINT ibexa_collaboration_participant_session_id_fk FOREIGN KEY (session_id) REFERENCES ibexa_collaboration (id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ibexa_collaboration_participant_internal
ADD CONSTRAINT ibexa_collaboration_participant_internal_pk FOREIGN KEY (id) REFERENCES ibexa_collaboration_participant (id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ibexa_collaboration_participant_internal
ADD CONSTRAINT ibexa_collaboration_participant_internal_user_id_fk FOREIGN KEY (user_id) REFERENCES ezuser (contentobject_id) ON DELETE RESTRICT;
ALTER TABLE ibexa_collaboration_participant_external
ADD CONSTRAINT ibexa_collaboration_participant_external_pk FOREIGN KEY (id) REFERENCES ibexa_collaboration_participant (id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ibexa_collaboration_invitation
ADD CONSTRAINT ibexa_collaboration_invitation_session_id_fk FOREIGN KEY (session_id) REFERENCES ibexa_collaboration (id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ibexa_collaboration_invitation
ADD CONSTRAINT ibexa_collaboration_invitation_participant_id_fk FOREIGN KEY (participant_id) REFERENCES ibexa_collaboration_participant (id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ibexa_collaboration_invitation
ADD CONSTRAINT ibexa_collaboration_invitation_sender_id_fk FOREIGN KEY (sender_id) REFERENCES ezuser (contentobject_id) ON DELETE RESTRICT;
84 changes: 84 additions & 0 deletions code_samples/collaboration/config/postgresql/ibexa_share.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
CREATE TABLE ibexa_collaboration
(
id SERIAL NOT NULL,
owner_id INT NOT NULL,
token VARCHAR(160) NOT NULL,
discriminator VARCHAR(190) NOT NULL,
is_active BOOLEAN NOT NULL,
has_public_link BOOLEAN NOT NULL,
created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP(0) WITH TIME ZONE NOT NULL,
PRIMARY KEY (id)
);
CREATE UNIQUE INDEX ibexa_collaboration_token_idx ON ibexa_collaboration (token);
CREATE INDEX ibexa_collaboration_owner_idx ON ibexa_collaboration (owner_id);
CREATE UNIQUE INDEX ibexa_collaboration_token_uc ON ibexa_collaboration (token);
COMMENT
ON COLUMN ibexa_collaboration.created_at IS '(DC2Type:datetimetz_immutable)';
COMMENT
ON COLUMN ibexa_collaboration.updated_at IS '(DC2Type:datetimetz_immutable)';
CREATE TABLE ibexa_collaboration_participant
(
id SERIAL NOT NULL,
session_id INT NOT NULL,
discriminator VARCHAR(190) NOT NULL,
scope VARCHAR(255) DEFAULT NULL,
token VARCHAR(255) DEFAULT NULL,
created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP(0) WITH TIME ZONE NOT NULL,
PRIMARY KEY (id)
);
CREATE INDEX ibexa_collaboration_participant_idx ON ibexa_collaboration_participant (session_id);
CREATE UNIQUE INDEX ibexa_collaboration_participant_token_idx ON ibexa_collaboration_participant (token);
COMMENT
ON COLUMN ibexa_collaboration_participant.created_at IS '(DC2Type:datetimetz_immutable)';
COMMENT
ON COLUMN ibexa_collaboration_participant.updated_at IS '(DC2Type:datetimetz_immutable)';
CREATE TABLE ibexa_collaboration_participant_internal
(
id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (id)
);
CREATE INDEX ibexa_collaboration_participant_internal_idx ON ibexa_collaboration_participant_internal (user_id);
CREATE TABLE ibexa_collaboration_participant_external
(
id INT NOT NULL,
email VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE ibexa_collaboration_invitation
(
id SERIAL NOT NULL,
session_id INT NOT NULL,
participant_id INT NOT NULL,
sender_id INT NOT NULL,
status VARCHAR(64) NOT NULL,
context JSON DEFAULT NULL,
created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP(0) WITH TIME ZONE NOT NULL,
PRIMARY KEY (id)
);
CREATE INDEX ibexa_collaboration_invitation_idx ON ibexa_collaboration_invitation (session_id);
CREATE INDEX ibexa_collaboration_invitation_idx ON ibexa_collaboration_invitation (participant_id);
CREATE INDEX ibexa_collaboration_invitation_idx ON ibexa_collaboration_invitation (sender_id);
COMMENT
ON COLUMN ibexa_collaboration_invitation.created_at IS '(DC2Type:datetimetz_immutable)';
COMMENT
ON COLUMN ibexa_collaboration_invitation.updated_at IS '(DC2Type:datetimetz_immutable)';
ALTER TABLE ibexa_collaboration
ADD CONSTRAINT ibexa_collaboration_owner_id_fk FOREIGN KEY (owner_id) REFERENCES ezuser (contentobject_id) ON DELETE RESTRICT NOT DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE ibexa_collaboration_participant
ADD CONSTRAINT ibexa_collaboration_participant_session_id_fk FOREIGN KEY (session_id) REFERENCES ibexa_collaboration (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE ibexa_collaboration_participant_internal
ADD CONSTRAINT ibexa_collaboration_participant_internal_pk FOREIGN KEY (id) REFERENCES ibexa_collaboration_participant (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE ibexa_collaboration_participant_internal
ADD CONSTRAINT ibexa_collaboration_participant_internal_user_id_fk FOREIGN KEY (user_id) REFERENCES ezuser (contentobject_id) ON DELETE RESTRICT NOT DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE ibexa_collaboration_participant_external
ADD CONSTRAINT ibexa_collaboration_participant_external_pk FOREIGN KEY (id) REFERENCES ibexa_collaboration_participant (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE ibexa_collaboration_invitation
ADD CONSTRAINT ibexa_collaboration_invitation_session_id_fk FOREIGN KEY (session_id) REFERENCES ibexa_collaboration (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE ibexa_collaboration_invitation
ADD CONSTRAINT ibexa_collaboration_invitation_participant_id_fk FOREIGN KEY (participant_id) REFERENCES ibexa_collaboration_participant (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE ibexa_collaboration_invitation
ADD CONSTRAINT ibexa_collaboration_invitation_sender_id_fk FOREIGN KEY (sender_id) REFERENCES ezuser (contentobject_id) ON DELETE RESTRICT NOT DEFERRABLE INITIALLY IMMEDIATE;
174 changes: 174 additions & 0 deletions code_samples/collaboration/src/Command/ManageSessionsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace App\Command;

use Ibexa\Contracts\Collaboration\Invitation\InvitationCreateStruct;
use Ibexa\Contracts\Collaboration\Invitation\InvitationQuery;
use Ibexa\Contracts\Collaboration\Invitation\InvitationStatus;
use Ibexa\Contracts\Collaboration\Invitation\InvitationUpdateStruct;
use Ibexa\Contracts\Collaboration\Invitation\Query\Criterion\Session;
use Ibexa\Contracts\Collaboration\InvitationServiceInterface;
use Ibexa\Contracts\Collaboration\Participant\ExternalParticipantCreateStruct;
use Ibexa\Contracts\Collaboration\Participant\InternalParticipantCreateStruct;
use Ibexa\Contracts\Collaboration\Participant\InternalParticipantUpdateStruct;
use Ibexa\Contracts\Collaboration\Session\Query\Criterion\Token;
use Ibexa\Contracts\Collaboration\Session\SessionQuery;
use Ibexa\Contracts\Collaboration\SessionServiceInterface;
use Ibexa\Contracts\Core\Repository\ContentService;
use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\Share\Collaboration\ContentSessionCreateStruct;
use Ibexa\Contracts\Share\Collaboration\ContentSessionScope;
use Ibexa\Contracts\Share\Collaboration\ContentSessionUpdateStruct;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class ManageSessionsCommand extends Command
{
protected static $defaultName = 'app:manage-sessions';

private InvitationServiceInterface $invitationService;

Check failure on line 37 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Property App\Command\ManageSessionsCommand::$invitationService has unknown class Ibexa\Contracts\Collaboration\InvitationServiceInterface as its type.

private SessionServiceInterface $sessionService;

Check failure on line 39 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Property App\Command\ManageSessionsCommand::$sessionService has unknown class Ibexa\Contracts\Collaboration\SessionServiceInterface as its type.

private ContentService $contentService;

private UserService $userService;

private PermissionResolver $permissionResolver;

public function __construct(
InvitationServiceInterface $invitationService,

Check failure on line 48 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Parameter $invitationService of method App\Command\ManageSessionsCommand::__construct() has invalid type Ibexa\Contracts\Collaboration\InvitationServiceInterface.
SessionServiceInterface $sessionService,

Check failure on line 49 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Parameter $sessionService of method App\Command\ManageSessionsCommand::__construct() has invalid type Ibexa\Contracts\Collaboration\SessionServiceInterface.
ContentService $contentService,
UserService $userService,
PermissionResolver $permissionResolver
) {
parent::__construct(self::$defaultName);

$this->invitationService = $invitationService;
$this->sessionService = $sessionService;
$this->contentService = $contentService;
$this->userService = $userService;
$this->permissionResolver = $permissionResolver;
}

public function execute(InputInterface $input, OutputInterface $output): int
{
$this->permissionResolver->setCurrentUserReference(
$this->userService->loadUserByLogin('admin')
);

// Create a sharing session for Content
$versionInfo = $this->contentService->loadContent(52)->getVersionInfo();
$createStruct = new ContentSessionCreateStruct(

Check failure on line 71 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Instantiated class Ibexa\Contracts\Share\Collaboration\ContentSessionCreateStruct not found.
$versionInfo,
$versionInfo->getInitialLanguage()
);
$createStruct->setHasPublicLink(false);

Check failure on line 75 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Call to method setHasPublicLink() on an unknown class Ibexa\Contracts\Share\Collaboration\ContentSessionCreateStruct.

$token = 'my-secret-token-12345';
$createStruct->setToken($token);

Check failure on line 78 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Call to method setToken() on an unknown class Ibexa\Contracts\Share\Collaboration\ContentSessionCreateStruct.

$sessionId = $this->sessionService->createSession($createStruct)->getId();

Check failure on line 80 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Call to method createSession() on an unknown class Ibexa\Contracts\Collaboration\SessionServiceInterface.

// Get a session by ID or token
$session = $this->sessionService->getSession($sessionId);

Check failure on line 83 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Call to method getSession() on an unknown class Ibexa\Contracts\Collaboration\SessionServiceInterface.
$session = $this->sessionService->getSessionByToken($token);

Check failure on line 84 in code_samples/collaboration/src/Command/ManageSessionsCommand.php

View workflow job for this annotation

GitHub Actions / Validate code samples (8.3)

Call to method getSessionByToken() on an unknown class Ibexa\Contracts\Collaboration\SessionServiceInterface.

// Find sessions
$sessionQuery = new SessionQuery(new Token($token));
$session = $this->sessionService->findSessions($sessionQuery)->getFirst();

// Update a session
$updateStruct = new ContentSessionUpdateStruct();
$updateStruct->setHasPublicLink(true);

$this->sessionService->updateSession($session, $updateStruct);

// Deactivate a session
$updateStruct = new ContentSessionUpdateStruct();
$updateStruct->setIsActive(false);

$this->sessionService->updateSession($session, $updateStruct);

// Manage participants
$user = $this->userService->loadUserByLogin('another_user');
$internalParticipantCreateStruct = new InternalParticipantCreateStruct(
$user,
ContentSessionScope::VIEW
);
$externalParticipantCreateStruct = new ExternalParticipantCreateStruct(
'[email protected]',
ContentSessionScope::VIEW,
'personal-secret-token-12345'
);

$internalParticipant = $this->sessionService->addParticipant($session, $internalParticipantCreateStruct);
$externalParticipant = $this->sessionService->addParticipant($session, $externalParticipantCreateStruct);

// Get and update participants
$participant = $this->sessionService
->getSession($session->getId())
->getParticipants()
->getByEmail($user->email);

$internalParticipantUpdateStruct = new InternalParticipantUpdateStruct(ContentSessionScope::EDIT);
$this->sessionService->updateParticipant($session, $participant, $internalParticipantUpdateStruct);

// Remove participant
$this->sessionService->removeParticipant($session, $externalParticipant);

// Check ownerships. If no user is provided, current user is used.
$this->sessionService->isSessionOwner(
$session,
$this->userService->loadUserByLogin('another_user')
);

// Check participation
$this->sessionService->isSessionParticipant(
$session,
$this->permissionResolver->getCurrentUserReference()
);

// Manage invitations
$invitationQuery = new InvitationQuery(new Session($session));
$invitations = $this->invitationService->findInvitations($invitationQuery)->getInvitations();

foreach ($invitations as $invitation) {
$output->writeln('Invitation ID: ' . $invitation->getId() . ' Status: ' . $invitation->getStatus());
}

$invitation = $this->invitationService->getInvitationByParticipant($participant);

// Create invitation - use when auto-inviting participants is not enabled
$invitationCreateStruct = new InvitationCreateStruct(
$session,
$internalParticipant
);

$this->invitationService->createInvitation($invitationCreateStruct);

// Update invitation
$invitationUpdateStruct = new InvitationUpdateStruct();
$invitationUpdateStruct->setStatus(InvitationStatus::STATUS_REJECTED);

$this->invitationService->updateInvitation($invitation, $invitationUpdateStruct);

// Delete invitation
$invitation = $this->invitationService->getInvitation(2);
$this->invitationService->deleteInvitation($invitation);

// Delete a session
$this->sessionService->deleteSession($session);

return Command::SUCCESS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---

Check warning on line 1 in docs/content_management/collaborative_editing/collaborative_editing.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/collaborative_editing/collaborative_editing.md#L1

[Ibexa.ReadingLevel] The grade level is 17.68. Aim for 8th grade or lower by using shorter sentences and words.
Raw output
{"message": "[Ibexa.ReadingLevel] The grade level is 17.68. Aim for 8th grade or lower by using shorter sentences and words.", "location": {"path": "docs/content_management/collaborative_editing/collaborative_editing.md", "range": {"start": {"line": 1, "column": 1}}}, "severity": "WARNING"}
description: Collaborative editing enables multiple users to work on the same content simultaneously.
page_type: landing_page
editions:
- lts-update
month_change: true
---

# Collaborative editing

With Collaborative editing [LTS update](editions.md#lts-updates) multiple users can work on the same content created in [[= product_name =]] simultaneously, streamlining the content creation and review process.

Users can invite both internal and external collaborators to a session, giving them access for editing or previewing.

Additionaly, they can collaborate using a Real-time collaboration, an advanced part of the collaboration feature, to write and review content in a live mode thanks to CKEditor.

Check warning on line 15 in docs/content_management/collaborative_editing/collaborative_editing.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/collaborative_editing/collaborative_editing.md#L15

[Ibexa.OxfordComma] Use a comma before the last 'and' or 'or' in a list of four or more items.
Raw output
{"message": "[Ibexa.OxfordComma] Use a comma before the last 'and' or 'or' in a list of four or more items.", "location": {"path": "docs/content_management/collaborative_editing/collaborative_editing.md", "range": {"start": {"line": 15, "column": 1}}}, "severity": "WARNING"}
Real-time collaboration syncs changes instantly and shows user avatars and colored tags to indicate who is editing specific part of the Rich Text field.

This feature also introduces new dashboard tabs for managing shared drafts and joining collaboration sessions easily.

Check warning on line 18 in docs/content_management/collaborative_editing/collaborative_editing.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/collaborative_editing/collaborative_editing.md#L18

[Ibexa.VerySimply] Avoid using 'easily'.
Raw output
{"message": "[Ibexa.VerySimply] Avoid using 'easily'.", "location": {"path": "docs/content_management/collaborative_editing/collaborative_editing.md", "range": {"start": {"line": 18, "column": 111}}}, "severity": "WARNING"}

[[= cards([
"content_management/collaborative_editing/collaborative_editing_guide",
"content_management/collaborative_editing/install_collaborative_editing",
"content_management/collaborative_editing/collaborative_editing_api"
], columns=3) =]]
Loading
Loading