Mettre en place un processus de publication sur Drupal 8
La sélection des sujets est terminée, le programme des deux jours de conférence du drupalcamp à Lannion des 27-28 et 29 octobre est maintenant connu !
Au programme : du drupal 8, du cache, du templating, des tests, du composer, de la config, des bonnes pratiques...
Deux salles, deux ambiances : une salle plus dédiées pour les débutants et les sujets plus larges et une salle avec sujets « barbus ».
D'autres salles seront aussi disponible pour des BOF (petites discussions improvisées ou suites informelles de sessions).
Personnellement je donnerai une présentation sur comment gérer son site drupal 8 avec composer et git le samedi matin.
Pour rappel, le 3e jour sera dédié aux ateliers et sprints.
Merci à toutes les personnes qui nous ont proposé des sujets !
Pour rappel, l'accès aux conférence est libre et gratuit, mais l'association propose des « packs weekend » comprenant les repas du midi, le café, le traditionnel sac de goodies et l’accès aux soirées communautaires, proposé à un prix plus que raisonnable (15€ pour les trois jours). Je ne peux que vous encourager à soutenir l'association et prendre votre pack : https://lannion2017.drupalcamp.bzh/reservations
Enfin, toujours pour proposer un tarif aussi accessible, il reste des packs de sponsoring disponible : https://lannion2017.drupalcamp.bzh/sponsors/forfaits
$query = \Drupal::entityQuery('node'); $query->condition('type', 'produit'); $nb_resultats = $query->count()->execute();
Formation Drupal 8 : De Drupal 7 à Drupal 8 (Session novembre)
admin
lun 02/10/2017 - 12:13
Drupal 8.4.0 approche à très grand pas, sa sortie est prévue dans les semaines qui viennent. Vous êtes toujours entrain de travailler sur des projets Drupal 7 et lorgnez sur Drupal 8 ?
Profitez de notre formation pour sauter le pas. Nous organisons une session du 27 au 29 novembre en inter entreprise à Paris.
Vous pourrez apprendre dans cette formation comment fonctionnent sous Drupal 8 vos habitudes et connaissances Drupal 7. Blocs, menus, champs, formateurs, formulaires, modules, outils, industrialisation, cache sont quelques uns des thèmes couverts au cours de ces 3 jours. La liste complète est à découvrir dans le programme détaillé.
Combien coûte cette formation ? De moins en moins selon le nombre de participants ! Le coût est partagé entre chacun. 3 à 6 personnes peuvent participer à chaque session. Le prix global est de 4 500 € HT, ce qui vous donne une formation de 1 500 à 750 € pour trois jours bien remplis d'informations, de réponses à vos questions et de super déjeuners car bien manger est primordial ! (Et puis nous sommes français quand même...).
Je sens que vous ne pouvez plus résister, correct ? Et bien dans ce cas, je vous invite à nous laisser un message pour que l'on vous réserve une place ou lève les doutes / questions que vous pourriez vous poser !
On vous attend !
Récupérer $_GET['test'] :
<span>$</span>
test<span> </span><span>=</span><span> \Drupal</span><span>::</span><span>request()->query->get</span><span>(</span><span>'</span>
test<span>'</span><span>);</span>
Récupérer $_POST['test'] :
<span>$</span>
test<span> </span><span>=</span><span> \Drupal</span><span>::</span><span>request</span><span>()-></span><span>request</span><span>-></span><span>get</span><span>(</span><span>'</span>
test<span>'</span><span>);</span>
Récupérer un tableau associatif avec toutes les variables $_GET :
<span>$variables_get = \Drupal::request()->query-></span>
all<span>(</span>
<span>);</span>
Récupérer un tableau associatif avec toutes les variables $_POST :
<span>$variables_post</span>
<span> </span><span>=</span><span> \Drupal</span><span>::request()->request-></span>
all<span>(</span>
<span>);</span>
Remplacement des Entity Field Query en drupal 7, les Entity Query permettent d'effectuer des requêtes sur nos types d'entités, (custom ou non) selon leurs propriétés ou leurs champs (fields).
Elles sont une bonne solution pour faire des queries même avancées, sans avoir à faire des jointures à en perdre la tête.
Récupération et chargement de l'ensemble des utilisateurs activés.
//On commence par donner le type d'entité que l'on souhaite « requêter » $query = \Drupal::entityQuery('user'); //On ajoute une condition, ici « status = 1 » pour ne récupérer que les utilisateurs actifs $query->condition('status', 1); //On lance la requête et récupère les ids des utilisateurs. $users_ids = $query->execute(); //on charge les utilisateurs en question $users = User::loadMultiple($users_ids);
Sous drupal 8, tout est entité (ou presque), on peut ainsi faire des requêtes sur :
Bref, beaucoup de chose !
La déclaration du type d'entité se fait lors de la génération de la requête :
Pour requêter les utilisateurs $query = \Drupal::entityQuery('user'); Pour requêter les noeuds $query = \Drupal::entityQuery('node');
$query->condition(champ_ou_propriété, valeur, opérateur);
Le champ sur lequel on fait une condition peut -être soit une propriété (nid, changed, created, title, name...) ou bien un champ « field » (field_tags, field_image...)
Par défaut l'opérateur est : = (égal)
$query->condition('uid', 1);
mais on peut évidement le spécifier :
$query->condition('uid', $user->id(), '');
On peut utiliser le IN, afin de, par exemple récupérer et charger tous les users en fonction d'un field "interest" qui fait référence à des termes de taxonomies :
$interests = [127, 128, 27]; $query = \Drupal::entityQuery('user'); $query->condition('field_interests', $interests, 'IN'); $profils_similaires = $query->execute(); $users = User::loadMultiple($profils_similaires);
Avec un "LIKE", afin de tester le même département :
$query->condition('field_adresse_zipcode', substr($code_postal, 0, 2).'%', 'LIKE');
Aussi on peut ajouter plusieurs conditions, ici je fais un test sur la date de naissance, qu'elle soit bien comprise entre $min_date & $max_date :
$query = \Drupal::entityQuery('user'); $query->condition('field_interests', $interests, 'IN'); $query->condition('field_birthday', $min_date->format('Y-m-d'), 'condition('field_birthday', $max_date->format('Y-m-d'), '>'); $profils_similaires = $query->execute(); $users = User::loadMultiple($profils_similaires);
On peut utiliser des conditions logiques « OR », ici je veux récuperer les utilisateurs qui ont les mêmes centres d'intérêts (field_interests) OU qui sont manuellement mis en avant (field_pinned)
$query = \Drupal::entityQuery('user'); $condition_or = $query->orConditionGroup(); $condition_or->condition('field_pinned',1); $condition_or->condition('field_interests', $interests, 'IN'); $query->condition($condition_or); $profils_similaires = $query->execute(); $users = User::loadMultiple($profils_similaires);
On peut faire des conditions un peu plus balaises, avec ici donc : soit les utilisateurs mis en avant (field_pinned) OU (qui ont les même centres d'intérêts (field_interest) ET qui sont nés après $max_date ET avant $min_date :
$query = \Drupal::entityQuery('user'); $condition_or = $query->orConditionGroup(); $condition_or->condition('field_pinned',1); $condition_and = $query->andConditionGroup(); $condition_and->condition('field_interests', $interests, 'IN'); $condition_and->condition('field_birthday', $min_date->format('Y-m-d'), 'condition('field_birthday', $max_date->format('Y-m-d'), '>'); $condition_or->condition($condition_and); $query->condition($condition_or); $profils_similaires = $query->execute(); $users = User::loadMultiple($profils_similaires);
$query->range(0, 20);
$query->sort('created');
ou en précisant le sens :
$query->sort('field_birthday', 'ASC');
Ce billet reprend des notes capturées sur le vif en écoutant la keynote de Dries de la Drupalcon Vienne 2017. Il est parfois difficile de faire des phrases complexes sur le vif. Veuillez m’en excuser !
Drupal progresse malgré la perception que l’on peut en avoir. Le projet est en bonne santé. Voici quelques chiffres qui illustrent cette tendance sur core et l’écosystème de la contrib : +22% issues fermées, +28% de contributeurs supplémentaires, +26% de sociétés impliquées. 45% des contributions à Drupal viennent d’Europe.
Rappel amical, les contribs peuvent aussi être faites hors code.
En 1 an, le nombre de projets stable a plus que doublé, passant de 600 à 1400 projets stables suite à l’incitation des mainteneurs à publier une version stable de leurs modules.
Quelques uns des modules les plus populaires ont atteint un état stable pour D8 : Panels, Commerce, Search API, CTools, Tokens, Pathauto. Mais il en manque encore : Backup & Migrate, OG, Rules, Feeds.
Concernant la montée de version de D7 à D8, il ne reste que 12 issues critiques pour avoir la migration de core terminée via Migrate. (Ce qui est toujours énorme…).
Release tous les 6 mois, super positifs. Modules expérimentaux avancent vers stable de release en release (media, layout discouvery, date time range, inline form errors)
Division du marché CMS et multiplication des solutions. Les technos hors Drupal majoritairement utilisées par les agences sont principalement JS (node, angular, react, vues.js)
Question : pour qui est drupal ?Ambitious digital experiences
De petit à grand site (blog, portfolio, brochure, community management, SMB site with integeration, omni channel website, multisite platform) => REACH
Drupal n’est plus pour les petits sites.
Drupal est maintenant pour le monde de l’entreprise ? Pas seulement (omnichannel website, multisite platform). C’est adapté mais il y a de la place pour les utilisateurs type petite boite (SMB site with integrations)
Drupal grandit et évolue.
Le constat est que l’administration de Drupal est vieillissante. Drupal est parfois difficile à utiliser, les mises à jour sont complexes et coûteuses. L’objectif est de changer cela.
Sur quoi se concentrer ?
1. De puissants outils de site building.
PEOPLE – Création du rôle du product owner + a UX designer Yoroy est core committer (premier non dev)
PROCESS – Commencement par le design & product management (besoin ? Chemin ok sur les priorités ?) / Initiatives non dév (media, layout, workflows) / Démo de layout et du start theme de Drupal. (Workspaces – Groupes de config / contenus à publier). Media in core (library browser)
TOOLS – Good UX is built with JS. Devs want modern JS
Reco 1 => Plus de découplé headless (API First)
Reco 2 => Rendre Drupal plus simple à utiliser / Augmenter les compétences d’experts JS pour Drupal (+ de compétences externes) / Dogfood web services APIs (utiliser nos API)
Reco 3 => Créer des nouvelles UIs pour prendre le pli et explorer des nouvelles voies de construire avec du JS
Tendance de fond pour créer des interfaces côté Client
2. Des mises à jour et de la maintenance plus simples.
Updates régulières (6 mois majeure, 1 mois mineure) / de +en + d’outils complexes (composer, git, extra libs)
Users want Auto updates. Complexe à implémenter mais c’est un challenge intéressant à relever qui en vaut la chandelle en le construisant en plusieurs étapes.
Cirage de pompes de CG
Drupal 8 possède une API dans son noyau permettant l'export et l'import de configuration, aussi appelée CMI pour Configuration Management Initiative. Ce que ne permettaient pas nativement les versions précédentes.
En Drupal 6 et 7, la solution la plus largement répandue pour palier à ce manque est l'utilisation du module Features permettant d'exporter la configuration dans des fichiers.
Depuis un peu plus d'une semaine, il est possible de proposer une présentation pour compléter le programme du drupalcamp Lannion.
N'hésitez plus et proposez votre sujet.
Que vous proposiez ou non une conférence, il est toujours temps de réserver votre pack week-end.
Nous vous attendons nombreux du 27 au 29 octobre prochain à Lannion.
Sur ce site, j'ai trois types de contenu :
Pour chacun de ces trois types de contenu, j'ai une vue (View) de listing qui correspond (cf le menu ci-dessus).
Par défaut quand on est sur la vue de listing des snippets par exemple, on a bien l'élément de menu "Snippets" qui est marqué comme actif, par contre quand on va sur le détail d'un snippet, on perd cette information. Nous allons voir comment sélectionner le menu item qui doit être actif en fonction du type de contenu sur lequel on se trouve.
1ère étape : surcharger le service MenuActiveTrail
dans mon module « kgaut » je créé le fichier KgautMenuActiveTrail.php dans le dossier src/Menu avec le contenu suivant :
routeMatch->getParameter('node'); if ($node instanceof NodeInterface) { $bundle = $node->bundle(); switch ($bundle) { case 'article': //Ici je demande à recherche le menu item qui utilise la route correspondant à mon type de contenu $links = $this->menuLinkManager->loadLinksByRoute('view.front_articles.page', [], $menu_name); break; case 'snippet': $links = $this->menuLinkManager->loadLinksByRoute('view.front_snippets.page', [], $menu_name); break; case 'realisation': $links = $this->menuLinkManager->loadLinksByRoute('view.front_realisations.page', [], $menu_name); break; } if (isset($links) && $links) { $found = reset($links); } } return $found; } }
C'est bien beau, mais maintenant il faut signaler à Drupal que l'on a surchargé un de ses services. Pour cela il faut créer le fichier src/MonModuleServiceProvider.php (attention c'est normé !) Dans mon cas j'ai donc créé le fichier KgautServiceProvider.php dans le dossier src de mon module kgaut (oui je suis très égocentrique).
En voici le contenu :
getDefinition('menu.active_trail')->setClass('Drupal\kgaut\Menu\KgautMenuActiveTrail'); } }
Un petit vidage de cache plus tard, et on est bon !
L'association Drupal France et l'équipe de bénévoles, organise comme chaque année un Drupal Camp, pour 2017, c'est en Bretagne, à Lannion du 27 au 29 octobre !
Drupal Camp, c'est quoi ? C'est l’évènement français incontournable pour tous les utilisateurs du CMS : développeurs, chefs de projets, Webmasters, Référenceurs... 2 Jours de conférences et un jours de sprints.
Les journées de vendredi et samedi sont dédiées aux conférences et discussion. La journée du dimanche est consacrée aux ateliers et sprints (contributions). Un atelier est déjà prévu : un sprint de traduction de drupal en breton. https://lannion2017.drupalcamp.bzh/brezhoneg.
L'accès aux conférence est libre et gratuit, mais l'association propose des « packs weekend » comprenant les repas du midi, le café, le traditionnel sac de goodies et l’accès aux soirées communautaires, proposé à un prix plus que raisonable (15€ pour les trois jours) je ne peux que vous encourager à soutenir l'association et prendre votre pack : https://lannion2017.drupalcamp.bzh/reservations
Afin de proposer un tarif aussi accessible, nous faisons appels à des partenaires pour nous aider financièrement dans l'organisation de cet évènement : https://lannion2017.drupalcamp.bzh/sponsors/forfaits
Enfin, comme chaque année, c'est vous qui faites le contenu des drupal camp ! N'hésitez-pas à proposer un sujet de conférence, même si vous n'êtes pas un expert Drupal, vous aurez toujours quelque chose à apprendre aux participants : https://lannion2017.drupalcamp.bzh/programme/guide-aux-conferenciers.
Espérant vous voir nombreux à Lannion fin octobre, n'hésitez-pas à me faire un petit coucou une fois là bas ! (Malheureusement, Jean-Michel, mon suricate ne devrait pas faire le déplacement...)
j'ai enfin terminé la migration de ce site vers Drupal 8 !
À l'origine un drupal 6, j'avais migré le site en 2012 vers Drupal 7, il était maintenant temps de tester la Migrate API pour tester la migration de contenu vers un site en drupal 8.
J'ai donc créé un module pour migrer l'ensemble des contenus de l'ancien site (articles, réalisations, snippets, commentaires, flag, redirections, fichiers...) Ayant eu du mal à trouver des ressources à jour et fonctionnant correctement, j'ai mis l'ensemble du module sur github, espérant que cela serve à d'autres : https://github.com/kgaut/kgaut_migrate.
Au niveau du front, comme vous le voyez, je suis resté simple (je suis développeur back après tout) j'ai créé un thème basé sur bartik, que j'ai légèrement personnalisé.
Je suis en train de faire le tour mais tout à l'air de fonctionner correctement. Si jamais vous remarquez quelque chose qui ne correspond pas à ce que vous attendez, n'hésitez-pas à me le signaler.
Dans drupal 8, parfois le thème ajoute des suggestions de template qui vont bien, en fonction du type de noeud et du view_mode, mais ça n'est pas toujours le cas, voici comment faire pour ajouter des suggestions de template via le hook HOOK_theme_suggestions_HOOK() :
function MONMODULE_theme_suggestions_node(array $variables) { $suggestions = []; /** @var \Drupal\node\Entity\Node $node */ $node = $variables['elements']['#node']; $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_'); $suggestions[] = $variables['theme_hook_original'] . '__' . $sanitized_view_mode; $bundle = $node->bundle(); $sanitized_bundle = strtr($bundle, '.', '_'); $suggestions[] = $variables['theme_hook_original'] . '__' . $sanitized_bundle; $suggestions[] = $variables['theme_hook_original'] . '__' . $sanitized_bundle . '__' . $sanitized_view_mode; return $suggestions; }
Pour un type de contenu article en mode de vue teaser on aura ainsi les possibilités de templates suivantes :
Ce site à été construit à l'origine sous Drupal 6, migré sous drupal 7 en 2012, Je suis en train de préparer la migration de ce site vers Drupal 8 via la Migrate API.
Vu que je galère pas mal à trouver des exemples à jours, complets et qui fonctionne, je me suis dis que ce serait intéressant de partager mon expérience, le plus simple étant de partager directement mon module de migration, je l'ai donc mis directement sur github afin que vous puissiez piocher directement dedans pour voir comment je migre l'intégralité du contenu de ce site vers une installation de Drupal 8.
J'ai tenté de faire un readme assez complet, mais les recettes YAML ne sont pas encore super bien documentées, c'est à faire... Pour l'ensemble, c'est du glané à droite à gauche, sur internet mais aussi dans les exemples du module migrate_plus.
En vrac ce que vous pourrez trouver :
Sera fait dans les prochains jours :
Comme je l'ai dis, je me suis beaucoup inspiré du sous-module migrate_example de migrate_plus, c'est pourquoi il reste encore pas mal de commentaire en anglais faisant référence à des types de contenu « beer », qu'il faut que j'adapte.
Remarque : Je ne suis pas un expert de migrate, l'ensemble est probablement améliorable, mais comme disait un sage : « le mieux est l'ennemi du bien », je cherche avant tout à avoir quelque chose qui fonctionne. Ceci étant dit, n'hésitez-pas à me faire part de vos remarques, commentaires ou questions via les commentaires ou bien les issues sur Github ! Je suis preneur de toute idée d'amélioration.
Le tout est disponible sur Github : https://github.com/kgaut/kgaut_migrate.
$fields['date_naissance'] = BaseFieldDefinition::create('datetime') ->setLabel(t('Date de naissance')) ->setRequired(TRUE) ->setDefaultValue(NULL) ->setSetting('datetime_type', 'date') ->setDisplayOptions('form', array( 'type' => 'date', )) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE);
Pour un champ DateTime : Drupal 8 - Entité - Champ de base datetime
Drupal vient avec des types d'entité prédéfinis (Node, ou User par exemple). À ces type d'entité il est possible d'ajouter des fields, mais il est aussi possible d'ajouter des propriétés.
Voici comment ajouter des propriétés (prénom et nom) aux utilisateurs.
Dans le fichier monmodule.module :
function monmodule_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) { if ($entity_type->id() === 'user') { $fields = []; $fields['firstname'] = \Drupal\Core\Field\BaseFieldDefinition::create('string') ->setLabel(t('Firstname')) ->setSettings(array( 'max_length' => 100, 'text_processing' => 0, )) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE); $fields['lastname'] = \Drupal\Core\Field\BaseFieldDefinition::create('string') ->setLabel(t('Lastname')) ->setSettings(array( 'max_length' => 100, 'text_processing' => 0, )) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE); return $fields; } }
Dans le fichier monmodule.install :
/** * Create user additional properties */ function monmodule_update_8001() { $entity_manager = \Drupal::entityManager(); $definition = $entity_manager->getFieldStorageDefinitions('user')['firstname']; $entity_manager->onFieldStorageDefinitionCreate($definition); $definition = $entity_manager->getFieldStorageDefinitions('user')['lastname']; $entity_manager->onFieldStorageDefinitionCreate($definition); } function monmodule_uninstall() { $entity_manager = \Drupal::entityManager(); $definition = $entity_manager->getLastInstalledFieldStorageDefinitions('user')['firstname']; $entity_manager->onFieldStorageDefinitionDelete($definition); $definition = $entity_manager->getLastInstalledFieldStorageDefinitions('user')['lastname']; $entity_manager->onFieldStorageDefinitionDelete($definition); }
Pour un projet en cours sur Drupal 8, j'ai besoin d'un moteur de recherche avec plusieurs indexes. J'utilise évidement le module search_api avec un moteur de recherche SolR, Vu que je suis en mode "développement" je vais utiliser une image docker qui me fournira une installation fonctionnelle de SolR, directement plugable avec Drupal.
Par habitude j'utilise les images de Thiebaud Schmittlin : https://github.com/TehesFR/docker-solr
Une fois docker installé l'image se lance avec la commande suivante :
docker run -p 8080:8983 tehes/docker-solr:4.10
Cela lancera une image SolR 4.10 sur le port 8080, si on souhaite utiliser une autre version du moteur il suffit d'adapter le tag :
docker run -p 8080:8983 tehes/docker-solr:6.6
Par contre dans mon cas, j'avais besoin de plusieurs cores pour mes différents indexes, j'ai donc adapté son image pour en créer une nouvelle : https://github.com/kgaut/docker-solr-multicore
C'est du « quick'n'Dirty » Je me suis basé uniquement sur la version 6.6 de SolR, la seule modification par rapport au DockerFile original est l'ajout d'une variable NB_CORES qui est par défaut à 4. L'image créé donc 4 core permettant d'avoir 4 indexes.
J'ai mis mon image sur Docker hub : https://hub.docker.com/r/kgaut/docker-solr-multicore/
Pour lancer l'image :
docker run -p 8080:8983 kgaut/docker-solr-multicore
Si jamais vous avez besoin de changer le nombre de cores, vous pouvez modifier cette image de la façon suivante :
git clone git@github.com:kgaut/docker-solr-multicore.git my-docker-solr-multicore cd my-docker-solr-multicore
Modifiez le fichier DockerFile et à la ligne 11 remplacez "env NB_CONTAINER 4" par "env NB_CONTAINER 8"
Construisez votre image (cette étape peut prendre un peu de temps) :
docker build -t my-docker-solr-multicore .
une fois terminée, lancez l'image :
docker run -p 8080:8983 my-docker-solr-multicore
et bim ! 8 cores :
Je suis loin d'être un expert docker, et si vous connaissez un moyen de passer le nombre de core voulu lors du lancement de l'image, je suis preneur. Même si vu que les cores sont créés au moment du build de l'image, je ne suis pas bien sur que cela soit possible...
Si vous avez d'autres idées d'améliorations, n'hésitez-pas, merci !
Dans le fichier monmodule.links.menu.yml :
monmodule.menu.cle: title: 'Titre de ma mage' description: 'Liste des options' route_name: view.admin_options.page parent: system.admin_content weight: 90
Quelques explications :
Petite astuce pour le nom de la route, rendez-vous sur la page d'édition de votre vue vous retrouverez les élement dans l'url (voici un exemple pour mon cas : http://monsite.dev/admin/structure/views/view/admin_options/edit/page
Si vous souhaitez ajouter le menu item au premier "niveau" d'un menu (donc sans parent), au lieu de "parent", il faut utiliser la clé "menu_name" qui doit référencer le nom machine du menu, voici la structure à adopter :
monmodule.menu.cle: title: 'Titre de ma mage' description: 'Liste des options' route_name: view.admin_options.page manu_name: "main" weight: 90
$field['photo'] = BaseFieldDefinition::create('image') ->setLabel(t('Photo')) ->setDescription(t("Photo du contenu")) ->setSettings([ 'file_directory' => 'dossier/image', 'alt_field_required' => FALSE, 'file_extensions' => 'png jpg jpeg', ]) ->setDisplayOptions('view', array( 'label' => 'hidden', 'type' => 'image', 'weight' => 0, )) ->setDisplayOptions('form', array( 'label' => 'hidden', 'type' => 'image_image', 'weight' => 4, )) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE);
Merci DuaelFr ;)