Select records from other languages in a scheduler task - typo3

I am developing an extension, which provides a scheduler task. In this task an API is triggered and the results are going to be written to the database. So far there is no problem.
But the whole typo3 (8.7 with php7.2) page have multiple languages. So I have to save all the API objects in their different language. This does not make any difficulties for the insert process, but in the update process I could not select the correct languages.
Some mysterios things I still solved, like the sys_language_uid parameter, which have to be _languageUid (more details) or that I do not find any solution for getting all possible languages from the sys_language table without using a own doctrine querybuilder over the ConnectionPool. (I also add the default language as an entry)
Now to the main problem, which I could not solve yet. After debugging the core I get, that the queries I build with the repository are correct.
the repository:
$query = $this->createQuery();
$query->getQuerySettings()->setIgnoreEnableFields(true);
$query->getQuerySettings()->setRespectSysLanguage(false);
$query->matching(
$query->logicalAnd(
$query->equals('identifier', $identifier),
$query->equals('sys_language_uid', $languageUid)
)
);
the resulting query is:
SELECT `TABLE`.* FROM `TABLE` `T`
WHERE (
(`T`.`identifier` = :dcValue1)
AND (`T`.`sys_language_uid` = :dcValue2)
) AND (`T`.`deleted` = 0)
Now I try to get the parent object from the database to proceed an update. The whole update-mapping is unit-tested and all debug outputs confirm that everything works fine.
I look back to core and find at TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbBackend::class on line 612:
foreach ($rows as $row) {
// If current row is a translation select its parent
if (isset($tableName) && isset($GLOBALS['TCA'][$tableName])
&& isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
&& isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
&& $tableName !== 'pages_language_overlay'
) {
if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
&& $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
) {
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
$queryBuilder->getRestrictions()->removeAll();
$row = $queryBuilder->select($tableName . '.*')
->from($tableName)
->where(
$queryBuilder->expr()->eq(
$tableName . '.uid',
$queryBuilder->createNamedParameter(
$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
\PDO::PARAM_INT
)
),
$queryBuilder->expr()->eq(
$tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
)
)
->setMaxResults(1)
->execute()
->fetch();
}
}
...
Like the comment on line 613 says, the row from the database will be overwritten in this foreach. All xdebug-data confirm that. If I disable the transOrigPointerField in my TCA it will work like expected, but the backend-view could not assign the multiple languages to one parent element.
I also tried to delete the TCA entry just for the scheduler task, but the TCA is cached and no temporary overwrite is possible.
Am I doing something wrong, or is it just a bug?
update
Take a look at the simplified example

I was editing the TYPO3-core for version 8.7 (branch TYPO3_8-7 after tag 8.7.19) now. At the file typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbBackend.php on line 618 I additonally check the respectSysLangauge option so the new check will be:
// If current row is a translation select its parent
if (isset($tableName) && isset($GLOBALS['TCA'][$tableName])
&& isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
&& isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
&& $tableName !== 'pages_language_overlay'
&& true === $querySettings->getRespectSysLanguage()
) {
After this I started the TYPO3-testsuite for this part of TYPO3 and everthing seems to be OK (all tests passed) and my problem is fixed. I dont know how to contribute correctly without spending hours with reading the whole contribution guide.

Related

How to limit showPrevNext in news to categories?

In the TYPO3 CMS 9.5.18 LTS with tx_news 8.3.0 we use the following extension Typoscript:
plugin.tx_news.settings{
# what is allowed to overwrite with TS
overrideFlexformSettingsIfEmpty := addToList(categories)
overrideFlexformSettingsIfEmpty := addToList(categoryConjunction)
# ids of categories
categories = 3
# category conjunction mode
categoryConjunction = or
}
I wonder why I have to add categories to overrideFlexformSettingsIfEmpty to get the result below. Never the less this post is more about how to achieve that the prev/next links (settings.detail.showPrevNext) does respect the category definition at all.
Our customer has 3 categories for news. If I go to a single page that does has a one category limitation (for the detail and the list page) I still e.g. can go "forward" to newer news in a total different category. However the list page only shows the news of that one selected category.
<f:if condition="{paginated.prev}">
<n:link newsItem="{paginated.prev}" settings="{settings}" class="ts-prev">
{paginated.prev.title}
</n:link>
</f:if>
Wasn't that never the case? Do I have to add some Typoscript or make a change in Fluid? The original code uses this settings variable as argument which contains the category limitation.
Okay I've had a look into the GeorgRinger\News\ViewHelpers\SimplePrevNextViewHelper and there aren't any limitation for the current chosen categories.
So here is what I did:
register a new optional argument categories to the viewhelper
add categories="{settings.categories}" to the simplePrevNext tag in the Detail.html
add an 'extra where' for the main query in the getNeighbours function
add the content for the additional where ( I did that first in the getNeighbours function )
Extra where:
if( is_array($newsOfCategory) && count($newsOfCategory) > 0 ){
$extraWhere[] = $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($newsOfCategory, Connection::PARAM_INT_ARRAY));
}
Content for the additional where:
if( is_string($categories) && preg_match('/^[0-9]+(,[0-9]+)*$/',$categories) ){
$categories = explode(',', $categories);
$tmpCon = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_category_record_mm');
$tmpQB = $tmpCon->createQueryBuilder();
$rows = $tmpQB
->select('uid_foreign')
->from('sys_category_record_mm')
->where(
$tmpQB->expr()->in('uid_local', $tmpQB->createNamedParameter($categories, Connection::PARAM_INT_ARRAY))
)
->andWhere(
$tmpQB->expr()->like('tablenames', $tmpQB->createNamedParameter('tx_news_domain_model_news'))
)
->execute()->fetchAll();
if( is_array($rows) && count($rows) > 0 ){
foreach($rows as $row){
$newsOfCategory[] = $row['uid_foreign'];
}
}
}
May be someone can use that in the future or the will integrate that to the repository.

TYPO3: Translated mm records in command controller

I am trying to get the translated records from the command controller by calling a function from extension repository like this.
function findAllForLang($lang){
//$lang = 1;
$query = $this->createQuery();
$query->getQuerySettings()->setRespectSysLanguage(false);
$query->getQuerySettings()->setLanguageMode(false);
$query->getQuerySettings()->setLanguageUid($lang);
$query->getQuerySettings()->setLanguageOverlayMode('hideNonTranslated');
$query->matching(
$query->logicalAnd(
$query->equals('sys_language_uid', $lang),
//$query->equals('mmfield.sys_language_uid', $lang)
)
);
return $query->execute();
I am getting the translated records. But the mm inline records are not translated and getting the default language records.
Is there any specific way to get all the translated inline records ?
TYPO3 version is 8.7.20
Thank you
There is a longstanding bug regarding fetching of translated relations with Extbase: https://forge.typo3.org/issues/57272
It was fixed for TYPO3 v9+.
For your case I suggest to build the query yourself with the (newer) doctrine-dbal API:
https://docs.typo3.org/m/typo3/reference-coreapi/8.7/en-us/ApiOverview/Database/QueryBuilder/Index.html

TYPO3 ConnectionPool find a file after the uid of the file reference and update data

The concept is that, after a successfull save of my object, it should update a text in the database (With a Hook). Lets call the field 'succText'. The table i would like to access is the sys_file but i only get the sys_file_reference id when i save the object. So i thought i could use the ConnectionPool to select the sys_file row of this file reference and then insert the data on the field 'succText'.
I tried this:
public function processDatamap_preProcessFieldArray(array &$fieldArray, $table, $id, \TYPO3\CMS\Core\DataHandling\DataHandler &$pObj) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
$findItemsId = $queryBuilder
->select('*')
->from('sys_file_reference')
->join(
'sys_file_reference',
'sys_file',
'reference',
$queryBuilder->expr()->eq('reference.uid', $queryBuilder->quoteIdentifier('uid_local'))
)
->where(
$queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter($fieldArray['downloads'], \PDO::PARAM_INT))
)
->execute();
}
But this give me back the sys_file_reference id and not the id and the field values of the sys_file table.
As for the update, i havent tried it yet, cause i haven't figured out yet, how to get the row that needs to be updated. I gues with a subquery after the row is found, i don't really know.
The processDatamap_preProcessFieldArray is going to be renamed to post. I only have it this way in order to get the results on the backend.
Thanks in advance,
You might want to make use of the FileRepository class here.
$fileRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\FileRepository::class);
$fileObjects = $fileRepository->findByRelation('tablename', 'fieldname', $uid);
Where $uid is the ID of the record that the files are connected to via file reference.
You will get back an array of file objects to deal with.
I resolved my problem by removing the first code and adding a filerepository instance.
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
$fileObjects = $fileRepository->findByRelation('targetTable', 'targetField', $uid);
VERY IMPORTANT!
If you are creating a new element then TYPO3 assigns a temp UID variable with a name that looks like this NEW45643476. In order to get the $uid from the processDatamap_afterDatabaseOperations you need to add this code before you get the instance of the fileRepository.
if (GeneralUtility::isFirstPartOfStr($uid, 'NEW')) {
$uid = $pObj->substNEWwithIDs[$uid];
}
Now as far as the text concerns, i extracted from a pdf. First i had to get the basename of the file in order to find its storage location and its name. Since i have only one file i don't really need a foreach loop and i can use the [0] as well. So the code looked like this:
$fileID = $fileObjects[0]->getOriginalFile()->getProperties()['uid'];
$fullPath[] = [PathUtility::basename($fileObjects[0]->getOriginalFile()->getStorage()->getConfiguration()['basePath']), PathUtility::basename($fileObjects[0]->getOriginalFile()->getIdentifier())];
This, gives me back an array looking like this:
array(1 item)
0 => array(2 items)
0 => 'fileadmin' (9 chars)
1 => 'MyPdf.pdf' (9 chars)
Now i need to save the text from every page in a variable. So the code looks like this:
$getPdfText = '';
foreach ($fullPath as $file) {
$parser = new Parser();
$pdf = $parser->parseFile(PATH_site . $file[0] . '/' . $file[1]);
$pages = $pdf->getPages();
foreach ($pages as $page) {
$getPdfText .= $page->getText();
}
}
Now that i have my text i want to add it on the database so i will be able to use it on my search action. I now use the connection pool to get the file from the sys_file.
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file');
$queryBuilder
->update('sys_file')
->where(
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($fileID))
)
->set('pdf_text', $getPdfText)
->execute();
Now everytime i choose a PDF from my extension, i save its text on the database.
EXTRA CONTENT
If you want to include the PDFParser as well and you are on composer mode, then add this on your composer.json:
"smalot/pdfparser" : "*"
and on the autoload:
"Smalot\\PdfParser\\" : "Packages/smalot/pdfparser/src/"
Then under: yourExtension/Classes/Hooks/DataHandler.php add the namespace:
use Smalot\PdfParser\Parser;
Now you are able to use the getPages() and getText() functions.
The Documentation
If i missed something let me know and i will add it.

zf2 When I do addFeature(new Feature\SequenceFeature,...) for Postgres PDO, insert fails because columns and values are nulled out

I'm in Zend Framework 2, trying to get the last inserted id after inserting using postgresql PDO. The insert works fine unless I add a SequenceFeature, like this:
class LogTable extends AbstractTableGateway
{
protected $table = 'log';
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
$this->featureSet = new Feature\FeatureSet();
$this->featureSet->addFeature(new Feature\SequenceFeature('id','log_id_seq'));
$this->resultSetPrototype = new ResultSet();
$this->resultSetPrototype->setArrayObjectPrototype(new Log());
print_r($this->getFeatureSet());
$this->initialize();
}
When I later do an insert like this:
$this->insert($data);
It fails, because INSERT INTO "log" () VALUES (), so for some reason zf2 is nulling out the columns and values to insert, but only if I add that SequenceFeature.
If I don't add that feature, the insert works fine, but I can't get the last sequence value. Debugging the Zend/Db/Sql/Insert.php, I found that the values function is accessed twice with the SequenceFeature in there, but only once when it's not in there. For some reason when the SequenceFeature is there, all the insert columns and values are nulled out, possibly because of this double call? I haven't investigated further yet, but maybe it's updating the sequence and then losing the data when making the insert?
Is this a bug, or is there just something I'm missing?
Screw it! We'll do it live!
Definitely not the best solution, but this works. I just cut and pasted the appropriate code from Zend/Db/TableGateway/Feature/SequenceFeature.php and added it as a function to my LogTable class:
public function nextSequenceId()
{
$sql = "SELECT NEXTVAL('log_id_seq')";
$statement = $this->adapter->createStatement();
$statement->prepare($sql);
$result = $statement->execute();
$sequence = $result->getResource()->fetch(\PDO::FETCH_ASSOC);
return $sequence['nextval'];
}
Then I called it before my insert in my LogController class:
$data['id'] = $this->nextSequenceId();
$id = $this->insert($data);
Et voila! Hopefully someone else will explain to me how I'm really supposed to do it, but this will work just fine in the interim.

Zend_Db - Building a Delete Query

I'm trying to recreate the following in Zend Framework and am not sure how to do it:
DELETE FROM mytablename WHERE date( `time_cre` ) < curdate( ) - INTERVAL 4 DAY
I was thinking something like:
$table = $this->getTable();
$db = Zend_Registry::get('dbAdapter');
$db->delete($table, array(
'date(`time_cre`) < curdate() - interval 4'
));
Does that seem correct?
What is the best way to handle something like this?
EDIT: Ack! Sorry, I was adapting this from a SELECT I was using to test and didn't change the syntax properly when I pasted it in. I've edited the example to fix it.
Figured it out...
public function pruneOld($days) {
$table = $this->getTable();
$db = Zend_Registry::get('dbAdapter');
$where = $db->quoteInto("DATE(`time_cre`) < CURDATE() - INTERVAL ? DAY", $days);
return $table->delete($where);
}
$table gets an reference of the table I want to edit...
$db grabs an instance of the database adapter so I can use quoteInto()...
$where builds the main part of the query accepting $days to make things a bit more flexible.
Create an action to call this method... something like:
public function pruneoldAction() {
// Disable the view/layout stuff as I don't need it for this action
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
// Get the data model with a convenience method
$data = $this->_getDataModel();
// Prune the old entries, passing in the number of days I want to be older than
$data->pruneOld(2);
}
And now hitting: http://myhost/thiscontroller/pruneold/ will delete the entries. Of course, anyone hitting that url will delete the entries but I've taken steps not included in my example to deal with this. Hope this helps someone.
I usually do this to delete a row
$table = new yourDB;
$row = $table->fetchRow($table->select()->where(some where params));
$row->delete();
try:
$db->delete($table, array(
'date(time_cre) < ?' => new Zend_Db_Expr('curdate() - interval 4')
));