Les Rencontres Interactives #18 : La dette technique
Les Rencontres Interactives #18 : La dette techniqueLaborouge
lun 28/05/2018 - 23:46
![Les Rencontres Interactives #18 : La dette technique affiche Rencontres Interactives #18](https://www.laborouge.com/sites/default/files/styles/illustration/public/upload/img/https_2f2fcdn.evbuc_.com2fimages2f448038592f904726592712f12foriginal.jpg?h=7c76c241&itok=Lr1DNYiU)
Les Rencontres Interactives #18 : La dette techniqueLaborouge
lun 28/05/2018 - 23:46
J'aurais le plaisir de vous présenter ma conférence intitulé "La dette technique : état des lieux" dans le cadre des Rencontres Interactives de Caen le 6 juin prochain.
Aprés avoir traité le sujet de la dette technique sous Drupal 7 au Drupal Camp de Lannion, je voulais approfondir ce sujet sur un aspect moins "technique" et plus "gestion de projet".
Cette conférence est donc le fruit de cette réflexion.
J'aurais le plaisir de vous présenter ma conférence intitulé "La dette technique : état des lieux" dans le cadre des Rencontres Interactives de Caen le 6 juin prochain.
Aprés avoir traité le sujet de la dette technique sous Drupal 7 au Drupal Camp de Lannion, je voulais approfondir ce sujet sur un aspect moins "technique" et plus "gestion de projet".
Cette conférence est donc le fruit de cette réflexion.
Voici comment créer un champ « fichier » au sein d'un type d'entité custom dans drupal 8 :
$fields['programme_pdf'] = BaseFieldDefinition::create('file') ->setLabel(t('Programme PDF')) ->setSetting('file_directory', 'formations/programme') // dossier d'upload ->setSetting('max_filesize', '10MB') // taille max du fichier ->setSetting('file_extensions', 'pdf') // extensions autorisées, à séparer par un espace ->setSetting('description_field', FALSE) // si on veut activer un champ « description » ->setDisplayOptions('form', [ 'label' => 'hidden', 'type' => 'file_generic', 'weight' => 4, ]) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE);
Le 21/03/2018, la Security Team de Drupal a annoncé dans son "Public Service
Announcement" PSA-2018-001
la sortie imminente de versions de sécurité pour Drupal, le mercredi 28/03/2018.
De telles préannonces sont particulièrement rares, la précédente remontant à la
faille d'injection SQL "Drupalgeddon" de novembre 2014, et sont réservées aux
mises à jour les plus critiques.
En temps normal, la couverture de sécurité est limitée aux versions supportées,
qui sont actuellement la version courante (8.5.x) et la précédente (7.x).
Fait inhabituel là aussi, la mise à jour du 28 couvre également les versions
obsolètes 8.3.x et 8.4.x, et un patch a également été annoncé sur le projet
D6LTS pour une mise à jour équivalente sur Drupal 6.x, alors que son support est
terminé depuis le 24/02/2016, ce qui renforce le niveau de risque estimé de la
faille que corrigent ces mises à jour, et l'importance de les appliquer dans les
meilleurs délais.
Mais quels sont ces délais, précisément ? Selon le bulletin PSA-2018-001, les
"exploits" de la faille révélée par le correctif pourraient être développés dans
les "heures ou jours qui suivent" la publication.
En 2014, la faille Drupalgeddon avait commencé à être exploitée dans les 6 heures
qui avaient suivi la publication du correctif. Avec l'élévation progressive du
niveau de risque des agresseurs, tout permet de supposer que le délai pourrait
être encore plus court cette fois.
La Security Team annonce une publication des correctifs mercredi entre 18:00
et 20:30 UTC, soit 20:00-21:30 heure de Paris, du fait de l'heure d'été.
Une marge de sécurité de 6 heures, comme celle dont ont bénéficié les sites en
2014, mène donc au mieux à 03:30 jeudi matin, et au pire 02:00. Dans tous les
cas, une mise à jour le jeudi matin 29/03 court donc un risque important
d'arriver trop tard.
En résumé: préparez votre équipe à une intervention dans la nuit de mercredi
à jeudi.
Selon le bulletin PSA-2018-001 et les compléments du project D6LTS, la
stratégie recommandée est la suivante:
Les versions exactes à utiliser pour les mises à jour seront indiquées lors
de la publication du correctif.
Ces mises à jour de sécurité ne modifieront pas le schéma de base de données
par rapport à la dernière version actuellement disponible sur chacune des
branches concernées.
Attention sur les sites utilisant 8.3.x/8.4.x, le rapport du moduleupdate
sur le tableau de bord d'administration disponible sur/admin/reports/status
indiquera une mise à jour recommandée vers
une release 8.5.x plutôt que vers la version de sécurité 8.3.x/8.4.x, mais
compte tenu des évolutions entre les versions 8.3/8.4/8.5, ceci présente un
risque d'effets de bord non souhaités, et il est donc préférable d'appliquer les
mises à jour indiquées par le bulletin de sécurité plutôt que celles indiquées
par le site, afin de pouvoir plus sereinement préparer la mise à jour en 8.5.x
pour avril 2018.
Si votre équipe ou prestataire a d'ores et déjà choisi une stratégie, mieux
vaut l'appliquer qu'improviser au dernier moment: ce sont ceux qui sont les
mieux placés pour connaître les contraintes d'exploitation de votre site.
A défaut, quatre principales tactiques sont envisageables pour ce déploiement.
Les détails dépendent évidemment de vos processus de déploiement et, le cas
échéant, de ceux de l'agence ou infogérant en charge de votre site. Cela étant
dit, les grandes lignes tactiques sont les suivantes, et commençent AVANT la
publication du bulletin:
La Security Team pas plus qu'aucune tierce partie ne peut communiquer plus
d'informations jusqu'à la publication des correctifs.
L'annonce du correctif sera publiées sur https://www.drupal.org/security, sur
Twitter, et par courriel pour les abonnés à la liste de diffusion de la Security
Team, à laquelle il est possible de s'abonner en allant sur sa page de profil
sur https://drupal.org (pas sur https://drupal.fr), et en se rendant sur
l'onglet My newsletters
pour s'abonner à cette liste de diffusion.
Les journalistes intéressés par plus d'informations sur les développements
de ce sujet sont invités à contacter directement
security-press@drupal.org
pour obtenir une version centrée sur leurs préoccupations. La Security Team
publiera un courriel résumé à destination de la presse en même temps que la
publication du code et du bulletin de version associé.
Cet article, créé par OSInet est mis
à disposition selon les termes de la
Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 France.
pour faciliter la diffusion de l'information de sécurité de Drupal.
Prenons deux types d'entités custom : « Bière » et « Brasserie » avec une relation 1-n entre les deux dans le sens :
Ainsi :
En drupalisme, on aurait une propriété « entity_reference » au niveau de notre bière qui fera référence à la brasserie.
Dans views, si on fait un listing des bières, pas de soucis pour accéder au contenu de la brasserie depuis la bière, par contre l'inverse n'est pas possible.
Depuis un listing de brasserie, il n'est pas possible d’accéder aux bières de la brasserie.
Pour cela il faut utiliser la classe en charge de views_data, définie dans l'annotation de notre type d'entité brasserie :
Et voila le contenu de ce fichier
namespace Drupal\mon_module\Entity\ViewsData; use Drupal\views\EntityViewsData; class BrasserieViewsData extends EntityViewsData { /** * {@inheritdoc} */ public function getViewsData() { $data = parent::getViewsData(); $data['brasserie']['bieres'] = [ 'title' => t('Bieres'), 'help' => t('Lie la brasserie aux bières produites'), 'relationship' => [ 'group' => t('bieres'), // Affiché en information dans la partie « relationship » de views 'label' => t('Actions de formation'), // Affiché en information dans la partie « relationship » de views 'base' => 'biere', // Table de base de l'entitée cible 'field table' => 'biere', // Table contenant le champ de l'entitée cible sur lequel on fera la jointure 'base field' => 'brasserie',// Champ de l'entité cible Champ sur lequel on fera la jointure 'relationship field' => 'brasserie_id', // Champ de l'entité source sur lequel on fera la jointure 'id' => 'standard', ], ]; return $data; } }
Et voila le travail !
Il vient d'être annoncé par l'équipe gérant la sécurité du CMS drupal qu'un gros correctif de sécurité pour drupal 7 et drupal 8 sera déployé mercredi 28/03/2018 entre 19h et 20h30 (heures Françaises).
On ne sait pas encore où se situe la faille. On sait seulement qu'elle semble (très) importante et qu'il est recommandé d'appliquer le patch immédiatement.
La dernière fois qu'un cas similaire s'est présenté, une fois le correctif disponible, les sites non patchés ont été très rapidement piratés. En effet : une fois que l'on a accès au patch, il est facile de trouver la faille corrigée et ainsi de l'exploiter sur des sites non protégés.
Si vous êtes développeur ou si vous gérez vos propres sites drupal, prévoyez une permanence mercredi soir pour vous occuper de ça.
Si vous avez un site drupal qui est géré par un tiers, assurez-vous qu'il soit au courant et qu'il s'en occupera au plus tôt.
Encore une fois, les versions 7 et 8 de drupal sont concernées.
Plus d'informations : https://www.drupal.org/psa-2018-001
Voici comment créer un Menu item (élément de menu) avec des paramètres GET.
Pour cela on utilise le fichier MON_MODULE.links.menu.yml (à noter que cela marchera aussi dans les fichiers MON_MODULE.links.action.yml et MON_MODULE.links.task.yml)
formations.element: title: 'Mon titre de menu' weight : 2 route_name: view.front_formations.page menu_name: menu-formation options: query: label: 'prise-de-vue' cat: 2
Ici l'url aura comme « query string » : ?label=prise-de-vue&cat=2
Pour rappel la clé menu_name attend le nom du menu dans lequel on veut placer l'élément de menu que l'on vient de définir.
Le type nombre décimal peut-être pratique pour stocker tout nombre à virgule (un prix par exemple).
Voici comment attacher une propriété « nombre décimal » à un type d'entité personnalisé.
$fields['prix'] = BaseFieldDefinition::create('decimal') ->setLabel(t('Prix')) ->setSetting('unsigned', TRUE) ->setSetting('scale', 2) ->setSetting('min', 0) ->setSetting('suffix', '€ TTC') ->setRequired(TRUE) 'type' => 'number', 'weight' => 5, )) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE);
Voici comment générer un lien pour « flaguer » une entité avec drupal 8 et le module flag :
$type_entite_a_flaguer = 'user'; $id_entitee_a_flaguer = $user->id(); $id_du_flag = 'follow_user'; $f = \Drupal::service('flag.link_builder'); $link = $f->build($type_entite_a_flaguer, $id_entitee_a_flaguer, $id_du_flag);
vous pourrez alors utiliser le lien dans un template en faisant par exemple :
{{- link -}}
Pour récupérer et retourner un webform où l'on veut via le code (que ce soit dans un bloc, un controller...) on peut utiliser les lignes suivantes :
// Ici, « contact » est le nom machine de mon webform $webform = \Drupal::entityTypeManager()->getStorage('webform')->load('contact'); return $webform->getSubmissionForm();
Voici comment récupérer le domaine actif quand on utilise le module Domain Access pour Drupal 8 :
$current_domain = \Drupal::service('domain.negotiator')->getActiveDomain();
Pour récupérer le domaine par défaut :
$default_domain = \Drupal::service('domain.negotiator')->loadDefaultDomain();
Pour créer un champ de base de type lien, dans la définition de votre entité :
$fields['mon_lien'] = BaseFieldDefinition::create('link') ->setLabel(t('Path')) // valeurs possible : LinkItemInterface::LINK_EXTERNAL ou LinkItemInterface::LINK_GENERIC ->setSetting('link_type', LinkItemInterface::LINK_GENERIC) //Activer ou non le titre ->setSetting('title', DRUPAL_DISABLED) ->setDisplayOptions('form', [ 'type' => 'link_default', 'weight' => 0, ]) ->setDisplayConfigurable('form', TRUE);
Avec Drupal et Drupal 8 encore plus la recherche passe la plupart du temps par Search API, une interface qui se branche devant plusieurs moteur de base de données (SolR, ElasticSearch, Database...)
Le plus souvent on va faire nos pages de résultats de recherche à l'aide de Views (et du module Search views pour Drupal 7). Mais dans certains cas on va vouloir avoir plus la main sur la recherche et donc aller interroger Search API directement dans le code.
Chargement de l'index (ici, le nom machine est « contenu »)
// Chargement de l'index « contenu » $query = Index::load('contenu')->query();
Terme sur lesquels vont être effectués la requête.
// On lancer la recherche sur « hello world » $query->keys("hello world");
// Les différentes possibilités sont // - « direct » => Requête directe // - « terms » => Multiple words // - « phrase » => Single phrase $parse_mode = \Drupal::service('plugin.manager.search_api.parse_mode')->createInstance('direct'); // Optionnellement, on peut choisir un opérateur spécifique (OR ou AND) $parse_mode->setConjunction('OR'); // Affectation du mode de la requête $query->setParseMode($parse_mode);
Évidement c'est optionnel, par défaut la recherche se fera sur l'ensemble des champs « fulltext » contenus dans l'index.
// Recherche uniquement sur le champ « body » $query->setFulltextFields(['body']); // Recherche uniquement sur les champs « body » et « title » $query->setFulltextFields(['body', 'title']);
L'objet retourné par Index::load('contenu')->query(); est une query classique drupal, sur laquelle on peut effectuer les traitement classique que l'on peut faire sur n'importe quelle Query drupal8.
À noter ici que le nom des champs doit être celui que l'on renseigne dans l'index, ils peuvent être différents des noms des champs définis dans nos types de contenu.
Quelques exemples en vrac :
// le champ private doit être « TRUE » $query->addCondition('private', TRUE);
// on veut que le contenu retourné soit un article ou un snippet $query->addCondition('type', ['article', 'snippet'], 'IN');
À noter le format de date à utiliser : "Y-m-d\TH:i:s\Z"
$btf = \DateTime::createFromFormat('d/m/Y', '21/10/2015'); $date_formatted = $btf->format("Y-m-d\TH:i:s\Z") // Date de création de l'article > date définie $query->addCondition('created', $date_formatted, '>'); // Et l'inverse $query->addCondition('created', $date_to, ');
Gestion de la pagination identique à une requête classique
// Récupération des 20 premiers résultats.
Exécution de la requête et récupération des résultats
$results_set = $query->execute(); //Nombre de résultats retournés $nb_results = $results_set->getResultCount() // Récupération des entités foreach ($results_set->getResultItems() as $item) { $resultat = $item->getOriginalObject()->getValue(); }
Pas trouvé grand chose sur le sujet à part cette page dans la documentation sur drupal.org : https://www.drupal.org/docs/8/modules/search-api/developer-documentatio…
Si vous avez d'autres liens pouvant aider, je suis preneur.
Voici comment créer une table dans une base de donnée autre que celle par défaut.
Code à mettre dans votre MODULE.install
Le principe : création d'un « HOOK_schema fake » et utilisation des HOOK_install et HOOK_uninstall pour créer / supprimer cette table en sélectionnant la bonne base.
Le code est pour drupal 8 mais il peut facilement être adapté pour drupal 7.
use \Drupal\Core\Database\Database; function MODULE_schema_autre_db() { $schema['users'] = [ 'description' => 'Members informations', 'fields' => [], 'primary key' => ['id'], ]; $schema['users']['fields']['id'] = [ 'description' => 'ID', 'type' => 'serial', 'not null' => TRUE, 'unsigned' => TRUE, ]; $schema['users']['fields']['uid'] = [ 'description' => 'Drupal ID', 'type' => 'int', 'not null' => TRUE, 'unsigned' => TRUE, ]; $schema['users']['fields']['mail'] = [ 'type' => 'varchar', 'length' => 255, ]; return $schema; } function MODULE_install() { Database::setActiveConnection('NOM_DB'); $schema = MODULE_schema_autre_db(); foreach ($schema as $name => $table) { Database::getConnection()->schema()->createTable($name, $table); } Database::setActiveConnection(); } function MODULE_uninstall() { Database::setActiveConnection('NOM_DB'); $schema = MODULE_schema_autre_db(); foreach ($schema as $name => $table) { Database::getConnection()->schema()->dropTable($name); } Database::setActiveConnection(); }