Olivier El Mekki

Typolight: Création de module backend pour gérer des ressources

La création d’une extension pour Typolight étant remplies de pièges et de problèmes inattendus, j’ai choisi de diviser les tutoriels sur la création d’un module backend en plusieurs éléments plus simples.

Dans ce tutoriel, nous verrons comment créer un module backend dans Typolight pour gérer des ressources simples. Par ressource, nous entendons une entité dotées d’attributs, stockées dans la base de donnée et avec laquelle l’application peut interagir. Les actions basiques que l’application peut accomplir sur ces ressources correspondent au paradigme CRUD : créer, lire, modifier, effacer. C’est précisément ces actions que recouvre un module backend.

Pré-requis:

Il est fortement recommandé que vous réalisiez les différentes étapes en même temps que votre lecture de ce tutoriel.

Conception des ressources

La première étape est de se représenter clairement les ressources qu’on va utiliser. Cela offre l’avantage de pouvoir utiliser l’interface de création de module de Typolight pour écrire automatiquement le squelette des fichiers.

Pour le tutoriel, nous prendrons l’exemple suivant.

Nous réalisons un site pour une entreprise disposant de plusieurs filiales. L’entreprise propose de multiples services, et chaque filiale propose quelques uns de ces services. Un service, par ailleurs, peut être proposé par plusieurs filiales. De services et des filiales peuvent apparaître ou disparaître. Une filiale peut voir sa liste de services proposés se modifier.

Ceux qui ont l’habitude de travailler sur des ressources auront repéré ici une relation many_to_many ( généralement réalisée grâce à une table de jointure ) entre les filiales et les services. On peut aussi y voir une relation filiales has_many services, et c’est ce que nous utiliserons, car has_many est mieux supporté par Typolight ( on pourra en outre par la suite pallier à cette insuffisance en utilisant les modèles ).

Pour ceux qui n’ont pas l’habitude de travailler sur les relations, cela signifie simplement qu’il faudra, dans notre base de donnée, un attribut de filiales permettant de lister les ids des services concernés (c’est ainsi que Typolight procède), ce qui s’exprime par : une filiale has many (a plusieurs) services.

Les autres attributs étant plus simples, nous pouvons nous représenter l’allure de nos ressources. Nous aurons donc deux types de ressources, filiales et services avec les attributs suivants :

Filiales :

Services :

Cela nous suffit pour démarrer.

Création automatique du squelette du module

Afin de gagner du temps, nous utilisons le module “créateur de modules” présent dans la section “système” des menus de l’interface backend. Ce module permet de créer le squelette des fichiers dont vous aurez besoin par la suite.

Il est difficile de savoir exactement tous les fichiers dont on va avoir besoin, mais on peut ensuite dupliquer un fichier existant pour faire une nouvelle classe. Pour cette raison, je vous conseille de toujours créer un module frontend et les fichiers de langues, même si vous n’avez pour l’instant que la partie backend clairement en tête.

Une fois que vous avez renseigné les informations d’identification de l’extension, cochez “ajouter un module back-office”. Parmi les trois champs textes qui vous sont proposés, vous ne vous servirez la plupart du temps que du second.

Pour accélérer l’écriture d’un module backend, Typolight offre la possibilité d’utiliser des DCA ( Data Configuration Array ). Il s’agit en fait de tableaux PHP dont les différents éléments définissent la manière dont une table sql doit être représentée en back-office, et quelles opérations peuvent être réalisées dessus. Autrement dit : pour peu que vous traitiez des ressources, les DCA font tout le boulot.

Pour créer automatiquement les fichiers DCA relatifs à une table sql, et pour créer le fichier .sql de définition de cette table par la même occasion, donnez simplement le nom de cette table dans le champs “Tables du module back office” ( vous pouvez séparer plusieurs tables par des virgules ). Dans le cas de notre module, nous rentrons donc : “tl_filiere_services,tl_filiere_filiales”.

Le champs “Classes du module back office” permet de créer des modules écrit en PHP standard, si vous avez des besoins vraiment spécifiques que les DCA ne couvrent pas, ou si votre module n’implique pas des ressources. Le champs “Modèles du module back office” est utilisé pour créer les vues que ces modules emploieront ( généralement préfixé par “be_” ). Nous nous focalisons pour cette fois sur la gestion des ressources et nous n’avons donc pas besoin de ces deux champs, vous pouvez les laisser vide.

Comme précisé plus haut, nous allons aussi ajouter un module frontend, bien que nous ne nous en serviront pas pour l’instant. Le champs “Classes du module front office” concerne les contrôleurs. C’est généralement ce type de fichier qui concentre la plus grosse partie d’une extension. Les vues utilisées par ces contrôleurs sont définies dans “Modèles du module front office” ( il ne faut pas ici confondre le terme “modèle” avec le modèle de MVC, il s’agit en fait des vues ). Le champs “Tables du module front office” permet de lister des tables sql qui seront ajoutées dans la base de donnée, mais pour lesquelles des fichiers DCA ne seront pas créés. Nous créons donc, pour disposer ensuite du squelette, le contrôleur FiliereFrontend et la vue fe_filiere.

Une fois les informations de votre module complétées, sauvegarder et fermer, et créer les fichiers en cliquant sur l’icône correspondante.

Création de la déclaration SQL

Vous disposez maintenant des fichiers nécessaires, installés dans system/modules/. Nous allons commencer par nous occuper de la définition sql de nos ressources. Cela se fait dans le fichier config/database.sql du module.

Les champs `pid` et `sorting`, parmi les champs ajoutés automatiquement, ne nous serviront pas. Le premier sert à contenir l’id du parent, dans les relations de type belongs to. Le second est utilisé lorsque l’utilisateur doit pouvoir définir un ordre arbitraire, comme dans le cas des éléments de contenus ou des articles. Nous pouvons donc supprimer ces deux champs dans les deux déclarations (n’oubliez pas de supprimer la clef pid aussi).

Le champs `tstamp` est automatiquement mis à jour lorsque vous sauvegardez une ressource, avant de pouvoir retrouver la date de dernière modification. Il peut être utile de rajouter par ailleurs un champs `created_at`.

Nous pouvons maintenant commencer à ajouter nos propres champs.

Attention tricky : lorsque vous éditez les déclarations de table. Le script install.php est assez exigeant sur leur formatage. Si vous mettez des espaces en trop ou en moins, il continuera de vous dire obstinément que la base de donnée n’est pas à jour quand même elle le sera.

Vous devez déjà également vous faire une idée de quel type d’élément de formulaire concernera le champs. Voici quelques exemples :

input type text,
chemin de fichier:          varchar(255) NOT NULL default '',
textarea:                   text NULL,
checkbox:                   char(1) NOT NULL default '',
liste de checkboxes,
select multiple,
liste de input type text:   blob NULL,
date:                       int(10) unsigned NOT NULL default '0'
                            (les dates sont stockées sous forme de timestamp unix)

En reprenant la description des ressources que nous avons vue dans la première partie de ce tutoriel, les définitions sql seront donc :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
--
-- Table `tl_filiere_services`
-- 
 
CREATE TABLE `tl_filiere_services` (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `tstamp` int(10) UNSIGNED NOT NULL DEFAULT '0',
  `name` varchar(255) NOT NULL DEFAULT '',
  `description` text NULL,
  `price` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
-- --------------------------------------------------------
 
--
-- Table `tl_filiere_filiales`
-- 
 
CREATE TABLE `tl_filiere_filiales` (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `tstamp` int(10) UNSIGNED NOT NULL DEFAULT '0',
  `name` varchar(255) NOT NULL DEFAULT '',
  `city` varchar(255) NOT NULL DEFAULT '',
  `phone` varchar(255) NOT NULL DEFAULT '',
  `email` varchar(255) NOT NULL DEFAULT '',
  `services` blob NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Le DCA

C’est ici que se fait la plus grosse partie du travail, dans le DCA des tables. Nous allons commencer par nous occuper du DCA de tl_filiere_filiales.

Un DCA est un énorme tableau PHP, composé de nombreux tableaux imbriqués ( ça vous rappelle des souvenirs? :) ). L’avantage majeure est de permettre de créer des modules très rapidement. L’inconvénient majeur est d’être particulièrement indébugable. La plupart du temps, une erreur dans le DCA se traduit par une erreur dans la classe DC_Table ou (pire encore) par rien du tout, sinon que “ça ne marche pas”, et les logs restent muets sur le problème. J’ai pris l’habitude de dire : “le truc génial dans les modules Typolight, c’est les DCA. Le truc horrible dans les modules Typolight, c’est les DCA.”. Dans les premiers temps, les DCA vous feront perdre plus de temps qu’en gagner, mais je trouve personnellement que ça en vaut la peine, tant tout est facilité une fois qu’on les a en main. Pour faire ses armes avec les DCA, le mieux à faire et de bien prendre soin de les générer par le créateur de module et de se référer aux nombreux DCA présents dans system/modules/backend/dca pour avoir des points de comparaison. Vous réaliserez bientôt à quel point Typolight est une application exigeante lorsqu’il s’agit de développer avec.

Vous trouverez des informations fort utiles dans la section développement de la documentation de Typolight ( en anglais ).

Voici une explication générale des principales clefs de premier niveau d’un DCA :
config: les options générales du module. De nombreux options différentes y sont regroupées : la détermination des tables parent et enfants, les callbacks lors des actions destructives sur les ressources, les limitations de ces opérations, etc.
list: la manières dont les ressources seront listées ( affichage de l’ensemble des ressources ). Selon quel(s) attribut(s) on les classe? Est-ce que c’est une simple liste? Un arbre? Quelles informations pour chaque ressource apparaissent dans cette liste? Quelles actions peuvent être exécutées? Y a-t-il un panneau de navigation? Si oui, que propose-t-il?
palettes,subpalettes: ces options concernent plus particulièrement la gestion d’une ressource isolée. Quels sont les différents champs qui seront éditables, et dans quel ordre seront-ils disposés? Certains champs ne doivent apparaître que si un autre champs a une valeur précise (on ne modifie pas la source d’un fichier si on n’a pas coché “ajouter un fichier”). Les subpalettes permettent de déterminer ces sections annexes du formulaire.
fields: le coeur du DCA. Si ‘palettes’ décident quels champs apparaîtront, ‘fields’ décide ce que seront ces champs. Un textarea? Un input text? Une liste de checkboxes?

Plusieurs options dans les DCA permettent de fournir les données en passant une fonction, sous la forme d’un tableau array( ‘classe’, ‘méthode’ ). Bien que la classe appelée puisse être n’importe quelle classe accessible à l’application, il est d’usage de rajouter une classe directement dans le fichier du DCA, en lui donnant le nom de la table sql concernée et en la faisant hériter de Backend. Cela fait particulièrement sens si la méthode en question n’est vraiment utile que dans le cadre du DCA, comme par exemple les méthodes servant à déterminer les informations présentées dans la liste des ressources.

Détermination des palettes

Puisque nous avons déjà commencé à penser les éléments de formulaires liés aux champs, nous allons commencer par la clef ‘palettes’ et la clef ‘fields’. Si vous comptez faire d’importantes modifications dans ‘config’ et ‘list’, leurs valeurs par défaut vous permettrons quand même de tester ‘fields’.

Le tableau de la clef  ‘palettes’ doit au moins comporter une clef ‘default’. Sa valeur est une chaîne de caractères représentant dans l’ordre les différents champs qui seront présentés, en utilisant le nom du champs dans la base de données. Dans notre DCA de tl_filiere_services, nous auront donc : ‘name;description,price;’. Les noms de champs peuvent être séparés par des virgules ou des points virgules. La différence est que le point virgule sépare des groupes, en traçant une ligne. C’est une notation qu’on retrouvera plusieurs fois dans Typolight.

Outre la clef ‘default’, il peut y avoir également une clef ‘__selector__’. Les selectors servent à modifier dynamiquement le formulaire de la ressource, selon les champs déclarés dans le tableau fourni en valeur de cette clef. Il y a deux types valides de sélecteurs : ceux dont le champs est représenté par une checkbox et les autres. Le sélecteur par checkbox sera une subpalette, le sélecteur normal sera plusieurs nouvelles palettes.

Dans le premier cas, le sélecteur est associé à une subpalette, qui liste les éléments à rajouter. Dans le second cas, une palette est ajoutée pour chaque valeur que peut prendre le champs concerné. Il est en outre possible de faire des palettes propres à plusieurs sélecteurs string en donnant au nom de la palette la liste des valeurs concaténées, comme par exemple la palette “flashexternal” de tl_module, pour type => flash et source => external. C’est assez complexe, mais on ne s’en sert heureusement pratiquement jamais.

Bien souvent, la seule palette “default” suffit, et c’est d’ailleurs ce que nous allons utiliser. Nous pouvons supprimer la clef “subpalettes”. La clef “palettes”, elle, devient :

'palettes' => array
(
  'default' => 'name;description,price;'
),

Définition des champs

Nous avons donc ici trois champs à définir. Chaque champs est défini par un tableau associatif dont la clef la plus importante est ‘inputType’. Vous trouverez une liste des clefs utilisables dans la documentation pour développeurs. Encore une fois, vous êtes fortement invités à aller voir les DCA déjà existants pour vous en inspirer.

La clef ‘label’ servira à déterminer, dans les fichiers de langue, le texte présenté comme nom du champs et la description qui en est faite si l’utilisateur backend est en mode explain.

La clef ‘exclude’ permet de désactiver par défaut le champs pour les non-administrateurs. Il faut manuellement leur définir des droits pour ce champs pour qu’ils puissent le modifier. C’est une bonne pratique que de définir cette propriété sur tous les champs, pour être sûr que la base de donnée ne soit pas accessible involontairement.

La clef ‘inputType’ définit quel type d’élément de formulaire sera utilisé. Les éléments de formulaire appartiennent à la classe Widget. Vous pouvez utiliser tous ceux qui sont déjà définis dans d’autres modules ( notamment dans system/modules/backend ) ou en créer vous même. Les ‘inputTypes’ les plus fréquents sont : ‘text’, ‘textarea’, ’select’, ‘checkbox’, ‘radio’…

La clef ‘eval’ permet de préciser le fonctionnement du widget. On peut aussi passer l’option “mandatory” pour rendre le champs obligatoire, l’option “rte” pour rajouter un éditeur wysiwyg sur un textarea, “rgxp” pour valider un champs text selon une expression régulière ( malheureusement prédéfinie ), etc. Vous trouverez une liste des options possible ici : http://dev.typolight.org/wiki/DevelopmentEvaluation .

Voici ce que nous aurons pour tl_filiere_services

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'fields' => array
(
  'name' => array
  (
    'label'        => &$GLOBALS['TL_LANG']['tl_filiere_services']['name'],
    'exclude'      => true,
    'inputType'    => 'text',
    'eval'         => array('mandatory'=>true, 'maxlength'=>255)
  ),
  'description' => array
  (
    'label'        => &$GLOBALS['TL_LANG']['tl_filiere_services']['description'],
    'exclude'      => true,
    'inputType'    => 'textarea',
    'eval'         => array('rte' => 'tinyMCE' )
  ),
  'price' => array
  (
    'label'        => &$GLOBALS['TL_LANG']['tl_filiere_services']['price'],
    'exclude'      => true,
    'inputType'    => 'text',
    'eval'         => array('mandatory'=>true, 'maxlength'=>255, 'rgxp' => 'digit')
  )
)

‘name’ est donc un input de type text, obligatoire avec 255 caractères maximum, ‘description’ est un textarea avec un éditeur wysiwyg et price est un input de type text, obligatoire, de 255 caractères maximum composés uniquement de chiffres.

Premiers tests, config.php

À ce point, il est bon de s’assurer que tout fonctionne correctement. Commencez par lancer install.php pour mettre à jour la base de donnée.

Pour activer un module, il faut le charger dans le tableau de configuration globale par l’intermédiaire de config/config.php . Nous voulons pour l’instant juste tester le module backend de tl_filiere_services, nous rajoutons donc une entrée à $GLOBALS[ 'BE_MOD' ] dans la catégorie ‘content’ ( cela aura pour effet de faire apparaître le module dans la section “contenu” de la sidebar du backend ).

$GLOBALS[ 'BE_MOD' ][ 'content' ][ 'tl_filiere_services' ] = array(
  'tables' => array( 'tl_filiere_services' )
);

Le fait d’utiliser la clef ‘tables’ stipule que nous allons utiliser des DCA. La valeur est un tableau pour qu’il soit possible de préciser plusieurs tables dans le cas d’une présentation sous la forme parent/enfants.

D’autres options peuvent être précisées, comme vous le voyez dans les commentaires du fichier créé automatiquement. ‘icon’ est le chemin de l’icône qui sera utilisé à côté du lien dans la sidebar du backend, ’stylesheet’ et ‘javascript’ permettent de rajouter des styles et scripts persos. ‘callback’ permet de définir la classe de module backend qui sera utilisé lorsqu’on décide de faire un module sans DCA et ‘key’ permet de rajouter ses actions personnalisées ( l’équivalent de ['list']['operations'] dans un DCA ) si on veut utiliser autre chose que le CRUD par défaut.

Nous pouvons maintenant faire un premier test, en nous rendant dans l’interface backend. Si tout se passe bien, vous voyez apparaître “tl_filiere_services” dans la section “contenu” de la sidebar. Nous donnerons un nom plus correct en utilisant les fichiers de langues. De même, dans la liste des ressources, le bouton “nouveau” n’a pas de label. Cela sera déterminé également dans les fichiers de langues, nous allons nous contenter de vérifier pour l’instant que le CRUD fonctionne correctement.

Créez quelques ressources, éditez-les, effacez-les. Puis passez à la suite quand vous serez assuré que tout fonctionne.

Paramétrage général du DCA

Nous allons ensuite revenir sur le fichier DCA de tl_filiere_services. Nos ressources étant relativement simples, nous n’avons pas besoin de rajouter quoi que ce soit dans ‘config’. Au pire, vous aurez besoin des callbacks. Il s’agit de tableau à deux entrées, classe et méthode, qui seront appelé lors d’événements particuliers ( save, delete, load ). Cela peut être utile si par exemple vous voulez vous assurer qu’une url est atteignable lorsque vous la sauvegardez, ou encore que vous voulez effacer un fichier uploadé propre à la ressource lorsque vous l’effacez. Les autres options, pour la plupart, concernent les relations parent/enfant ou à des besoins très spécifiques et rares ( je vous laisse vous référer au guide du développeur pour des informations complètes ).

La clef ‘list’ est la seconde clef la plus important du DCA. Elle vous permet de définir comment la liste des ressources sera présentée. ’sorting’ et ‘label’ sont d’une importance cruciale pour cela. ‘global_operations’ et ‘operations’ n’ont la plupart du temps pas à être changée, et offrent les actions classiques de CRUD.

vous trouverez la documentation de la clef ‘list’ ici :

’sorting’ sert à déterminer comment les ressources seront classées. Le mode permet de choisir si les ressources seront simplement placées les unes à la suite des autres, ou par groupe selon leur parent, ou par une ordre défini par l’utilisateur (requiert le champs ’sorting’ dans la table) ou encore sous forme d’arbre…

‘flag’ permet de définir un second niveau de classement : ascendant, descendant, alphabétique, par date… Le classement sera effectué sur le ou les champs indiqués dans le array de la clef ‘fields’.

Si vous désirez ajouter un panneau de navigation, c’est également ici que cela se fait ( un exemple de ceci sera donnée dans le DCA de tl_filiere_filiales ).

La clef ‘label’ permet de déterminer quelles informations seront présentées pour chaque ressource dans la liste des ressources. Les champs montrés seront déterminés par ‘fields’ et leur formatage sera déterminé par ‘format’, qui prend le même type de string que printf(). Si vous désirez quelque chose de plus complexe, en particulier si vous avez besoin de manipuler les données des champs plutôt que de les afficher tels quels, vous pouvez utiliser un callback avec ‘label_callback’. La méthode utilisées devra renvoyer un string, qui sera ajouté à la suite du texte déjà fourni par format et fields, si précisé.

tip: Si vous devez fournir beaucoup de contenu, vous pouvez limiter la taille d’un block en lui donnant comme classes limit_height et hN, où N est la hauteur en pixel. Cela aura pour effet de mettre une flèche clickable pour afficher l’ensemble du contenu et de le réduire à la taille choisi par défaut.

Dans notre cas, nous allons classer les services par ordre alphabétique en montrant simplement leur nom. Étant donné que les services ont peu d’informations et que celles-ci sont relativement uniques, nous n’avons pas vraiment besoin de l’opération “copy” et nous la supprimons donc.

Voici donc notre DCA au final :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
$GLOBALS['TL_DCA']['tl_filiere_services'] = array
(
  // Config
  'config' => array
  (
    'dataContainer'    => 'Table',
    'enableVersioning' => true
  ),
 
  // List
  'list' => array
  (
    'sorting' => array
    (
      'mode'    => 1,
      'fields'  => array('name'),
      'flag'    => 1
    ),
    'label' => array
    (
      'fields' => array('name'),
      'format' => '%s'
    ),
    'global_operations' => array
    (
      'all' => array
      (
        'label'       => &$GLOBALS['TL_LANG']['MSC']['all'],
        'href'        => 'act=select',
        'class'       => 'header_edit_all',
        'attributes'  => 'onclick="Backend.getScrollOffset();"'
      )
    ),
    'operations' => array
    (
      'edit' => array
      (
        'label' => &$GLOBALS['TL_LANG']['tl_filiere_services']['edit'],
        'href'  => 'act=edit',
        'icon'  => 'edit.gif'
      ),
      'delete' => array
      (
        'label'       => &$GLOBALS['TL_LANG']['tl_filiere_services']['delete'],
        'href'        => 'act=delete',
        'icon'        => 'delete.gif',
        'attributes'  => 'onclick="if (!confirm(\'' . $GLOBALS['TL_LANG']['MSC']['deleteConfirm'] . '\')) return false; Backend.getScrollOffset();"'
      ),
      'show' => array
      (
        'label' => &$GLOBALS['TL_LANG']['tl_filiere_services']['show'],
        'href'  => 'act=show',
        'icon'  => 'show.gif'
      )
    )
  ),
 
  // Palettes
  'palettes' => array
  (
    'default' => 'name;description,price;'
  ),
 
  // Fields
  'fields' => array
  (
    'name' => array
    (
      'label'       => &$GLOBALS['TL_LANG']['tl_filiere_services']['name'],
      'exclude'     => true,
      'inputType'   => 'text',
      'eval'        => array('mandatory'=>true, 'maxlength'=>255)
    ),
    'description' => array
    (
      'label'      => &$GLOBALS['TL_LANG']['tl_filiere_services']['description'],
      'exclude'    => true,
      'inputType'  => 'textarea',
      'eval'       => array('rte' => 'tinyMCE' )
    ),
    'price' => array
    (
      'label'      => &$GLOBALS['TL_LANG']['tl_filiere_services']['price'],
      'exclude'    => true,
      'inputType'  => 'text',
      'eval'       => array('maxlength'=>255, 'rgxp' => 'digit')
    )
  )
);

tl_filiere_filiales

Nous allons maintenant pouvoir nous occuper du DCA de tl_filiere_filiales :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
$GLOBALS['TL_DCA']['tl_filiere_filiales'] = array
(
  // Config
  'config' => array
  (
    'dataContainer'    => 'Table',
    'enableVersioning' => true
  ),
 
  // List
  'list' => array
  (
    'sorting' => array
    (
      'mode'        => 1,
      'fields'      => array('name'),
      'flag'        => 1,
      'panelLayout' => 'filter,search,limit;'
    ),
    'label' => array
    (
      'fields' => array('name', 'city'),
      'format' => '%s @ %s'
    ),
    'global_operations' => array
    (
      'all' => array
      (
        'label'       => &$GLOBALS['TL_LANG']['MSC']['all'],
        'href'        => 'act=select',
        'class'       => 'header_edit_all',
        'attributes'  => 'onclick="Backend.getScrollOffset();"'
      )
    ),
    'operations' => array
    (
      'edit' => array
      (
        'label' => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['edit'],
        'href'  => 'act=edit',
        'icon'  => 'edit.gif'
      ),
      'copy' => array
      (
        'label' => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['copy'],
        'href'  => 'act=copy',
        'icon'  => 'copy.gif'
      ),
      'delete' => array
      (
        'label'      => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['delete'],
        'href'       => 'act=delete',
        'icon'       => 'delete.gif',
        'attributes' => 'onclick="if (!confirm(\'' . $GLOBALS['TL_LANG']['MSC']['deleteConfirm'] . '\')) return false; Backend.getScrollOffset();"'
      ),
      'show' => array
      (
        'label' => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['show'],
        'href'  => 'act=show',
        'icon'  => 'show.gif'
      )
    )
  ),
 
  // Palettes
  'palettes' => array
  (
    'default' => 'name;city,phone,email;services;'
  ),
 
  // Fields
  'fields' => array
  (
    'name' => array
    (
      'label'     => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['name'],
      'exclude'   => true,
      'search'    => true,
      'inputType' => 'text',
      'eval'      => array('mandatory'=>true, 'maxlength'=>255)
    ),
    'city' => array
    (
      'label'     => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['city'],
      'exclude'   => true,
      'filter'    => true,
      'search'    => true,
      'inputType' => 'text',
      'eval'      => array('mandatory'=>true, 'maxlength'=>255)
    ),
    'phone' => array
    (
      'label'      => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['phone'],
      'exclude'    => true,
      'inputType'  => 'text',
      'eval'       => array('mandatory'=>true, 'maxlength'=>255)
    ),
    'email' => array
    (
      'label'      => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['email'],
      'exclude'    => true,
      'search'     => true,
      'inputType'  => 'text',
      'eval'       => array('mandatory'=>true, 'maxlength'=>255, 'rgxp'=>'email')
    ),
    'services' => array
    (
      'label'      => &$GLOBALS['TL_LANG']['tl_filiere_filiales']['services'],
      'exclude'    => true,
      'inputType'  => 'checkboxWizard',
      'foreignKey' => 'tl_filiere_services.name',
      'eval'       => array('multiple'=>true)
    )
  )
);

Nous retrouvons ici en substance ce que nous avons déjà vu avec le DCA précédent. Les quatre premières champs sont de type texte. Le champs ‘email’ est validé par une expression régulière s’assurant que cette adresse est légale.

Le champs ’services’ est de type ‘checkboxWizard’. Il s’agit d’un widget propre à Typolight qui crée une liste de checkboxes. Leur valeur est déterminée par la clef ‘foreignKey’. Cette clef sert à définir une table et un champs. Une checkbox sera créée pour chaque entrée de cette table. Le label de la checkbox sera la valeur du champs indiqué et la valeur de la checkbox sera l’id de l’entrée. Les valeurs des checkboxes sur ON seront stockée en tant que array serialisé dans la base de donnée ( d’où le type blob pour `services` dans la définition sql de la table tl_filiere_filiales ).

Si une filiale n’avait dû avoir qu’un seul service, nous aurions pu utiliser ’select’ comme inputType plutôt que checkboxWizard, pour utiliser un select classique. Un select peut se référer de la même manière à une table et l’un de ses champs pour déterminer sa valeur et son contenu texte. Il est possible également de lui passer arbitrairement des valeurs avec la clef ‘options’, ou d’utiliser un callback avec ‘options_callback’.

Nous voyons également ici l’usage de panelLayout, présent dans ['list']['sorting']. Quatre options sont disponibles : search, sort, filter et limit. Vous pouvez préciser leur disposition en les plaçant dans l’ordre que vous voulez, dans un string. Les séparer par des virgules les met les uns à la suite des autres, utiliser un point virgule fait passer à la ligne.

Pour déterminer qu’un champs peut être filtré, il suffit de placer ‘filter’ => true dans sa déclaration. De même, pour déterminer qu’il peut être cherché, il faut y mettre ’search’ => true. ‘limit’ ajoute un pager à la page.

Retour sur le fichier de configuration.

Il faut maintenant ajouter ce nouveau module dans le fichier de configuration. Comme nous en avons deux, il peut être intéressant de les grouper. Plutôt que de simplement les mettre dans la section ‘Contenu’, nous allons créer une nouvelle section, ‘Filière’. Nous utiliserons le nom du module ( dans mon cas, ‘filiere’ ) dans le fichier de configuration, et le fichier de langue se chargera d’y attribuer la valeur ‘Filière’ pour le français.

$GLOBALS[ 'BE_MOD' ][ 'filiere' ] = array
(
  'tl_filiere_services' => array
  (
    'tables' => array( 'tl_filiere_services' )
  ),
  'tl_filiere_filiales' => array
  (
    'tables' => array( 'tl_filiere_filiales' )
  ),
);

Cela remplace donc ce que nous avions mis dans le fichier de configuration précédemment. La nouvelle section sera ajoutée à la fin de la liste. Il est possible de la placer plus précisément avec la fonction array_insert(). Si nous avions voulu le mettre en première position, cela aura donné :

array_insert( $GLOBALS[ 'BE_MOD' ], 0, array
  (
    'filiere'  => array
    (
      'tl_filiere_services' => array
      (
        'tables' => array( 'tl_filiere_services' )
      ),
      'tl_filiere_filiales' => array
      (
        'tables' => array( 'tl_filiere_filiales' )
      ),
    )
  )
);

Écriture des fichiers de langue

Il ne reste plus qu’à créer les fichiers de langue. Si vous avez utilisé le créateur de modules et que vous avez spécifié les langues que vous souhaitez maintenir, les fichiers ont été créés automatiquement dans languages/. Nous allons nous concentrer sur languages/fr/.

Dans ce dossier, comme dans tout dossier de langue, vous trouverez par défaut les fichiers suivants :
- default.php : un fichier fourre-tout où vous pouvez mettre tout ce qui ne rentre pas ailleurs. Utile, sémantiquement, pour mettre les définitions de langues qui peuvent être partagées entre plusieurs extensions.
- modules.php : le fichier de langue général, permettant de donner une nom aux sections et modules dans la sidebar du back-office et en divers endroits.
- un fichier de langue par dca : sert à définir le nom des champs, les noms des boutons et les divers contenus textes internationalisables utilisés par le module.

Nous n’utiliserons pas ici default.php. Dans modules.php, nous aurons :

1
2
3
$GLOBALS['TL_LANG']['MOD']['filiere'] = 'Fillières';
$GLOBALS['TL_LANG']['MOD']['tl_filiere_filiales'] = array('Filliales', 'Gérez les informations relatives à vos filiales.');
$GLOBALS['TL_LANG']['MOD']['tl_filiere_services'] = array('Services', 'Gérez les informations relatives à vos services.');

La première entrée corespond au nom de la section que nous avons utilisé dans le fichier config.php. Les deux suivantes représentent les deux DCA. Leur valeur est un tableau à deux entrées, la première étant le titre, la seconde la description. Cette description est notament utilisées dans l’accueil du back-office, dans la section principale.

Dans le fichier tl_filiere_services.php ( toujours dans languages/fr/ ), nous avons :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * Fields
 */
$GLOBALS['TL_LANG']['tl_filiere_services']['name'] = array('Nom', 'Donnez un nom à ce service.');
$GLOBALS['TL_LANG']['tl_filiere_services']['description'] = array('Description', 'Décrivez le service.');
$GLOBALS['TL_LANG']['tl_filiere_services']['price'] = array('Tarif', 'Entrez la valeur numérique du tarif, sans précisez la devise, en séparant la décimale par un point. Exemple: "19.99"');
 
/**
 * Buttons
 */
$GLOBALS['TL_LANG']['tl_filiere_services']['new']    = array('Nouveau service', 'Cliquez ici pour créer un nouveau service');
$GLOBALS['TL_LANG']['tl_filiere_services']['edit']   = array('Modifier', 'Cliquez ici pour modifier les informations relatives à ce service.');
$GLOBALS['TL_LANG']['tl_filiere_services']['delete'] = array('Effacer', 'Cliquez ici pour effacer le service.');
$GLOBALS['TL_LANG']['tl_filiere_services']['show']   = array('Voir', 'Cliquez ici pour afficher les informations relatives à ce service.');

Les champs fields sont ceux utilisés dans le DCA pour représenter les différents input. On y associe un tableau à deux entrées, le premier étant le titre, le second étant la description placée en dessous du widget si l’utilisateur à choisi d’avoir l’aide affichée.

Le même principe s’applique pour les boutons : le titre en première valeur, la description ( placée dans l’attribut title du lien ) en seconde valeur.

Vous pouvez également placer ici les textes de langue utilisés par votre DCA, comme par exemple les options fixes d’un select ( un squelette est placé dans le fichier lorsqu’il est créé automatiquement, dans la partie “References” du fichier ).

En dernier lieu, voici le fichier tl_filiere_filiales.php :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * Fields
 */
$GLOBALS['TL_LANG']['tl_filiere_filiales']['name'] = array('Nom', 'Donnez le nom de cette filiale.');
$GLOBALS['TL_LANG']['tl_filiere_filiales']['city'] = array('Ville', 'Donnez la ville de cette filiale.');
$GLOBALS['TL_LANG']['tl_filiere_filiales']['phone'] = array('Téléphone', 'Quel est le numéro de téléphone de cette filiale?');
$GLOBALS['TL_LANG']['tl_filiere_filiales']['email'] = array('Email', 'Donnez l\adresse email de cette filiale.');
$GLOBALS['TL_LANG']['tl_filiere_filiales']['services'] = array('Services', 'Liste des services que cette filiale propose.');
 
/**
 * Buttons
 */
$GLOBALS['TL_LANG']['tl_filiere_filiales']['new']    = array('Nouvelle filiale', 'Cliquez ici pour créer une nouvelle filiale.');
$GLOBALS['TL_LANG']['tl_filiere_filiales']['edit']   = array('Modifier', 'Cliquez ici pour modifier les informations de cette filiale.');
$GLOBALS['TL_LANG']['tl_filiere_filiales']['copy']   = array('Dupliquer', 'Cliquez ici pour créer une nouvelle filiale sur la base des informations de celle-ci.');
$GLOBALS['TL_LANG']['tl_filiere_filiales']['delete'] = array('Effacer', 'Cliquez ici pour effacer cette filiale.');
$GLOBALS['TL_LANG']['tl_filiere_filiales']['show']   = array('Voir', 'Cliquez ici pour voir les informations de cette filiale.');

Il ne vous reste plus qu’à faire l’équivalent de ces fichiers pour chaque langue que vous voudrez maintenir.

Vous trouverez les fichiers de ce que nous venons de faire ici. Certains fichiers sont vides, ils sont relatifs aux modules frontend et seront utilisés dans un autre tutoriel. En effet, si vous disposez maintenant de quoi gérer basiquement des ressources en back-office, elles sont en l’état totalement inaccessibles au visiteur de votre site. Il va donc s’agir maintenant de coder un module frontend permettant d’utiliser ces ressources. Je reviendrai d’abord dans le prochain tuto sur l’utilisation des modèles. Ensuite, nous verrons les modules frontend. Enfin, nous reviendront plus en profondeur sur les modules backend. La suite au prochain épisode, merci de m’avoir lu.

2 Réponses à “Typolight: Création de module backend pour gérer des ressources”

  1. Bouctoubou dit:

    Tutoriel qui devrait se trouver sur Typolight.fr, si ce n’est pas déjà le cas ?

  2. kik dit:

    Merci :)

    Oui, c’est déjà le cas, mais sur le forum. Ce contenu est bien sûr réutilisable par typolight.fr s’ils le désirent.

Laisser un commentaire