I am looking for a way to simplify the creation of content elements in TYPO3.
I am following the official documentation: https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/ContentElements/AddingYourOwnContentElements.html
Now, in step 2 we have this daunting beauty:
// Configure the default backend fields for the content element
$GLOBALS['TCA']['tt_content']['types']['yourextensionkey_newcontentelement'] = [
'showitem' => '
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
--palette--;;general,
--palette--;;headers,
bodytext;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:bodytext_formlabel,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;;frames,
--palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
--palette--;;language,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
--palette--;;hidden,
--palette--;;access,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories,
categories,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes,
rowDescription,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
',
'columnsOverrides' => [
'bodytext' => [
'config' => [
'enableRichtext' => true,
'richtextConfiguration' => 'default',
],
],
],
];
It looks to me, as if all that needs to be replaced is 'yourextensionkey_newcontentelement' and the rest comes from the core. Unless you know what you are doing and want to do this differently.
My questions:
What exactly does this do? (I am aware, it sets up some TCA for the forms of editing the CE.)
If this is the same for standard cases, can we get the entire array from the core, e.g. by providing a function for that? Would that be a good approach?
Do you have other ideas for simplifying this?
Are there methods available to write this human-readable and convert it or with autoexpand (e.g. by PhpStorm plugin)?
I am aware that there is an initiative working on improving how CE are handled longterm. What I am looking for now are things we can do shortterm to simplify CE creation. I am also aware there are extensions like "mask" or "dce" but we don't advertise them in the official docs, we advertise this: Create Custom Content Elements
Disclaimer: I am not an expert on creating content elements in TYPO3. Most of the time I write extensions with plugins or other functionality. This may be a stupid question / suggestion. Just let me know.
I understand your "problem" of the repeating code and copy / paste work, though it allows a lot of flexibility.
To your questions:
What exactly does this do? (I am aware, it sets up some TCA for the forms of editing the CE.)
The TYPO3 content elements are organised in so called palettes. A palette can contain several properties.
For example the palette "header" has:
header
header_layout
header_position
date
header_link
So if you want to include all that default header fields, just include the header palette to have all included fields.
You can see the most of the default palettes in frontend/Configuration/TCA/tt_content.php (see array with key 'palettes').
Within the column overrides you can easily override specific values / properties / settings, which are already defined in the core. In your example, it overrides the RTE settings of the field "bodytext".
The string itself looks a bit cryptic. The placeholders and what they do:
--div--;Label of the tab --> Starts a new tab with given label
--palette--;;hidden --> Loads a new palette WITHOUT a specific label
--palette--;Your label;hidden --> Loads a new palette WITH a specific label
If this is the same for standard cases, can we get the entire array from the core, e.g. by providing a function for that? Would that be a good approach?
Unfortunately, this is not an array, but a string. So you can't merge, except you want to split strings and combine them again. Somewhere within that string, you need to include your own fields / palettes. Also, not every new content element needs all fields. So in my opinion, it is better readable to have it fully implemented for every new content element.
Example:
$GLOBALS['TCA']['tt_content']['types']['alert'] = array(
'showitem' => '
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
--palette--;;general,
--palette--;LLL:EXT:your_ext/Resources/Private/Language/backend.locallang.xlf:tt_content.alert.palettes.general.title;alert,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;;frames,
--palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
--palette--;;language,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
--palette--;;hidden,
--palette--;;access,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes,
rowDescription,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
',
'columnsOverrides' => [
'bodytext' => [
'config' => [
'enableRichtext' => true
]
],
]
);
In this example, I load a custom palette directly after the core palette "general". This could also be done by a helper method. I don't like it :-)
function merge($yourDefinition): string
{
return '
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
--palette--;;general,
'.$yourDefinition.'
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;;frames,
--palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
--palette--;;language,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
--palette--;;hidden,
--palette--;;access,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes,
rowDescription,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended
';
}
Do you have other ideas for simplifying this?
Like mentioned, with a helper method or elsewhere string combining. In my opinion this will lead to less flexibility and also poor readability.
I don't like code repeating, but in this case, I define every new content element with it's own, uncombined / unmerged string...
Related
I'm looking for a way to extend the backend of the TYPO3 extension cart_products with one additional select field. I have already created some different ViewHelpers for the ProductBackendVariants and now need a way to choose the corresponding Viewhelper.
Best would be if there is a selection direct after the type select in the general product type. But I am not able to add some additional configuration fields to this tab.
Already added
'formtemplate' => [
'label' => 'ProductDetail Variant Form',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [
['Standard', 1],
['Matten', 2],
['Zaunpaket', 3],
],
'size' => 1,
'minitems' => 1,
'maxitems' => 1,
],
],
to the tx_cartproducts_domain_model_product_product.php but nothing appears in the backend.
I don't know that specific extension but some hints in general:
it seems that you have tried to modified the TCA directly in the extension (cart_products/Configuration/TCA/tx_cartproducts_domain_model_product_product.php). You should never change anything in a 3rd party extension (neither the TYPO3 core) because you loose the ability to update.
You need to create your own extension and add a file to Configuration/TCA/Overrides. Ideally you name the file exactly like the file you like to extend for easier understanding. See the documentation for details.
To add a new field to the TCA you not only need to define the name and type but you also need to create the database field. That's done in ext_tables.sql of your custom extension.
It's not sufficent to create the TCA config and SQL mentioned above you also need to tell, where the new field should be visible in the backend. That's done with the showitem setting. See documentation for details.
To ease the process of extending an existing TCA I can recommend the extension TCA Builder which makes it in many cases much easier to create the TCA code. See these examples how that's done. Nevertheless you need to create ext_tables.sql.
Last but not least: when you made changes to ext_tables.sql you need to run the database compare in the TYPO3 install tool.
I've made a custom page in backpack admin panel. This page is non-CRUD (not related to any model). There are several forms on it, with date pickers, select inputs, etc. So I'am trying to find a way to use backpack fields to create these date pickers and select inputs. Because it seems to be awkward to embed custom js-controls into the project, as Backpack already has appropriate fields.
The only solution I came up with, is to create a crud controller for random model, disable all operations except create, use create operation view as custom page (backpack fields are available this way), and finally override store() method - to prevent creating new model entry in DB.
So, is there a proper way to access backpack fields on custom (non-CRUD) page?
Backpack 4.x fields aren't meant to be used outside CRUDs, but you can do that.
Option A
At their core, Backpack fields are just Blade views, so you can load them using the Blade helper #include(). Just make sure to pass along all variables that the blade file needs. I believe in 99% of the fields that will be a $field and a $crud variable, so this will work:
#php
// set the CRUD model to something (anything)
// but ideally it'd be the model of the entity that has the form
$crud = app()->make('crud');
$crud->setModel(\App\Models\Monster::class);
#endphp
#include('crud::fields.number', [
'crud' => $crud,
'field' => [
'name' => 'price',
'label' => 'Price',
'prefix' => '$'
]
])
This way, you only load the bits you actually want (the inputs), without the overhead of a CrudController. You can point the form to your custom controller and do the saving yourself. What you need to pass for a $field above is a Backpack field definition in array form.
This way is super-simple, but it has a big downside if you ask me. The field definition has to be 100% correct and complete, you lose all the magic and assumption logic that Backpack usually does to make your life easier when you add field using addField(). That's why in most cases I think it's more convenient to go with Option B.
Option B
Instead of manually loading all each field Blade view, add them using addField(), then load all of them just like Backpack does it in the Create or Update operation:
#php
$crud = app()->make('crud');
$crud->setModel(\App\Models\Monster::class);
$crud->addField([
'name' => 'price',
'label' => 'Price',
'prefix' => '$'
]);
#endphp
<form method="post">
#include('crud::form_content', [ 'fields' => $crud->fields(), 'action' => 'create' ])
</form>
The above will produce an output like this:
The benefit of this second option is that you can "forget" to mention stuff in the field definition and Backpack will assume it, you can use the fluent syntax, you can use most Backpack features, really...
I have created a custom field in woocommerce where i want shopowners choose a year of publishing (books in this case).
So far i have:
//Custom Product Date Field
woocommerce_wp_text_input(
array(
'id' => '_custom_product_date_field',
'placeholder' => 'Publicatiedatum',
'label' => __('Publicatiedatum:', 'woocommerce'),
'type' => 'date',
'date-type' => 'years'
)
);
How can i set the date-type to years, as the last key => value (date-type:years) is not working?
I was curious since I was looking for details on using the date field in a woocommerce custom field. I didn't need year only specifically but it was an interesting rabbit hole to run down.
Trying to understand a bit more how woocommerce_wp_text_input creates these fields it started to become more apparent that supplying the type attribute simply passes it on to the HTML attributes. That said, these are standard HTML elements, not a wrapper of sorts that produces more fancy fields utilizing things like jQuery which seems like what the OP was expecting.
Looking over the specs for the date-related HTML text input fields it becomes apparent that there is not an <input> of type="year" available in the spec. We are limited to the type's specified in the specs.
I was able to successfully create a month input with the following:
woocommerce_wp_text_input(
[
'id' => '_my_month',
'label' => __('My month', 'woocommerce'),
'value' => get_post_meta($post->ID, '_my_month', true),
'type' => 'month',
'custom_attributes' => [
'min' => '2020-01',
],
]
);
So understandably, from what I can see, doing what you are asking isn't possible unless the HTML specifications add a year input or woocommerce provides a bandaid when specifying type as year. Hopefully this better explains how woocommerce_wp_text_input expects the data to be formatted and what is really supported.
As an aside that might assist in completing the requirements of the original question through alternate means, you could attempt to implement a jQuery UI picker which supports year only. I however feel that the jQuery UI picker using year only is a bit clumsy being it provides a popup to simply choose a date from a dropdown AND the left/right pagination of the popup still pages through months while showing only years. You might as well just use a dropdown or a number field with the date min/max values you require, both have example code that can be seen in other answers on that aforementioned answer I linked.
I've special requirement on my project and I need help. I am using TYPO3 8.7.8. I've a custom extension to render tag labels in frontend. We can add the tags as TCA record in backend storage folder. In the TCA record, you can tag name. My requirement is, when I save the TCA record I want to create a TYPO3 page automatically with the same name as tag in a specific position. Everytime when I add a TCA record, I need to create corresponding page automatically. Is this possible? I can use hook while saving TCA. But is there any function to create pages automatically?
After automatic page creation, I want to insert a plugin content element in that page with a specific flexform value automatically. I know this is a strange requirement, but I would like to know if it is possible or not.
Exactly, you'd trigger a hook on saving and then as next step you can use the data handler to generate the new page (and possible content).
To create the page and content, use something like the following data structure
$data = [
'pages' => [
'NEW_1' => [
'pid' => 456,
'title' => 'Title for page 1',
],
],
'tt_content' => [
'NEW_123' => [
'pid' => 'NEW_1',
'header' => 'My content element',
],
],
];
Then call the datahandler with that structure:
$tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
$tce->stripslashes_values = 0;
$tce->start($data, []);
$tce->process_datamap();
Find out more in the docs at
https://docs.typo3.org/typo3cms/CoreApiReference/ApiOverview/Typo3CoreEngine/Database/Index.html#data-array
and
https://docs.typo3.org/typo3cms/CoreApiReference/ApiOverview/Typo3CoreEngine/UsingDataHandler/Index.html
Are you sure you need additional pages?
In general your problem sounds like you need one page where the plugin is inserted and where the plugin in dependency of an url-parameter (which can be converted with realurl into a path segment) shows only information depending of the selected record (tag).
If no tag is selected you can output a list with all available tags as a menu to navigate to all possible tags.
With a little effort (less than writing a hook like intended) you can add all tags to your menu.
I'm trying to create a node form for a custom type. I have organic groups and taxonomy both enabled, but want their elements to come out in a non-standard order. So I've implemented hook_form_alter and set the #weight property of the og_nodeapi subarray to -1000, but it still goes after taxonomy and menu. I even tried changing the subarray to a fieldset (to force it to actually be rendered), but no dice. I also tried setting
$form['taxonomy']['#weight'] = 1000
(I have two vocabs so it's already being rendered as a fieldset) but that didn't work either.
I set the weight of my module very high and confirmed in the system table that it is indeed the highest module on the site - so I'm all out of ideas. Any suggestions?
Update:
While I'm not exactly sure how, I did manage to get the taxonomy fieldset to sink below everything else, but now I have a related problem that's hopefully more manageable to understand. Within the taxonomy fieldset, I have two items (a tags and a multi-select), and I wanted to add some instructions in hook_form_alter as follows:
$form['taxonomy']['instructions'] = array(
'#value' => "These are the instructions",
'#weight' => -1,
);
You guessed it, this appears after the terms inserted by the taxonomy module. However, if I change this to a fieldset:
$form['taxonomy']['instructions'] = array(
'#type' => 'fieldset', // <-- here
'#title' => 'Instructions', // <-- and here for good measure
'#value' => "These are the instructions",
'#weight' => -1,
);
then it magically floats to the top as I'd intended. I also tried textarea (this also worked) and explicitly saying markup (this did not).
So basically, changing the type from "markup" (the default IIRC) to "fieldset" has the effect of no longer ignoring its weight.
This sounds pretty strange because the manipulation of the form elements' #weight parameter always works reliably for me as advertised. One thing to note, though, is that the weights only affect the order relative to the elements siblings, so if the element you want to move around is on a level below that of the other elements, you'd have to change the weight of the parent element that is on the same level as the ones you want to move against.
To clarify, if you have a hierarchy like so,
$element['foo'];
$element['bar'];
$element['bar']['baz']
you can not move 'baz' relative to 'foo' by setting the weight of 'baz'. You'd have to either set the weight on 'bar' (moving it also), or pull out 'baz' and bring it to the same level as 'foo'.
Another possible reason could be CCK: If you have CCK installed, it allows you to set the order of its own as well as other fields under admin/content/node-type/<yourNodeType>/fields. It changes the order by registering the pre-render callback content_alter_extra_weights(), so this would run after your changes in hook_form_alter.
Edit: Update to answer the question update
The markup field type has a special behavior when used inside fieldsets, which is hinted on in the forms api documentation:
Note: if you use markup, if your content is not wrapped in tags (generally <p> or <div>), your content will fall outside of collapsed fieldsets.
It looks like if it does not only fall outside of collapsed fieldsets, but also refuses to respect the weight in those cases. Wrapping the content of the markup field in <p> tags makes it respect the weight on my machine:
$form['taxonomy']['instructions'] = array(
'#value' => "<p>These are the instructions</p>",
'#weight' => -1,
);
Sometimes (or always, when weighting CCK elements) the line that works in your hook_form_alter or $form['#after_build'] callback is this one:
$form['#content_extra_fields']['taxonomy']['weight'] = 5;
Wanted to insert a <div> I could use in JS. This didn't work for me:
$form['you_as_reviewer']['ui_container'] = array(
'#type' => 'markup',
'#value' => '<div id="le_reviewer_tags_ui"/>',
'#weight' => 5,
);
Weight was ignored.
This worked:
$form['you_as_reviewer']['ui_container'] = array(
'#type' => 'markup',
'#prefix' => '<div>',
'#value' => '<div id="le_reviewer_tags_ui"/>',
'#suffix' => '</div>',
'#weight' => 5,
);
Added prefix and suffix.
I do not quite understand what it is you want to achieve. Could you maybe clarify? Do you want to change the position of the taxonomy's drop-down on the page?
In the mean time you could install the Drupal Devel module (if you haven't done so yet). Then enable "Display form element keys and weights" from Admin > Devel Settings.
This should help you to debug your problem.
Edit after feedback:
I looked into it some more. The taxonomy weight is set in taxonomy.module on line 556 (Drupal 6.12):
$form['taxonomy']['#weight'] = -3;
To test I also implemented hook_form_alter for my module like this:
function mymodule_form_alter(&$form, $form_state, $form_id) {
...
$form['taxonomy']['#weight'] = -9;
...
}
This works for me i.e. it moves the taxonomy drop-down to the top of the page. I can change the weight and it moves accordingly on the rendered page.
Since you said you tried setting $form['taxonomy']['#weight'] in your original post I can currently think of only two possible checks:
Make sure the cache is cleared before testing. (you can use the Devel module for this)
Check to see if your hook_form_alter is called after taxonomy_form_alter
I you post the code you currently have we could look at it in more detail.
Please note: the weights displayed by the Devel module are not very useful for this situation. The weights of the elements on the "sub-forms" are displayed and not the weight of the "sub-form" itself. E.g. when I set $form['taxonomy']['#weight'] = -9; the -9 is not displayed by the Devel module but rather the weights of the elements inside $form['taxonomy'].
Have you specified the weight of another field and now your node form is not organized properly? The form api is kinda touchy and altering the form can result in things getting mixed up. I sometimes have to reassign a weight to my submit/preview buttons to get them back at the bottom of the form where they belong.
Just to cover all bases, make sure that you are clearing your cache as necessary. Drupal stores forms in it's cache by default, if you have the caching module enabled.
This is working for me
$form['forgot_password']['#markup'] = '<div class="text-align-right"><a class="text-primary" href="/user/password" target="_blank" title="Forgot Password?">Forgot Password?</a></div>';
$form['forgot_password']['#weight'] = 3;