Cette page ne doit être appliquée que si le script vous demande de passer la migration manuelle code.20260130.UD-UserAttributes
Dans un objectif de simplification et d'uniformisation, le type de contenu abstrait "utilisateur de l'annuaire" (org.ametys.plugins.userdirectory.Content.user) a été modifié pour inclure des attributs présents dans la plupart des surcharges et listés ci-dessous:
Tout type de contenu "Utilisateur" profite donc désormais de ces 3 nouveaux champs (en plus des attributs "title", "user", "orgunits")
Lors de la récupération de la photo de profil d'un utilisateur à partir de ses identifiants de connexion, c'est désormais l'attribut "image" qui sera retourné non plus "illustration/image".
Suite à cette modification, une migration du modèle, une migration graphique et une migration des données est nécessaire.
Cette migration est nécessaire dès lors que vous possédez un type de contenu Utilisateur qui étend l'un des 2 types:
- "Utilisateur de l'annuaire" (org.ametys.plugins.userdirectory.Content.user)
- "Membre d'un projet" (org.ametys.plugins.workspaces.Content.member)
Ou que vous possédez une surcharge du type "Membre d'un projet" (org.ametys.plugins.workspaces.Content.member)
Le type de contenu abstrait org.ametys.plugins.userdirectory.Content.user rajoute les attributs firstname (non obligatoire), lastname (non obligatoire) et image (non obligatoire)
Le type de contenu org.ametys.plugins.workspaces.Content.member (Membre projet) redéfini quant à lui les attributs firstname et lastname pour les rendre obligatoire.
Si vos types de contenu Utilisateurs définissent les attributs "firstname" et "lastname", vous pouvez supprimer leur définition, ceux-ci étant à présent apportés par le type parent.
Vérifiez au préalable que la définition noyau de ces attributs convient à votre projet (libellé, caractère obligatoire, ...).
Pour toutes vos vues définies avec la directive override="true", supprimez la référence à ces attributs, celle-ci étant déjà incluse dans les vues parentes.
La photo de profil dans vos types de contenus est certainement définie dans un composite "illustration".
Deux cas de figure:
Autre cas possible, vous avez un attribut hors composite mais pas nommé "image" (ex: photo, avatar) pour l'image de profil de vos utilisateurs.
Supprimer sa déclaration et ses références dans vos vues.
Attention ! L'image de profil utilisée par défaut dans l'application correspond à l'attribut "image", l'attribut "illustration/image" ou autre attribut ne sera pas retourné sans un développement supplémentaire.
Les XSL et code java doivent être adaptés à ces nouveaux changements.
Recherchez dans vos XSL de rendu de vos utilisateurs, l'utilisation des attributs modifiés (illustration/image dans la quasi totalité des cas)
Par exemple
<xsl:if test="metadata/illustration/showIllustration = 'true' and metadata/illustration/image">
<img alt="{metadata/illustration/alt-text}" src="{resolver:resolveCroppedImage(metadata/illustration/image/@type, metadata/illustration/image/@path, 72, 72)}"/>
</xsl:if>devient
<xsl:if test="metadata/showIllustration = 'true' and metadata/image">
<img alt="" src="{resolver:resolveCroppedImage(metadata/image/@type, metadata/image/@path, 72, 72)}"/>
</xsl:if>Dans la majorité des cas, l'image de profil est une image décorative et doit avoir une alternative vide.
Si ce n'est pas le cas, vous pouvez utiliser le titre du contenu ou la concaténation du nom/prénom de la fiche utilisateur.
En effet l'attribut illustration/alt-text n'a pas d'équivalent dans le nouveau modèle
Vérifiez également vos sources java qui pourraient faire référence à l'attribut "illustration/image" pour un type "utilisateur". De la même manière qu'aux autres endroits, remplacez "illustration/image" par "image".
Par exemple, si l'image de profil est alimentée par une source externe, via une SCC ou un opérateur de SCC, une modification du code Java ou de la configuration de la SCC sera nécessaire.
Une migration des données est nécessaire pour le déplacement des images de profils depuis l'ancien attribut (exemple : "illustration/image") vers le nouvel attribut "image".
Exécuter le script ci-après afin d'effectuer cette migration, en renseignant les paramètres selon votre cas.
Le script devra être exécuté pour chaque type de contenu utilisateur utilisé (membres, agents, ...)
Paramètres :
/*$
* @param {Boolean} handle="false" Sauvegarder les modifications
* @param {String;widget:edition.select-content-types} contentType Type de contenus à migrer
* @param {String} imagePath="illustration/image" Chemin de l'image à déplacer
* @param {String} [otherAttributeNames] Attributs supplémentaires à déplacer, séparés par des virgules (ex: showIlustration)
*/
const StringUtils = Java.type("org.apache.commons.lang3.StringUtils");
const logger = Ametys.console;
let contents = Repository.query(`//element(*, ametys:content)[@ametys-internal:contentType='${contentType}']`);
let total = contents.getSize();
let handled = 0;
let migrated = 0;
// prepare attributes to migrate
let oldImgPath = _prepareImagePath(imagePath);
let otherAttributes = _prepareOtherAttributes(otherAttributeNames);
logger.info(`Start to migrate image for ${total} user contents of type ${contentType}`);
logger.info(`=> image at path ${oldImgPath} will be moved to ametys:image`);
if (otherAttributes.length && oldImgPath.indexOf('/') > 0)
{
logger.info(`=> other attributes ${otherAttributes} will be moved to content root`);
}
contents.forEach(
content =>
{
if (handle)
{
Content.migrate(
content,
[_moveImage],
true, /* old version are incomptatible */
null, /* no tag */
false, /* verbose */
true /* synchronize live */
);
}
else
{
_moveImage(content);
}
handled++;
if (handled % 500 == 0)
{
logger.info(`${handled}/${total} handled contents`);
}
}
);
logger.info(`${migrated}/${handled} user contents have been migrated.`);
function _moveImage(content)
{
let contentNode = content.getNode();
if (contentNode.hasNode(oldImgPath))
{
let imgNode = contentNode.getNode(oldImgPath);
let cmpNode = imgNode.getParent();
if (contentNode.hasNode("ametys:image"))
{
logger.error(`Content at $[contentNode.getPath()} already contains a 'image' attribute`);
}
else
{
// Move image
contentNode.getSession().move(imgNode.getPath(), contentNode.getPath() + "/ametys:image");
if (cmpNode.getPrimaryNodeType().getName() == "ametys:composite")
{
// Move other attributes of composite if needed
for (var i=0; i < otherAttributes.length; i++)
{
if (cmpNode.hasProperty(otherAttributes[i]))
{
let oldProperty = cmpNode.getProperty(otherAttributes[i]);
contentNode.setProperty(otherAttributes[i], oldProperty.getValue(), oldProperty.getType());
oldProperty.remove();
}
}
// Remove composite
cmpNode.remove();
}
migrated++;
}
}
}
function _prepareImagePath(path: string)
{
let convertPath = "";
let pathSegments = path.split('/');
for (var i=0; i < pathSegments.length; i++)
{
if (i != 0)
{
convertPath += "/";
}
convertPath += pathSegments[i].startsWith("ametys:") ? pathSegments[i] : "ametys:" + pathSegments[i];
}
return convertPath;
}
function _prepareOtherAttributes(attrs: string)
{
let convertAttrs = [];
if (attrs)
{
let attrNames = attrs.split(',');
for (var i=0; i < attrNames.length; i++)
{
let attrName = attrNames[i].trim();
convertAttrs[i] = attrName.startsWith("ametys:") ? attrName : "ametys:" + attrName;
}
}
return convertAttrs;
}Si vous utilisez les espaces projets, vous devez nécessairement exécuter ce script au moins une fois sur le type de contenu Membre d'un projet avec "illustration/image" comme chemin de l'image à migrer.