Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add translation feature #4094

Merged
merged 55 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
20e4c74
chore: document README.md
gabrielfs7 Sep 4, 2024
48ff6bb
feat: adding base classes for translation
gabrielfs7 Sep 4, 2024
24fb952
chore: fix di injection
gabrielfs7 Sep 4, 2024
15769eb
feat: adding ACL for new endpoints and handling request
gabrielfs7 Sep 4, 2024
e752f58
chore: add unit tests and prepare code to extract data based on resou…
gabrielfs7 Sep 4, 2024
7fbbf13
chore: add list of translation types
shpran Sep 5, 2024
f9a2eca
chore: move props to tao-core
shpran Sep 5, 2024
6e1dc20
chore: change property values ID
shpran Sep 5, 2024
75ec984
chore: rename property
shpran Sep 5, 2024
01d50c2
chore: move domains to their extensions
shpran Sep 5, 2024
1276114
chore: document translation process metadata
gabrielfs7 Sep 5, 2024
086cc22
chore: document translation metadata
gabrielfs7 Sep 5, 2024
0ef1330
feat: add all translation status in response based on metadata
gabrielfs7 Sep 5, 2024
5820d20
chore: move classes to proper namespaces
gabrielfs7 Sep 6, 2024
1738870
chore: move constants to ontology
gabrielfs7 Sep 6, 2024
b7fdee0
feat: add new endpoint to get master unit and increase contract output
gabrielfs7 Sep 6, 2024
9bc1a32
chore: adding unit tests
gabrielfs7 Sep 6, 2024
05944c8
chore: adding unit tests
gabrielfs7 Sep 6, 2024
c866e86
feat: add possibility to hide button in tree by path and feature flag
gabrielfs7 Sep 9, 2024
41375eb
feat: implement form modifiers, make uniq id readonly
shpran Sep 9, 2024
bf4f835
chore: add dynamic metadata, so it can be extended later
gabrielfs7 Sep 10, 2024
3f03ed5
Merge branch 'feat/ADF-1781/translations-feature' of https://github.c…
gabrielfs7 Sep 10, 2024
58c3acc
feat: add custom metadata for resources
gabrielfs7 Sep 10, 2024
f75c3be
chore: add abstract modifier with ontology to avoid code duplications…
shpran Sep 10, 2024
b50c1de
chore: simplify entities and make them agnostic
gabrielfs7 Sep 10, 2024
8e0232a
Merge branch 'feat/ADF-1781/translations-feature' of https://github.c…
gabrielfs7 Sep 10, 2024
4a92817
chore: simplify entities and make them agnostic
gabrielfs7 Sep 10, 2024
b93da9f
chore: use unique ids and parent classes ids instead
gabrielfs7 Sep 10, 2024
1a91a2d
chore: use proxies instead of manager
shpran Sep 10, 2024
613eeab
chore: add unit tests
gabrielfs7 Sep 10, 2024
052c54e
chore: add unit tests
gabrielfs7 Sep 10, 2024
b3e2fdb
chore: remove syntax error
gabrielfs7 Sep 11, 2024
9056d64
chore: fix cs
gabrielfs7 Sep 11, 2024
0c23fb8
chore: add unit tests
gabrielfs7 Sep 11, 2024
f1a4ee5
chore: add api documentation
gabrielfs7 Sep 11, 2024
7c082f7
chore: add unit tests for modifier
shpran Sep 11, 2024
85c4fe8
chore: add setValue and setElementValue method to forms
shpran Sep 11, 2024
6bbba71
feat: introduce property aliases
shpran Sep 11, 2024
3de82d2
feat: add possibility to translate resources agnostically
gabrielfs7 Sep 11, 2024
49e0242
chore: add missing php docs
gabrielfs7 Sep 12, 2024
c4dd57d
Merge pull request #4098 from oat-sa/feat/ADF-1783/translate-resources
gabrielfs7 Sep 12, 2024
35148cf
chore: remove tmp code
gabrielfs7 Sep 12, 2024
c5c81d6
fix: avoid duplicate properties
gabrielfs7 Sep 12, 2024
debed91
feat: make endpoints only require resource id or locale to work
gabrielfs7 Sep 12, 2024
c9d15a3
chore: add missing unit tests cs fixes
gabrielfs7 Sep 12, 2024
d8d7ae6
chore: remove rules from non-translator user
gabrielfs7 Sep 12, 2024
6315e5d
chore: add PR suggestions
gabrielfs7 Sep 12, 2024
965f4ea
chore: allow update translation status
gabrielfs7 Sep 12, 2024
c5003fa
chore: fix cs
shpran Sep 13, 2024
8789fa7
fix: add extra validation and allow multiple uniqueIds search
gabrielfs7 Sep 13, 2024
5c86a48
chore: update unit tests
gabrielfs7 Sep 13, 2024
03d8a29
chore: can only translate original resource
gabrielfs7 Sep 13, 2024
804ac95
chore: add language prefix to tao ontology
shpran Sep 13, 2024
2623693
fix: validate the only ready for translate resources are translated
gabrielfs7 Sep 13, 2024
314ef31
Merge branch 'feat/ADF-1781/translations-feature' of https://github.c…
gabrielfs7 Sep 13, 2024
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Here you can find the environment variables including feature flags
| DATA_STORE_STATISTIC_PUB_SUB_TOPIC | Topic name for statistic metadata Pub/Sub | - |
| REDIRECT_AFTER_LOGOUT_URL | Allows to configure the redirect after logout via environment variable. The fallback is the configured redirect on urlroute.conf.php | - |
| PORTAL_URL | The Portal url used on the back button of Portal theme | - |
| FEATURE_TRANSLATION_ENABLED | Enable access to items/tests translations feature | - |


# Routing
Expand Down
19 changes: 17 additions & 2 deletions actions/class.Main.php
Original file line number Diff line number Diff line change
Expand Up @@ -521,11 +521,13 @@ private function getSections($shownExtension, $shownStructure)
$sections = [];
$user = $this->getSession()->getUser();
$structure = MenuService::getPerspective($shownExtension, $shownStructure);
$sectionVisibilityFilter = $this->getSectionVisibilityFilter();

if (!is_null($structure)) {
foreach ($structure->getChildren() as $section) {
$resolver = new ActionResolver($section->getUrl());

if (!$this->getSectionVisibilityFilter()->isVisible($section->getId())) {
if (!$sectionVisibilityFilter->isVisible($section->getId())) {
continue;
}

Expand All @@ -540,6 +542,19 @@ private function getSections($shownExtension, $shownStructure)

if (FuncProxy::accessPossible($user, $resolver->getController(), $resolver->getAction())) {
foreach ($section->getActions() as $action) {
$sectionPath = $sectionVisibilityFilter->createSectionPath(
[
$section->getId(),
$action->getId()
]
);

if (!$sectionVisibilityFilter->isVisible($sectionPath)) {
$section->removeAction($action);

continue;
}

$this->propagate($action);
$resolver = new ActionResolver($action->getUrl());
if (
Expand Down Expand Up @@ -580,7 +595,7 @@ protected function getUserService()
return $this->getServiceLocator()->get(tao_models_classes_UserService::SERVICE_ID);
}

private function getSectionVisibilityFilter(): SectionVisibilityFilterInterface
private function getSectionVisibilityFilter(): SectionVisibilityFilter
{
if (empty($this->sectionVisibilityFilter)) {
$this->sectionVisibilityFilter = $this->getServiceLocator()->get(SectionVisibilityFilter::SERVICE_ID);
Expand Down
62 changes: 62 additions & 0 deletions actions/class.Translation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2024 (original work) Open Assessment Technologies SA;
*/

declare(strict_types=1);

use oat\tao\model\http\HttpJsonResponseTrait;
use oat\tao\model\Translation\Service\ResourceTranslationRetriever;
use oat\tao\model\Translation\Service\ResourceTranslatableRetriever;

class tao_actions_Translation extends tao_actions_CommonModule
{
use HttpJsonResponseTrait;

public function translations(): void
{
try {
$this->setSuccessJsonResponse(
$this->getResourceTranslationRetriever()->getByRequest($this->getPsrRequest())
);
} catch (Throwable $exception) {
$this->setErrorJsonResponse($exception->getMessage());
}
}

public function translatable(): void
{
try {
$this->setSuccessJsonResponse(
$this->getResourceTranslatableRetriever()->getByRequest($this->getPsrRequest())
);
} catch (Throwable $exception) {
$this->setErrorJsonResponse($exception->getMessage());
}
}

private function getResourceTranslationRetriever(): ResourceTranslationRetriever
{
return $this->getServiceManager()->getContainer()->get(ResourceTranslationRetriever::class);
}

private function getResourceTranslatableRetriever(): ResourceTranslatableRetriever
{
return $this->getServiceManager()->getContainer()->get(ResourceTranslatableRetriever::class);
}
}
120 changes: 120 additions & 0 deletions doc/taoApi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,60 @@ info:
version: v1

paths:
/tao/Translation/translations:
get:
summary: Return a list of translations for a given translatable resource
parameters:
- in: query
name: resourceType
required: true
schema:
type: string
description: The RDF resource type
- in: query
name: uniqueId
required: true
schema:
type: string
description: The uniqueId of a translation
- in: query
name: languageUri
required: false
schema:
type: string
description: The RDF language URI
responses:
200:
$ref: '#/components/responses/TranslationsResponse'
400:
$ref: '#/components/responses/BadRequestResponse'
500:
$ref: '#/components/responses/InternalServerErrorResponse'
/tao/Translation/translatable:
get:
summary: Return translatable resources
parameters:
- in: query
name: resourceType
required: true
schema:
type: string
description: The RDF resource type
- in: query
name: uniqueIds
required: true
schema:
type: array
items:
type: string
description: The uniqueIds for a translatable
responses:
200:
$ref: '#/components/responses/TranslationsResponse'
400:
$ref: '#/components/responses/BadRequestResponse'
500:
$ref: '#/components/responses/InternalServerErrorResponse'
/tao/Languages/index:
get:
summary: Get a list of available languages in the TAO platform
Expand Down Expand Up @@ -46,6 +100,60 @@ paths:
$ref: '#/components/responses/InternalServerErrorResponse'
components:
schemas:
Translations:
properties:
success:
type: boolean
example: true
data:
type: object
properties:
resources:
type: array
items:
properties:
originResourceUri:
type: string
resourceUri:
type: string
resourceLabel:
type: string
metadata:
type: object
properties:
key:
type: object
properties:
value:
type: string
literal:
type: string
Translatable:
properties:
success:
type: boolean
example: true
data:
type: object
properties:
resources:
type: array
items:
properties:
resourceUri:
type: string
resourceLabel:
type: string
metadata:
type: object
properties:
key:
type: object
properties:
value:
type: string
literal:
type: string
ResourceRelationResource:
description: 'A resource related to another resources'
type: object
Expand Down Expand Up @@ -104,6 +212,18 @@ components:
message:
type: string
responses:
TranslationsResponse:
description: The list of translations
content:
application/json:
schema:
$ref: '#/components/schemas/Translations'
TranslatableResponse:
description: The list of translatable
content:
application/json:
schema:
$ref: '#/components/schemas/Translatable'
ResourceRelationsResponse:
description: Bad request
content:
Expand Down
21 changes: 19 additions & 2 deletions helpers/form/class.FormContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@

use oat\oatbox\service\ServiceManager;
use oat\oatbox\validator\ValidatorInterface;
use oat\tao\model\form\Modifier\AbstractFormModifier;
use oat\tao\model\security\xsrf\TokenService;
use Psr\Container\ContainerInterface;
use tao_helpers_form_FormFactory as FormFactory;
use oat\tao\helpers\form\elements\xhtml\CsrfToken;
use oat\tao\helpers\form\elements\FormElementAware;
Expand All @@ -46,6 +48,7 @@ abstract class tao_helpers_form_FormContainer
public const IS_DISABLED = 'is_disabled';
public const ADDITIONAL_VALIDATORS = 'extraValidators';
public const ATTRIBUTE_VALIDATORS = 'attributeValidators';
public const FORM_MODIFIERS = 'formModifiers';

public const WITH_SERVICE_MANAGER = 'withServiceManager';

Expand Down Expand Up @@ -118,6 +121,14 @@ public function __construct(array $data = [], array $options = [])
// initialize the elements of the form
$this->initElements();

foreach ($options[self::FORM_MODIFIERS] ?? [] as $modifierClass) {
$modifier = $this->getContainer()->get($modifierClass);

if ($modifier instanceof AbstractFormModifier) {
$modifier->modify($this->form, $options);
}
}

if ($this->form !== null) {
$this->form->evaluateInputValues();

Expand Down Expand Up @@ -317,8 +328,7 @@ private function configureFormValidators(iterable $validators, tao_helpers_form_
private function getSanitizerValidationFeeder(): SanitizerValidationFeederInterface
{
if (!isset($this->sanitizerValidationFeeder)) {
$serviceManager = $this->serviceManager ?? ServiceManager::getServiceManager();
$this->sanitizerValidationFeeder = $serviceManager->getContainer()->get(SanitizerValidationFeeder::class);
$this->sanitizerValidationFeeder = $this->getContainer()->get(SanitizerValidationFeeder::class);
}

return $this->sanitizerValidationFeeder;
Expand All @@ -335,4 +345,11 @@ private function withServiceManager(array &$options): void

unset($options[self::WITH_SERVICE_MANAGER]);
}

private function getContainer(): ContainerInterface
{
$serviceManager = $this->serviceManager ?? ServiceManager::getServiceManager();

return $serviceManager->getContainer();
}
}
5 changes: 4 additions & 1 deletion manifest.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
use oat\tao\model\routing\ServiceProvider\RoutingServiceProvider;
use oat\tao\model\search\ServiceProvider\SearchServiceProvider;
use oat\tao\model\StatisticalMetadata\StatisticalMetadataServiceProvider;
use oat\tao\model\Translation\ServiceProvider\TranslationServiceProvider;
use oat\tao\model\user\TaoRoles;
use oat\tao\model\user\UserSettingsServiceProvider;
use oat\tao\model\webhooks\WebhookServiceProvider;
Expand Down Expand Up @@ -308,6 +309,7 @@
[AccessRule::GRANT, TaoRoles::BASE_USER, ['ext' => 'tao', 'mod' => 'File', 'act' => 'accessFile']],
[AccessRule::GRANT, TaoRoles::BASE_USER, ['ext' => 'tao', 'mod' => 'Log', 'act' => 'log']],
[AccessRule::GRANT, TaoRoles::BASE_USER, ['ext' => 'tao', 'mod' => 'TaskQueueWebApi']],
[AccessRule::GRANT, TaoRoles::BACK_OFFICE, ['ext' => 'tao', 'mod' => 'Translation']],
[AccessRule::GRANT, TaoRoles::BACK_OFFICE, ['ext' => 'tao', 'mod' => 'Languages', 'act' => 'index']],
[AccessRule::GRANT, TaoRoles::BACK_OFFICE, ['ext' => 'tao', 'mod' => 'ResourceRelations', 'act' => 'index']],
[AccessRule::GRANT, TaoRoles::BACK_OFFICE, ['ext' => 'tao', 'mod' => 'File', 'act' => 'upload']],
Expand Down Expand Up @@ -418,7 +420,8 @@
MenuServiceProvider::class,
FormDataProviderServiceProvider::class,
PropertyServiceProvider::class,
DynamicConfigServiceProvider::class
DynamicConfigServiceProvider::class,
TranslationServiceProvider::class
],
'middlewares' => [
MiddlewareConfig::class,
Expand Down
47 changes: 47 additions & 0 deletions migrations/Version202409040743452141_tao.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace oat\tao\migrations;

use Doctrine\DBAL\Schema\Schema;
use oat\oatbox\reporting\Report;
use oat\tao\model\accessControl\func\AccessRule;
use oat\tao\model\accessControl\func\AclProxy;
use oat\tao\model\user\TaoRoles;
use oat\tao\scripts\tools\migrations\AbstractMigration;
use oat\tao\scripts\update\OntologyUpdater;

final class Version202409040743452141_tao extends AbstractMigration
{
public function getDescription(): string
{
return 'Add new access to ' . TaoRoles::BACK_OFFICE;
}

public function up(Schema $schema): void
{
OntologyUpdater::syncModels();
$this->addReport(Report::createSuccess('Ontology models successfully synchronized'));

AclProxy::applyRule($this->getRule());
$this->addReport(Report::createSuccess('Applied access for role ' . TaoRoles::BACK_OFFICE));
}

public function down(Schema $schema): void
{
AclProxy::revokeRule($this->getRule());
}

private function getRule(): AccessRule
{
return new AccessRule(
AccessRule::GRANT,
TaoRoles::BACK_OFFICE,
[
'ext' => 'tao',
'mod' => 'Translation'
]
);
}
}
Loading
Loading