EXT:interest 

Version

3.0

Language

en

Description

REST and CLI API for adding, updating, and deleting records in TYPO3. The extension tracks relations, so records can be inserted in any order. Uses remote ID mapping so you don't have to keep track of what UID a record has gotten after import. Data is inserted using backend APIs as if a real human did it, so you can can inspect the record history and undo actions.

Keywords

import, data, cli, rest, datahandler

Copyright

2023 by Pixelant.net

Authors
  • Pixelant.net
Email

info@pixelant.net

License

This extension is published under the GNU General Public License v2.0

Rendered

Sun, 19 Apr 2026 09:39:08 +0000

TYPO3 

The content of this document is related to TYPO3 CMS, a GNU/GPL CMS/Framework available from typo3.org .

For Contributors 

You are welcome to help improve this guide if you missing something. Just click on "Edit me on GitHub" on the top right to submit your change request or report a problem.

Table of Contents 

Introduction 

Why is it called Interest? 

"Interest" is a portmanteau of the words "integration" and "REST [API]".

What does it do? 

A CLI and REST frontend to TYPO3's DataHandler 

This is an import extension that provides CLI and REST endpoints for creating, updating, and deleting records in TYPO3. It uses TYPO3's DataHandler API, so data is inserted as if you submitted a form in the Backend. This means changes are visible in the record history, and you can even revert to previous versions.

Backend user permissions 

REST calls authenticate as a backend user. That user's permissions are the permissions of the Interest extension. This means the REST calls can only create the records the backend user has permission to do. The extension uses TYPO3's own backend permission checking functionality.

Remote ID mapping 

During an import, you don't want to have to keep track of what UID each new record receives in the TYPO3 database, and it might be different between your development, staging, and live environments anyway. Sometimes one record in the import source can become multiple records in TYPO3.

The interest extension keeps track of the UIDs for you and maps them to whatever name (aka. a "remote ID") your data source gives it. The remote ID can be any string, not just a number. That makes debugging much easier!

Some remote ID examples:

  • News-123
  • NewsLink-123
  • myCoolImage.jpg
  • Hash-ab3235fdab0a

You can even manually create remote IDs for records in TYPO3, so the remote ID ContactPage always represents the UID of the Contact Us page. Another example is files that are uploaded to TYPO3 manually: You can use the file name and parts of the path as a remote ID and common reference point.

Send data in any order: Relation tracking and insert deferral 

Import data often out of order. You have to insert the parent page before you insert the children.

The Interest extension makes it possible to insert data in any order. Yes, you can send over the data of the child pages before the parent is present.

It does this with relation tracking and insert deferral:

  • Relation tracking: Interest keeps track of a record's relations. Especially the records that have not yet been created. Once one of the missing related records are created, the extension will make sure that the relations are set up. It will even track the intended order of relations: If record 7 is created before record 4, it will insert the new record before the other.
  • Insert deferral: Some records are not useful or create errors if they exist without one or more of their relations. Interest can store the data away and wait with inserting it until the relation is created. By default sys_file_reference records are deferred until the related sys_file is created. The extension provides an API for registering more insert deferrals.

Flexible file uploading 

Files can be created based on an URL, Base64-encoded data, or a URL to a MediaHandler-compatible website, such as YouTube and Vimeo.

You can even make relations to files e.g. on an Amazon S3 or Microsoft Azure mount within TYPO3: It is easy to give them remote IDs based on their ID in the external storage system.

The extension also supports putting files in any number of subfolders based on a hash of the filename. E.g.: interest_files/a/0/image.jpg

Data transformations 

All record data values can be modified using PHP and stdWrap. Each call to an endpoint can also include meta data that is not saved to the database, but can be used to make conditional decisions, such as deciding which PID a record should go into depending on some otherwise unused value in the source data.

Optimizations 

Interest includes a number of optimizations that will make it easier for you to maintain a data import with few changes:

  • The same operation will not be repeated twice in a row. The extension hashes the data of the record operation and checks it.
  • Files are not downloaded again, unless the file has changed. The extension tracks modification date and ETag headers.
  • Disable reference indexing. Updating the reference index is time consuming when you are writing a lot of data. You can disable it during import and and update the index afterwards.
  • For TYPO3 v9, the CLI module also disables registering of Extbase commands to greatly improve performance.

What doesn't it do? 

High Performance 

This extension has not been made to make imports faster, only better. Using TYPO3's backend APIs will always be slower than a list of SQL commands. On the other side, Interest tries to ensure that the date will always be as TYPO3 expects it, using core APIs whenever possible. And you can run it in multiple threads.

Read operations 

Read operations are currently not supported. It is a question how helpful it would be. In our experience, an import scenario where you need to use the Interest extension to read out data is not well designed. You should be able to trust that the data you are sending to Interest is correct: Check the data beforehand.

The only exception to this is the touch timestamps that are attached to each remote ID mapping. They can be used to see when a remote ID was touched last (i.e. when Interest was sent an operation directed towards it, even if no data was written in the end). It can be used to track when your data source last mentioned a particular remote ID to the Interest extension, for example to know which records to delete because they are no longer mentioned in your data source.

Frontend operations 

Just like the TYPO3 Backend, the Interest extension is not suited as your website. It is an admin tool.

Extension Configuration 

The extension configuration is global and affects the Interest extension across the entire TYPO3 instance.

You can edit these configurations by:

  • Going to the Settings module in the TYPO3 Backend and clicking Extension Configuration. Scroll down until you find interest and click it to open the editing form.
  • By setting environment variables.

Properties 

REST 

URL Entry Point

URL Entry Point
Required

true

Type

string

Default

rest

Key

entryPoint

Environment variable

APP_INTEREST_ENTRY_POINT

If you would like to make REST calls to https://example.org/entrypoint/..., the value here should be set to "entrypoint".

Token lifetime

Token lifetime
Required

false

Type

int

Default

86400

Key

tokenLifetime

Environment variable

APP_INTEREST_TOKEN_TTL

The authentication token's lifetime in seconds. Zero means no expiry.

Behavior 

Handle Empty Files

Handle Empty Files
Required

true

Type

int

Default
Key

handleEmptyFile

How to handle files that are empty. Available options are:

0
Treat as any other file. You can also use this option if you want to handle empty files with a custom \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent. Just make sure your EventHandler it is executed after \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Handler\PersistFileDataEventHandler.
1
Stop processing the record. This might result in pending relation records that will never be resolved in the database, but if that's OK for you it won't cause any issues.
2
Fail. The operation will be treated as failed and returns an error.

Handle Existing Files

Handle Existing Files
Required

true

Type

string from \TYPO3\CMS\Core\Resource\DuplicationBehavior

Default

cancel

Key

handleExistingFile

How to handle files that already exist in the filesystem. Uses the same configuration options as \TYPO3\CMS\Core\Resource\DuplicationBehavior :

cancel
Fail with exception.
rename
Rename the new file.
replace
Replace the existing file.

Log 

Logging of REST calls, including request and response data and execution time.

Enable logging

Enable logging
Required

false

Type

int

Default
Key

log

Environment variable

APP_INTEREST_LOG

Enable logging and specify where to log. Available values:

0
Disabled. No logging.
1
Log in response headers
2
Log in database.
3
Log in both response headers and database.

Logging threshold

Logging threshold
Required

false

Type

int

Default
Key

logMs

Environment variable

APP_INTEREST_LOGMS

The execution time in milliseconds above which logging is enabled.

Backend User Configuration 

Access privileges and page mounts 

REST API 

The REST API's write permissions are drawn from the authenticated backend user. You can configure the user in the TYPO3 backend as any other user. We recommend using a non-admin user with permissions limited to exactly the tables, page mounts, and file storage that are required.

Authenticating admin users is also possible and slightly more performant. However, it also comes with potential security risks.

CLI commands 

The write permissions of the CLI commands is drawn from the default _cli_ backend user.

UserTS Configuration 

Since calls to the Interest extension are not happening within a page scope, extension-specific configurations are made using TypoScript in UserTS. Many properties support stdWrap, so it is possible to configure advanced data transformations.

Accessing metadata 

Both the CLI commands and REST endpoints support sending metadata alongside with the record data. The metadata is common to the command or request, and it can e.g. be used to set storage PID dependent on a criteria.

Here's an example using market metadata to configure the PID:

tx_interest.persistence {
  storagePid {
    cObject = CASE
    cObject {
      key.data = field : metaData | market

      Denmark = TEXT
      Denmark.value =

      Norway = TEXT
      Norway.value = 1814

      Sweden = TEXT
      Sweden.value =
    }
  }
Copied!

UserTS properties 

UserTS configuration properties are set within tx_interest in the UserTS field of a backend user or backend user group, or within a .userts file.

persistence.hashedSubfolders

persistence.hashedSubfolders
Required

false

Type

int

Default

The number of layers of subfolders to create within fileUploadFolderPath. Each folder is named using characters from a hash of the file name.

If the file name is image.jpg and hashedSubfolders = 3, the file will be saved in 0/d/5/image.jpg within fileUploadFolderPath.

persistence.fileUploadFolderPath

persistence.fileUploadFolderPath
Required

true

Type

string (combined storage identifier)

Default

1:tx_interest

The location where uploaded files will be stored.

persistence.storagePid

persistence.storagePid
Required

false

Type

int | stdWrap

Default
 

Where new records will be stored. Can also be supplied in the record data by setting the pid field.

relationOverrides.[table].[field]

relationOverrides.[table].[field]
Required

false

Type

bool | stdWrap

If a field is or isn't wrongly a relation, this is your friend. Override whatever is in the TCA and whatever the extension itself thinks. Play God.

relationTypeOverride.[table].[field]

relationTypeOverride.[table].[field]
Required

false

Type

string | stdWrap

Override the TCA type of the field. E.g.: change "text" to "inline". The incoming value is always set to the field's current type.

isSingleRelationOverrides.[table].[field]

isSingleRelationOverrides.[table].[field]
Required

false

Type

string | stdWrap

Override whether a field supports 1:n or m:n relations. Should be set to true (1) if it is a 1:n relation and false (0) if it is a m:n relation.

Implementing 

Choosing REST or CLI? 

The extension supports both REST and CLI (Command Line Interface) endpoints. Which one you use depends on your needs, but here's the way we usually keep them apart:

  • Use CLI when the service sending data to the extension is in the same environment. This is good for situations where you need more advanced parsing and transformation of input data.
  • Use REST when the data source is external and supports REST export. DataCater is an ETA (Extract Transform Load) service that integrates well with the Interest extension's REST API.
  • Use Webhook to utilize TYPO3's built-in reactions support to authenticate
    a REST-like request (see above).

Common Properties 

Which-ever implementation you choose, there are a number of common properties that you will be using:

[table]
The name of the table you are writing records to.
[remoteId]
The record reference. See: Remote ID mapping
[language]
A language as an RFC 1766/3066 string, e.g. nb or sv-SE. Will be set to the default language if not supplied.
[workspace]
(Not yet implemented. Will be ignored.)
[data]

The data you would like to write to a record. Compatible with DataHandler, but using remote IDs for references.

{
   "title": "What are your inteRESTed in?",
   "item_count": 100,
   "images": [
      "Reference-File-543",
      "Reference-File-876"
   ],
   "page": ["Page-916"]
}
Copied!
[metaData]
Additional data, external to the record data, sent with a request. Can be used in conditions and transformations. See: Accessing metadata

Command Line API 

Authentication 

Operations performed using the CLI endpoints are all authenticated using the default _cli_ backend user.

Common options 

Some commands share CLI options. These are explained here.

-b | --batch

-b | --batch
Name

--batch

Shortcut

-b

Type

boolean

Enables batch operations. The REMOTE_ID argument will be ignored, but has to be set to some string to avoid errors.

With this option enabled, the data JSON string's first level is an array of record data with the key set to the record's remote ID.

typo3 interest:create pages ignoredRemoteId --batch --data='{"newPage1":{"title":"Test Name","pid":"siteRootPage"},"newPage2":{...}}'
Copied!

-d | --data

-d | --data
Name

--data

Shortcut

-d

Type

JSON string

Record data as a JSON string. This string can also be piped in.

# These commands have identical results.

typo3 interest:create ... --data='{"title":"Test Name","pid":"siteRootPage"}'

echo -n '{"title":"Test Name","pid":"siteRootPage"}' | typo3 interest:create ...
Copied!

--disableReferenceIndex

--disableReferenceIndex
Name

--disableReferenceIndex

Shortcut

none

Type

Boolean

Disable updating the reference index during the request. This has a positive performance impact. You can (and should) reindex the reference index manually afterwards.

typo3 interest:create ... --disableReferenceIndex
Copied!

-m | --metaData

-m | --metaData
Name

--metaData

Shortcut

-m

Type

JSON string

Meta data for the operation.

# These commands have identical results.

typo3 interest:create ... --metaData='{"context":"NewZealand"}'
Copied!

Available commands/endpoints 

All commands can be executed using TYPO3's default CLI endpoint, typo3 [command], e.g. typo3 interest:create ....

interest:clearhash 

Interest stores a hash of the data in each operation together with the remote ID. This command clears this hash. The hash is used to prevent an operation from being executed repeatedly.

Sometimes, especially in connection with testing, clearing the hash and re-running the operation makes sense.

typo3 interest:clearhash REMOTE_ID [-c|--contains]
Copied!

Options 

-c | --contains

-c | --contains
Name

--contains

Shortcut

-c

Type

boolean

Interpret REMOTE_ID as a partial remote ID and match any remote ID containing this string.

interest:create 

Create a record.

typo3 interest:create ENDPOINT REMOTE_ID [LANGUAGE [WORKSPACE]] [-u|--update] [-d|--data]  [-m|--metaData] [-b|--batch] [--disableReferenceIndex]
Copied!

Additional options 

-u | --update

-u | --update
Name

--update

Shortcut

-u

Type

boolean

If the record already exists, update it instead.

interest:delete 

Delete a record.

typo3 interest:delete REMOTE_ID [LANGUAGE [WORKSPACE]] [--disableReferenceIndex]
Copied!

interest:pendingrelations 

View statistics and optionally try to resolve pending relations.

typo3 interest:pendingrelations [-r|--resolve]
Copied!

Options 

-r | --resolve

-r | --resolve
Name

--resolve

Shortcut

-r

Type

boolean

Try to resolve any resolvable pending relations in addition to showing statistics.

interest:update 

Update a record.

typo3 interest:update ENDPOINT REMOTE_ID [LANGUAGE [WORKSPACE]] [-c|--create] [-d|--data]  [-m|--metaData] [-b|--batch] [--disableReferenceIndex]
Copied!

Additional options 

-c | --create

-c | --create
Name

--update

Shortcut

-u

Type

boolean

If the record doesn't exist, create it instead.

REST API 

Basic request URL 

Requests to the REST API usually follows a simple pattern, where all of the parts, except from the endpoint, are optional or can be supplied elsewhere:

http://www.example.org/[endpoint]/[table]/[remoteId]/[language]/[workspace]
Copied!

Example with values added:

http://www.example.org/rest/tt_content/Content-531/nb-NO/0
Copied!

Optional URL parts 

[language] and [workspace] are fully optional and can be left out entirely:

http://www.example.org/[endpoint]/[table]/[remoteId]
Copied!

They can also be supplied in the query string:

http://www.example.org/[endpoint]/[table]/[remoteId]?language=[language]&workspace=[workspace]
Copied!

Authentication 

Scheme: basic 

Initial authentication is done using the basic HTTP authentication schema, where a Base64-encoded string is submitted to the server. The string is a TYPO3 backend username and the corresponding password, separated by a colon: :.

Given the username "testuser" and password "test1234", the concatenated string will be "testuser:test1234" and the Base64-encoded version: "dGVzdHVzZXI6dGVzdDEyMzQ=".

curl -XPOST \
     -H 'Authorization: basic dGVzdHVzZXI6dGVzdDEyMzQ=' \
     -v 'https://example.org/rest/authenticate'
Copied!

This request will return a JSON response body including a token that can be used on subsequent requests:

{"success":true,"token":"f3c0946fb05aae4ad50897e9060ab4e8"}
Copied!

When using Apache there is a need to add the following to .htaccess

RewriteEngine On
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
Copied!

Scheme: bearer (OAuth) 

For any other request, you must use bearer HTTP authentication scheme, supplying an authentication token, such as the one supplied by the basic authentication request mentioned above:

curl -XPOST \
     -H 'Authorization: bearer f3c0946fb05aae4ad50897e9060ab4e8' \
     -v 'https://example.org/rest/pages/testPage' \
     -d '{"data":{"title":"Test Name","pid":"siteRootPage"}}'
Copied!

Batch requests 

[table] and [remoteId] must be supplied, but can be a part of the [data] array. This makes it possible to supply batch data affecting multiple records and tables.

Given a request with only [table] supplied in the URL:

http://www.example.org/[endpoint]/[table]
Copied!

Multiple records in the same table 

You can insert or update multiple records within [table]. Your data array could look something like this:

{
   "Record-1": {
      "title": "My first record",
      "page": ["Page-916"]
   },
   "Record-2": {
      "title": "My second record",
      "page": ["Page-376"]
   }
}
Copied!

Multiple records in multiple tables 

You can also leave out the table and insert or update multiple records within multiple tables:

"pages": {
   "Page-1": {
      "title": "My first page"
   },
   "Page-2": {
      "title": "My second page"
   }
},
"tt_content": {
   "Content-1": {
      "heading": "Welcome to the first page",
      "pid": "Page-1"
   },
   "Content-2": {
      "heading": "Welcome to the second page",
      "pid": "Page-2"
   }
}
Copied!

Multilingual records 

It is even possible to insert records in multiple languages by adding a language layer to the data:

"pages": {
   "Page-1": {
      "en": {
         "title": "My first page"
      },
      "nb": {
         "title": "Min første side"
      }
   },
   "Page-2": {
      "en": {
         "title": "My second page"
      }
   },
},
"tt_content": {
   "Content-1": {
      "en": {
         "heading": "Welcome to the first page",
         "pid": "Page-1"
      },
      "nb" {
         "heading": "Velkommen til den første siden",
         "pid": "Page-1"
      }
   },
}
Copied!

HTTP Methods 

POST 

Create a record.

PUT 

Update a record.

PATCH 

Update a record if it exists, otherwise create it.

DELETE 

Delete a record.

curl -XDELETE \
     -H 'Authorization: bearer f3c0946fb05aae4ad50897e9060ab4e8' \
     -v 'https://example.org/rest/pages/testPage'
Copied!

Optional HTTP Headers 

Interest-Disable-Reference-Index

Interest-Disable-Reference-Index
Required

false

Type

Boolean

Disable updating the reference index during the request. This has a positive

performance impact. You can (and should) reindex the reference index manually afterwards.

Webhook (Reaction) 

TYPO3 v12 adds the possibility to receive webhooks and react to them with reactions using the typo3/cms-reactions core extension. When it is installed, the Interest extension can use a webhook as a wrapper for a REST request.

Differences between a webhook and REST request 

The implementation is similar to a normal REST request, except:

  • The entry point is always the reaction extension's entry point: /typo3/reaction/{reactionId}
  • Backend user authentication is handled by the reactions extension.
  • The HTTP method of a reaction request must always be POST, so you must instead provide the action in the JSON array key method (POST, PUT, PATCH, or DELETE).
  • table, remoteId, language, and workspace can only be supplied in the JSON array.

Create an Interest reaction 

To create a new reaction navigate to the System > Reactions backend module. Then click on the button Create new reaction to add a new reaction.

In the form, select the reaction type "Interest operation (create, update, or delete)" and fill in the other general configuration fields.

Remember to hold on to the content of the Secret field. The secret is necessary to authorize the reaction from the outside. It can be recreated anytime, but will be visible only once (until the record is saved). Click on the "dices" button next to the form field to create a random secret. Store the secret somewhere safe.

When you save and close the form, the reaction will be visible in the list of reactions.

Request URL 

By clicking on the Example button in the list of reactions, you'll see skeleton of a cURL request for use on the command. You can adjust and run it in the console, using our placeholders as payload:

curl -X 'POST' \
    'https://example.com/typo3/reaction/a5cffc58-b69a-42f6-9866-f93ec1ad9dc5' \
      -d '{"method":"PATCH","table":"pages","remoteId":"testPage","data":{"title":"Test Name","pid":"siteRootPage"}}' \
      -H 'accept: application/json' \
      -H 'x-api-key: d9b230d615ac4ab4f6e0841bd4383fa15f222b6b'
Copied!

Request data 

Your payload data should always contain at least contain the key method.

Specifying the HTTP request header 

Because webhook-based requests always use the POST method, it must be overridden in your data. This means

method refers to the HTTP request method that would otherwise have been used by the REST request. The available values are POST, PUT, PATCH, and DELETE. More information in the REST API section on HTTP Methods.

Example request data 

The request data can be formatted for both single and batch operations. A single-record request could look like this:

{
   "method": "PATCH",
   "table": "pages",
   "remoteId": "testPage",
   "data": {
      "title": "Test Name",
      "pid":"siteRootPage"
   }
}
Copied!

A batch request could look like this:

{
   "method": "POST",
   "data": {
      "pages": {
         "Page-1": {
            "title": "My first page",
         },
         "Page-2": {
            "title": "My second page",
         }
      },
      "tt_content": {
         "Content-1": {
            "heading": "Welcome to the first page",
            "pid": "Page-1"
         },
         "Content-2": {
            "heading": "Welcome to the second page",
            "pid": "Page-2"
         }
      }
   }
}
Copied!

You can read more about creating batch requests in the REST API section on Batch requests.

File Handling 

File Data Sources 

When writing to the sys_file table, the extension introduces two virtual fields:

  • fileData: Base64-encoded file data.
  • url: A publicly accessible URL. The file data will be downloaded from this location or handled by an OnlineMediaHelper (such as for YouTube and Vimeo URLs, that are turned into .youtube and .vimeo files).

Base64-encoded file data 

With the extension's default configuration, this example will create the file at fileadmin/tx_interest/image.png.

{
  "sys_file": {
    "ExampleRemoteId": {
      "fileData": "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8...",
      "name": "image.png"
    }
  }
}
Copied!

The name field must be supplied and include a file extension.

Publicly accessible URL 

Normal URL 

The default behavior is that the file specified in the url field is downloaded to the download location specified in the persistence.fileUploadFolderPath UserTS configuration parameter.

With the extension's default configuration, this example will download the file at https://example.com/image.png to fileadmin/tx_interest/image.png.

{
  "sys_file": {
    "ExampleRemoteId": {
      "url": "https://example.com/image.png",
      "name": "image.png"
    }
  }
}
Copied!

The name field must be supplied and include a file extension.

URL with OnlineMediaHelper 

If an OnlineMediaHelper is registered for the URL, it will take priority over any other file handling.

With the extension's default configuration, this example will create the file fileadmin/tx_interest/TYPO3_-_Channel_Intro.youtube.

{
  "sys_file": {
    "ExampleRemoteId": {
      "url": "https://youtu.be/zpOVYePk6mM",
    }
  }
}
Copied!

If the name field is used, the file will use the name supplied there and the correct extension added if necessary (e.g. .youtube).

File name sanitation 

File names are sanitized using TYPO3's standard sanitation methods, so expect spaces to be replaced with underscores, etc.

Changing and Extending 

If you need additional functionality or the existing functionality of the extension isn't quite what you need, this section tells you how to change the behavior of the Interest extension. It also tells you how to extend the functionality, as well as a bit about the extension's inner workings.

PSR-14 Events 

Events 

The events are listed in order of execution.

class HttpRequestRouterHandleByEvent
Fully qualified name
\FriendsOfTYPO3\Interest\Router\Event\HttpRequestRouterHandleByEvent

Called in HttpRequestRouter::handleByMethod(). Can be used to modify the request and entry point parts before they are passed on to a RequestHandler.

EventHandlers for this event should implement \FriendsOfTYPO3\Interest\Router\Event\HttpRequestRouterHandleByEventHandlerInterface.

getEntryPointParts ( )

Returns an array of the entry point parts, i.e. the parts of the URL used to detect the correct entry point. Given the URL http://www.example.com/rest/tt_content/ContentRemoteId and the default entry point rest, the entry point parts will be ['tt_content', 'ContentRemoteId'].

returntype

array

setEntryPointParts ( $entryPointParts)
param array $entryPointParts
 
getRequest ( )
returntype

PsrHttpMessageServerRequestInterface

setRequest ( $request)
param Psr\Http\Message\ServerRequestInterface $request
 
class RecordOperationSetupEvent
Fully qualified name
\FriendsOfTYPO3\Interest\DataHandling\Operation\Event\RecordOperationSetupEvent

Called inside the AbstractRecordOperation::__construct() when a *RecordOperation object has been initialized, but before data validations.

EventHandlers for this event should implement \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\RecordOperationEventHandlerInterface.

EventHandlers for this event can throw these exceptions:

\FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Exception\StopRecordOperationException
To quietly stop the record operation. This exception is only logged as informational and the operation will be treated as successful. E.g. used when deferring an operation.
\FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Exception\BeforeRecordOperationEventException
Will stop the record operation and log as an error. The operation will be treated as unsuccessful.
getRecordOperation ( )
returntype

FriendsOfTYPO3InterestDataHandlingOperationAbstractRecordOperation

class RecordOperationInvocationEvent
Fully qualified name
\FriendsOfTYPO3\Interest\DataHandling\Operation\Event\RecordOperationInvocationEvent

Called as the last thing inside the AbstractRecordOperation::__invoke() method, after all data persistence and pending relations have been resolved.

EventHandlers for this event should implement \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\AfterRecordOperationEventHandlerInterface.

getRecordOperation ( )
returntype

FriendsOfTYPO3InterestDataHandlingOperationAbstractRecordOperation

class HttpResponseEvent
Fully qualified name
\FriendsOfTYPO3\Interest\Middleware\Event\HttpResponseEvent

Called in the middleware, just before control is handled back over to TYPO3 during an HTTP request. Allows modification of the response object.

EventHandlers for this event should implement \FriendsOfTYPO3\Interest\Middleware\Event\HttpResponseEventHandlerInterface.

getResponse ( )
returntype

PsrHttpMessageResponseInterface

setResponse ( $response)
param Psr\Http\Message\ResponseInterface $response
 

How it works 

Internal representation and identity 

Inside the extension, a record's state and identity is maintained by two data transfer object classes:

  • A record's unique identity from creation to deletion is represented by \FriendsOfTYPO3\Interest\Domain\Model\Dto\RecordInstanceIdentifier.
  • A record's current state, including the data that should be written to the database is represented by \FriendsOfTYPO3\Interest\Domain\Model\Dto\RecordRepresentation.

When creating a RecordRepresentation, you must also supply a RecordInstanceIdentifier:

use FriendsOfTYPO3\Interest\Domain\Model\Dto\RecordInstanceIdentifier;
use FriendsOfTYPO3\Interest\Domain\Model\Dto\RecordRepresentation;

new RecordRepresentation(
    [
        'title' => 'My record title',
        'bodytext' => 'This is a story about ...',
    ],
    new RecordInstanceIdentifier(
        'tt_content',
        'ContentElementA',
        'en'
    )
);
Copied!

Record operations 

Record operations are the core of the Interest extension. Each represents one operation requested from the outside. One record operation is not the same as one database operation. Some record operations will not be executed (if it is a duplicate of the previous operation on the same remote ID) or deferred (if the record operation requires a condition to be fulfilled before it can be executed).

The record operations are invokable, and are executed as such:

Record operation types 

There are three record operations:

  • Create
  • Update
  • Delete

All are subclasses of \FriendsOfTYPO3\Interest\DataHandling\Operation\AbstractRecordOperation, and share its API. CreateRecordOperation and CreateRecordOperation are direct subclasses of AbstractConstructiveRecordOperation, which adds a more complex constructor.

class AbstractConstructiveRecordOperation
Fully qualified name
\FriendsOfTYPO3\Interest\DataHandling\Operation\AbstractConstructiveRecordOperation
class CreateRecordOperation
Fully qualified name
\FriendsOfTYPO3\Interest\DataHandling\Operation\CreateRecordOperation
class UpdateRecordOperation
Fully qualified name
\FriendsOfTYPO3\Interest\DataHandling\Operation\UpdateRecordOperation
__construct ( $recordRepresentation, $metaData)
param FriendsOfTYPO3\Interest\Domain\Model\Dto\RecordRepresentation $recordRepresentation
 
param array $metaData
 
getContentRenderer ( )

Returns a special ContentObjectRenderer for this operation. The data array is populated with operation-specific information when the operation object is initialized. It is not updated if this information changes.

 $contentObjectRenderer->data = [
    'table' => $this->getTable(),
    'remoteId' => $this->getRemoteId(),
    'language' => $this->getLanguage()->getHreflang(),
    'workspace' => null,
    'metaData' => $this->getMetaData(),
    'data' => $this->getDataForDataHandler(),
];
Copied!
returntype

TYPO3CMSFrontendContentObjectContentObjectRenderer

dispatchMessage ( $message)
param \FriendsOfTYPO3\Interest\DataHandling\Operation\Message\MessageInterface $message
 

Dispatch a message, to be picked up later, in another part of the operation's execution flow.

returntype

mixed

getDataFieldForDataHandler ( $fieldName)
param string $fieldName
 

Get the value of a specific field in the data for DataHandler. Same as $this->getDataForDataHandler()[$fieldName].

returntype

mixed

getDataForDataHandler ( )

Get the data that will be written to the DataHandler. This is a modified version of the data in $this->getRecordRepresentation()->getData().

returntype

array

getDataHandler ( )

Returns the internal DataHandler object used in the operation.

returntype

FriendsOfTYPO3InterestDataHandlingDataHandler

getHash ( )

Get the unique hash of this operation. The hash is generated when the operation object is initialized, and it is not changed. This hash makes it possible for the Interest extension to know whether the same operation has been run before.

returntype

string

getLanguage ( )

Returns the record language represented by a \TYPO3\CMS\Core\Site\Entity\SiteLanguage object, if set. $this->getRecordRepresentation()->getRecordInstanceIdentifier()->getLanguage()

returntype

TYPO3CMSCoreSiteEntitySiteLanguage|null

getMetaData ( )

Returns the metadata array for the operation. This metadata is not used other than to generate the uniqueness hash for the operation. You can use it to transfer useful information, e.g. for transformations. See: Accessing metadata

returntype

array

getRemoteId ( )

Returns the table name. Shortcut for $this->getRecordRepresentation()->getRecordInstanceIdentifier()->getRemoteIdWithAspects()

returntype

string

getTable ( )

Returns the table name. Shortcut for $this->getRecordRepresentation()->getRecordInstanceIdentifier()->getTable()

returntype

string

getRecordRepresentation ( )
returntype

FriendsOfTYPO3InterestDomainModelDtoRecordRepresentation

getStoragePid ( )

Gets the PID of the record as originally set during object construction, usually by the \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Handler\ResolveStoragePid event.

returntype

void

getSettings ( )

Returns the settings array from UserTS (tx_interest.*).

returntype

array

getUid ( )

Returns the record UID, or zero if not yet set. $this->getRecordRepresentation()->getRecordInstanceIdentifier()->getUid()

returntype

int

getUidPlaceholder ( )

Returns a DataHandler UID placeholder. If it has not yet been set, it will be generated as a random string prefixed with "NEW". $this->getRecordRepresentation()->getRecordInstanceIdentifier()->getUidPlaceholder()

returntype

string

hasExecuted ( )

Returns true if the operation has executed the DataHandler operations.

returntype

bool

isDataFieldSet ( $fieldName)
param string $fieldName
 

Check if a field in the data array is set. Same as isset($this->getDataForDataHandler()[$fieldName]).

returntype

bool

isSuccessful ( )

Returns true if the operation has executed the DataHandler operations without errors.

returntype

bool

retrieveMessage ( $message)
param string $messageFqcn
 

Pick the last message of class $messageFqcn from the message queue. Returns null if no messages are left in the queue.

returntype

FriendsOfTYPO3InterestDataHandlingOperationMessageMessageInterface|null

setDataFieldForDataHandler ( $fieldName, $value)
param string $fieldName
 

Set the value of a specific field in the data for DataHandler. Same as:

returntype

void

setDataForDataHandler ( $dataForDataHandler)

Set the data that will be written to the DataHandler.

param array $dataForDataHandler
 
setHash ( $hash)
param string $hash
 

Override the record operation's uniqueness hash. Changing this value can have severe consequences for data integrity.

returntype

void

setStoragePid ( $storagePid)
param int $storagePid
 

Sets the storage PID. This might override a PID set by the \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Handler\ResolveStoragePid event, which usually handles this task.

returntype

void

setUid ( $uid)
param int $uid
 

Sets the record UID. $this->getRecordRepresentation()->getRecordInstanceIdentifier()->setUid($uid)

returntype

void

unsetDataField ( $fieldName)
param string $fieldName
 

Unset a field in the data array. Same as:

returntype

void

class DeleteRecordOperation
Fully qualified name
\FriendsOfTYPO3\Interest\DataHandling\Operation\DeleteRecordOperation
__construct ( $recordRepresentation)

You cannot send metadata information to a delete operation.

param FriendsOfTYPO3\Interest\Domain\Model\Dto\RecordRepresentation $recordRepresentation
 

Record Operation Messages 

Classes implementing \FriendsOfTYPO3\Interest\DataHandling\Operation\Message\MessageInterface can be used to carry information within the execution flow of an instance of \FriendsOfTYPO3\Interest\DataHandling\Operation\AbstractRecordOperation. This is especially useful between EventHandlers.

For example, \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Handler\Message\PendingRelationMessage is used to carry information about pending relations between the event that discovers them and the event that persists the information to the database — if the record operation was successful.

Sending a message in \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Handler\MapUidsAndExtractPendingRelations:

Retrieving messages and using the message data to persist the information to the database in \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Handler\PersistPendingRelationInformation:

Mapping table 

The extension keeps track of the mapping between remote IDs and TYPO3 records in the table tx_interest_remote_id_mapping. In addition to mapping information, the table contains metadata about each record.

Touching and the touched 

When a record is created or updated, the touched timestamp is updated. The timestamp is also updated if the remote request intended to update the record, but the Interest extension decided not to do it, for example because there was nothing to change. In this way, the time a record was last touched may more recent than the record's modification date.

The time the record was last touched can help you verify that a request was processed — or to find the remote IDs that were not mentioned at all. In the latter case, knowing remote IDs that are no longer updated regularly can tell you which remote IDs should be deleted.

Relevant methods 
class RemoteIdMappingRepository
Fully qualified name
\FriendsOfTYPO3\Interest\Domain\Repository\RemoteIdMappingRepository
touch ( $remoteId)

Touches the remote ID and nothing else. Sets the touched timestamp for the remote ID to the current time.

param string $remoteId

The remote ID of the record to touch.

touched ( $remoteId)

Returns the touched timestamp for the record.

param string $remoteId

The remote ID of the record to touch.

returntype

int

findAllUntouchedSince ( $timestamp, $excludeManual = true)

Returns an array containing all remote IDs that have not been touched since $timestamp.

param int $timestamp

Unix timestamp.

param bool $excludeManual

When true, remote IDs flagged as manual will be excluded from the result. Usually a good idea, as manual entries aren't usually a part of any update workflow.

returntype

bool

findAllTouchedSince ( $timestamp, $excludeManual = true)

Returns an array containing all remote IDs that have been touched since $timestamp.

param int $timestamp

Unix timestamp.

param bool $excludeManual

When true, remote IDs flagged as manual will be excluded from the result. Usually a good idea, as manual entries aren't usually a part of any update workflow.

returntype

bool

Example 

Fetching all remote IDs that have not been touched since the same time yesterday.

use FriendsOfTYPO3\Interest\Domain\Model\Dto\RecordInstanceIdentifier;
use FriendsOfTYPO3\Interest\Domain\Model\Dto\RecordRepresentation;
use FriendsOfTYPO3\Interest\Domain\Repository\RemoteIdMappingRepository;
use FriendsOfTYPO3\Interest\DataHandling\Operation\DeleteRecordOperation;
use TYPO3\CMS\Core\Utility\GeneralUtility;

$mappingRepository = GeneralUtility::makeInstance(RemoteIdMappingRepository::class);

foreach($mappingRepository->findAllUntouchedSince(time() - 86400) as $remoteId) {
    (new DeleteRecordOperation(
        new RecordRepresentation(
            [],
            new RecordInstanceIdentifier('table', $remoteId)
        );
    ))();
}
Copied!

Metadata 

The mapping table also contains a field that can contain serialized meta information about the record. Any class can add and retrieve meta information from this field.

Here's two existing use cases:

  • Foreign relation sorting order by \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Handler\ForeignRelationSortingEventHandler
  • File modification info by \FriendsOfTYPO3\Interest\DataHandling\Operation\Event\Handler\PersistFileDataEventHandler
Relevant methods 
class RemoteIdMappingRepository
Fully qualified name
\FriendsOfTYPO3\Interest\Domain\Repository\RemoteIdMappingRepository
getMetaData ( $remoteId)

Retrieves all of the metadata entries as a key-value array.

param string $remoteId

The remote ID of the record to return the metadata for.

returntype

array

getMetaDataValue ( $remoteId, $key)

Retrieves a metadata entry.

param string $remoteId

The remote ID of the record to return the metadata for.

param string $key

The originator class's fully qualified class name.

returntype

string, float, int, array, or null

getMetaDataValue ( $remoteId, $key, $value)

Sets a metadata entry.

param string $remoteId

The remote ID of the record to return the metadata for.

param string $key

The originator class's fully qualified class name.

param string|float|int|array|null $value

The value to set.

Example 

This simplified excerpt from PersistFileDataEventHandler shows how metadata stored in the record is used to avoid downloading a file if it hasn't changed. If it has changed, new metadata is set.

use GuzzleHttp\Client;
use FriendsOfTYPO3\Interest\Domain\Repository\RemoteIdMappingRepository;

$mappingRepository = GeneralUtility::makeInstance(RemoteIdMappingRepository::class);

$metaData = $mappingRepository->getMetaDataValue(
    $remoteId,
    self::class
) ?? [];

$headers = [
    'If-Modified-Since' => $metaData['date'],
    'If-None-Match'] => $metaData['etag'],
];

$response = GeneralUtility::makeInstance(Client::class)
    ->get($url, ['headers' => $headers]);

if ($response->getStatusCode() === 304) {
    return null;
}

$mappingRepository->setMetaDataValue(
    $remoteId,
    self::class,
    [
        'date' => $response->getHeader('Date'),
        'etag' => $response->getHeader('ETag'),
    ]
);

return $response->getBody()->getContents();
Copied!

Sitemap