Ez-publish ( 20 / 75 articles - Voir la liste )

Astuce [eZ5] Authentifier un utilisateur programmatiquement

Pour authentifier programmatiquement un utilisateur en front-office, vous pouvez utiliser cette méthode :

<?php

use eZ\Publish\Core\MVC\Symfony\Security\User as SecurityUser;
use eZ\Publish\API\Repository\Values\User\User;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

    /**
     * Connecte l'utilisateur en argument.
     *
     * @param User $user
     *   Utilisateur eZ Publish récupéré depuis le repository
     *
     * @throws \Exception Si une erreur survient lors de la récupération du service de gestion de jeton.
     */
    public function login(User $user)
    {
        // Authentification pour Symfony
        $roles =['ROLE_USER'];
        $security_user = new SecurityUser($user, $roles);
        $security_user->setAPIUser($user);

        $token = new UsernamePasswordToken($security_user, null, 'ezpublish_front', $roles);
        $this->container->get('security.token_storage')->setToken($token);

        // Authentification pour le repo eZ Publish
        $this->repository->setCurrentUser($user);
    }

Marque-page [eZ5] Les répertoires dans le cœur d'eZ Publish 5

Le cœur d'eZ Publish 5 se trouve dans le répertoire vendor/ezsystems/ezpublish-kernel/ de l'application.

Voici quelques répertoires utiles qu'il contient :

  • eZ/Publish/API/Repository/ : contient les interfaces des services avec les signatures de toutes leurs méthodes.
  • eZ/Publish/Core/ : contient l'implémentation des interfaces du répertoire précédent
    • Repository/ : contient l'implémentation de ces mêmes services.
    • Base/Exceptions/ : contient toutes les exceptions fournies par eZ, et surtout leurs constructeurs.
    • Persistence/Legacy/Content/Search/Gateway/CriterionHandler/ : contient les critères de recherche fournis par eZ.
    • Persistence/Legacy/Content/Search/Gateway/SortClauseHandler/ : contient les méthodes de tri fournies par eZ.

Remarque :

Les 4 derniers répertoires, sont sous eZ/Publish/Core/.

Astuce [eZ5] Rechercher des contenus par mot clé

L'extension eZTags pour eZ Publish fournit un système de mots clés pour regrouper des contenus par thématique. Avec elle arrive un nouveau type de champ, pour taguer vos contenus.

Dans eZ Publish 4.x (ou en mode legacy), le template de ce champ affiche un lien vers une page qui liste les contenus avec ce mot clé. La version pour eZ Publish 5 est disponible ici. Malheureusement, elle ne fournit aucune méthode pour trouver des contenus à partir d'un mot clé.

Voici trois méthodes pour récupérer ces contenus.

<?php

// [...]

use \Netgen\TagsBundle\API\Repository\Values\Tags\Tag;

/**
 * Retrouve les contenus avec le mot clé en argument.
 *
 * @param string $keyword Mot clé recherché
 * @param int $offset Offset pour les résultats de la recherche
 * @param int $limit Nombre maximal de résultats de recherche
 *
 * @return \eZ\Publish\API\Repository\Values\Content\Content[]
 *
 * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException Si l'utilisateur courant n'a pas le droit de voir les tags
 * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException Si aucun tag avec cet ID n'existe
 */
public function getRelatedContentByKeyword($keyword, $offset = 0, $limit = 50) {

    $rootTagID = 2;

    // Recherche du tag correspond au mot clé
    $tag = $this->getTagByKeyword($rootTagID, $keyword);

    $relatedContentList = array();

    if (!empty($tag)) {

        // Recherche des contenus avec le mot clé souhaité
        $tagService         = $this->container->get('ezpublish.api.service.tags');
        $relatedContentList = $tagService->getRelatedContent($tag, $offset, $limit);
    }

    return $relatedContentList;
}

/**
 * Retrouve un Tag à partir de son mot clé.
 * Le premier trouvé parmi les descendants de celui dont l'ID est en argument est retourné.
 *
 * @param string $rootTagID ID du tag parmi les descendants duquel rechercher
 * @param string $keyword Mot clé recherché
 *
 * @return \Netgen\TagsBundle\API\Repository\Values\Tags\Tag
 *
 * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException Si l'utilisateur courant n'a pas le droit de voir le tag courant
 * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException Si aucun tag avec cet ID n'existe
 */
public function getTagByKeyword($rootTagID, $keyword) {

    $tag = null;

    // Récupération du tag racine
    $tagService = $this->container->get('ezpublish.api.service.tags');
    $rootTag    = $tagService->loadTag($rootTagID);

    if (!empty($rootTag)) {

        // Récupération des tags descendants
        $descendantTagList = $this->getTagDescendantList($rootTag);

        if (!empty($descendantTagList)) {

            // Parcours des tags descendants
            for ($i = 0, $length = count($descendantTagList); $i < $length && $tag == null; $i++) {

                if ($descendantTagList[$i]->keyword == $keyword) { 
                    $tag = $descendantTagList[$i];
                }
            }
        }
    }

    return $tag;
}

/**
 * Retourne tous les tags descendant de celui en argument.
 *
 * @param \Netgen\TagsBundle\API\Repository\Values\Tags\Tag $rootTag Tag racine
 *
 * @return \Netgen\TagsBundle\API\Repository\Values\Tags\Tag[]
 *
 * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException Si l'utilisateur courant n'a pas le droit de voir le tag courant
 */
public function getTagDescendantList( Tag $rootTag ) {

    // Récupération des tag descendants
    $descendantTagList = array();

    $tagService       = $this->container->get( 'ezpublish.api.service.tags' );
    $childrenTagList  = $tagService->loadTagChildren( $rootTag );

    // Parcours des tags enfants
    foreach ( $childrenTagList as $childTag ) {

        $descendantTagList[] = $childTag;

        // Récupération des descendants
        $descendantTagList = array_merge( $descendantTagList, $this->getTagDescendantList( $childTag ) );
    }

    return $descendantTagList;
}

Remarques :

  • Ces méthodes peuvent être utilisées dans un contrôleur, où l'attribut $container (ContainerInterface) est disponible.
  • Tous vos tags doivent avoir une racine commune (= une seule arborescence). Cette racine servira de base pour les recherches.
  • Dans la première méthode, la variables $rootTagID (identifiant du tag racine) est en dur et devrait être récupérée depuis un fichier de configuration.
  • Dans cet exemple, la recherche ne fonctionne pas pour les synonymes. La deuxième méthode peut être améliorée pour les gérer.

Astuce [eZ5] Ajouter des filtres et des fonctions à Twig

Twig fournit de nombreuses fonctions et une liste de filtres pour simplifier le développement des templates.

Quelques exemples :

{# Des fonctions natives : #}
Contenu d'une variable : {{ dump(my_var) }}
Nombre aléatoire : {{ random(5) }}

{# Des filtres natifs : #}
Taille d'un tableau : {{ my_array|length }}
Mise en minuscule : {{ my_string|upper }}
Échappement de caractère : {{my_string|escape}}

L'intérêt de Twig c'est qu'il est très facilement extensible, et vous vous pouvez créer vos propres fonctions et vos propres filtres. Par exemple :

{# Une nouvelle fonction : #}
Affiche l'Url actuelle : {{ current_uri() }}

{# Un nouveau filtre : #}
{{ "Ma phrase est trop longue parce que la fin n'est pas intéressante."|truncate(28) }}

Prérequis

  • Vous avez déjà créé le Bundle Acme/MyBundle, et l'avez activé dans le fichier ezpublish/EzPublishKernel.php.

Remarque :

Si ce n'est le nom du fichier de kernel, tout cet exemple est valable pour une application Symfony 2 non eZ.

Création de l'extension Twig

L'ajout de filtres et fonctions se fait via un fichier PHP, qu'on appelle une extension Twig.

Créez le répertoire Twig/ dans votre bundle, et le fichier MyExtension.php à l'intérieur :

<?php
namespace AT\APIToolsBundle\Twig;

use \Symfony\Component\DependencyInjection\ContainerInterface;

class MyExtension extends \Twig_Extension {

    /**
     * @var \Symfony\Component\DependencyInjection\ContainerInterface;
     */
    protected $container;

    /**
     * Contructeur de l'extension Twig MyExtension.
     *
     * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
     */
    public function __construct(ContainerInterface $container) {

        $this->container = $container;
    }

    /**
     * Retourne le nom de l'extension.
     *
     * @return string
     */
    public function getName() {

        return 'MyExtension';
    }

    /**
     * Retourne la liste des Filtres de template à ajouter à Twig.
     *
     * @return array
     */
    public function getFilters() {

        return [
            'truncate' => new \Twig_Filter_Method($this, 'truncate'),
        ];
    }

    /**
     * Retourne la liste des Fonctions de template à ajouter à Twig.
     *
     * @return array
     */
    public function getFunctions() {

        return [
            'current_uri' => new \Twig_Function_Method($this, 'getCurrentURI'),
        ];
    }

    /**
     * Retourne l'URI courante.
     *
     * @return string $_SERVER['REQUEST_URI']
     */
    public function getCurrentURI() {

        return $_SERVER['REQUEST_URI'];
    }

    /**
     * Tronque le texte en argument.
     * Si la longueur du texte est supérieure à $maxLength, $suffix est ajouté à la chaîne.
     *
     * @param string $text Chaîne à tronquer
     * @param int $maxLength Longueur maximale autorisée pour la chaîne
     * @param string $suffix Le sufixe à ajouter si besoin
     * @return string
     */
    public function truncate($text, $maxLength, $suffix = '...') {

        $truncatedText = $text;

        mb_internal_encoding('UTF-8');

        $length      = mb_strlen($text );
        $sufixlength = mb_strlen($suffix);

        // Si le texte est trop long
        if ($length > $maxLength && $length >= $sufixlength) {

            $truncatedText = mb_substr($text, 0, $maxLength - $sufixlength) . $suffix;
        }

        return $truncatedText;
    }
}

Explications :

  • La classe MyExtension étend la classe Twig_Extension fournie par Symfony.
  • La méthode getName() retourne le nom de votre choix pour votre extension.
  • Les méthodes getFilters() et getFunctions() retournent la liste des filtres et des fonctions à ajouter à Twig.
  • Le nom du filtre ou de la méthode est défini par la clé dans le tableau (ici truncate et current_uri).
  • Pour instancier un nouveau filtre, on utilise new \Twig_Filter_Method($this, 'méthode_à_appeler').
  • Et de la même manière, pour une nouvelle fonction new \Twig_Function_Method($this, 'méthode_à_appeler').
  • Les deux dernières méthodes sont celles appelées dans les constructeurs. Elles contiennent le code métier qui effectue le traitement.

Informer Symfony

L'extension Twig est terminée mais Symfony ne sait pas encore qu'elle existe. Il vous faut la déclarer en tant que service, dans le fichier Resources/config/services.yml de votre bundle :

parameters:
    acme_my.twig_extension.class: Acme\MyBundle\Twig\MyExtension

services:
    acme_my.twig_extension:
        class: %acme_my.twig_extension.class%
        arguments: [@service_container]
        tags:
            - { name: twig.extension }

Explications :

  • Chaque paramètre et chaque service du fichier a un identifiant unique (ex : acme_my.twig_extension).
  • On définit la classe MyExtension comme paramètre. Si on déplace ou renomme la classe par la suite, seul le paramètre sera à changer.
  • On déclare l'extension Twig en tant que service, en spécifiant la classe à utiliser et l'argument à passer au constructeur.
  • On tague le service avec twig.extension pour que Symfony sache de quel type de service il s'agit.

Astuce [eZ5] Créer une page de login

Objectif

Le but de cet article est de proposer un exemple de page de connexion. Il s'articule autour de deux fichiers principaux : un template Twig et un contrôleur PHP.

Il est réalisé entièrement en mode Symfony, sans utiliser le stack Legacy.

Si l'utilisateur saisit de mauvais identifiants, un message d'erreur est affiché. Une fois connecté, il est redirigé vers la page qu'il consultait avant de se connecter.

Prérequis

  • Vous avez déjà créé le Bundle Acme/MyBundle, et l'avez activé dans le fichier ezpublish/EzPublishKernel.php.
  • Le pagelayout utilisé par défaut est AcmeMyBundle::pagelayout.html.twig. Il possède un bloc nommé col_main.
  • Dans le pagelayout, la variable redirect_uri doit être définie et contenir l'url courante (ex: /Ma-rubrique/Mon-article).

Création du template

Dans le répertoire de templates de votre bundle (Resources/views/), créez les répertoires user/connection/, qui contiendront tous les templates pour la connexion des utilisateurs (connexion, inscription, ...)

Créez ensuite le fichier login.html.twig dans le répertoire user/connection/.

Voici à quoi il peut ressembler :

{# Surcharge du bloc 'col_main' du template pagelayout.html.twig, pour la page de connexion #}
{% extends noLayout ? viewbaseLayout : "AcmeMyBundle::pagelayout.html.twig" %}

{#
    Affiche un article en mode Full

    Paramètres :
    - noLayout      : False
    - fail_login    : Si la connexion a échouée
    - redirect_uri  : URI vers laquelle rediriger après la connexion
 #}

{% block col_main %}

<div class="main-content user user-login panel">

    {# Titre #}
    <header class="heading">
        <h1>Connexion</h1>
    </header>

    {# Contenu #}
    <div class="content-body">

        {# Message d'erreur #}
        {% if fail_login %}
            <div class="alert alert-danger">
                <button data-dismiss="alert" class="close" type="button">×</button>
                <p><strong>Erreur !</strong></p>
                <p>Identifiant ou mot de passe invalide.</p>
            </div>
        {% endif %}

        {# Formulaire #}
        <form class="form-horizontal" method="post"
              action="{{ path( 'acme_my_user_login', { 'redirectURI': redirect_uri } ) }}">

            <div class="form-group {% if fail_login %}has-error{% endif %}">
                <label class="col-lg-3 control-label" for="login">Identifiant</label>
                <div class="col-lg-4">
                    <input type="text" placeholder="Identifiant" 
                           id="login" name="Login" class="form-control input-small">
                </div>
                <div class="validation col-lg-5">
                    <img src="{{ asset( "bundles/acmemy/images/error.png" ) }}" alt="Erreur" />
                </div>
            </div>

            <div class="form-group {% if fail_login %}has-error{% endif %}">
                <label class="col-lg-3 control-label" for="password">Mot de passe</label>
                <div class="col-lg-4">
                    <input type="password" placeholder="Mot de passe" 
                           id="password" name="Password" class="form-control input-small">
                </div>
                <div class="validation col-lg-5">
                    <img src="{{ asset( "bundles/acmemy/images/error.png" ) }}" alt="Erreur" />
                </div>
            </div>

            <div class="form-group">
                <div class="col-lg-10 text-right">
                    <button class="btn btn-primary" type="submit">Connexion</button>
                </div>
            </div>
        </form>
    </div>
</div>
{% endblock %}

Explications :

  • Ligne 2, on commence par étendre le pagelayout par défaut pour modifier le bloc principal.
  • Juste en dessous, en commentaire, on liste les paramètres disponibles/nécessaires dans le template.
  • Ensuite, on commence la surcharge du bloc principal de la page : col_main.
  • On affiche un message d'erreur si les identifiants sont mauvais ({% if fail_login %}).
  • On affiche un formulaire avec un champ identifiant et un champ mot de passe.
  • Le formulaire pointe vers la route acme_my_user_login, sur laquelle Symfony va brancher le futur contrôleur. L'URL courante lui sera transmise en argument.

Remarques :

  • L'image d'erreur doit être présente dans le répertoire Resources/public/images/ du bundle.
  • Ce template utilise le framework CSS Bootstrap 3.
  • Les textes devraient être entre les tags {% trans %} et {% endtrans %}, pour pouvoir être traduits facilement par la suite si besoin.

Création du contrôleur

C'est le contrôleur qui va gérer les actions de connexion et déconnexion des utilisateurs. Il aura donc deux actions : login et logout.

Créez le fichier UserConnectionController.php dans le répertoire Controller/ de votre Bundle :

<?php
namespace Acme\MyBundle\Controller;

use \eZ\Bundle\EzPublishCoreBundle\Controller;
use \eZ\Publish\API\Repository\Exceptions\NotFoundException;
use \eZ\Publish\API\Repository\Values\User\User;
use \Symfony\Component\HttpFoundation\Cookie;
use \Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Controleur pour gérer les vues de connexion des utilisateurs.
 */
class UserConnectionController extends Controller {

    /**
     * Gestion de l'affichage de la page de login.
     *  - Affiche la page de login par défaut
     *  - Affiche d'éventuelles erreur de connexion
     *  - Connecte et redirige l'utilisateur
     *
     * @param string URI vers laquelle rediriger après la déconnexion
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function loginAction( $redirectURI ) {

        $failLogin = false;

        // Suppression d'un éventuel doublon dans l'URI
        if ( ( $offset = strpos( $redirectURI, '/user/login' ) ) === 0 ) {
            $redirectURI = substr( $redirectURI, $offset );
        }

        $request = $this->getRequest()->request;

        // Si le formulaire de connexion a été soumis
        if ( $request->has( 'Login' ) && $request->has( 'Password' ) ) {

            $login       = $request->get( 'Login' );
            $password    = $request->get( 'Password' );

            if ( trim( $login ) != '' && trim( $password ) != '' ) {
                $userService = $this->getRepository()->getUserService();

                try {
                    $user = $userService->loadUserByCredentials( $login, $password );
                    return $this->connectUser( $user, $redirectURI );

                } catch (NotFoundException $e) {
                    $failLogin = true;
                }
            } else {
                $failLogin = true;
            }
        }

        return $this->render(
            'AcmeMyBundle:user\connection:login.html.twig',
            array(
                'noLayout'      => false,
                'fail_login'    => $failLogin,
                'redirect_uri'  => $redirectURI
            )
        );
    }

    /**
     * Déconnecte l'utilisateur courant.
     *
     * @param string URI vers laquelle rediriger après la déconnexion
     *
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException Si l'utilisateur anonyme n'existe pas
     */
    public function logoutAction( $redirectURI ) {

        // Suppression d'un éventuel doublon dans l'URI
        if ( ( $offset = strpos( $redirectURI, '/user/logout' ) ) === 0 ) {
            $redirectURI = substr( $redirectURI, $offset );
        }

        // Récupération de l'utilisateur anonyme
        $userService  = $this->getRepository()->getUserService();
        $anonymousUser = $userService->loadAnonymousUser();

        return $this->connectUser( $anonymousUser, $redirectURI );
    }

    /**
     * Connecte l'utilisateur en argument et retourne une redirection 302.
     * Si l'utilisateur en argument est anonyme, alors c'est une déconnexion.
     *
     * @param \eZ\Publish\API\Repository\Values\User\User $user L'utilisateur à connecter
     * @param string $redirectURI URI vers laquelle rediriger l'utilisateur après connexion
     *
     * @return RedirectResponse
     */
    protected function connectUser( User $user, $redirectURI = '/' ) {

        $repository = $this->getRepository();
        $repository->setCurrentUser( $user );
        $session    = $this->getRequest()->getSession();

        try {
             $response = new RedirectResponse( $redirectURI );

        } catch (NotFoundException $e) {
             $response = new RedirectResponse( '/' );
        }

        $userService    = $repository->getUserService();
        $anonymousUser  = $userService->loadAnonymousUser();

        // Si l'utilisateur en argument est anonyme
        if ( $user->id === $anonymousUser->id ) {
            // Déconnexion de l'utilisateur courant
            $response->headers->setCookie( new Cookie( 'is_logged_in', 'false' ) );
            $session->remove( 'eZUserLoggedInID' );

        } else {
            // Connexion de l'utilisateur
            $response->headers->setCookie( new Cookie( 'is_logged_in', 'true' ) );
            $session->set( 'eZUserLoggedInID', $user->id );
        }

        return $response;
    }
}

Explications :

  • Le contrôleur est une classe PHP qui étend la classe Controller du Bundle eZ\Bundle\EzPublishCoreBundle fourni par eZ Publish.
  • Ses deux premières méthodes sont des actions et leurs noms finissent d'ailleurs par Action. Elles retournent un objet Response.
  • La troisième est une méthode utilitaire pour connecter un utilisateur.
  • Par défaut, loginAction() inclue le template de login dans sa réponse, grâce aux lignes :
return $this->render(
    'AcmeMyBundle:modules/user:login.html.twig',
    array(
        'noLayout'      => false,
        'fail_login'    => $failLogin,
        'redirect_uri'  => $redirectURI
    )
);
  • Si le formulaire est soumis et valide, on appelle la méthode connectUser() qui connecte l'utilisateur et on fournit une réponse de type redirection.

Configuration de la route

Pour que Symfony sache quoi faire lorsqu'on appelle la page http://mon-site/user/login, il faut modifier le fichier Resources/config/routing.yml dans votre bundle.

Ajoutez-y les deux routes suivantes, pour se connecter et se déconnecter :

acme_my_user_login:
    pattern:  /user/login{redirectURI}
    defaults: { _controller: AcmeMyBundle:UserConnection:login }
    requirements:
        redirectURI: ".*"

acme_my_user_logout:
    pattern:  /user/logout{redirectURI}
    defaults: { _controller: AcmeMyBundle:UserConnection:logout }
    requirements:
        redirectURI: ".*"

Explications :

  • Une route doit être nommée par un identifiant unique de votre choix (ex : acme_my_user_login). C'est lui qui est appelé dans la fonction path() côté template.
  • Elle associe un pattern d'URL, à l'action d'un contrôleur.
  • Pour la première route, si on appelle la page /user/login<quelque chose>, la méthode loginAction() du contrôleur UserConnectionController du bundle AcmeMyBundle sera exécutée. La méthode recevra quelque chose en argument.
  • Les éléments dans requirements permettent entre autres de spécifier la forme que doit avoir le paramètre. Ici, n'importe qu'elle chaîne est autorisée.

Astuce [eZ5] Créer un bundle

Qu'est-ce qu'un Bundle ?

Bundle est le nom Symfony pour un module, une brique, une extension (terme eZ Publish 4.X). Il peut contenir tout ou une partie du design, une API technique, une fonctionnalité particulière, ...

Il peut être dépendant d’autres Bundles, mais est réutilisable. Il est identifié par un nom de domaine (namespace) et un nom de Bundle (finissant par Bundle), le tout concaténé.

Création du Bundle

La création du Bundle peut se faire via ligne de commande, à partir du répertoire racine de l’application eZ Publish :

php ezpublish/console generate:bundle --namespace="MonNamespace/MonBundle"

Explications :

  • Le paramètre namespace est le nom du namespace et le nom du Bundle concaténés.
  • Le nom de domaine peut être le nom du projet, celui de l’entreprise, ... Il peut contenir des /.
  • Le nom du Bundle doit finir par Bundle.

Nom du Bundle

L’assistant de création du Bundle propose alors le nom final du Bundle : MonNamespaceMonBundle.

Appuyez sur Entrée pour conserver ce nom standard.

Remarque :

Si vous créez le Bundle principal de votre application, et que vous souhaitez avoir un namespace et un nom de Bundle identique, c'est à cette étape que vous pouvez simplifier le nom final pour éviter d'avoir MonNamespaceMonNamespaceBundle.

Emplacement du Bundle

Vous pouvez ensuite choisir le chemin où se trouvera le Bundle.

Par défaut il sera créé dans le répertoire src/ à la racine de l’application eZ Publish, ce qu'il est préférable de conserver : appuyez sur Entrée.

Format de la configuration

Choisissez yml comme format de configuration, et validez avec Entrée.

Génération et autres configurations

Pour une première fois, choisissez de générer la structure complète du Bundle.

Appuyez sur Entrée pour confirmer toute la génération.

Même chose pour les questions suivantes.

Fichiers générés

L’assistant de création de Bundle a généré l’arborescence suivante :

Arborescence bundle généré

Comme tout bundle Symfony, il se compose de 3 répertoires principaux :

  • Controller/ : vous y créerez vos contrôleurs (équivalents de vos modules dans eZ4).
  • Resources/ : s'y trouvent tous les fichiers non PHP (templates Twig, fichiers de config, js, CSS, ...).
  • Tests/ : répertoire contenant vos tests unitaires.

Erreur [eZ5] FatalErrorException: Error: Class 'XSLTProcessor' not found

Si vous rencontrez l'erreur suivante après l'installation d'eZ Publish 5 :

FatalErrorException: Error: Class 'XSLTProcessor' not found in 
[...]\vendor\ezsystems\ezpublish-kernel\eZ\Publish\Core\FieldType\XmlText\Converter\Html5.php line 77

C'est que l'extension xsl n'est pas activée pour PHP.

Erreur [eZ5] The extension "ext/fileinfo" must be loaded in order for this class to work

Lorsque vous migrez vers eZ Publish Community Project 2013.06, vous pouvez rencontrer cette erreur :

The extension "ext/fileinfo" must be loaded in order for this class to work.

Fileinfo est une extension pour PHP. Elle est généralement déjà packagée sous Linux, mais pas sous Windows avec WampServer.

Activez l'extension php_fileinfo et redémarrez apache.

Marque-page [eZ] Créer un datatype

Dans eZ Publish 4, un datatype est un type de champ pour une classe de contenu.

EZ Publish en fournit un certain nombre : image, ligne de texte, texte riche, case à cocher, nombre, ... Vous pouvez également créer votre propre datatype, pour facilité la contribution et l'affichage d'un champ.

On peut par exemple imaginer un champ couleur, avec côté back-office une pipette ou une palette de couleur pour choisir facilement sa couleur.

Voici un tutoriel complet pour créer son propre datatype, rédigé par Jérôme Vieilledent et Nicolas Pastorino pour PHP Solutions.

Astuce [eZ5] Développez en mode dev

Avec eZ Publish 5, pour activer la console de développement de Symfony vous devez modifier la configuration Apache, à priori dans votre virtual host.

Remplacez index.php par index_dev.php :

DirectoryIndex index.php
...
RewriteRule .* /index.php

Remarque :

EZ Publish et Symfony doivent également être configurés en mode développement.

Erreur [eZ5] L'arbre des contenus n'est plus disponible dans le back-office

Avec eZ Publish 5, le mot de passe de la base de données est stocké à deux endroits : dans eZ et dans Symfony.

Si tout fonctionne correctement dans le back-office, excepté l'arbre des contenus qui n'apparait pas, c'est probablement que les deux mots de passes sont différents.

Pour vérifier qu'ils sont bien configurés, vérifier dans ces deux fichiers :

  • ezpublish_legacy/override/site.ini.append.php
  • ezpublish/config/ezpublish.yml

S'il y a d'autres problèmes dans le back-office, comme l'impossibilité de naviguer dans les contenus via les éléments enfants, c'est sans doute un problème de cache.

Astuce [eZ4] Les préférences utilisateur

Le module user d'eZ Publish fournit une vue pour stocker simplement des préférences utilisateurs : user/preferences.

Concrètement, ces préférences sont stockées en base de données, avec pour clé le couple (user_id, preference_name) et pour valeur celle de notre choix.

Ce fonctionnement est souvent utilisé en back-office, pour afficher tel ou tel bloc de la page, comme la barre de droite par exemple.
Pour la partie publique côté front-office il est à éviter, car tous les utilisateurs anonymes auront la même préférence. (Ou alors il ne faut pas leur permettre de modifier la valeur.)

Créer/modifier une préférence

Côté template ou HTML

Il suffit d'appeler l'URL user/preferences, en proposant par exemple un lien à l'utilisateur.

Pour afficher/masquer la barre de droite du back-office, par exemple on a juste ce genre de liens :

{* Afficher la barre *}
<a href="{'user/preferences/set/admin_right_menu_show/1'|ezurl('no')}">
    Afficher la barre de droite
</a>

{* Masquer la barre *}
<a href="{'user/preferences/set/admin_right_menu_show/0'|ezurl('no')}">
    Masquer la barre de droite
</a>

Explication :

Pour créer/modifier une préférence, il faut appeler la vue user/preferences, avec 3 paramètres : set, le nom de la préférence puis sa valeur.

Côté PHP

Il faut utiliser la méthode setValue() de la classe eZPreferences :

eZPreferences::setValue( 'my_preference_name', 'my_value' );

Remarque :

Par défaut, la préférence sera associée à l'utilisateur connecté. Un troisième argument est disponible ($user_id), pour l'affecter à un autre utilisateur.

Récupérer la valeur de la préférence

Côté template

{ezpreference( 'my_preference_name' )}

Explication :

L'utilisateur courant est automatiquement utilisé.

Côté PHP

Il faut utiliser la méthode value() de la classe eZPreferences :

eZPreferences::value( 'my_preference_name' );

Remarques :

  • Pour récupérer la valeur pour un autre utilisateur que celui connecté, utilisez le second argument facultatif.
  • La méthode values() de la classe eZPreferences permet de récupérer toutes les préférences d'un utilisateur.

Astuce [eZ4] Différences entre les méthodes variable() et variableArray() de la classe eZINI

La classe eZINI propose deux méthodes pour récupérer des variables sous forme de tableau : variable() et variableArray().

variable()

C'est la méthode habituelle que vous utilisez pour récupérer des chaînes de caractères. Par exemple pour récupérer la valeur dans cette configuration :

[MySection]
MyProperty=value

La méthode retournera :

value1

Elle fonctionne aussi pour un tableau de valeurs :

[MySection]
MyProperty[]
MyProperty[]=value1
MyProperty[]=value2
...

La méthode retournera :

Array (
  '0' => value1
  '1' => value2
  ...
)

variableArray()

Cette méthode permet de récupérer un tableau de valeurs pour ce genre de configuration :

[MySection]
MyProperty=value1;value2;value3;...

La méthode retournera :

Array (
  '0' => value1
  '1' => value2
  '2' => value3
  ...
)

Astuce [eZ5] Générer la configuration Symfony du projet existant

Lorsque vous migrez un site eZ Publish 4.x vers 5.x sans utiliser l'assistant d'installation automatisée, vous devez générer la configuration Symfony de votre site.

EZ Publish 5 est maintenant un projet Symfony et utilise la gère sa configuration en fichiers .yml. Pour passer des anciens .ini vers les nouveaux .yml, utilisez la commande suivante à la racine de votre projet :

php ezpublish/console ezpublish:configure --env=prod <group> <admin-siteaccess>

Remplacez <group> par votre groupe de siteaccess (ex: mon_site), et <admin-siteaccess> par le nom du siteaccess de votre back-office.

Remarques :

  • Symfony permet de switcher simplement entre les environnements de production et de développement. Pour chacun d'eux elle propose un fichier de configuration par défaut : ezpublish/config/ezpublish_dev.yml et ezpublish/config/ezpublish_prod.yml.
    Remplacez --env=prod par --env=dev pour utiliser la configuration de développement.
  • Le groupe de siteaccess est une nouvelle notion introduite par eZ Publish 5.

Astuce [eZ5] Les liens symboliques dans eZ Publish 5

EZ Publish 5 utilise le principe des assets de Symfony. Les fichiers statiques (css, js, images, ...) que vous utilisez dans vos bundles doivent donc être aussi présents dans le répertoire web/.

De plus, tous les fichiers uploadés via le back-office (qui est encore en eZ Publish 4), sont stockés par défaut dans le répertoire ezpublish_legacy/var/storage/. De la même manière, ils doivent aussi se retouver dans le répertoire web/ pour être servis par apache.

Pour mettre à jour votre répertoire web/, vous avez le choix entre copier tous les fichiers statiques, ou créer des liens symboliques.

Pour cela eZ Publish a surchargé la console de Symfony, et vous propose ces deux commandes (à lancer à la racine de votre projet) :

php ezpublish/console assets:install --symlink web
php ezpublish/console ezpublish:legacy:assets_install --symlink web

Explications :

  • La première commande crée des liens symboliques dans le répertoire web/, pointant vers les ressources des bundles.
  • La seconde crée des liens pointant vers les ressources du répertoire ezpublish_legacy/.

Remarque :

L'option --symlink web est facultative. Si vous la retirer (ou si elle ne fonctionne pas), eZ Publish créera des copies des fichiers au lieu des liens symboliques.

Astuce [eZ4] Utiliser l'API Ajax d'eZ Publish

L'extension ezjscore d'eZ Publish permet d'appeler des fonctions PHP via des requêtes Ajax. Vous pouvez l'utiliser pour mettre à jour une partie de la page sans la recharger complètement.

Le principe

Le javascript va lancer une requête Ajax à la vue call du module ezjscore (et donc appeler l'URL /ezjscore/call). Cette vue va retourner le résultat d'une méthode PHP, en fonction de la configuration du fichier ezjscore.ini.

Le résultat est alors disponible côté js et peut être utilisé pour modifier une partie de la page.

PHP

Les méthodes disponibles pour un appel Ajax doivent être implémentées dans des classes héritant de ezjscServerFunctions.

Par exemple dans le fichier monextension/classes/MyServerCallFunctions.php :

<?php

/**
 * Classe de fonctions à appeler en ajax.
 */
class MyServerCallFunctions extends ezjscServerFunctions {

    /**
     * Retourne le message "Hello x !", avec x le premier élément du tableau de paramètres, 
     *  ou "world" si aucun paramètre n'est passé.
     *
     * @param array $args Arguments
     * @return string
     */
    public static function helloMessage( array $args ) {

        // Log de l'appel de la fonction
        eZLog::write( 'Appel Ajax : ' . __METHOD__, 'debug.log' );

        if ( !empty( $args ) ) {
            $message = 'Hello ' . $args[0] . ' !';
        } else {
            $message = 'Hello world !';
        }
        return $message;
    }
}

Configuration

Pour que votre classe soit utilisable vous devez la déclarer le fichier ezjscore.ini :

Par exemple, dans le fichier monextension/settings/ezjscore.ini.append.php :

<?php /* #?ini charset="utf-8"?

[ezjscServer]
# Liste des fonctions accessibles via des appels Ajax
FunctionList[]=my_function_name

[ezjscServer_my_function_name]
# Nom de la classe PHP
Class=MyServerCallFunctions
# Nom du fichier contenant la classe
File=extension/monextension/classes/MyServerCallFunctions.php
# Nom des fonctions proposées par la classe
Functions[]=my_function_name

*/ ?>

Remarque :

Une fois le fichier modifié, videz les caches et régénérez les autoloads, pour qu'eZ Publish trouve votre nouvelle classe.

Un premier test

Vous pouvez déjà appeler votre fonction en tapant l'url suivante dans votre navigateur :

http://monsite.com/index.php/ezjscore/call/my_function_name::helloMessage::dude

On appelle bien la vue call du module ezjscore, à laquelle on fournit le nom d'une fonction et la liste des arguments, séparés par ::.

Remarque :

Cela ne fonctionne que si vous êtes connecté en tant qu'administrateur. Pour éviter ça, vous devez autorisez d'autres rôles à accéder à la vue ezjscore/call. Vous pouvez même définir des limitations, pour n'autoriser que l'accès à la fonction my_function_name.

Javascript

Maintenant que votre fonction est accessible, voici comment l'utiliser dans vos template.

Tout d'abord, vous devez ajouter le code suivant à votre template, pour inclure l'API Javascript d'ezjscore :

{ezscript_require( array( 'ezjsc::jquery', 'ezjsc::jqueryio' ) )}

Remarques :

  • L'exemple présenté utilise jQuery. Vous pouvez également utiliser l'API YUI fournie avec eZ.
  • Le premier élément du tableau (ezjsc::jquery) est facultatif si vous avez déjà inclus jQuery dans votre page, et peut même poser problème si la version de jQuery incluse est différente.

Voici maintenant le code Javascript :

var  dataSent = {arg0: 'dude', arg1: 'not_used'};
$.ez( 
    'my_function_name::helloMessage::dude::not_used',
    dataSent,
    function(data) {        
        // Si l'appel Ajax a retourné une erreur
        if ( data.error_text ) {
            console.error('Erreur : ' + data.error_text )
        // Si l'appel Ajax a retourné des résultats
        } else if ( data.content.length > 0 ) {
            console.info('Résultat : ' + data.content);
        }
    }
 );

Explications :

  • Si une erreur se produit, le message est disponible dans la variable data.error_text.
  • Si l'appel réussit, le résultat est présent dans la variable data.content.

Astuce [eZ4] Utiliser les alias de fetch

Pour simplifier l'utilisation des fetch dans les templates, eZ Publish propose d'utiliser des alias.

Exemple d'utilisation

Par exemple, si vous voulez compter récursivement les articles fils du nœud courant, le fetch standard serait :

{def $nb = fetch( 'content', 'tree_count',
    hash( 
        'parent_node_id', $node.node_id,
        'class_filter_type', 'include',
        'class_filter_array', array( 'article' ) 
    ) 
)}

Si vous utilisez souvent ce même fetch, vous aimerez sans doute lui créer un alias. La syntaxe devient alors :

{def $nb = fetch_alias( 
    'children_article_count', 
    hash( 'parent_node_id', $node.node_id ) 
)}

Explications :

  • L'alias s'appelle children_article_count()
  • Il n'a qu'un seul paramètre : l'ID du nœud parent.

Configuration

Pour informer eZ Publish de votre alias, il faut le déclarer dans le fichier fetchalias.ini.

Par exemple, dans le fichier monextension/settings/fetchalias.ini.append.php :

<?php /* #?ini charset="utf-8"?

[children_article_count]
# Compte récursivement le nombre d'articles sous le nœud dont l'ID est en paramètre
Module=content
FunctionName=tree_count
Constant[class_filter_type]=include
Constant[class_filter_array]=article
Parameter[parent_node_id]=parent_node_id

*/ ?>

Explications :

  • La section (children_article_count) est le nom de votre alias, à utiliser dans votre template.
  • Le module et la fonction sont ceux que vous auriez appelés dans le fetch standard.
  • Les constantes sont les paramètres fixes que vous auriez passés au fetch standard.
  • Les paramètres permettent de mapper le nom des paramètres de l'alias avec ceux du fetch standard.

Remarques :

  • N'oubliez pas de vider les caches pour qu'eZ Publish prennent en compte cette configuration.
  • EZ Publish fournit déjà des alias, visibles dans le fichier settings/fetchalias.ini.

Astuce [eZ4] Créer ses fetch personnalisés

EZ Publish fournit un grand nombre de fonctionnalités, accessibles dans les templates via des fetch (voir la documentation).

Vous pouvez créer vos propres fetch via le système de function_definition des modules.

Le module

  • Commencez par créer un nouveau module (ou utilisez un module déjà existant dans votre extension).
  • Si c'est un nouveau, vous devez le déclarer dans le fichier module.ini.

Par exemple, dans le fichier monextension/settings/module.ini.append.php :

<?php /* #?ini charset="utf-8"?

[ModuleSettings]
ExtensionRepositories[]=monextension
ModuleList[]=monmodule

Le PHP

Vous n'avez qu'un seul fichier à créer dans votre extension : modules/monmodule/function_definition.php.

Ce fichier contient un tableau php qui liste les fonctions disponibles. Pour chacune d'elle, vous préciserez son nom, son type (lecture ou écriture), la méthode PHP à appeler et les paramètres à lui passer.

Par exemple :

<?php
$FunctionList = array();

$FunctionList['tree_unique'] = array( 
    'name' => 'tree_unique',
    'operation_types' => array( 'read' ),
    'call_method' => array( 
        'class' => 'ATContentFunctionCollection',
        'method' => 'fetchObjectTree' 
    ),
    'parameter_type' => 'standard',
    'parameters' => array( 
        array( 
            'name' => 'parent_node_id',
            'type' => 'integer',
            'required' => true 
        )  
    ) 
);

Explications :

  • Cette fonction s'appelle tree_unique
  • Elle est de type lecture (elle n'effectue pas de modifications sur les données, mais en retourne)
  • Elle retourne le résultat de la méthode fetchObjectTree() de la classe ATContentFunctionCollection
  • Elle a un paramètre obligatoire : l'ID du nœud parent

Remarque :

Vous devez bien sur avoir créer une classe ATContentFunctionCollection possédant la méthode fetchObjectTree (). Le nom de la classe n'a pas d'importance, mais dans le cœur d'eZ Publish on ajoute FunctionCollection pour mettre en évidence les fonctionnalités utilisables dans les fetch.

Dans les templates

Vous pouvez maintenant utiliser votre fetch après avoir vider les caches. Utilisez-le comme n'importe quel fetch natif :

{def $node_list = fetch( 
    'monextension', 'tree_unique', 
    hash( 'parent_node_id', $node.node_id ) 
)}

Marque-page [eZ4] Récupérer les urls et les chemins vers les répertoires

EZ Publish fournit la classe eZSys, propose pas mal de méthodes pour récupérer par exemple :

  • Le chemin vers le répertoire var
  • L'url du serveur
  • Le port utilisé
  • Le chemin vers le répertoire du projet
  • La version de php
  • ...

Cette classe se trouve dans lib/ezutils/classes/ezsys.php.

Marque-page [eZ4] Un système de chat

Si vous avez besoin d'un système de chat dans votre site eZ Publish, voici une extension qui peut faire l'affaire : eZ phpFreeChat.

Interface de l'extension eZ phpFreeChat

Elle fournit un module pour eZ Publish, dont l'unique vue affiche un chat. Le chat est composé d'une discussion principale visible par tous les utilisateurs connectés au chat et permet des discussions privées entre deux utilisateurs (en cliquant sur leur nom).

Remarque :

L'extension utilise la version 1.3 de phpfreechat, qui existe maintenant en 2.1.1.