@simongeorges sur le web
Expert Drupal / SEO
key: 'value'
tableau:
- valeur 1
- valeur 2
Pour les performances : https://github.com/hirak/prestissimo
<?php
non fermés : éviter l'envoi du bufferÀ connaître pour comprendre et être compris
* basés sur le standard PEAR
Avantages :
drush cr
(cache-rebuild)drush uli
(user-login)drush sql-dump
drush site-install
drush archive-dump
/ drush archive-restore
drush cim
/ drush cex
drupal site:mode dev
(désactivée depuis la rc12)drupal generate:module
drupal generate:plugin:block
drupal generate:routesubscriber
drupal generate:form:config
drupal generate:entity
composer create-project drupal-composer/drupal-project:8.x-dev some-dir --stability dev --no-interaction
composer create-project drupal/drupal my_site_name
installe un nouveau site
composer require drupal/core:8.8.1 --update-with-dependencies
met à jour le cœur
Attention, encore quelques problèmes avec Composer
Utilisé par beaucoup de développeurs dans le milieu du web
Très utile pour patcher des modules car utilisé par la communauté Drupal et sur drupal.org
Une connaissance basique de quelques commandes suffit
Possibilité d'avoir une interface graphique : SourceTree, Gitg, GitLab, GitHub
custom
.info.yml
(https://www.drupal.org/node/2000204)name
type: module
/!\core: 8.x
core_version_requirement: ^8.8 || ^9
.module
(souvent vide en D8).install
facultatif (configuration désormais indépendante)Créer ce module : il doit simplement apparaître dans la liste des modules.
Recense toutes les fonctions de Drupal, et leur documentation. Un IDE aura cette même documentation dans le code.
Aborde certains topics en profondeur : form API, schema API, hooks, etc
https://api.drupal.org/api/drupal/core%21core.api.php/group/extending/8.6.x
examples
Pour chaque concept de Drupal, des exemples concrets d'utilisation (ajax, form, blocks, cache, render, cron, dbtng, email, menu, node_access, theming, ...).
Ils sont fonctionnels : on peut les activer et voir leur impact en terme d'interface.
Très bien documentés, ne pas hésiter à lire, comprendre et reprendre le code de ces modules.
.permissions.yml
vendor/bin/drupal generate:permissions
permission_callbacks:
- \Drupal\my_module\MyClass::myFunction
fichier src/Plugin/Block/TestBlock.php
namespace Drupal\mon_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'Test' block.
*
* @Block(
* id = "test_block",
* admin_label = @Translation("Test block"),
* )
*/
class TestBlock extends BlockBase {
public function build() {
return array('#markup' => '',);
}
public function blockAccess(AccountInterface $account) { }
public function blockForm($form, FormStateInterface $form_state) { }
public function blockSubmit($form, FormStateInterface $form_state) { }
}
Créer un bloc :
premium_status
vendor/bin/drupal generate:plugin:block
\Drupal::currentUser()->hasPermission('view premium');
Attention au cache d'implementations
Les render arrays sont les blocs constituant une page Drupal. Ce sont des arrays PHP qui définissent des données (c-a-d la structure) ; On est obligés de produire des render arrays. Ceci afin qu'ils puissent être modifiés via les hooks d'altérations ou par la couche de theming.
Les propriétés sont toujours préfixées par un #
et la propriété par défaut
est #markup
, elle permet d'indiquer du balisage simple.
Un render array est converti en HTML avec la fonction render();
// Un render array simple
'ma_cle1' => array(
'#markup' => "<h2>Du texte basique</h2>",
),
// Des propriétés utiles
'ma_cle2' => array(
'#markup' => "Du texte basique",
'#prefix' => '<h2>',
'#suffix' => '</h2>',
),
Une fonction de #theme
peut être renseignée ainsi que ses paramètres
(https://www.drupal.org/developing/api/8/render/arrays)
// Un render array qui produit un tableau HTML
'ma_cle1' => array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => "Aucune donnée pour ce tableau",
),
Des propriétés utiles :
#type
: Le type d'élement#cache
: contexts, tags, ... /!\#markup
: Pour fournir directement de l'HTML#pre_render
/ #post_render
: agit sur le tableau#prefix
/ #suffix
, #weight
, #attached
, #access
, ...// Un render array en html
render($array);
// (quasiment jamais invoqué directement)
// Un objet en render array
$object->toRenderable();
Ajouter un '<h3>' autour du bloc précédent
Altérer le bloc dans un hook_block_view_BASE_ID_alter()
afin de changer le
<h3>
en <h4>
-> Impossible à faire directement... Il faut ajouter un #pre_render dans le hook_block_view_alter(), et la fonction appelée dans le pre_render aura accès au $build du contenu du bloc.
node/{node}
) à une action (afficher un
noeud)node/123
est
un chemin, pointant vers la route node/{node}
où l'argument est 123)node/123
) vers un chemin arbitraire
renseigné par le contributeur (mon-noeud
){node}
) et peuvent être chargés dans le
Controller (en les typant avec une classe)fichier src/Controller/ModuleController.php :
<?php
namespace Drupal\mon_module\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Provides a basic controller.
*/
class ModuleController extends ControllerBase {
public function abc_view() {
return array('#markup' => '',);
}
}
Type d'élément de menu
Paramètres de routage : https://www.drupal.org/node/2092643
#.routing.yml
mymodule.abc_view:
path: '/abc/def'
defaults:
_title: 'My ABC page'
_controller: '\Drupal\module\Controller\ModuleController::abc_view'
requirements:
_permission: 'access my module'
# , = ET
requirements:
_permission: 'access my module,access content'
# + = OU
requirements:
_permission: 'access my module+access content'
# Personnalisé
requirements:
_custom_access: '\Drupal\my_module\MyClass::my_function'
La function renvoit alors AccessResult::allowed() ou AccessResult::forbidden()
#.links.menu.yml
mymodule.abc_view_tab:
title: 'My ABC page'
route_name: mymodule.abc_view
description: 'Displays my ABC page'
parent: mymodule.abc_view
#.links.task.yml
mymodule.abc_view_edit:
title: 'Edit'
route_name: mymodule.abc_view_edit
base_route: mymodule.abc_view
#.links.action.yml pour les actions
Créer une page Suis-je Premium ? reproduisant le comportement du bloc
Créer une page Est-il Premium ? affichant la même chose, mais avec en argument l'uid de l'utilisateur
Créer une page Page Premium avec du contenu "Lorem Ipsum" et ne s'affichant que si l'utilisateur courant a la permission de voir le contenu premium
vendor/bin/drupal generate:controller
Pour voir les routes, utilisez la console : drupal debug:router
Utiliser des ParamConverter
Quelques fonctions de l'API à connaitre :
\Drupal::currentUser()
: utilisateur actuellement connectéNode::load()
et Node::loadMultiple()
pour charger des nœudsUser::load()
et User::loadMultiple()
pour charger des utilisateurs\Drupal::entityTypeManager()->getStorage('node')->loadMultiple($nids);
$entity->save()
pour enregistrer un nœud, un utilisateur, ...$user->getDisplayName()
pour afficher un nom d'utilisateurNode::create(['type' => article])->save();
$node->set('body' => ['value' => 'My body']); $node->save();
$db = \Drupal::database();
$result = $db->select();
$result = $db->insert();
$result = $db->delete();
$result = $db->udpate();
$result = $db->merge();
$ids = \Drupal::entityQuery('user')->condition('name', 'test')->execute();
$users = User::loadMultiple($ids);
$results = $db->select('contact', 'c')
->fields('c')
->condition('created', REQUEST_TIME)
->execute() // ->fetch*()
;
foreach ($results as $result) {
// faire qqch
}
Récupération de résultats :
global $base_url
-> http://monsite.combase_path()
-> /
ou /mon-dossier-drupal
drupal_get_path()
(module, theme) -> chemin vers un module ou un thème
(drupal_get_path('module', 'devel')
donne modules/devel)use Drupal\Core\Url;
// Récupérer une Url.
$url = Url::fromRoute('contact.site_page', array())->toString();
$url = Url::fromUserInput('/contact')->toString();
// Ou générer un lien.
$link = Link::createFromRoute('text', 'route');
$link = Link::fromTextAndUrl('text', $url);
Créer une page Utilisateurs premium listant les utilisateurs du site ayant un accès premium.
vendor/bin/drupal generate:controller
$ids = \Drupal::entityQuery('user')
->condition('roles', 'premium')
->execute();
$users = User::loadMultiple($ids);
hook_form_alter()
donnera mon_module_form_alter()
hook_XXXXXXX_alter()
: permettent de modifier des données créés par
d'autres modulesnamespace Drupal\my_module\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
class MyModuleSubscriber implements EventSubscriberInterface {
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('my_function');
return $events;
}
function my_function(GetResponseEvent $event) {
$event->setResponse(new RedirectResponse('http://example.com/'));
}
}
my_module.services.yml
)services:
my_module.redirect_all:
class: Drupal\my_module\EventSubscriber\MyModuleSubscriber
tags:
- {name: event_subscriber}
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
class RouteSubscriber extends RouteSubscriberBase {
protected function alterRoutes(RouteCollection $collection) {
if ($route = $collection->get('user.login')) {
$route->setPath('/login');
}
if ($route = $collection->get('user.logout')) {
$route->setRequirement('_access', 'FALSE');
}
}
}
services:
example.route_subscriber:
class: Drupal\example\Routing\RouteSubscriber
tags:
- { name: event_subscriber }
Empêcher d'accéder à l'édition du profil d'un utilisateur (/user/{user}/edit) en interdisant l'accès à la route.
vendor/bin/drupal generate:routesubscriber
Faites la même chose en supprimant juste le lien de menu (hook_menu_local_tasks_alter)
$dispatcher = \Drupal::service('event_dispatcher');
$dispatcher->dispatch('my_object.my_event', $params);
Un formulaire est une structure déclarative composée d'éléments de la form API. La majeure partie des traitements est effectuée par celle-ci, rendant la création ou la modification de formulaire rapide et sécurisée.
Référentiel des composants disponibles sur api.drupal.org
\Drupal::formBuilder()->getForm('Drupal\mymodule\MyModuleForm');
// Déclaration
public function getFormId() {
return 'my_module_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
public function validateForm(array &$form, FormStateInterface $form_state) {
// Logique de validation.
$form_state->setErrorByName('form_field', 'message');
}
public function submitForm(array &$form, FormStateInterface $form_state) {
// Traitement des données soumises.
}
Les données soumises et validées sont contenues dans
$form_state->getValue('key')
.
Après exécution du _submit()
, l'utilisateur est redirigé vers le formulaire
vidé de ses valeurs, ou bien vers une route définie par
$form_state->setRedirectUrl($url)
Chaque formulaire a un identifiant unique qui permet de l'altérer facilement par les autres modules.
#element_validate
)example.form:
path: '/example-form'
defaults:
_title: 'Example form'
_form: '\Drupal\mymodule\Form\ExampleForm'
But: définir pour quels types de contenu la fonctionnalité premium est activée. C'est-à-dire, sur quels types on affichera l'option "Contenu premium" dans les formulaires de création ou de modification de nœud.
Article
Page de base
vendor/bin/drupal generate:form
$config->set('name', value)->save();
pour définir$config->get('name');
pour récupérer$config->clear('name')->save();
pour supprimer une valeur'content_types'
de l'objet premium.settings
$form['my_key'] = [
'#type' => 'entity_autocomplete',
'#target_type' => 'user',
];
http://www.foreach.be/blog/how-manipulate-forms-drupal-8
http://drupal.stackexchange.com/questions/146434/send-a-form-to-twig-template
$this->entityFormBuilder()->getForm($form_id, 'form_mode');
Dans un fichier de configuration '/config/install/image.style.mon_style.yml' (l'exporter depuis l'interface)
status: true
dependencies: { }
name: mon_style
label: 'mon style'
effects:
mon_style_desaturate:
uuid: mon_style_desaturate
id: image_desaturate
weight: 1
data: { }
Celui-ci nous servira pour les images qui seront sur les articles premium
hook_schema()
-> crée une ou plusieurs tableshook_schema_alter()
déclare une modification (mais ne la réalise pas)hook_update_N()
servent à réaliser des actions sur la structure ou
les donnéesVia l'API, déclarer une table avec deux colonnes, nid et status.
Installer cette table via un hook_update_N()
ou réinstaller le module.
Ajouter la checkbox au dessus du titre
Enregistrer la valeur dans notre table à l'enregistrement
La mettre à sa valeur par défaut à l'affichage du formulaire
Rappels
hook_form_alter(&$form, $form_state, $form_id)
dpm($var)
Utiliser l'API Node Access (documentation)
Déclarer le hook_node_access()
et ne retourner AccessResult::forbidden()
uniquement si les 4 conditions sont réunies :
Attention: ne fonctionnera pas pour les listes (dont la page d'accueil)
pour des raisons de performances, il faudrait declarer hook_node_grants()
.
Rester simple et ne controler l'accès qu'à un nœud complet.
De même hook_node_access()
n'est pas appelé pour le superadmin.
// Déclaration du contenu du mail avec hook_mail().
function monmodule_mail($key, &$message, $params) {
$account = $params['account'];
$node = $params['node'];
$message['subject'] = "Bonjour " . format_username($account);
$message['body'][] = "Le noeud {$node->title} a été créé";
$message['body'][] = "A bientot";
}
// Envoi du mail.
$mailManager = \Drupal::service('plugin.manager.mail');
$mailManager->mail('monmodule', 'my_key', 'toto@example.com', 'fr-FR', array(
'node' => node_load(123),
'account' => user_load_by_name('toto'),
);
On peut utiliser la notion de tokens (jetons). 'body
' est un tableau de lignes.
La clé permet à un module de gérer plusieurs types de mail.
<strong>Contenu premium</strong>
sur chaque contenu premiumLe module fournit toujours le markup par défaut.
Le hook_theme()
définit des hooks/clés qui pourront ensuite être utilisés via
la propriété #theme
des render arrays. Ces theme hooks généreront ensuite
le markup HTML via un template.
On peut fournir des variables à ces theme hooks. Des fonctions preprocess peuvent ajouter ou modifier des variables, et également ajouter des suggestions de templates.
function forum_theme() {
return array(
'forums' => array(
'template' => 'forums',
'variables' => array(
'forums' => NULL,
'topics' => NULL,
),
),
);
}
// Mais toujours privilégier les render arrays, car altérables
$build['forums'] = array(
'#theme' => 'forums',
'#forums' => $forums,
'#topics' => $topics,
);
Exemples d'utilisations de theme()
:
Liste complete des implementation de theme du cœur
$image = array(
'#theme' => 'image',
'#path' => drupal_get_path('module', 'monmodule') . '/monimage.png',
);
print drupal_render($image); // Quasi-automatiquement appelé par les hooks
https://www.drupal.org/developing/api/8/render/pipeline#html-main-content-renderer-pipeline
<!DOCTYPE html>
<html lang="{{ language.language }}" dir="{{ language.dir }}">
<head>
<meta charset="utf-8">
{{ head }}
<title>{{ head_title }}</title>
{{ styles }}
</head>
<body class="{{ classes }}" {{ attributes }}>
{{ page_top }}
{{ page }}
{{ scripts }}
{{ page_bottom }}
</body>
</html>
Sur toute une section : {% filter upper %} Texte {% endfilter %}
abs, batch, capitalize, convert_encoding, date, date_modify, default, escape, first, format, join, json_encode, keys, last, length, lower, merge, nl2br, number_format, raw, replace, reverse, round, slice, sort, split, striptags, title, trim, upper, url_encode
{# Template A #}
{% block foo %}
<p>Mon premier paragraphe</p>
{% endblock %}
{# Template B #}
{% extends 'TemplateA' %}
{% block foo %}
{{ parent() }}
<p>Mon deuxième paragraphe</p>
{% endblock %}
{# Rendu Template B #}
<p>Mon premier paragraphe</p>
<p>Mon deuxième paragraphe</p>
parameters:
twig.config:
debug: true
dans sites/default/services.yml
{{ dump(_context|keys) }}
pour voir tout ce qui est disponiblename: My Theme
type: theme
version: 0.1
core: 8.x
base theme: false
regions:
branding: "Branding"
header: "Header"
breadcrumb: "Breadcrumb"
content: "Content"
footer: "Footer"
regions_hidden:
- sidebar_first
- sidebar_second
libraries:
- my_theme/global_styling
Utiliser dans le hook_node_view() un render array #markup pour afficher
Convertir le render array utilisé pour utiliser une fonction de theme (et donc un template), en implémentant un hook_theme().
Passer en paramètre le nœud et transformer "Contenu premium" en "Titre du nœud est un contenu premium".
Ajouter une image de médaille dans ce template.
Créer un preprocess pour transformer cette image en variable.
Dans le preprocess, transformer l'image en render array, et utiliser la fonction render() dans le template.
Ajouter des suggestions pour ce template.
global-styling:
css:
theme:
css/style.min.css
js:
js/theme.js
dependencies:
- core/jquery
- core/drupal.ajax
libraries-override:
classy/base:
css:
components:
css/components/menu.css: false
user/drupal.user: false
# jQuery Update
core/jquery:
js:
assets/vendor/jquery/jquery.min.js: js/jquery-5000.max.js
libraries-extend:
user/drupal.user:
- my_theme/user
classy/node:
- my_theme/node
Ajouter une classe à l'image et créer un fichier CSS:
img.premium-icon {
float: right;
}
L'ajouter lorsqu'un nœud premium est affiché.
Voir les modules example
Un stream est un chemin, une URI, vers un fichier interne ou externe :
https://www.drupal.org/node/2015613
$violations = $entity->validate();
$violations->count(); // extends \IteratorAggregate
$violation->getMessage();
http://md-systems.github.io/drupal-8-caching
$key = 'my-unique-cache-key';
if ($cache = \Drupal::cache()->get($key)) {
$data = $cache->data;
} else {
$data = my_slow_calculation();
\Drupal::cache()->set($key, $data);
}
// Alternative for multiple items
\Drupal::cache()->getMultiple($keys);
\Drupal::cache()->setMultiple($items);
EntityInterface::getCacheTags()
, 'config.system.performance')$build['#cache'] = array(
'keys' => 'my_cache',
'contexts' => 'url',
'tags' => array(),
'max-age' => Cache::PERMANENT,
);
Directement dans le render array
https://www.drupal.org/developing/api/8/cache/contexts
3 contextes par défaut sur tous les éléments (dans le services.yml) :
parameters:
renderer.config:
required_cache_contexts: ['languages:language_interface', 'theme',
'user.permissions']
Html::escape(UrlHelper::stripDangerousProtocols($uri));
Html::escape($string);
check_markup($text, $format_id = NULL, $langcode = '',
$filter_types_to_skip = array());
Xss::filter($string, array $html_tags = NULL);
// GET
$request = \Drupal::httpClient()->get($url, [
'auth' => ['username','password']
]);
$status = $request->getStatusCode();
$response = $request->getBody();
// POST
$request = $client->post($url, [
'json' => [
'id'=> 'data-explorer'
]
]);
$response = json_decode($request->getBody());
return new JsonResponse($values);
$output = $this->serializer->serialize($entity, 'json');
$entity = $this->serializer->deserialize($output,
\Drupal\node\Entity\Node::class, 'json');
id: basics
label: Import articles
migration_groups:
- mon_groupe
source:
plugin: csv
path: 'public://data.csv'
header_row_count: 1
keys:
- Id
process:
title: title
body: body
type:
plugin: default_value
default_value: article
destination:
plugin: entity:node
composer create-project drupal-composer/drupal-project
composer require drupal/devel:8.*
Table of contents | t |
---|---|
Exposé | ESC |
Autoscale | e |
Full screen slides | f |
Presenter view | p |
Source files | s |
Slide numbers | n |
Blank screen | b |
Notes | 2 |
Help | h |