1 Commits

Author SHA1 Message Date
jbcr
d631e6555e enclosure js code into anonymous function (#28)
* encosure js code into annonymous function
2019-12-05 15:25:50 +01:00
63 changed files with 302 additions and 1153 deletions

View File

@@ -1,55 +0,0 @@
name: PHP Composer
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
strategy:
fail-fast: false
matrix:
php-version: [7.3, 7.4, 8.0, 8.1]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: xdebug
- name: PHP Version
run: php -v
- name: PHP Lint
run: find . -type f -name '*.php' -exec php -l {} \; | (! grep -v "No syntax errors detected" )
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v2
with:
path: vendor
key: ${{ runner.os }}-php${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php${{ matrix.php-version }}-
- name: Install dependencies
run: composer update --prefer-dist --no-progress --no-scripts
- name: PHPUnit
run: php -d xdebug.mode=coverage vendor/bin/phpunit --log-junit junit.xml
- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@v1
if: always()
with:
files: junit.xml

1
.gitignore vendored
View File

@@ -3,4 +3,3 @@ composer.lock
.phpunit.result.cache
.php_cs.cache
.php_cs
.php-cs-fixer.cache

View File

@@ -2,7 +2,7 @@
$finder = PhpCsFixer\Finder::create()->in(__DIR__.'/src');
return (new PhpCsFixer\Config())
return PhpCsFixer\Config::create()
->setRules([
'@Symfony' => true,
'declare_strict_types' => true,

View File

@@ -1,53 +1,3 @@
# Version 3.4.0
* Added error count columns to job tables
# Version 3.3.2
* Fix NotModifiedContentFilter when creating new translation
# Version 3.3.1
* Allow Dataflow 4
# Version 3.2.0
* Fixed History page pagination is hidden by footer on Ibexa 3.3 #38
* Added filter on history page to filter out jobs with 0 items
* Date and times will now be displayed using the user defined timezone, and stored using php timezone
# Version 3.1.0
* Allow `LocationCreateStruct` objects inside the `$locations` argument of `ContentCreateStructure` to have more control over the created locations.
# Version 3.0.1
* Bump minimum PHP version to PHP 7.3 like code-rhapsodie/dataflow-bundle dependency.
* Allow PHP 8.x.
* Add GitHub Action to run tests.
# Version 3.0.0
* Add compatibility with Ibexa Content 3.3
* Add compatibility with Symfony 5.x
# Version 2.3.0
* Added a button to display exceptions / log in a modal
* Add log in `CodeRhapsodie\EzDataflowBundle\Filter\NotModifiedContentFilter` and `CodeRhapsodie\EzDataflowBundle\Writer\ContentWriter`
# Version 2.2.0
* Added `NotModifiedContentFilter` and a bunch of `FieldComparator` classes
# version 2.1.0
* ContentWriter return created content
# version 2.0.1
* Enclosure js code into anonymous function
# version 2.0.0
* Update to use Dataflow v2.0+

View File

@@ -178,7 +178,7 @@ class MyDataflowType extends AbstractDataflowType
/**
* @var ContentStructureFactory
*/
private $contentStructureFactory;
private contentStructureFactory;
public function __construct(ContentWriter $contentWriter, ContentStructureFactory $contentStructureFactory)
{
@@ -206,8 +206,6 @@ class MyDataflowType extends AbstractDataflowType
ContentStructureFactoryInterface::MODE_INSERT_OR_UPDATE //Optional value. Other choice : ContentStructureFactoryInterface::MODE_INSERT_ONLY or ContentStructureFactoryInterface::MODE_UPDATE_ONLY
);
});
// If you want the writer log
$this->contentWriter->setLogger($this->logger);
$builder->addWriter($this->contentWriter);
}
}
@@ -215,85 +213,6 @@ class MyDataflowType extends AbstractDataflowType
This example uses `ContentStructureFactory` to check if the content exists and returns the adequate `ContentStrucure` to pass to the content writer.
## Use the NotModifiedContentFilter
When updating contents, you might want to ignore contents where the update would not result in any actual changes in fields values. In that case, you can add the `NotModifiedContentFilter` as one of your steps.
```php
// In your DataflowType
public function __construct(NotModifiedContentFilter $notModifiedContentFilter)
{
$this->notModifiedContentFilter = $notModifiedContentFilter;
}
//[...]
protected function buildDataflow(DataflowBuilder $builder, array $options): void
{
//[...]
// If you want the filter log
$this->notModifiedContentFilter->setLogger($this->logger);
$builder->addStep($this->notModifiedContentFilter);
//[...]
}
```
This filter compares each field value in the `ContentUpdateStructure` received to the fields values in the existing content object. If all values are identical, this filter will return `false`, otherwise, the `ContentUpdateStructure` will be returned as is.
Not all field types are supported by this filter. Il a field type is not supported, values will be assumed different. If your dataflow is dealing with content types containing unsupported field types, it is better to simply not use the `NotModifiedContentFilter` to prevent unnecessary overhead.
### Supported field types
- ezstring
- ezauthor
- ezboolean
- ezcountry
- ezdate
- ezdatetime
- ezemail
- ezfloat
- ezisbn
- ezobjectrelation
- ezobjectrelationlist
- ezkeyword
- ezselection
- eztext
- eztime
- eztags
- novaseometas
- ezurl
- ezmatrix
- ezgmaplocation
- ezrichtext
### Add custom field comparator
If you want to add support for a field type, simply create your own comparator.
```php
<?php
use CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\AbstractFieldComparator;
use eZ\Publish\Core\FieldType\Value;
//[...]
class MyFieldComparator extends AbstractFieldComparator
{
//[...]
protected function compareValues(Value $currentValue, Value $newValue): bool
{
// Return true if values are identical, false if values are different.
}
}
```
```yaml
# Service declaration
App\FieldComparator\MyFieldComparator:
parent: 'CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\AbstractFieldComparator'
tags:
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'my_field_type_identifier' }
```
# Admin UI
## Access to the eZ Dataflow UI
@@ -383,10 +302,6 @@ Go to the eZ Dataflow admin UI and click on the "Oneshot" tab. Click on the "+"
Finally, click on the "Create" button.
# Rights
If a non-administrator user needs read-only access to the dataflow interface, add the `Setup / Administrate` and `eZ Dataflow / View` policies in one of their roles.
# Issues and feature requests
Please report issues and request features at https://github.com/code-rhapsodie/ezdataflow-bundle/issues.
@@ -400,3 +315,4 @@ already.
# License
This package is licensed under the [MIT license](LICENSE).

View File

@@ -41,24 +41,18 @@
}
},
"require": {
"php": "^7.3||^8.0",
"ext-json": "*",
"code-rhapsodie/dataflow-bundle": "^3.0||^4.0",
"ezsystems/ezplatform-admin-ui": "^2.3",
"ezsystems/ezplatform-kernel": "^1.3",
"http-interop/http-factory-guzzle": "^1.2"
"code-rhapsodie/dataflow-bundle": "^2.0 || dev-master",
"ezsystems/ezplatform-admin-ui": "^1.0",
"ezsystems/ezpublish-kernel": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^7||^8||^9",
"phpunit/phpunit": "^7||^8",
"doctrine/dbal": "^2.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true,
"allow-plugins": {
"php-http/discovery": true
}
"sort-packages": true
},
"extra": {
"branch-alias": {

View File

@@ -1,21 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version = '1.0' encoding = 'UTF-8'?>
<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false" bootstrap="tests/bootstrap.php" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" colors="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory>./src/</directory>
</include>
<exclude>
<directory>tests/</directory>
<directory>vendor/</directory>
</exclude>
</coverage>
<php>
<ini name="error_reporting" value="-1"/>
</php>
<testsuites>
<testsuite name="EzDataflow tests suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<phpunit
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="Tests/bootstrap.php"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
colors="false"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Port tests suite">
<directory suffix="Test.php">./Tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>Tests/</directory>
<directory>vendor/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle;
use CodeRhapsodie\EzDataflowBundle\DependencyInjection\CodeRhapsodieEzDataflowExtension;
use CodeRhapsodie\EzDataflowBundle\DependencyInjection\Compiler\FieldComparatorCompilerPass;
use CodeRhapsodie\EzDataflowBundle\Security\PolicyProvider;
use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\EzPublishCoreExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -24,8 +23,6 @@ class CodeRhapsodieEzDataflowBundle extends Bundle
{
parent::build($container);
$container->addCompilerPass(new FieldComparatorCompilerPass());
/** @var EzPublishCoreExtension $eZExtension */
$eZExtension = $container->getExtension('ezpublish');
$eZExtension->addPolicyProvider(new PolicyProvider());

View File

@@ -4,15 +4,15 @@ declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Controller;
use CodeRhapsodie\DataflowBundle\Entity\Job;
use CodeRhapsodie\DataflowBundle\Entity\ScheduledDataflow;
use CodeRhapsodie\EzDataflowBundle\Form\CreateOneshotType;
use CodeRhapsodie\EzDataflowBundle\Form\CreateScheduledType;
use CodeRhapsodie\EzDataflowBundle\Gateway\ExceptionJSONDecoderAdapter;
use CodeRhapsodie\EzDataflowBundle\Gateway\JobGateway;
use CodeRhapsodie\EzDataflowBundle\Gateway\ScheduledDataflowGateway;
use CodeRhapsodie\DataflowBundle\Entity\Job;
use CodeRhapsodie\DataflowBundle\Entity\ScheduledDataflow;
use Doctrine\DBAL\Query\QueryBuilder;
use eZ\Publish\Core\MVC\Symfony\Security\Authorization\Attribute;
use EzSystems\EzPlatformAdminUi\Notification\NotificationHandlerInterface;
use EzSystems\EzPlatformAdminUiBundle\Controller\Controller;
use Pagerfanta\Adapter\DoctrineDbalAdapter;
use Pagerfanta\Pagerfanta;
@@ -27,17 +27,22 @@ class DashboardController extends Controller
{
/** @var JobGateway */
private $jobGateway;
/** @var NotificationHandlerInterface */
private $notificationHandler;
/** @var ScheduledDataflowGateway */
private $scheduledDataflowGateway;
public function __construct(JobGateway $jobGateway, ScheduledDataflowGateway $scheduledDataflowGateway)
public function __construct(JobGateway $jobGateway, NotificationHandlerInterface $notificationHandler, ScheduledDataflowGateway $scheduledDataflowGateway)
{
$this->jobGateway = $jobGateway;
$this->notificationHandler = $notificationHandler;
$this->scheduledDataflowGateway = $scheduledDataflowGateway;
}
/**
* @Route("/", name="coderhapsodie.ezdataflow.main")
*
* @return Response
*/
public function main(): Response
{
@@ -64,6 +69,10 @@ class DashboardController extends Controller
/**
* @Route("/repeating", name="coderhapsodie.ezdataflow.repeating")
*
* @param Request $request
*
* @return Response
*/
public function getRepeatingPage(Request $request): Response
{
@@ -92,6 +101,10 @@ class DashboardController extends Controller
/**
* @Route("/oneshot", name="coderhapsodie.ezdataflow.oneshot")
*
* @param Request $request
*
* @return Response
*/
public function getOneshotPage(Request $request): Response
{
@@ -105,30 +118,35 @@ class DashboardController extends Controller
public function history(Request $request): Response
{
$this->denyAccessUnlessGranted(new Attribute('ezdataflow', 'view'));
$filter = (int) $request->query->get('filter', JobGateway::FILTER_NONE);
return $this->render('@ezdesign/ezdataflow/Dashboard/history.html.twig', [
'pager' => $this->getPager($this->jobGateway->getListQueryForAdmin($filter), $request),
'filter' => $filter,
'pager' => $this->getPager($this->jobGateway->getListQueryForAdmin(), $request),
]);
}
/**
* @Route("/history", name="coderhapsodie.ezdataflow.history")
*
* @param Request $request
*
* @return Response
*/
public function getHistoryPage(Request $request): Response
{
$this->denyAccessUnlessGranted(new Attribute('ezdataflow', 'view'));
$filter = (int) $request->query->get('filter', JobGateway::FILTER_NONE);
return $this->render('@ezdesign/ezdataflow/Dashboard/history.html.twig', [
'pager' => $this->getPager($this->jobGateway->getListQueryForAdmin($filter), $request),
'filter' => $filter,
'pager' => $this->getPager($this->jobGateway->getListQueryForAdmin(), $request),
]);
}
/**
* @Route("/history/schedule/{id}", name="coderhapsodie.ezdataflow.history.workflow")
*
* @param Request $request
* @param int $id
*
* @return Response
*/
public function getHistoryForScheduled(Request $request, int $id): Response
{
@@ -142,15 +160,10 @@ class DashboardController extends Controller
private function getPager(QueryBuilder $query, Request $request): Pagerfanta
{
$pager = new Pagerfanta(
new ExceptionJSONDecoderAdapter(
new DoctrineDbalAdapter($query, function ($queryBuilder) {
return $queryBuilder->select('COUNT(DISTINCT id) AS total_results')
->resetQueryPart('orderBy')
->setMaxResults(1);
})
)
);
$pager = new Pagerfanta(new DoctrineDbalAdapter($query, function ($queryBuilder) {
return $queryBuilder->select('COUNT(DISTINCT id) AS total_results')
->setMaxResults(1);
}));
$pager->setMaxPerPage(20);
$pager->setCurrentPage($request->query->get('page', 1));

View File

@@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @Route("/ezdataflow/job")
@@ -26,7 +26,7 @@ class JobController extends Controller
private $jobGateway;
/** @var NotificationHandlerInterface */
private $notificationHandler;
/** @var Symfony\Component\Translation\TranslatorInterface|Symfony\Contracts\Translation\TranslatorInterface */
/** @var TranslatorInterface */
private $translator;
public function __construct(
@@ -41,6 +41,10 @@ class JobController extends Controller
/**
* @Route("/details/{id}", name="coderhapsodie.ezdataflow.job.details")
*
* @param int $id
*
* @return Response
*/
public function displayDetails(int $id): Response
{
@@ -51,24 +55,12 @@ class JobController extends Controller
]);
}
/**
* @Route("/details/log/{id}", name="coderhapsodie.ezdataflow.job.log")
*/
public function displayLog(int $id): Response
{
$this->denyAccessUnlessGranted(new Attribute('ezdataflow', 'view'));
$item = $this->jobGateway->find($id);
$log = array_map(function ($line) {
return preg_replace('~#\d+~', "\n$0", $line);
}, $item->getExceptions());
return $this->render('@ezdesign/ezdataflow/Item/log.html.twig', [
'log' => $log,
]);
}
/**
* @Route("/create", name="coderhapsodie.ezdataflow.job.create", methods={"POST"})
*
* @param Request $request
*
* @return Response
*/
public function create(Request $request): Response
{

View File

@@ -4,11 +4,11 @@ declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Controller;
use CodeRhapsodie\DataflowBundle\Entity\ScheduledDataflow;
use CodeRhapsodie\EzDataflowBundle\Form\CreateScheduledType;
use CodeRhapsodie\EzDataflowBundle\Form\UpdateScheduledType;
use CodeRhapsodie\EzDataflowBundle\Gateway\JobGateway;
use CodeRhapsodie\EzDataflowBundle\Gateway\ScheduledDataflowGateway;
use CodeRhapsodie\EzDataflowBundle\Gateway\JobGateway;
use CodeRhapsodie\DataflowBundle\Entity\ScheduledDataflow;
use eZ\Publish\Core\MVC\Symfony\Security\Authorization\Attribute;
use EzSystems\EzPlatformAdminUi\Notification\NotificationHandlerInterface;
use EzSystems\EzPlatformAdminUiBundle\Controller\Controller;
@@ -16,7 +16,7 @@ use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @Route("/ezdataflow/scheduled_workflow")
@@ -32,12 +32,8 @@ class ScheduledDataflowController extends Controller
/** @var TranslatorInterface */
private $translator;
public function __construct(
JobGateway $jobGateway,
NotificationHandlerInterface $notificationHandler,
ScheduledDataflowGateway $scheduledDataflowGateway,
TranslatorInterface $translator
) {
public function __construct(JobGateway $jobGateway, NotificationHandlerInterface $notificationHandler, ScheduledDataflowGateway $scheduledDataflowGateway, TranslatorInterface $translator)
{
$this->jobGateway = $jobGateway;
$this->notificationHandler = $notificationHandler;
$this->scheduledDataflowGateway = $scheduledDataflowGateway;
@@ -46,6 +42,10 @@ class ScheduledDataflowController extends Controller
/**
* @Route("/create", name="coderhapsodie.ezdataflow.workflow.create", methods={"POST"})
*
* @param Request $request
*
* @return Response
*/
public function create(Request $request): Response
{
@@ -58,12 +58,12 @@ class ScheduledDataflowController extends Controller
if ($form->isSubmitted() && $form->isValid()) {
/** @var ScheduledDataflow $newWorkflow */
$newWorkflow = $form->getData();
try {
$this->scheduledDataflowGateway->save($newWorkflow);
$this->notificationHandler->success($this->translator->trans('coderhapsodie.ezdataflow.workflow.create.success'));
} catch (\Exception $e) {
$this->notificationHandler->error($this->translator->trans('coderhapsodie.ezdataflow.workflow.create.error',
['message' => $e->getMessage()]));
$this->notificationHandler->error($this->translator->trans('coderhapsodie.ezdataflow.workflow.create.error', ['message' => $e->getMessage()]));
}
return new JsonResponse(['redirect' => $this->generateUrl('coderhapsodie.ezdataflow.main')]);
@@ -79,6 +79,10 @@ class ScheduledDataflowController extends Controller
/**
* @Route("/{id}/delete", name="coderhapsodie.ezdataflow.workflow.delete", methods={"post"})
*
* @param int $id
*
* @return Response
*/
public function delete(int $id): Response
{
@@ -90,8 +94,7 @@ class ScheduledDataflowController extends Controller
return new JsonResponse(['code' => 200]);
} catch (\Exception $e) {
$this->notificationHandler->error($this->translator->trans('coderhapsodie.ezdataflow.workflow.delete.error',
['message' => $e->getMessage()]));
$this->notificationHandler->error($this->translator->trans('coderhapsodie.ezdataflow.workflow.delete.error', ['message' => $e->getMessage()]));
return new JsonResponse(['code' => $e->getCode()]);
}
@@ -99,6 +102,11 @@ class ScheduledDataflowController extends Controller
/**
* @Route("/{id}/edit", name="coderhapsodie.ezdataflow.workflow.edit")
*
* @param Request $request
* @param int $id
*
* @return Response
*/
public function edit(Request $request, int $id): Response
{
@@ -115,8 +123,7 @@ class ScheduledDataflowController extends Controller
$this->scheduledDataflowGateway->save($editDataflow);
$this->notificationHandler->success($this->translator->trans('coderhapsodie.ezdataflow.workflow.edit.success'));
} catch (\Exception $e) {
$this->notificationHandler->error($this->translator->trans('coderhapsodie.ezdataflow.workflow.edit.error',
['message' => $e->getMessage()]));
$this->notificationHandler->error($this->translator->trans('coderhapsodie.ezdataflow.workflow.edit.error', ['message' => $e->getMessage()]));
}
return new JsonResponse(['redirect' => $this->generateUrl('coderhapsodie.ezdataflow.main')]);
@@ -132,6 +139,10 @@ class ScheduledDataflowController extends Controller
/**
* @Route("/{id}/enable", name="coderhapsodie.ezdataflow.workflow.enable")
*
* @param int $id
*
* @return Response
*/
public function enableDataflow(int $id): Response
{
@@ -142,6 +153,22 @@ class ScheduledDataflowController extends Controller
return $this->redirectToRoute('coderhapsodie.ezdataflow.main');
}
/**
* @Route("/{id}/disable", name="coderhapsodie.ezdataflow.workflow.disable")
*
* @param int $id
*
* @return Response
*/
public function disableDataflow(int $id): Response
{
$this->denyAccessUnlessGranted(new Attribute('ezdataflow', 'edit'));
$this->changeDataflowStatus($id, false);
return $this->redirectToRoute('coderhapsodie.ezdataflow.main');
}
private function changeDataflowStatus(int $id, bool $status)
{
try {
@@ -155,16 +182,4 @@ class ScheduledDataflowController extends Controller
$this->notificationHandler->error(sprintf('An error occured : "%s".', $e->getMessage()));
}
}
/**
* @Route("/{id}/disable", name="coderhapsodie.ezdataflow.workflow.disable")
*/
public function disableDataflow(int $id): Response
{
$this->denyAccessUnlessGranted(new Attribute('ezdataflow', 'edit'));
$this->changeDataflowStatus($id, false);
return $this->redirectToRoute('coderhapsodie.ezdataflow.main');
}
}

View File

@@ -35,6 +35,10 @@ class ContentCreator implements ContentCreatorInterface
}
/**
* @param ContentCreateStructure $structure
*
* @return Content
*
* @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
* @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException
* @throws \eZ\Publish\API\Repository\Exceptions\ContentValidationException
@@ -54,21 +58,17 @@ class ContentCreator implements ContentCreatorInterface
}
/**
* @param array $locations
*
* @return LocationCreateStruct[]
*/
private function getLocationCreateStructs(array $locations): array
{
$locationCreateStructs = [];
foreach ($locations as $locationOrIdOrRemoteIdOrStruct) {
if ($locationOrIdOrRemoteIdOrStruct instanceof LocationCreateStruct) {
$locationCreateStructs[] = $locationOrIdOrRemoteIdOrStruct;
continue;
}
foreach ($locations as $locationOrIdOrRemoteId) {
$locationCreateStructs[] = new LocationCreateStruct([
'parentLocationId' => $this->matcher->matchLocation($locationOrIdOrRemoteIdOrStruct)->id,
'parentLocationId' => $this->matcher->matchLocation($locationOrIdOrRemoteId)->id,
]);
}

View File

@@ -30,6 +30,10 @@ class ContentUpdater implements ContentUpdaterInterface
}
/**
* @param ContentUpdateStructure $structure
*
* @return Content
*
* @throws NoMatchFoundException
* @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
* @throws \eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException

View File

@@ -17,6 +17,8 @@ class ContentStructFieldFiller implements ContentStructFieldFillerInterface
/**
* ContentStructFieldFiller constructor.
*
* @param iterable $fieldValueCreators
*/
public function __construct(iterable $fieldValueCreators)
{
@@ -45,7 +47,10 @@ class ContentStructFieldFiller implements ContentStructFieldFillerInterface
}
/**
* @param mixed $hash
* @param string $fieldTypeIdentifier
* @param mixed $hash
*
* @return Value
*
* @throws UnsupportedFieldTypeException
*/

View File

@@ -9,5 +9,10 @@ use eZ\Publish\API\Repository\Values\ContentType\ContentType;
interface ContentStructFieldFillerInterface
{
/**
* @param ContentType $contentType
* @param ContentStruct $contentStruct
* @param array $fieldHashes
*/
public function fillFields(ContentType $contentType, ContentStruct $contentStruct, array $fieldHashes): void;
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\Field;
use eZ\Publish\API\Repository\FieldType;
use eZ\Publish\API\Repository\FieldTypeService;
use eZ\Publish\Core\FieldType\Value;
@@ -12,6 +13,9 @@ class DefaultFieldValueCreator implements FieldValueCreatorInterface
/** @var FieldTypeService */
private $fieldTypeService;
/** @var FieldType[] */
private $fieldTypes = [];
public function __construct(FieldTypeService $fieldTypeService)
{
$this->fieldTypeService = $fieldTypeService;
@@ -24,6 +28,22 @@ class DefaultFieldValueCreator implements FieldValueCreatorInterface
public function createValue(string $fieldTypeIdentifier, $hash): Value
{
return $this->fieldTypeService->getFieldType($fieldTypeIdentifier)->fromHash($hash);
return $this->getFieldType($fieldTypeIdentifier)->fromHash($hash);
}
/**
* @param string $fieldTypeIdentifier
*
* @return FieldType
*
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
*/
private function getFieldType(string $fieldTypeIdentifier): FieldType
{
if (!isset($this->fieldTypes[$fieldTypeIdentifier])) {
$this->fieldTypes[$fieldTypeIdentifier] = $this->fieldTypeService->getFieldType($fieldTypeIdentifier);
}
return $this->fieldTypes[$fieldTypeIdentifier];
}
}

View File

@@ -8,10 +8,18 @@ use eZ\Publish\Core\FieldType\Value;
interface FieldValueCreatorInterface
{
/**
* @param string $fieldTypeIdentifier
*
* @return bool
*/
public function supports(string $fieldTypeIdentifier): bool;
/**
* @param mixed $hash
* @param string $fieldTypeIdentifier
* @param mixed $hash
*
* @return Value
*/
public function createValue(string $fieldTypeIdentifier, $hash): Value;
}

View File

@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\FieldComparator;
use eZ\Publish\API\Repository\FieldTypeService;
use eZ\Publish\API\Repository\Values\Content\Field;
use eZ\Publish\Core\FieldType\Value;
abstract class AbstractFieldComparator implements FieldComparatorInterface
{
/** @var FieldTypeService */
private $fieldTypeService;
public function __construct(FieldTypeService $fieldTypeService)
{
$this->fieldTypeService = $fieldTypeService;
}
public function compare(Field $field, $hash): bool
{
$newValue = $this->fieldTypeService->getFieldType($field->fieldTypeIdentifier)->fromHash($hash);
return $this->compareValues($field->value, $newValue);
}
/**
* Returns true if values are equals, false otherwise.
*/
abstract protected function compareValues(Value $currentValue, Value $newValue): bool;
}

View File

@@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\FieldComparator;
use eZ\Publish\API\Repository\Values\Content\Field;
class DelegatorFieldComparator implements FieldComparatorInterface
{
/** @var FieldComparatorInterface[] */
private $delegates;
/**
* FieldComparator constructor.
*/
public function __construct()
{
$this->delegates = [];
}
public function compare(Field $field, $hash): bool
{
if (isset($this->delegates[$field->fieldTypeIdentifier])) {
return $this->delegates[$field->fieldTypeIdentifier]->compare($field, $hash);
}
// No comparator to handle this field type, we assume the value is different.
return false;
}
public function registerDelegateFieldComparator(FieldComparatorInterface $typedFieldComparator, string $fieldTypeIdentifier): void
{
$this->delegates[$fieldTypeIdentifier] = $typedFieldComparator;
}
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\FieldComparator;
use eZ\Publish\API\Repository\Values\Content\Field;
interface FieldComparatorInterface
{
/**
* @return bool true if identical, false otherwise
*/
public function compare(Field $field, $hash): bool;
}

View File

@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\FieldComparator;
use eZ\Publish\Core\FieldType\Value;
class MapLocationFieldComparator extends AbstractFieldComparator
{
protected function compareValues(Value $currentValue, Value $newValue): bool
{
return (string) $currentValue === (string) $newValue
&& (float) $currentValue->longitude === (float) $newValue->longitude
&& (float) $currentValue->latitude === (float) $newValue->latitude
;
}
}

View File

@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\FieldComparator;
use eZ\Publish\Core\FieldType\Value;
class MatrixFieldComparator extends AbstractFieldComparator
{
protected function compareValues(Value $currentValue, Value $newValue): bool
{
if (count($currentValue->rows) !== count($newValue->rows)) {
return false;
}
foreach ($newValue->rows as $index => $row) {
if (count($currentValue->rows[$index]->getCells()) !== count($row->getCells())) {
return false;
}
if (!empty(array_diff_assoc($currentValue->rows[$index]->getCells(), $row->getCells()))) {
return false;
}
}
return true;
}
}

View File

@@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\FieldComparator;
use eZ\Publish\Core\FieldType\Value;
class NovaSEOMetasFieldComparator extends AbstractFieldComparator
{
protected function compareValues(Value $currentValue, Value $newValue): bool
{
$map = [];
foreach ($currentValue->metas as $meta) {
$map[$meta->getName()] = $meta->getContent();
}
foreach ($newValue->metas as $meta) {
if (!isset($map[$meta->getName()]) || $map[$meta->getName()] !== $meta->getContent()) {
return false;
}
}
return count($currentValue->metas) === count($newValue->metas);
}
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\FieldComparator;
use eZ\Publish\Core\FieldType\Value;
class SimpleFieldComparator extends AbstractFieldComparator
{
protected function compareValues(Value $currentValue, Value $newValue): bool
{
return (string) $currentValue === (string) $newValue;
}
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Core\FieldComparator;
use eZ\Publish\Core\FieldType\Value;
class UrlFieldComparator extends AbstractFieldComparator
{
protected function compareValues(Value $currentValue, Value $newValue): bool
{
return $currentValue->link === $newValue->link && $currentValue->text === $newValue->text;
}
}

View File

@@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\DependencyInjection\Compiler;
use CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\DelegatorFieldComparator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class FieldComparatorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has(DelegatorFieldComparator::class)) {
return;
}
$delegatorDef = $container->findDefinition(DelegatorFieldComparator::class);
foreach ($container->findTaggedServiceIds('coderhapsodie.ezdataflow.field_comparator') as $id => $tags) {
foreach ($tags as $attributes) {
if (!isset($attributes['fieldType'])) {
throw new \InvalidArgumentException(sprintf('Service "%s" must define the "fieldType" attribute on "coderhapsodie.ezdataflow.field_comparator" tags.', $id));
}
$delegatorDef->addMethodCall(
'registerDelegateFieldComparator',
[new Reference($id), $attributes['fieldType']]
);
}
}
}
}

View File

@@ -9,6 +9,8 @@ class InvalidArgumentTypeException extends \Exception
/**
* @param string|array $expectedTypes
* @param mixed $received
*
* @return InvalidArgumentTypeException
*/
public static function create($expectedTypes, $received): self
{

View File

@@ -6,6 +6,12 @@ namespace CodeRhapsodie\EzDataflowBundle\Exception;
class UnknownFieldException extends \Exception
{
/**
* @param string $fieldIdentifier
* @param string $contentTypeIdentifier
*
* @return UnknownFieldException
*/
public static function create(string $fieldIdentifier, string $contentTypeIdentifier): self
{
return new self(sprintf(

View File

@@ -19,6 +19,8 @@ final class ContentStructureFactory implements ContentStructureFactoryInterface
/**
* ContentStructureFactory constructor.
*
* @param ContentService $contentService
*/
public function __construct(ContentService $contentService)
{
@@ -26,8 +28,12 @@ final class ContentStructureFactory implements ContentStructureFactoryInterface
}
/**
* @param mixed $parentLocations
* @param int $mode One of the constant ContentStructureFactoryInterface::MODE_*
* @param array $data
* @param string $remoteId
* @param string $language
* @param string $contentType
* @param mixed $parentLocations
* @param int $mode One of the constant ContentStructureFactoryInterface::MODE_*
*
* @return false|ContentStructure
*

View File

@@ -11,6 +11,10 @@ interface ContentStructureFactoryInterface
public const MODE_UPDATE_ONLY = 3;
/**
* @param array $data
* @param string $remoteId
* @param string $language
* @param string $contentType
* @param int|string $parentLocations Int for location id or string for remote location id
* @param int $mode ContentStructureFactoryInterface
*

View File

@@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Filter;
use CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\FieldComparatorInterface;
use CodeRhapsodie\EzDataflowBundle\Model\ContentUpdateStructure;
use eZ\Publish\API\Repository\ContentService;
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
use Psr\Log\LoggerAwareTrait;
/**
* Filters ContentUpdateStructure that would not result in any actual changes in the content.
*/
class NotModifiedContentFilter
{
use LoggerAwareTrait;
/** @var ContentService */
private $contentService;
/** @var FieldComparatorInterface */
private $comparator;
public function __construct(ContentService $contentService, FieldComparatorInterface $comparator)
{
$this->contentService = $contentService;
$this->comparator = $comparator;
}
public function __invoke($data)
{
if (!$data instanceof ContentUpdateStructure) {
return $data;
}
if ($data->getId()) {
$content = $this->contentService->loadContent($data->getId(), [$data->getLanguageCode()]);
} else {
try {
$content = $this->contentService->loadContentByRemoteId($data->getRemoteId(), [$data->getLanguageCode()]);
} catch (NotFoundException $e) {
// New translation
return $data;
}
}
foreach ($data->getFields() as $identifier => $hash) {
$field = $content->getField($identifier, $data->getLanguageCode());
if (null === $field || !$this->comparator->compare($field, $hash)) {
// At least one field is different, continue the dataflow.
return $data;
}
}
// All fields are identical, filter this item out.
$this->log('info', 'Not modified content skipped', ['id' => $data->getId(), 'remote_id' => $data->getRemoteId()]);
return false;
}
private function log(string $level, string $message, array $context = [])
{
if (null === $this->logger) {
return;
}
$this->logger->log($level, $message, $context);
}
}

View File

@@ -6,6 +6,7 @@ namespace CodeRhapsodie\EzDataflowBundle\Form;
use CodeRhapsodie\DataflowBundle\Entity\Job;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -29,7 +30,7 @@ class CreateOneshotType extends AbstractType
'placeholder' => 'coderhapsodie.dataflow.options.placeholder',
],
])
->add('requestedDate', UserTimezoneAwareDateTimeType::class, [
->add('requestedDate', DateTimeType::class, [
'label' => 'coderhapsodie.dataflow.requestedDate',
'years' => range(date('Y'), date('Y') + 5),
])

View File

@@ -7,6 +7,7 @@ namespace CodeRhapsodie\EzDataflowBundle\Form;
use CodeRhapsodie\DataflowBundle\Entity\ScheduledDataflow;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -37,7 +38,7 @@ class CreateScheduledType extends AbstractType
],
'label' => 'coderhapsodie.dataflow.frequency',
])
->add('next', UserTimezoneAwareDateTimeType::class, [
->add('next', DateTimeType::class, [
'years' => range(date('Y'), date('Y') + 5),
'label' => 'coderhapsodie.dataflow.create.next',
])

View File

@@ -6,6 +6,7 @@ namespace CodeRhapsodie\EzDataflowBundle\Form;
use CodeRhapsodie\DataflowBundle\Entity\ScheduledDataflow;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -31,7 +32,7 @@ class UpdateScheduledType extends AbstractType
],
'label' => 'coderhapsodie.dataflow.frequency',
])
->add('next', UserTimezoneAwareDateTimeType::class, [
->add('next', DateTimeType::class, [
'years' => range(date('Y'), date('Y') + 5),
'label' => 'coderhapsodie.dataflow.update.next',
])

View File

@@ -1,51 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Form;
use eZ\Publish\API\Repository\Exceptions\NotFoundException;
use eZ\Publish\API\Repository\UserPreferenceService;
use Symfony\Component\Form\DataTransformerInterface;
class UserTimezoneAwareDateTimeTransformer implements DataTransformerInterface
{
/** @var UserPreferenceService */
private $userPreferenceService;
public function __construct(UserPreferenceService $userPreferenceService)
{
$this->userPreferenceService = $userPreferenceService;
}
public function transform($value)
{
if (!$value instanceof \DateTimeInterface) {
return $value;
}
return (new \DateTime('now', $this->userTimezone()))->setTimestamp($value->getTimestamp());
}
public function reverseTransform($value)
{
if (!$value instanceof \DateTimeInterface) {
return $value;
}
$dateTimeWithUserTimeZone = new \DateTime($value->format('Y-m-d H:i:s'), $this->userTimezone());
return (new \DateTime())->setTimestamp($dateTimeWithUserTimeZone->getTimestamp());
}
private function userTimezone(): \DateTimeZone
{
try {
$tz = $this->userPreferenceService->getUserPreference('timezone')->value ?? 'UTC';
} catch (NotFoundException $e) {
$tz = 'UTC';
}
return new \DateTimeZone($tz);
}
}

View File

@@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Form;
use eZ\Publish\API\Repository\UserPreferenceService;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\FormBuilderInterface;
class UserTimezoneAwareDateTimeType extends AbstractType
{
/** @var UserPreferenceService */
private $userPreferenceService;
public function __construct(UserPreferenceService $userPreferenceService)
{
$this->userPreferenceService = $userPreferenceService;
}
public function getParent()
{
return DateTimeType::class;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new UserTimezoneAwareDateTimeTransformer($this->userPreferenceService));
}
}

View File

@@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Gateway;
use Pagerfanta\Adapter\AdapterInterface;
class ExceptionJSONDecoderAdapter implements AdapterInterface
{
/** @var AdapterInterface */
private $adapter;
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
public function getNbResults()
{
return $this->adapter->getNbResults();
}
public function getSlice($offset, $length)
{
$slice = $this->adapter->getSlice($offset, $length);
array_walk($slice, static function (&$value) {
if (isset($value['exceptions'])) {
$value['exceptions'] = json_decode($value['exceptions'], true, 512, JSON_THROW_ON_ERROR);
}
});
return $slice;
}
}

View File

@@ -10,9 +10,6 @@ use Doctrine\DBAL\Query\QueryBuilder;
final class JobGateway
{
public const FILTER_NONE = 0;
public const FILTER_NON_EMPTY = 1;
/** @var JobRepository */
private $jobRepository;
@@ -33,17 +30,10 @@ final class JobGateway
->addOrderBy('i.requested_date', 'DESC');
}
public function getListQueryForAdmin(int $filter): QueryBuilder
public function getListQueryForAdmin(): QueryBuilder
{
$qb = $this->jobRepository->createQueryBuilder('w')
->addOrderBy('w.requested_date', 'DESC')
;
if (self::FILTER_NON_EMPTY === $filter) {
$qb->andWhere('w.count > 0');
}
return $qb;
return $this->jobRepository->createQueryBuilder('w')
->addOrderBy('w.requested_date', 'DESC');
}
public function getListQueryForScheduleAdmin(int $id): QueryBuilder
@@ -54,6 +44,9 @@ final class JobGateway
->addOrderBy('w.requested_date', 'DESC');
}
/**
* @param Job $job
*/
public function save(Job $job)
{
$this->jobRepository->save($job);

View File

@@ -29,12 +29,17 @@ final class ScheduledDataflowGateway
->addOrderBy('s.label', 'ASC');
}
/**
* @param ScheduledDataflow $scheduledDataflow
*/
public function save(ScheduledDataflow $scheduledDataflow)
{
$this->scheduledDataflowRepository->save($scheduledDataflow);
}
/**
* @param int $id
*
* @throws \Throwable
*/
public function delete(int $id): void

View File

@@ -22,6 +22,8 @@ class LocationMatcher implements LocationMatcherInterface
/**
* @param mixed $valueToMatch
*
* @return Location
*
* @throws NoMatchFoundException
* @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
*/

View File

@@ -10,6 +10,8 @@ interface LocationMatcherInterface
{
/**
* @param mixed $valueToMatch
*
* @return Location
*/
public function matchLocation($valueToMatch): Location;
}

View File

@@ -6,7 +6,6 @@ namespace CodeRhapsodie\EzDataflowBundle\Model;
use CodeRhapsodie\EzDataflowBundle\Exception\InvalidArgumentTypeException;
use eZ\Publish\API\Repository\Values\Content\Location;
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
class ContentCreateStructure extends ContentStructure
{
@@ -19,14 +18,17 @@ class ContentCreateStructure extends ContentStructure
/**
* ContentCreateStructure constructor.
*
* @param array $locations
* A location can be any of the following:
* <ul>
* <li>an integer, the id of the Location object</li>
* <li>a string, the remote id of the Location object</li>
* <li>a Location object</li>
* <li>a LocationCreateStruct object</li>
* </ul>
* @param string $contentTypeIdentifier
* @param string $languageCode
* @param array $locations
* A location can be any of the following:
* <ul>
* <li>an integer, the id of the Location object</li>
* <li>a string, the remote id of the Location object</li>
* <li>a Location object</li>
* </ul>
* @param array $fields
* @param string|null $remoteId
*
* @throws InvalidArgumentTypeException
*/
@@ -39,28 +41,35 @@ class ContentCreateStructure extends ContentStructure
$this->remoteId = $remoteId;
}
/**
* @return string
*/
public function getContentTypeIdentifier(): string
{
return $this->contentTypeIdentifier;
}
/**
* @return array
*/
public function getLocations(): array
{
return $this->locations;
}
/**
* @param array $locations
*
* @throws InvalidArgumentTypeException
*/
private function setLocations(array $locations)
{
foreach ($locations as $locationOrIdOrRemoteIdOrStruct) {
if (!is_int($locationOrIdOrRemoteIdOrStruct)
&& !is_string($locationOrIdOrRemoteIdOrStruct)
&& !$locationOrIdOrRemoteIdOrStruct instanceof Location
&& !$locationOrIdOrRemoteIdOrStruct instanceof LocationCreateStruct
foreach ($locations as $locationOrIdOrRemoteId) {
if (!is_int($locationOrIdOrRemoteId)
&& !is_string($locationOrIdOrRemoteId)
&& !$locationOrIdOrRemoteId instanceof Location
) {
throw InvalidArgumentTypeException::create(['int', 'string', Location::class, LocationCreateStruct::class], $locationOrIdOrRemoteIdOrStruct);
throw InvalidArgumentTypeException::create(['int', 'string', Location::class], $locationOrIdOrRemoteId);
}
}

View File

@@ -15,16 +15,25 @@ abstract class ContentStructure
/** @var array */
protected $fields;
/**
* @return string|null
*/
public function getRemoteId(): ?string
{
return $this->remoteId;
}
/**
* @return string
*/
public function getLanguageCode(): string
{
return $this->languageCode;
}
/**
* @return array
*/
public function getFields(): array
{
return $this->fields;

View File

@@ -15,6 +15,13 @@ class ContentUpdateStructure extends ContentStructure
$this->fields = $fields;
}
/**
* @param int $id
* @param string $languageCode
* @param array $fields
*
* @return ContentUpdateStructure
*/
public static function createForContentId(int $id, string $languageCode, array $fields): self
{
$struct = new self($languageCode, $fields);
@@ -23,6 +30,13 @@ class ContentUpdateStructure extends ContentStructure
return $struct;
}
/**
* @param string $remoteId
* @param string $languageCode
* @param array $fields
*
* @return ContentUpdateStructure
*/
public static function createForContentRemoteId(string $remoteId, string $languageCode, array $fields): self
{
$struct = new self($languageCode, $fields);
@@ -31,6 +45,9 @@ class ContentUpdateStructure extends ContentStructure
return $struct;
}
/**
* @return int|null
*/
public function getId(): ?int
{
return $this->id;

View File

@@ -1,6 +1,3 @@
imports:
- { resource: services/comparators.yaml }
services:
_defaults:
public: false
@@ -9,39 +6,25 @@ services:
CodeRhapsodie\EzDataflowBundle\Controller\DashboardController:
public: true
tags:
- { name: controller.service_arguments }
arguments:
$jobGateway: '@CodeRhapsodie\EzDataflowBundle\Gateway\JobGateway'
$notificationHandler: '@EzSystems\EzPlatformAdminUi\Notification\NotificationHandlerInterface'
$scheduledDataflowGateway: '@CodeRhapsodie\EzDataflowBundle\Gateway\ScheduledDataflowGateway'
calls:
- ['setContainer', ['@service_container']]
- ['performAccessCheck', []]
CodeRhapsodie\EzDataflowBundle\Controller\ScheduledDataflowController:
public: true
tags:
- { name: controller.service_arguments }
arguments:
$jobGateway: '@CodeRhapsodie\EzDataflowBundle\Gateway\JobGateway'
$notificationHandler: '@EzSystems\EzPlatformAdminUi\Notification\NotificationHandlerInterface'
$scheduledDataflowGateway: '@CodeRhapsodie\EzDataflowBundle\Gateway\ScheduledDataflowGateway'
$translator: '@translator'
calls:
- [ 'setContainer', [ '@service_container' ] ]
- [ 'performAccessCheck', [ ] ]
CodeRhapsodie\EzDataflowBundle\Controller\JobController:
public: true
tags:
- { name: controller.service_arguments }
arguments:
$jobGateway: '@CodeRhapsodie\EzDataflowBundle\Gateway\JobGateway'
$notificationHandler: '@EzSystems\EzPlatformAdminUi\Notification\NotificationHandlerInterface'
$translator: '@translator'
calls:
- [ 'setContainer', [ '@service_container' ] ]
- [ 'performAccessCheck', [ ] ]
CodeRhapsodie\EzDataflowBundle\Writer\RepositoryWriter:
abstract: true
@@ -116,11 +99,6 @@ services:
CodeRhapsodie\EzDataflowBundle\Form\CreateOneshotType:
tags: ['form.type']
CodeRhapsodie\EzDataflowBundle\Form\UserTimezoneAwareDateTimeType:
arguments:
$userPreferenceService: '@eZ\Publish\API\Repository\UserPreferenceService'
tags: ['form.type']
CodeRhapsodie\EzDataflowBundle\Gateway\ScheduledDataflowGateway:
arguments:
$scheduledDataflowRepository: '@CodeRhapsodie\DataflowBundle\Repository\ScheduledDataflowRepository'
@@ -132,48 +110,17 @@ services:
CodeRhapsodie\EzDataflowBundle\Tab\RepeatingTab:
parent: EzSystems\EzPlatformAdminUi\Tab\AbstractTab
public: false
arguments:
$httpKernelRuntime: '@twig.runtime.httpkernel'
tags:
- {name: ezplatform.tab, group: coderhapsodie-ezdataflow}
CodeRhapsodie\EzDataflowBundle\Tab\OneshotTab:
parent: EzSystems\EzPlatformAdminUi\Tab\AbstractTab
public: false
arguments:
$httpKernelRuntime: '@twig.runtime.httpkernel'
tags:
- {name: ezplatform.tab, group: coderhapsodie-ezdataflow}
CodeRhapsodie\EzDataflowBundle\Tab\HistoryTab:
parent: EzSystems\EzPlatformAdminUi\Tab\AbstractTab
public: false
arguments:
$httpKernelRuntime: '@twig.runtime.httpkernel'
tags:
- {name: ezplatform.tab, group: coderhapsodie-ezdataflow}
CodeRhapsodie\EzDataflowBundle\Filter\NotModifiedContentFilter:
arguments:
$contentService: '@eZ\Publish\API\Repository\ContentService'
$comparator: '@CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\FieldComparatorInterface'
CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\FieldComparatorInterface: '@CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\DelegatorFieldComparator'
CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\DelegatorFieldComparator:
CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\AbstractFieldComparator:
arguments:
$fieldTypeService: '@eZ\Publish\API\Repository\FieldTypeService'
abstract: true
_cr.admin_tabs.ezdataflow_group:
parent: EzSystems\EzPlatformAdminUi\Component\TabsComponent
autowire: true
autoconfigure: false
public: false
arguments:
$template: '@@ezdesign/ezdataflow/parts/tab/ezdataflow.html.twig'
$groupIdentifier: 'coderhapsodie-ezdataflow'
tags:
- { name: ezplatform.admin_ui.component, group: 'coderhapsodie-ezdataflow' }

View File

@@ -1,42 +0,0 @@
services:
CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\SimpleFieldComparator:
parent: 'CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\AbstractFieldComparator'
tags:
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezauthor' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezboolean' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezcountry' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezdate' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezdatetime' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezemail' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezfloat' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezinteger' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezisbn' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezkeyword' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezobjectrelation' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezobjectrelationlist' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezrichtext' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezselection' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'eztext' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezstring' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'eztime' }
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'eztags' }
CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\UrlFieldComparator:
parent: 'CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\AbstractFieldComparator'
tags:
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezurl' }
CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\NovaSEOMetasFieldComparator:
parent: 'CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\AbstractFieldComparator'
tags:
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'novaseometas' }
CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\MatrixFieldComparator:
parent: 'CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\AbstractFieldComparator'
tags:
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezmatrix' }
CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\MapLocationFieldComparator:
parent: 'CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\AbstractFieldComparator'
tags:
- { name: 'coderhapsodie.ezdataflow.field_comparator', fieldType: 'ezgmaplocation' }

View File

@@ -20,16 +20,11 @@ coderhapsodie.ezdataflow.history.title: History
coderhapsodie.ezdataflow.history.list.title: 'Executions list'
coderhapsodie.ezdataflow.history.list.name: Name
coderhapsodie.ezdataflow.history.list.request: 'Requested on'
coderhapsodie.ezdataflow.history.list.count: 'Number of successes'
coderhapsodie.ezdataflow.history.list.errors: 'Number of errors'
coderhapsodie.ezdataflow.history.list.count: 'Items count'
coderhapsodie.ezdataflow.history.list.start: 'Started on'
coderhapsodie.ezdataflow.history.list.end: 'Finished on'
coderhapsodie.ezdataflow.history.list.view: 'View details'
coderhapsodie.ezdataflow.history.list.status: Status
coderhapsodie.ezdataflow.history.filter.label: Filter results
coderhapsodie.ezdataflow.history.filter.none: All results
coderhapsodie.ezdataflow.history.filter.non_empty_only: Only non empty
coderhapsodie.ezdataflow.history.filter.with_error_only: Only with errors
coderhapsodie.ezdataflow.job.status.pending: Pending
coderhapsodie.ezdataflow.job.status.running: Running
coderhapsodie.ezdataflow.job.status.complete: Completed
@@ -41,18 +36,16 @@ coderhapsodie.ezdataflow.history.details.request: 'Requested on'
coderhapsodie.ezdataflow.history.details.status: Status
coderhapsodie.ezdataflow.history.details.start: 'Started on'
coderhapsodie.ezdataflow.history.details.end: 'Finished on'
coderhapsodie.ezdataflow.history.details.count: 'Items successfully processed'
coderhapsodie.ezdataflow.history.details.count: 'Items count'
coderhapsodie.ezdataflow.history.details.options: 'Run options'
coderhapsodie.ezdataflow.history.details.errors: Errors
coderhapsodie.ezdataflow.history.details.type: 'Name of the dataflow executed'
coderhapsodie.ezdataflow.history.details.log: 'View log'
coderhapsodie.ezdataflow.workflow.repeating.new.title: 'Add a new repeating dataflow'
coderhapsodie.ezdataflow.workflow.new.cancel: Cancel
coderhapsodie.ezdataflow.workflow.new.submit: Create
coderhapsodie.ezdataflow.history.list.empty: 'No execution yet.'
coderhapsodie.ezdataflow.workflow.list.empty: 'No repeating workflow configured yet'
coderhapsodie.ezdataflow.workflow.history.title: 'Execution history'
coderhapsodie.ezdataflow.workflow.log.title: 'Execution log'
coderhapsodie.ezdataflow.workflow.list.delete: Delete
coderhapsodie.ezdataflow.workflow.delete: 'Are you sure you want to delete this dataflow schedule?'
coderhapsodie.ezdataflow.workflow.create.success: 'Dataflow schedule successfully added.'

View File

@@ -20,8 +20,7 @@ coderhapsodie.ezdataflow.history.title: Historique
coderhapsodie.ezdataflow.history.list.title: 'Liste des exécutions'
coderhapsodie.ezdataflow.history.list.name: Nom
coderhapsodie.ezdataflow.history.list.request: 'Demandé le'
coderhapsodie.ezdataflow.history.list.count: 'Nombre de succès'
coderhapsodie.ezdataflow.history.list.errors: 'Nombre d''erreurs'
coderhapsodie.ezdataflow.history.list.count: 'Nombre d''objets'
coderhapsodie.ezdataflow.history.list.start: 'Commencé le'
coderhapsodie.ezdataflow.history.list.end: 'Terminé le'
coderhapsodie.ezdataflow.history.list.view: 'Voir le détail'
@@ -41,26 +40,24 @@ coderhapsodie.ezdataflow.history.details.count: 'Nombre d''objets mis à jour'
coderhapsodie.ezdataflow.history.details.options: 'Options de lancement'
coderhapsodie.ezdataflow.history.details.errors: Erreurs
coderhapsodie.ezdataflow.history.details.type: 'Nom du dataflow exécuté'
coderhapsodie.ezdataflow.history.details.log: 'Voir le log'
coderhapsodie.ezdataflow.workflow.repeating.new.title: 'Nouvel programmation d''un dataflow récurrent'
coderhapsodie.ezdataflow.workflow.new.cancel: Annuler
coderhapsodie.ezdataflow.workflow.new.submit: Créer
coderhapsodie.ezdataflow.history.list.empty: 'Aucune exécution pour le moment.'
coderhapsodie.ezdataflow.workflow.list.empty: 'Aucun dataflow n''a été programmé.'
coderhapsodie.ezdataflow.workflow.history.title: 'Historique des exécutions'
coderhapsodie.ezdataflow.workflow.log.title: 'Log de l''exécution'
coderhapsodie.ezdataflow.workflow.list.delete: Supprimer
coderhapsodie.ezdataflow.workflow.delete: 'Êtes-vous sûr de vouloir supprimer ce dataflow ?'
coderhapsodie.ezdataflow.workflow.create.success: 'La programmation du dataflow a bien été ajoutée.'
coderhapsodie.ezdataflow.workflow.create.error: 'Une erreur est survenue lors de l''ajout de la programmation du dataflow : "%message%".'
coderhapsodie.ezdataflow.workflow.delete.success: 'La programmation du dataflow a bien été supprimée.'
coderhapsodie.ezdataflow.workflow.create.error: 'Une erreur est survenue lors de l''ajout de la programamtion du dataflow : "%message%".'
coderhapsodie.ezdataflow.workflow.delete.success: 'La programmation du dataflow a bien été supprimé.'
coderhapsodie.ezdataflow.workflow.delete.error: 'Une erreur est survenue lors de la suppression de la programmation du dataflow : "%message%".'
coderhapsodie.ezdataflow.workflow.oneshot.new.title: 'Nouvelle exécution ponctuelle'
coderhapsodie.ezdataflow.job.create.success: 'Votre exécution a bien été ajoutée.'
coderhapsodie.ezdataflow.job.create.error: 'Une erreur est survenue lors de l''ajout de l''exécution : "%message%".'
coderhapsodie.dataflow.label: 'Nom de la programmation du dataflow'
coderhapsodie.dataflow.label: 'Nom de la programamtion du dataflow'
coderhapsodie.dataflow.oneshot.label: 'Nom de l''exécution ponctuelle du dataflow'
coderhapsodie.dataflow.dataflowType: 'Dataflow à exécuter'
coderhapsodie.dataflow.dataflowType: 'Dataflow a exécuter'
coderhapsodie.dataflow.options: 'Options passées au dataflow (YAML)'
coderhapsodie.dataflow.options.title: 'Entrez les options comme un tableau clé/valeur YAML'
coderhapsodie.dataflow.options.placeholder: "option1: valeur1\noption2: valeur2\n"
@@ -73,6 +70,6 @@ coderhapsodie.dataflow.create.enabled: 'Activé ?'
coderhapsodie.ezdataflow.workflow.repeating.edit.title: "Édition de la programmation d'un dataflow"
coderhapsodie.ezdataflow.workflow.edit.submit: Sauvegarder
coderhapsodie.dataflow.update.next: 'Prochaine exécution'
coderhapsodie.ezdataflow.workflow.edit.success: 'La programmation du dataflow a été mise à jour avec succès.'
coderhapsodie.ezdataflow.workflow.edit.success: 'La programmation du dataflow a été mis à jour avec succès.'
coderhapsodie.ezdataflow.workflow.edit.error: 'Une erreur est survenue lors de la modification de la programmation du dataflow : "%message%".'
coderhapsodie.ezdataflow.notfound: 'Les données demandées sont introuvables'

View File

@@ -2,34 +2,6 @@
<div class="ez-table-header">
<div class="ez-table-header__headline">{{ 'coderhapsodie.ezdataflow.history.list.title'|trans }}</div>
<div class="ez-table-header__tools">
<label for="ezdataflow_history_filter">{{ 'coderhapsodie.ezdataflow.history.filter.label'|trans }}</label>
<select id="ezdataflow_history_filter" name="filter">
<option value="0" {{ filter == 0 ? 'selected="selected"' }}>{{ 'coderhapsodie.ezdataflow.history.filter.none'|trans }}</option>
<option value="1" {{ filter == 1 ? 'selected="selected"' }}>{{ 'coderhapsodie.ezdataflow.history.filter.non_empty_only'|trans }}</option>
</select>
</div>
</div>
{% include '@ezdesign/ezdataflow/parts/tab/job_list.html.twig' with {
identifier: 'ezdataflow_history_results',
paginate_route: 'coderhapsodie.ezdataflow.history',
paginate_params: {'filter': filter}
} %}
<script>
(function ($) {
$(document).ready(function ($) {
// Manage ajax pagination
$('#ezdataflow_history_filter').change(function (e) {
e.preventDefault();
$('#loading_ezdataflow_history_results').removeClass('d-none');
$('#ezdataflow_history_results')
.html('')
.load('{{ path('coderhapsodie.ezdataflow.history') }}?filter=' + this.value + ' #ezdataflow_history_results>*', null, function () {
$('#loading_ezdataflow_history_results').addClass('d-none');
});
});
});
})(jQuery);
</script>
{% include '@ezdesign/ezdataflow/parts/tab/job_list.html.twig' with {identifier: 'ezdataflow_history_results', paginate_route: 'coderhapsodie.ezdataflow.history'} %}

View File

@@ -1,23 +1,21 @@
{% extends ["@ezdesign/layout.html.twig", "@ezdesign/ui/layout.html.twig"] %}
{% block body_class %}ez-has-full-width-footer{% endblock %}
{% extends "@ezdesign/layout.html.twig" %}
{% block breadcrumbs %}
{% include ['@ezdesign/parts/breadcrumbs.html.twig', '@ezdesign/ui/breadcrumbs.html.twig'] with { items: [
{% include '@ezdesign/parts/breadcrumbs.html.twig' with { items: [
{ value: 'breadcrumb.admin'|trans(domain='messages')|desc('Admin') },
{ value: 'coderhapsodie.ezdataflow'|trans|desc('EzDataflow') }
]} %}
{% endblock %}
{% block page_title %}
{% include ['@ezdesign/parts/page_title.html.twig', '@ezdesign/ui/page_title.html.twig'] with {
{% include '@ezdesign/parts/page_title.html.twig' with {
title: 'coderhapsodie.ezdataflow'|trans|desc('EzDataflow'),
iconName: 'workflow'
} %}
{% endblock %}
{% block content %}
{{ ez_render_component_group('coderhapsodie-ezdataflow', {'filter': app.request.query.get('filter', 0)}, '@ezdesign/ezdataflow/parts/tab/ezdataflow.html.twig') }}
{{ ez_platform_tabs('coderhapsodie-ezdataflow', {}, '@ezdesign/ezdataflow/parts/tab/ezdataflow.html.twig') }}
<div class="modal fade ez-modal show" id="ez-modal--history-details" tabindex="-1" role="dialog" aria-modal="true">
<div class="modal-dialog" role="document" style="max-width: 90%">
@@ -44,10 +42,6 @@
$('.history-details-aware').delegate('.modal-history-details', 'click', function (e) {
e.preventDefault();
$('#modal_content-details').html('');
$('#ez-modal--history-details h3').html("{{ 'coderhapsodie.ezdataflow.workflow.history.title'|trans }}");
if ($(this).hasClass('modal-history-log')) {
$('#ez-modal--history-details h3').html("{{ 'coderhapsodie.ezdataflow.workflow.log.title'|trans }}");
}
$('#ez-modal--history-details').modal('show');
$.ajax(this.href, {
success: function (result) {

View File

@@ -40,10 +40,9 @@
success: function (result) {
if (result.redirect) {
if (window.location.href === result.redirect) {
window.location.reload();
document.location.reload();
}
window.location = result.redirect;
window.location.reload();
return;
}

View File

@@ -125,10 +125,9 @@
success: function (result) {
if (result.redirect) {
if (window.location.href === result.redirect) {
window.location.reload();
document.location.reload();
}
window.location = result.redirect;
window.location.reload();
return;
}

View File

@@ -1,7 +1,8 @@
{% import '@ezdesign/ezdataflow/macros.twig' as macros %}
{% block content %}
<div class="container ez-main-container history-details-aware">
<div class="container ez-main-container">
{% if item is not null %}
<h2>{{ 'coderhapsodie.ezdataflow.history.job.title'|trans }}{{ item.id }}</h2>
@@ -20,7 +21,7 @@
</tr>
<tr>
<td>{{ 'coderhapsodie.ezdataflow.history.details.request'|trans }}</td>
<td>{{ date(item.requestedDate)|ez_short_datetime }}</td>
<td>{{ item.requestedDate|date('d/m/Y H:i:s') }}</td>
</tr>
<tr>
<td>{{ 'coderhapsodie.ezdataflow.history.details.status'|trans }}</td>
@@ -28,11 +29,11 @@
</tr>
<tr>
<td>{{ 'coderhapsodie.ezdataflow.history.details.start'|trans }}</td>
<td>{{ item.startTime ? date(item.startTime)|ez_short_datetime : '—' }}</td>
<td>{{ item.startTime|date('d/m/Y H:i:s') }}</td>
</tr>
<tr>
<td>{{ 'coderhapsodie.ezdataflow.history.details.end'|trans }}</td>
<td>{{ item.endTime ? date(item.endTime)|ez_short_datetime : '—' }}</td>
<td>{{ item.endTime|date('d/m/Y H:i:s') }}</td>
</tr>
<tr>
<td>{{ 'coderhapsodie.ezdataflow.history.details.count'|trans }}</td>

View File

@@ -1,3 +0,0 @@
{% for line in log %}
<p>{{ line|nl2br }}</p>
{% endfor %}

View File

@@ -17,7 +17,6 @@
<th>{{ 'coderhapsodie.ezdataflow.history.list.name'|trans }}</th>
<th>{{ 'coderhapsodie.ezdataflow.history.list.request'|trans }}</th>
<th>{{ 'coderhapsodie.ezdataflow.history.list.count'|trans }}</th>
<th>{{ 'coderhapsodie.ezdataflow.history.list.errors'|trans }}</th>
<th>{{ 'coderhapsodie.ezdataflow.history.list.start'|trans }}</th>
<th>{{ 'coderhapsodie.ezdataflow.history.list.end'|trans }}</th>
<th>{{ 'coderhapsodie.ezdataflow.history.list.status'|trans }}</th>
@@ -28,11 +27,10 @@
{% for job in pager.currentPageResults %}
<tr>
<td>{{ job.label }}</td>
<td>{{ date(job.requested_date)|ez_short_datetime }}</td>
<td>{{ job.count|default('') }}</td>
<td>{{ job.exceptions|length }}</td>
<td>{{ job.start_time ? date(job.start_time)|ez_short_datetime : '' }}</td>
<td>{{ job.end_time ? date(job.end_time)|ez_short_datetime : '—' }}</td>
<td>{{ job.requested_date|date('d/m/Y H:i:s') }}</td>
<td>{{ job.count|default('-') }}</td>
<td>{{ job.start_time ? job.start_time|date('d/m/Y H:i:s') : '-' }}</td>
<td>{{ job.end_time ? job.end_time|date('d/m/Y H:i:s') : '-' }}</td>
<td>{{ macros.translateStatus(job.status) }}</td>
<td class="ez-table__cell ez-table__cell--has-action-btns text-right">
<a href="{{ path('coderhapsodie.ezdataflow.job.details', {id: job.id}) }}"
@@ -43,14 +41,6 @@
xlink:href="/bundles/ezplatformadminui/img/ez-icons.svg#about-info"></use>
</svg>
</a>
<a href="{{ path('coderhapsodie.ezdataflow.job.log', {id: job.id}) }}"
class="btn btn-icon mx-2 modal-history-details modal-history-log"
title="{{ 'coderhapsodie.ezdataflow.history.details.log'|trans }}">
<svg class="ez-icon ez-icon--small-medium">
<use xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="/bundles/ezplatformadminui/img/ez-icons.svg#article"></use>
</svg>
</a>
</td>
</tr>
{% endfor %}
@@ -68,6 +58,7 @@
</div>
<script>
(function ($) {
$(document).ready(function ($) {
// Manage ajax pagination

View File

@@ -24,7 +24,7 @@
<tr>
<td>{{ item.label }}</td>
<td>{{ item.frequency }}</td>
<td>{{ date(item.next)|ez_short_datetime }}</td>
<td>{{ item.next|date('d/m/Y H:i:s') }}</td>
<td>{{ ('coderhapsodie.ezdataflow.' ~ (item.enabled ? 'yes' : 'no'))|trans }}</td>
<td class="ez-table__cell ez-table__cell--has-action-btns text-right">
<a href="{{ path('coderhapsodie.ezdataflow.history.workflow', {id: item.id}) }}"

View File

@@ -16,7 +16,7 @@ class HistoryTab extends AbstractControllerBasedTab implements OrderedTabInterfa
*/
public function getControllerReference(array $parameters): ControllerReference
{
return new ControllerReference(DashboardController::class.'::history', [], $parameters);
return new ControllerReference(DashboardController::class.'::history');
}
/**

View File

@@ -9,6 +9,9 @@ trait UserSwitcherAwareTrait
/** @var UserSwitcherInterface */
protected $userSwitcher;
/**
* @param UserSwitcherInterface $userSwitcher
*/
public function setUserSwitcher(UserSwitcherInterface $userSwitcher): void
{
$this->userSwitcher = $userSwitcher;

View File

@@ -4,18 +4,15 @@ declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Writer;
use CodeRhapsodie\DataflowBundle\DataflowType\Writer\DelegateWriterInterface;
use CodeRhapsodie\EzDataflowBundle\Core\Content\ContentCreatorInterface;
use CodeRhapsodie\EzDataflowBundle\Core\Content\ContentUpdaterInterface;
use CodeRhapsodie\EzDataflowBundle\Model\ContentCreateStructure;
use CodeRhapsodie\EzDataflowBundle\Model\ContentStructure;
use CodeRhapsodie\EzDataflowBundle\Model\ContentUpdateStructure;
use Psr\Log\LoggerAwareTrait;
use CodeRhapsodie\DataflowBundle\DataflowType\Writer\WriterInterface;
class ContentWriter extends RepositoryWriter implements DelegateWriterInterface
class ContentWriter extends RepositoryWriter implements WriterInterface
{
use LoggerAwareTrait;
/** @var ContentCreatorInterface */
private $creator;
@@ -34,40 +31,15 @@ class ContentWriter extends RepositoryWriter implements DelegateWriterInterface
public function write($item)
{
if (!$item instanceof ContentStructure) {
$this->log('warning', 'Data is not a ContentStucture');
return;
}
if ($item instanceof ContentCreateStructure) {
$this->log('info', 'Save content', [
'content_type' => $item->getContentTypeIdentifier(),
'content_location' => $item->getLocations(),
]);
return $this->creator->createFromStructure($item);
$this->creator->createFromStructure($item);
}
if ($item instanceof ContentUpdateStructure) {
$this->log('info', 'Update content', ['id' => $item->getId(), 'remote_id' => $item->getRemoteId()]);
return $this->updater->updateFromStructure($item);
$this->updater->updateFromStructure($item);
}
}
/**
* {@inheritdoc}
*/
public function supports($item): bool
{
return $item instanceof ContentStructure;
}
private function log(string $level, string $message, array $context = [])
{
if (null === $this->logger) {
return;
}
$this->logger->log($level, $message, $context);
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Writer;
use CodeRhapsodie\DataflowBundle\DataflowType\Writer\WriterInterface;
use CodeRhapsodie\EzDataflowBundle\UserSwitcher\UserSwitcherAwareInterface;
use CodeRhapsodie\EzDataflowBundle\UserSwitcher\UserSwitcherAwareTrait;
use CodeRhapsodie\DataflowBundle\DataflowType\Writer\WriterInterface;
abstract class RepositoryWriter implements WriterInterface, UserSwitcherAwareInterface
{

View File

@@ -1,49 +0,0 @@
<?php
namespace CodeRhapsodie\EzDataflowBundle\Tests\Core\FieldComparator;
use CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\DelegatorFieldComparator;
use CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\FieldComparatorInterface;
use eZ\Publish\API\Repository\Values\Content\Field;
use PHPUnit\Framework\TestCase;
class DelegatorFieldComparatorTest extends TestCase
{
/** @var DelegatorFieldComparator */
private $delegatorFieldComparator;
protected function setUp(): void
{
$type1FieldComparatorMock = $this->createMock(FieldComparatorInterface::class);
$type1FieldComparatorMock->method('compare')->willReturnCallback(function (Field $field, $hash) {
return $hash === 'rightValue1';
});
$type2FieldComparatorMock = $this->createMock(FieldComparatorInterface::class);
$type2FieldComparatorMock->method('compare')->willReturnCallback(function (Field $field, $hash) {
return $hash === 'rightValue2';
});
$this->delegatorFieldComparator = new DelegatorFieldComparator();
$this->delegatorFieldComparator->registerDelegateFieldComparator($type1FieldComparatorMock, 'type1');
$this->delegatorFieldComparator->registerDelegateFieldComparator($type2FieldComparatorMock, 'type2');
}
/**
* @dataProvider fieldProvider
*/
public function testField(string $type, bool $expected, $hash)
{
$field = new Field(['fieldTypeIdentifier' => $type]);
$return = $this->delegatorFieldComparator->compare($field, $hash);
$this->assertSame($expected, $return);
}
public function fieldProvider(): iterable
{
yield ['type1', true, 'rightValue1'];
yield ['type1', false, 'wrongValue'];
yield ['type2', true, 'rightValue2'];
yield ['type2', false, 'wrongValue'];
yield ['otherType', false, 'rightValue1'];
}
}

View File

@@ -1,138 +0,0 @@
<?php
declare(strict_types=1);
namespace CodeRhapsodie\EzDataflowBundle\Tests\Filter;
use CodeRhapsodie\EzDataflowBundle\Core\FieldComparator\FieldComparatorInterface;
use CodeRhapsodie\EzDataflowBundle\Filter\NotModifiedContentFilter;
use CodeRhapsodie\EzDataflowBundle\Model\ContentUpdateStructure;
use eZ\Publish\API\Repository\ContentService;
use eZ\Publish\API\Repository\Values\Content\Field;
use eZ\Publish\Core\Repository\Values\Content\Content;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class NotModifiedContentFilterTest extends TestCase
{
/** @var ContentService|MockObject */
private $contentServiceMock;
/** @var FieldComparatorInterface|MockObject */
private $comparatorMock;
/** @var NotModifiedContentFilter */
private $notModifiedContentFilter;
protected function setUp(): void
{
$this->contentServiceMock = $this->createMock(ContentService::class);
$this->comparatorMock = $this->createMock(FieldComparatorInterface::class);
$this->notModifiedContentFilter = new NotModifiedContentFilter($this->contentServiceMock, $this->comparatorMock);
}
public function testNotContentUpdateStructure()
{
$data = 'notAStruct';
$returnValue = ($this->notModifiedContentFilter)($data);
$this->assertSame($data, $returnValue);
}
public function testIdenticalContent()
{
$id = 10;
$field1 = 'field1';
$value1 = 'value1';
$field2 = 'field2';
$value2 = 'value2';
$contentField1 = new Field();
$contentField2 = new Field();
$data = ContentUpdateStructure::createForContentId($id, 'lang', [
$field1 => $value1,
$field2 => $value2,
]);
$content = $this->createMock(Content::class);
$content
->expects($this->exactly(2))
->method('getField')
->withConsecutive([$field1], [$field2])
->willReturnOnConsecutiveCalls($contentField1, $contentField2)
;
$this->contentServiceMock
->expects($this->once())
->method('loadContent')
->with($id)
->willReturn($content)
;
$this->comparatorMock
->expects($this->exactly(2))
->method('compare')
->withConsecutive([$contentField1, $value1], [$contentField2, $value2])
->willReturn(true)
;
$return = ($this->notModifiedContentFilter)($data);
$this->assertFalse($return);
}
public function testDifferentContent()
{
$id = 10;
$field1 = 'field1';
$value1 = 'value1';
$field2 = 'field2';
$value2 = 'value2';
$field3 = 'field3';
$value3 = 'value3';
$contentField1 = new Field();
$contentField2 = new Field();
$data = ContentUpdateStructure::createForContentId($id, 'lang', [
$field1 => $value1,
$field2 => $value2,
$field3 => $value3,
]);
$content = $this->createMock(Content::class);
$content
->expects($this->exactly(2))
->method('getField')
->withConsecutive([$field1], [$field2])
->willReturnOnConsecutiveCalls($contentField1, $contentField2)
;
$this->contentServiceMock
->expects($this->once())
->method('loadContent')
->with($id)
->willReturn($content)
;
$this->comparatorMock
->expects($this->exactly(2))
->method('compare')
->withConsecutive([$contentField1, $value1], [$contentField2, $value2])
->willReturnOnConsecutiveCalls(true, false)
;
$return = ($this->notModifiedContentFilter)($data);
$this->assertSame($data, $return);
}
public function testLoadEmptyByRemoteId()
{
$remoteId = 'abc';
$data = ContentUpdateStructure::createForContentRemoteId($remoteId, 'lang', []);
$this->contentServiceMock
->expects($this->once())
->method('loadContentByRemoteId')
->with($remoteId)
->willReturn(new Content())
;
$return = ($this->notModifiedContentFilter)($data);
$this->assertFalse($return);
}
}

View File

@@ -1,11 +0,0 @@
<?php
// Skip autoloading if already done by phpunit alias (including from meta repo if this is vendor)
if (defined('PHPUNIT_COMPOSER_INSTALL')) {
return;
}
$autoloadFile = __DIR__ . '/../vendor/autoload.php';
if (!file_exists($autoloadFile)) {
throw new RuntimeException('Install dependencies to run test suite.');
}
require_once $autoloadFile;