Dynamic url.pageId in plugin.tx_seo config for own extension - slug

In my TYPO3 9 installation I'am working on a custom sitemap.xml for my own extension (Events). There I want to have a separate pageId / detail page for each entry.
As i understood in the config section of tx_seo I can only provide one specific pageId as detail page id - correct?
I tried this config:
plugin.tx_seo {
config {
xmlSitemap {
sitemaps {
veranstaltung {
provider = TYPO3\CMS\Seo\XmlSitemap\RecordsXmlSitemapDataProvider
config {
table = tx_ntevents_domain_model_veranstaltung
sortField = kursnummer
lastModifiedField = tstamp
recursive = 1
pid = 110
url {
pageId = # here different pages which can be found in tx_ntevents_domain_model_veranstaltung.seite1 #
fieldToParameterMap {
uid = nt_events_pi1[veranstaltung] # is this correct? #
}
additionalGetParameters {
nt_events_pi1.controller = Veranstaltung
nt_events_pi1.action = show
}
}
...
With this setup tx_seo finds all entries but does not
provide a specific page id for each entry and
does not generate the nice slug url
Here is the slug code from config.yaml
NtEvents:
type: Extbase
extension: NtEvents
plugin: Events
routes:
-
routePath: '/{veranstaltung-url}'
_controller: 'Veranstaltung::show'
_arguments:
veranstaltung-url: veranstaltung
defaultController: 'Veranstaltung::list'
aspects:
veranstaltung-url:
type: PersistedAliasMapper
tableName: tx_ntevents_domain_model_veranstaltung
routeFieldName: slug
routeValuePrefix: /

You can create your own DataProvider, in your case it would probably be easiest to copy the original "\TYPO3\CMS\Seo\XmlSitemap\RecordsXmlSitemapDataProvider" into your own extension and rewrite it according to your wishes or adapt it accordingly.

Related

Do I need to use Prisma's connect & disconnect API? Or is it not safe to just update my Relations via IDs as strings?

I am using prisma + mysql (on planetscale). When I link two items that are in different tables, I normally use connect or disconnect:
const getUser = await prisma.user.update({
where: {
id: 9
},
data: {
posts: {
| connect: {
| id: 11
| },
create: {
title: "My new post title"
}
}
}
})
I am wondering whether that's necessary or why that's necessary?
I also noticed that I can just update records in my database by updating the id (as a plain string), and it will still work. e.g.:
// example for updating a one-to-many relationship:
const getUser = await prisma.user.update({
where: {
id: 9
},
data: {
postId: "123192312i39123123"
}
}
})
... or if it's an explicit many-to-many relation, I can just edit the row in the relation-table & update the id.
Is this a bad way of doing things? Am I going to break something later down the line in doing it this way?
Your cloud provider is not relevant in the context of the question. It will not affect how your framework(prisma) behaves in updates.
I am wondering whether that's necessary or why that's necessary?
You have a user with a one to many relation: user => n posts.
You have an existing post in the db, and you want to add that post to the posts collection of a user.
That posts relation can be either explicit or implicit. The connect clause handles the addition of relation:
{
posts: {
connect: { id: 11 }
}
}
Without using the connect you'd have to create a new post:
{
posts: {
create: {
title: "My new post title"
}
}
}
update records in my database by updating the id (as a plain string)
Not sure what you mean here, mind sharing the schema?
or if it's an explicit many-to-many relation, I can just edit the row in the relation-table & update the id
If it's explicit many-to-many then it's OK to manually edit the id fields. As long as the ids are found and the relation makes sense, there's no problem with manual updates.

How to omit "index" entry from an autogenerated Docusaurus V2 sidebar

At work we have a basic Docusaurus v2 page for user documentation, and I can't share it for privacy reasons. Suffice it to say it has a sidebar which is autogenerated, where the top level contains a number of folders as categories and each category only contains .md files.
At the top level (the level of the categories) there is an empty index.md file that only exists so that the page will load. The autogenerated sidebar includes an index entry that points to a blank page. I would like to hide/get rid of this entry.
I have looked at this github discussion on something similar, but I haven't been able to make the solutions work. The sidebar.js file has the following simple contents:
module.exports = {
docs: [
{
type: 'autogenerated',
dirName: '.'
},
],
};
I have tried adding an exclude: ['path\to\index\file'] line, but this results in the error "exclude" is not allowed.
What is the proper way of hiding this index entry from the sidebar? Alternatively, is there a way to set up the site so that the index.md file is not needed at all?
I have the same setup:
/folder1
/file
/folder2
/file
index
And I wand to autogenerate the sidebar with two categories only:
folder1
folder2
Moreover, I wanted to click on the navbar and see index.
I was able to do so by:
Create a custom sidebar
function skipIndex(items) {
return items.filter(({ type, id }) => {
return type !== 'doc' || id !== 'index';
});
}
module.exports = async function sidebarItemsGenerator({ defaultSidebarItemsGenerator, ...args }) {
const sidebarItems = await defaultSidebarItemsGenerator(args);
return skipIndex(sidebarItems);
}
Then in the docusaurus.config.js
presets: [
[
'classic',
({
// https://docusaurus.io/docs/api/plugins/#docusaurus/plugin-content-docs#configuration
docs: {
sidebarItemsGenerator: require('./sidebar.js'),
},
And finally in the index.md file I must add this metadata, otherwise when I reach the index page, the sidebar disappears because the page is not included:
---
displayed_sidebar: docsSidebar
---

Create Country List from JSON

I am trying to create a combo box with a list of countries in SAP UI5.
I have created a combo box and have created dynamic list of some countries, but to create more than 100 countries, the only easy way is to create a JSON file of countries and then populate in Controller.js.
I tried to create a JSON file but I am unsure whether I have to store it under model folder or root.
What changes do I have to make in my XML view and controller, and where should I attach countries.json file?
You are looking at something called as "Aggregation Binding" Aggregation Binding in XML views
Here is an example to refer to which explains
How to create a model using data from json file
How to Bind model data to the XML view control(you have to bind comboBox instead of table)
How to bind json data model to an XML view
Let me know if this helps.
Maybe you don't need to create the countries.json file at all :)
As UI5 leverages Common Locale Data Repository (CLDR) internally and provides the data via sap.ui.core.LocaleDataAPI, which includes language names, country names, currency names, singular/plural modifications, and more..
A list of supported regions for the locale data are stored in a JSON format here. In one of those files, if you look at the property "territories", you'll see that the country names are listed among them. You can filter every irrelevant territory out that is not considered a country, and then bind the rest in the items aggregation of the combo box.
Demo
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/ui/core/Locale",
"sap/ui/core/LocaleData",
"sap/ui/model/json/JSONModel",
"sap/ui/core/mvc/XMLView",
], function(Locale, LocaleData, JSONModel, XMLView) {
"use strict";
XMLView.create({
definition: `<mvc:View xmlns:mvc="sap.ui.core" xmlns="sap.m"
height="100%"
displayBlock="true">
<ComboBox class="sapUiTinyMargin"
width="15rem"
placeholder="Select a country.."
filterSecondaryValues="true"
showSecondaryValues="true"
items="{
path: '/',
templateShareable: false,
key: 'code',
sorter: { path: 'name' }
}">
<core:ListItem xmlns:core="sap.ui.core"
key="{code}"
text="{name}"
additionalText="{code}" />
</ComboBox>
</mvc:View>`,
models: createCountryModel(getCountries()),
}).then(view => view.placeAt("content"));
function createCountryModel(countries, sizeLimit = 300) {
const model = new JSONModel(countries);
model.setSizeLimit(sizeLimit);
model.setDefaultBindingMode("OneWay");
return model;
}
function getCountries() {
const territories = getTerritories();
return extractCountriesFrom(territories, byCustomCheck());
}
function getTerritories(localeId) {
const currentConfig = sap.ui.getCore().getConfiguration();
const locale = localeId ? new Locale(localeId) : currentConfig.getLocale();
const localeData = new LocaleData(locale);
return localeData.getTerritories(); // includes country names
}
function extractCountriesFrom(territories, customCheck = () => true) {
const isValidCountry = createCountryCheck(customCheck);
const toObject = code => Object.freeze({
code: code,
name: territories[code],
});
const countryObjects = Object.keys(territories)
.filter(isValidCountry)
.map(toObject);
return Object.freeze(countryObjects);
}
function createCountryCheck(customCheck, obviouslyNotCountries = [
"EU", // "European Union"
"EZ", // "Eurozone"
"UN", // "United Nations"
"ZZ", // "Unknown Region"
]) {
return territoryCode => territoryCode.length == 2
&& !obviouslyNotCountries.includes(territoryCode)
&& customCheck(territoryCode);
}
function byCustomCheck() { // returns a function that returns boolean
// E.g.: list of sanctioned countries you want to exclude
const list = [
"AF",
"KP",
"IR",
// ...
];
return countryCode => !list.includes(countryCode);
}
}));
<script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.core, sap.m"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-async="true"
data-sap-ui-compatversion="edge"
data-sap-ui-xx-waitfortheme="init"
></script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
As you can see in the example, the ComboBox is successfully populated with the countries. When a new LocaleData instance is created, a request is sent immediately (currently via sync XHR) to get the data which are translated in the language that UI5 detects from the client settings. If no language could be detected, the en.json file will be retrieved.src
The above approach has the following advantages:
No need to create and maintain a separate "country" list. ✔️
Multilingual support ✔️
Reusability ✔️ - When UI5 tries to fetch the same locale data file, which is the case when e.g. a Calendar is used, the browser can serve the file quickly from the cache since the same file was already fetched before.
Note
When creating a JSONModel to store more than 100 country names, keep in mind to increase the size limit as well. The current default limit is 100.

Using Dancer2::Plugin::DBIC to pull values from database

I have a webapp where a user can log in and see a dashboard with some data. I'm using APIary for mock data and in my Postgres Database each of my users have an ID. These ID's are also used in the APIary JSON file with relevant information.
I'm using REST::Client and JSON to connect so for example the url for the user's dashboard is: "/user/dashboard/12345" (in Apiary)
and in the database there is a user with the ID "12345".
How can I make it so when the user logs in, their ID is used to pull the data that is relevant to them? (/user/dashboard/{id})? Any documentation or advice would be much appreciated!
The docs of Dancer2::Plugin::Auth::Extensible are showing one part of what you need to do already. In short, save the user ID in the session. I took part of code in the doc and added the session.
post '/login' => sub {
my ($success, $realm) = authenticate_user(
params->{username}, params->{password}
);
if ($success) {
# we are saving your user ID to the session here
session logged_in_user => params->{username};
session logged_in_user_realm => $realm;
} else {
# authentication failed
}
};
get '/dashboard' => sub {
my $client = REST::Client->new();
# ... and now we use the user ID from the session to get the
# from the webservice
$client->GET( $apiary . '/user/dashboard/' . session('logged_in_user') );
my $data = $client->responseContent();
# do stuff with $data
};
For those who want to know what I ended up doing:
Dancer2::Plugin::Auth::Extensible has
$user = logged_in_user();
When I printed this it showed me a hash of all the values that user had in the database including the additional ID I had. So I accessed the id with
my $user_id = $user->{user_id};
And appended $user_id to the end of the url!

Grails hibernate/Searchable stops server to start by giving the exception below

We are using Grails 2.1.1 and Searchable plugin 0.6.4 in our Grails applications and implemented searchable on some domains which are indicated below with all the mappings.
class User {
.....
static hasMany = [userEducations : UserEducations , userWorkings : UserWorkings ]
......
static searchable = {
content: spellCheck 'include'
all termVector: "yes"
userEducations component: true
userWorkings component: true
}
......
}
class UserEducations {
.....
Schools schools
.....
static belongsTo = [user : User ]
......
static searchable = {
content: spellCheck 'include'
all termVector: "yes"
schools component: true
}
......
}
class UserWorkings {
.....
Organizations organizations
.....
static belongsTo = [user : User ]
....
static searchable = {
content: spellCheck 'include'
all termVector: "yes"
organizations component: true
}
......
}
class Schools {
......
static searchable = true
......
}
class Organizations {
......
static searchable = true
......
}
The data is saving successfully with correct mapping and constraints.
The problem starts when we have the drowslike below in table user with relationship
User a1 -> UserEducations b1 -> Schools d1
and
User a2 -> UserEducations b2 -> Schools d1
or
User a1 -> UserWorkings c1 -> Organizations e1
and
User a2 - > UserWorkings c2 -> Organizations e1
(We are not sure about above fact may be the problem happened due to large no. of data.)
Then when we try to start the server then we receive below exception and server wouldn't start
We have tried by removing searchable index and restarting again then it also not start.
The server starts only when we truncate tables corresponding to above 5 domains.
18:30:54,133 [Compass Gps Index [pool-5-thread-5]] ERROR indexer.ScrollableHibernateIndexEntitiesIndexer - {hibernate}: Failed to index the database
org.compass.core.engine.SearchEngineException: Processor [4]: Failed to add job [Job Create [alias [Organizations] uid [Organizations#456#]] Resource [{Organizations} [stored/uncompressed,indexed,omitNorms<alias:Organizations>],[stored/uncompressed,indexed,omitNorms,omitTf<$/Organizations/id:456>],[stored/uncompressed,indexed<active:true>],[stored/uncompressed,indexed<dateCreated:2013-02-28-14-03-05-0-PM>],[stored/uncompressed,indexed,tokenized<aaa:109122482450911>],[stored/uncompressed,indexed<lastUpdated:2013-02-28-14-03-05-0-PM>],[stored/uncompressed,indexed,tokenized<name:Asc>],[stored/uncompressed,indexed<version:0>],[stored/uncompressed,indexed,omitNorms,omitTf<$/uid:Bs#456#>]]] after [10000ms] and backlog size [100]
at org.compass.core.lucene.engine.transaction.support.AbstractConcurrentTransactionProcessor$Processor.addJob(AbstractConcurrentTransactionProcessor.java:496)
at org.compass.core.lucene.engine.transaction.support.AbstractConcurrentTransactionProcessor.create(AbstractConcurrentTransactionProcessor.java:158)
at org.compass.core.lucene.engine.LuceneSearchEngine.createOrUpdate(LuceneSearchEngine.java:290)
at org.compass.core.lucene.engine.LuceneSearchEngine.create(LuceneSearchEngine.java:268)
at org.compass.core.impl.DefaultCompassSession.create(DefaultCompassSession.java:413)
at org.compass.core.impl.DefaultCompassSession.create(DefaultCompassSession.java:397)
at org.compass.core.impl.ExistingCompassSession.create(ExistingCompassSession.java:305)
at org.compass.gps.device.hibernate.indexer.ScrollableHibernateIndexEntitiesIndexer$RowBuffer.flush(ScrollableHibernateIndexEntitiesIndexer.java:212)
at org.compass.gps.device.hibernate.indexer.ScrollableHibernateIndexEntitiesIndexer$RowBuffer.close(ScrollableHibernateIndexEntitiesIndexer.java:206)
at org.compass.gps.device.hibernate.indexer.ScrollableHibernateIndexEntitiesIndexer.performIndex(ScrollableHibernateIndexEntitiesIndexer.java:151)
at org.compass.gps.device.support.parallel.ConcurrentParallelIndexExecutor$1$1.doInCompassWithoutResult(ConcurrentParallelIndexExecutor.java:104)
at org.compass.core.CompassCallbackWithoutResult.doInCompass(CompassCallbackWithoutResult.java:29)
at org.compass.core.CompassTemplate.execute(CompassTemplate.java:133)
at org.compass.gps.impl.SingleCompassGps.executeForIndex(SingleCompassGps.java:147)
at org.compass.gps.device.support.parallel.ConcurrentParallelIndexExecutor$1.call(ConcurrentParallelIndexExecutor.java:102)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:679)
Our problem is similar to below post
http://grails.1312388.n4.nabble.com/hibernate-Searchable-failing-to-index-on-program-start-td4119566.html
We have tried our best to sort out the problem but no luck.
Please help us to solve this problem.
Go to your console and enter:
grails install-searchable-config
Then open myproject/grails-app/conf/Searchable.groovy and search for the following and change the values as I pointed out.
mirrorChanges = false
bulkIndexOnStartup = false
in Bootstrap.groovy:
class BootStrap {
def searchableService
def init = { servletContext ->
// do any data loading you would normally do
// Manually start the mirroring process to ensure that it comes after the automated migrations.
println "Performing bulk index"
searchableService.reindex()
println "Starting mirror service"
searchableService.startMirroring()
}
}