Cet article regroupe les différents cas que j'ai eu à résoudre pour effectuer la migration d’extensions vers J4.
Il est évolutif et se complètera au fus et à mesure.

Après la paragraphe recensant quelques liens vers des articles utiles, vous trouverez un petit récapitulatif de ce que j'ai rencontré comme correction pour passer une extension de Joomla3 à Joomla4.

Cette liste n'est pas exhaustive bien sûr, et se complétera au fur et à mesure

Documentation

Avant tout voici déjà quelques publications concernant le migration d’extension de J3 vers J4 :

JApplication

isAdmin() deprecated 4.0 Use isClient('administrator') instead.

Remplacer JFactory::getApplication()->isAdmin() par JFactory::getApplication()->isClient('administrator')

JRequest

Remplacer JRequest::get par JFactory::getApplication()->input->get

JError

Remplacer
JError::raiseWarning('',
par
JFactory::getApplication()->enqueueMessage(
et ajouter 'WARNING' en dernier paramètre.

JDispatcher

  • Deprecated class placeholder. You should use JEventDispatcher instead
  • The CMS' Event classes will be replaced with the `joomla/event` package

Actuellement (exemple) :

$dispatcher = JDispatcher::getInstance();
$result = $dispatcher->trigger('onContentBeforeSave', array($this->_context, &$table, $isNew, $data));

Supprimer $dispatcher = JDispatcher::getInstance();

remplacer $result = $dispatcher->trigger('onContentBeforeSave', array($this->_context, &$table, $isNew, $data));

par $result = Factory::getApplication()->triggerEvent('onContentBeforeSave', array($this->_context, &$table, $isNew, $data));

JRegistry

JRegistry::getInstance : si l'objet et de passer l'information du model à la vue, recuperer l'information d'une autre manière (getItem et getState par exemple)

JViewLegacy

Remplacer $this->assign('P1',$P2) et $this->assignRef('P1',$P2)
par : $this->P1 = $P2;

JTable

Remplacer :

$table = Table::getInstance('Name', 'Prefix');

Par:

$table = \Joomla\CMS\Factory::getApplication()
->bootComponent('com_mycomponent')->getMVCFactory()->createTable('Name', 'Prefix');

Vu que le nom du composant est défini il ne faut plus definir le chemin des tables.

Il faut donc supprimer les

JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_mycomponent/table');

JDataBase

Remplacer :

if (!$db->query()) {
Factory::getApplication()->enqueueMessage($db->getErrorMsg(),'ERROR');
return false;
}

par

try {
 $db->execute();
} catch (Exception $e) {
 Factory::getApplication()->enqueueMessage(JText::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()),'ERROR');
 return false;
}

Idem il faut remplacer les $db->getErrorNum() et les $db->getErrorMsg() par une gestion des exceptions comme ci-dessus.

Par exemple, remplacer :

$db->setQuery($query);
$results = $db->loadObjectList();
if ($db->getErrorNum()) {
 JFactory::getApplication()->enqueueMessage($db->getErrorMsg(),'WARNING');
 return false;
}

par

$db->setQuery($query);
try {
 $results = $db->loadObjectList();
} catch (Exception $e) {
 JFactory::getApplication()->enqueueMessage(JText::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()),'ERROR');
 return false;
}

Query

$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select(...)->limit('10');

Il faut remplacer la methode limit par SetLimit()

insert/update dans la database / structure des tables

Si à l'insert ou à l'update les champs n'ont pas de valeur et qu'aucune valeur par défaut n'a été définie, il y aura retour en erreur.

Idem, si les champs numériques ou de type 'date' ont des valeurs de type chaine (par exmeple : chaine vide -ce peut être qui est asssez fréquent-).

Il faudra donc correctement initialiser tous les champs ou définir toujours des valeurs par défaut pour chaque champ dans les tables de la base de données.

Si vous utilisez la clase Table (voir la documentation de Joomla...) ceci peut se 'courtourner' assez facilement par la création d'une classe intermédiaire.

Pour contourner le problème en attendant de faire 'le ménage" dans toutes mes structures de table, voici ce que je fais dans ma méthode check globale, donc présente dans une classe héritant de la classe Table' (anciennement JTable) et héritée de toutes mes tables ( la place de JTable) :

class GskiTable extends JTable
{
.
.
.
 public function check(){
   foreach ($this->getFields() as $k => $v)
  {
    if (!\in_array($k, $this->tblkeys) && (strpos($k, '_') !== 0))
    {
      if($this->$k == ''){
       if($this->isNum($v->Type) || $this->isDate($v->Type)){
        if(strtoupper($v->Null) == "YES"){
         $this->$k = null;
        }else{
         if(!is_null($v->Default)){
          $this->$k = $v->Default;
         }else{
          if($this->isNum($v->Type)){
          $this->$k = '0';
         }
        }
       }
      }
     }
    }
   }
   return parent::check();
  }
  public function isNum($Ptype){
   $Ptype = strtolower($Ptype);
   if (substr($Ptype,0,4) == 'int('){
    return true;
   }
   if ($Ptype == 'double'){
    return true;
   }
   return false;
  }
  public function isDate($Ptype){
   $Ptype = strtolower($Ptype);
    if (substr($Ptype,0,4) == 'date'){
     return true;
    }
   return false;
  }

Que fait ce script :

La méthode check est appelé par la méthode store de vos classes héritées de la classe Table du framework (où ici héritées de GskiTable qui hérite elle même de Table).
Donc ici on rajoute un contrôle et mise à jour éventuel des champs de vos tables avant d'appeler la méthode check de la classe mère Table, ceci avant tout insert ou update dans la table en question.

Pour les champs de type numériques ou dates, s'il contiennent une chaine vide, on remplace soit par null (si null est autorisé), soit par la valeur par défaut définies dans la structure de la table.

Si null n'est pas autorisé, ni aucune valeur par défaut, pour les numériques on met la valeur zéro.

Ceci règle pas tous les problèmes, reste à  traiter :

  • les dates ou null n'est pas autorisé, ni aucune valeur par défaut définie
  • les chaines de caractères
  • les autres types éventuels

Tout ceci avec prudence bien sûr, car on détourne un contrôle de champ non complété qui en général est intéressant en débogage.

JHtml

remplacer JHtml::_('behavior.formvalidation');
par JHtml::_('behavior.formvalidator');

remplacer JHtml::_('behavior.tooltip');
par JHtml::_('bootstrap.tooltip');

JForm

  • Use renderField() instead of getControlGroup
  • Use renderFieldset() instead of getControlGroups

Exemple :

Chercher : form->getControlGroup(

et remplacer par form->renderField(

JForm tooltips d'aide

Voir l'article spécifique...

JQuery

Si vous utilisez JQuery dans vos templates de vues :

$wa = Joomla\CMS\Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useScript('jquery');

Pour appeler JQuery, qui de base je vous le rapelle n'est plus utilisé par le framework de Joomla 4.

Mise en forme html / boostrap

Il faudra probablement revoir au moins partiellement vos appel de classes css pour les rendre compatible avec la nouvelle version de boostrap.
Ce n'est pas du fonctionnel mais de la mise en forme. Il est donc difficile de donner des règles générales.

Il faut vérifier chacune de vos vues, et voir son comportement adaptatif.

Installer

Remplacer $parent->get('manifest')->version

par $parent->getManifest()->version

JavaScript

Remplacer : JFactory::getDocument()->addScriptDeclaration(....
Par : 
$wa = Joomla\CMS\Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->addInlineScript(...

Remplacer : JFactory::getDocument()->addStyleSheet(....
Par : 
$wa = Joomla\CMS\Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle(...

 Classes JModel

Si à l'appel d'un de vos model heritant de JModelItem, vous avez l'erreur :
Error: Class..... contains 1 abstract method and must therefore be declared abstract or implment the remaining Mathod....

Ceci signifie que votre model n'hérite pas de la bonne classe, et qu'il manque des définitions de méthodes.

Vous heritez peut-etre de ItemModel au lieu de ListModel

Si vous n'avez pas besoins des méthode de ces model, mais que de l'accès DB, essayez alors de le faire heriter d'un classe d'un niveau précedent, comme

class ...... extends BaseDatabaseModel

JForm et les champs de type calendar

Problème avec la selection des heures, qui ne passent pas dans le champ.

A priori cela provient des champ list de type select avec l'option : 

HTMLHelper::_('formbehavior.chosen',

Il faut utiliser l'option :layout="joomla.form.field.list-fancy-select" dans la definition xml du champ (à condition d'utiliser la classe Form bien sûr)

 Trucs utiles

Version de joomla:

$version = new Version();
$joomlaversion = explode('.', $version->getShortVersion());

ou simplement utiliser la superglobale JVERSION par exemple :

$jversion = preg_replace('#[^0-9\.]#i', '', JVERSION);
if (version_compare($jversion, '4.0.0', '>=')) { // Joomla 4 et +

Autres

Remplacer Par
 Factory::getUser() Factory::getApplication()->getIdentity()
Factory::getDBO()

use Joomla\Database\DatabaseInterface;
...
Factory::getContainer()->get(DatabaseInterface::class);

Table::getInstance($name, $prefix, $prefix); Factory::getApplication()->bootComponent('...')->getMVCFactory()->createTable($name, $prefix, $config);