Extbase Hooks - execute code upon record creation - typo3

I want to create a standard typo3 extension but when I create a record (or modify it) I want to calculate something (in my case I want to call the Google Map API to get coordinates from a given address).
SO I search for a hook or something. Any idea?

One of my project example, may helps you for hook in backend when record has been changed.
In your extension file ext_localconf.php
// Hook for cancellation
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = 'EXT:femanager/class.tx_femanager_tcemainprocdm.php:tx_femanager_tcemainprocdm';
hook file class.tx_femanager_tcemainprocdm.php where you can execute
your script
class tx_femanager_tcemainprocdm{
function processDatamap_postProcessFieldArray ($status, $table, $id, &$fieldArray, &$reference){
// $status also called action like delete
// $table - table name of excute backend action
// $id - record UID
// $fieldArray - fields of your table
if($table = 'your_extension_table_name'){
// your script
}
}
}

Maybe this answer is useful to you.
Register your class as a data handling hook in your extension. This one "is called AFTER all commands of the commandmap" were executed. Maybe you need to look for a more appropriate one.
Then in your registered Hook i.e. 'typo3conf/ext/your_ext/Classes/Hooks/AfterCreate.php' do your calculation. Hope this sets you on the right track.

In my special case there was no need to calculate the coordinates when the record got saved. So I just used the listAction in the controller, check if coordinates are there and if not call the Google API (and send an email if the Google API does not give a coordinate back).
In another case where the new record comes from a frontend plugin and I had to do something with this data I used the createAction in the Controller. (I am not sure if the createAction is also called when the record is created from the backend.)

Related

How can I create and update pages dynamically in Sulu CMS?

I have the following situation:
A database stores information about houses (address, number of rooms, date built, last selling price, etc.)
This database is being manipulated through an app (let's call that app the "backend house app") that cannot be directly integrated in a Sulu-driven app. I can access the stored data through an API that gives me JSON-representations of House-objects. I can also have the app launch some sort of call to a Sulu-driven app when a house is created, updated or deleted.
The Sulu-driven app (let's call that the "frontend house app") with templates for "house", "room", etc., is connected to a different database on a different server. This Sulu-driven app's website-environment shows house-pages with room-pages where some content is pre-filled through a connection to the "backend house app". Other content only exists on the database of the "frontend house app", like user comments, appraisals of interior design, etc., according to configured aspects of the Sulu-templates.
What I want to achieve, is a way to automate the creation, updating and deletion of "frontend house app"-pages based on activity in the "backend house app".
For instance, when a new house is added in the "backend house app", I want it to notify the "frontend house app" so that the "frontend house app" will automatically create the entire node-tree for the newly added house. Meaning: a "house"-page with the required data filled in, "room"-pages for each room, etc., so that the content manager of the "frontend house app" can see the entire tree of the newly added house in the workspace and can start manipulating content in the already available templates. In addition to automatically creating these pages, I also want to pre-set the rights to update and create, since the content manager of the "frontend house app" must not be able to create new rooms or change the name of the house, for instance.
I did not manage to get it working, I'll just add what I already done to show where I got stuck.
I started out with the following code, in a controller that extends Sulu's own WebsiteController:
$documentManager = $this->get('sulu_document_manager.document_manager');
$nodeManager = $this->get('sulu_document_manager.node_manager');
$parentHousesDocument = $documentManager->find('/cmf/immo/routes/nl/huizen', 'nl');
$newHouseDocument = $documentManager->create('page');
// The backendApi just gives a House object with data from the backend
// In this case we get an existing House with id 1
$house = $backendApi->getHouseWithId(1);
$newHouseDocument->setTitle($house->getName()); // For instance 'Smurfhouse'
$newHouseDocument->setLocale('nl'); // Nl is the only locale we have
$newHouseDocument->setParent($parentHouseDocument); // A default page where all the houses are listed
$newHouseDocument->setStructureType('house'); // Since we have a house.xml template
// I need to grab the structure to fill it with values from the House object
$structure = $newHouseDocument->getStructure();
$structure->bind([
'title' => $house->getName(),
'houseId' => $house->getId(),
]);
$newHouseDocument->setWorkflowStage(WorkflowStage::PUBLISHED); // You would expect this to automatically publish the document, but apparently it doesn't... I took it from a test I reverse-engineered in trying to create a page, I have no clue what it is supposed to change.
$nodeManager->createPath('/cmf/immo/routes/nl/huizen/' . $house->getId());
$documentManager->persist(
$newHouseDocument,
'nl',
[
'path' => '/cmf/immo/contents/huizen/' . Slugifier::slugify($house->getName()), // Assume for argument's sake that the Slugifier just slugifies the name...
'auto_create' => true, // Took this value from a test that creates pages, don't know whether it is necessary
'load_ghost_content' => false, // Idem
]
);
$documentManager->flush();
Now, when I fire the controller action, I first get the exception
Property "url" in structure "house" is required but no value was given.
I tried to fix this by just manually binding the property 'url' with value '/huizen/' . $house->getId() to $structure, at the point where I bind the other values. But this doesn't fix it, as apparently the url value is overwritten somewhere in the persist event chain, and I haven't yet found where.
However, I can, just for testing purposes, manually override the url in the StructureSubscriber that handles the mapping for this particular persist event. If I do this, something gets created in the Sulu-app-database - hurray!
My phpcr_nodes table lists two extra records, one for the RouteDocument referring to /cmf/immo/routes/nl/huizen/1, and one for the PageDocument referring to /cmf/immo/contents/huizen/smurfhouse. Both have the workspace_name column filled with the value default_live. However, as long as there are not also records that are complete duplicates of these two records except with the value default in the workspace_name column, the pages will not appear in the Sulu admin CMS environment. Needless to say, they will also not appear on the public website proper.
Furthermore, when I let the DocumentManager in my controller action try to ->find my newly created document, I get a document of the class UnknownDocument. Hence, I cannot have the DocumentManager go ->publish on it; an Exception ensues. If I visit the pages in the Sulu admin environment, they are hence unpublished; once I publish them there, they can be found by the DocumentManager in the controller action - even if I later unpublish them. They are no longer UnknownDocument, for some reason. However, even if they can be found, I cannot have the DocumentManager go ->unpublish nor ->publish - that just has NO effect on the actual documents.
I was hoping there would be a Sulu cookbook-recipe or another piece of documentation that extensively describes how to create fully published pages dynamically, thus without going through the 'manual labor' of the actual CMS environment, but so far I haven't found one... All help is much appreciated :)
PS: For the purposes of being complete: we're running Sulu on a Windows server environment on PHP 7.1; dbase is PostgreSQL, Sulu being a local forked version of release tag 1.4.7 because I had to make some changes to the way Sulu handles uploaded files to get it to work on a Windows environment.
EDIT: a partial solution for making a new house page if none exists already (not explicitly using the AdminKernel, but should of course be run in a context where the AdminKernel is active):
public function getOrCreateHuisPagina(Huis $huis)
{
$parent = $this->documentManager->find('/cmf/immo/routes/nl/huizen', 'nl'); // This is indeed the route document for the "collector page" of all the houses, but this doesn't seem to give any problems (see below)
try {
$document = $this->documentManager->find('/cmf/immo/routes/nl/huizen/' . $huis->id(), 'nl'); // Here I'm checking whether the page already exists
} catch(DocumentNotFoundException $e) {
$document = $this->setupPublishedPage();
$document->setTitle($huis->naam());
$document->setStructureType('huis_detail');
$document->setResourceSegment('/huizen');
$document->setParent($parent);
$document->getStructure()->bind([
'title' => $huis->naam(), // Not sure if this is required seeing as I already set the title
'huis_id' => $huis->id(),
]);
$this->documentManager->persist(
$document,
'nl',
[
'parent_path' => '/cmf/immo/contents/huizen', // Explicit path to the content document of the parnt
]
);
}
$this->documentManager->publish($document, 'nl');
return $document;
}
First of all I think the following line does not load what you want it to load:
$parentHousesDocument = $documentManager->find('/cmf/immo/routes/nl/huizen', 'nl');
It loads the route instead of the page document, so it should look like the following:
$parentHousesDocument = $documentManager->find('/cmf/immo/contents/nl/huizen', 'nl');
Regarding your error with the URL, instead of overriding the StructureSubscriber you should simple use the setResourceSegment method of the document, which does exactly what you need :-)
And the default_live workspace is wrong, is it possible that you are running these commands on the website kernel? The thing is that the WebsiteKernel has the default_live workspace as default, and therefore writes the content in this workspace. If you run the command with the AdminKernel it should land in the default workspace, and you should be able to copy it into the default_live workspace with the publish method of the DocumentManager.
I hope that helps :-)

Trying to add a logic hook to suiteCRM when creating or updating a task

This is my first try into coding for sugarCRM / suiteCRM.
I should say I've been coding for Wordpress for nearly 10 years now, but I'm completely lost now I'm starting to dig into suiteCRM.
I've read that you can add a logic hook to modify the data after saving it to the database, but I don't know where to start...
Imagine I create a task for today, july 7th, related to a client I use to visit every 2 months, so there's a field in Accounts named "Visiting frequency". I'd like to add a future date (july 7th + 60 days = september 7th aprox) into the task's "Future Visiting Date" field, so I can use it to create that particular future task via Workflow.
What I'm trying to do is to calculate a field in tasks (Future visiting date), that equals to the amount of days on the accounts module's field (Visiting frequency) added to the task's own Date field.
I've been able to make it work, using the following layout:
Inside \custom\modules\Tasks\logic_hooks.php
<?php
$hook_version = 1;
$hook_array['before_save'] = Array();
$hook_array['before_save'][] = Array(
1, //Processing index. For sorting the array.
'future_task_date_on_task_creation', //Label. A string value to identify the hook.
'custom/modules/Tasks/future_visit_date.php', //The PHP file where your class is located.
'before_save_class', //The class the method is in.
'future_visit_date' //The method to call.
);
?>
Inside \custom\modules\Tasks\future_visit_date.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class before_save_class {
function future_visit_date($bean, $event, $arguments) {
$bean->rhun_fecha_sig_c = date("Y-m-d H:i:s", $date);
}
}
?>
With this setup, the Future Visiting Date gets filled with the calculated date.
I've also read that this setup is not advised, and that I should use the Extension Framework and put the first file in this path:
/custom/Extension/modules/Tasks/Ext/LogicHooks/<file>.php
But I can't make it work.
Do I have to create the LogicHooks folder if it's not there?
Which filename should I assign to this file?
Do I have to change something else inside the code?
Yes, create the LogicHooks directory if it doesn't exist. The PHP file can be called anything you like.
/custom/Extension/modules/Tasks/Ext/LogicHooks/MyLogicHookFile.php
Define your logic hooks in this file as before.
<?php
$hook_version = 1;
$hook_array['before_save'] = Array();
$hook_array['before_save'][] = Array(
1, //Processing index. For sorting the array.
'future_task_date_on_task_creation', //Label. A string value to identify the hook.
'custom/modules/Tasks/future_visit_date.php', //The PHP file where your class is located.
'before_save_class', //The class the method is in.
'future_visit_date' //The method to call.
);
Then run a repair and rebuild from the Admin panel.
The main advantage to using the Extension framework is that it allows multiple developers to add components to a Sugar instance without worrying about overwriting existing code.
More info can be found about it in the Developer Guide

Change the status bar of an ADempiere window

How can I change the status bar text in an ADempiere window in order to show a message when a new record is created?
Also, how can I create a pop-up that appears when a new record is created?
You can put a message in center of window when a new record is created, this function already exists on iDempiere, but on ADempiere you'll need to change code for each docaction, or for each table you code is listening for.
On Idempiere you can check the code of class AbstractADWindowContent.java on package org.adempiere.ui.zk
check this link , line 2104
You can put a status message in status bar in Adempiere using the following method in org.compiere.model.GridTable
/**
* Create and fire Data Status Info Event
* #param AD_Message message
* #param info additional info
*/
protected void fireDataStatusIEvent (String AD_Message, String info)
{
DataStatusEvent e = createDSE();
e.setInfo(AD_Message, info, false,false);
fireDataStatusChanged (e);
}
You will find an example of its use within the same class, when a row is saved via the dataSave(boolean) method. If all goes to plan and record is saved at the end of the method you'll see
fireDataStatusIEvent("Saved", "");
This puts the default “Saved” message see in the application when you click save in any tab.
There are two recommended approaches to customising Adempiere.
Callouts; are used to add complex defaulting & validation in the
User Interface
Model Validators; are used to apply business logic or validation when a number of data model events, such as a record being saved, occur. But, not all changes are happening at the time the UI events are occurring... as with the accounting module, for example, so the model validator mechanisms assume no user interface exists.
Your requirement to have something happen in the UI when a data model event occurs falls between the two. For your requirement it might be easiest just to modify this default message (highlighted above in dataSave()) to display what you would like. But GridTable is at the core of the application so keep in mind that any time you update/upgrade Adempiere in the future you will need to make this modification again!

Get field values in record.js

I override the record view, by creating in custom/modules/myModule/clients/base/views/record/record.js this file record.js. I want to get the value of a field for the current object and I use this.model.get('duration'), but I get nothing.The only field available is "id". How can I retrieve the values for others fileds?
When the record.js script is initially called, the model won't have fully loaded, so the only available field with be the id.
Your best bet is probably to override the _renderHtml function; by the the time the view is being rendered all the model details will have fully loaded:
_renderHtml: function() {
// custom code involving this.model.get('duration')
// call parent
app.view.View.prototype._renderHtml.call(this);
}
Note that you may find _renderHtml is called multiple times, sometines before the model is fully loaded. This is just a quirk of Sugar so it may be best to add a check in your code:
if (this.model.get('duration')) {
// custom code involving this.model.get('duration')
}
Dont forget that app.model.get('myfield') only delivers the right content (from this field) when your field is already displayed in detailview - else you will get "undefined"!
So you
Have to call the rest api (rest/v10/yourmodel/yourid) - than you
have all the values available
Display your fields (even you dont want to) to be able to use it in app.model.get('yourfield'), an alternative you could append your record.js (after rendering) with $('div [data-name="yourfield"]').hide();
I know this question is quite old already (but if someone else run into this he could find this useful).

Symfony form gets messy when calling getObject() in form configuration

I have a Strain model that has a belongsTo relationship with a Sample model, i. e. a strain belongs to a sample.
I am configuring a hidden field in the StrainForm configure() method this way:
$defaultId = (int)$this->getObject()->getSample()->getTable()->getDefaultSampleId();
$this->setWidget('sample_id', new sfWidgetFormInputHidden(array('default' => $defaultId)));
Whenever I create a new Strain, the $form->save() fails. The debug toolbar revealed that it tries to save a Sample object first and I do not know why.
However, if I retrieve the default sample ID using the table it works like a charm:
$defaultId = (int)Doctrine_Core::getTable('Sample')->getDefaultSampleId();
$this->setWidget('sample_id', new sfWidgetFormInputHidden(array('default' => $defaultId)));
My question here is what can be happening with the getObject()->getSample()... sequence of methods that causes the StrainForm to think it has to save a Sample object instead of Strain.
I tried to debug with xdebug but I cannot came up with a clear conclusion.
Any thoughts?
Thanks!!
When you call getSample its creating a Sample instance. This is automatically attached to the Strain object, thus when you save you also save the Sample.
An altenrative to calling getSample would be to chain through Strain object to the Sample table since i assume youre only doing this so your not hardcodeing the Sample's name in related form:
// note Sample is the alias not necessarily the Model name
$defaultId = Doctrine_Core::getTable($this->getObject()->getTable()->getRelation('Sample')->getModel())->getDefaultId();
Your solution probably falls over because you can't use getObject() on a new form (as at that stage the object simply doesn't exist).
Edit: Why don't you pass the default Sample in via the options array and then access it from within the form class via $this->getOption('Sample') (if I remember correctly)?