Générateur de fichiers OCmod pour une boutique en ligne sur Opencart

Est-il réaliste de se concentrer sur vos algorithmes lors du développement de modifications pour le populaire moteur de boutique en ligne Opencart, et de laisser la préparation d'un fichier à télécharger sur ce CMS à la merci de scripts spéciaux? En fait, c'est ce qui faciliterait beaucoup la vie des développeurs sous Opencart, et dans cet article je propose ma solution.



Petite introduction:



Le format ocmod est une solution assez élégante pour définir les modifications des fichiers source, quel que soit leur format. Une des parties du format est un fichier XML, dans lequel il est écrit dans quel fichier et où dans ce fichier certaines modifications doivent être apportées. Voici un exemple de fichier ocmod (extrait d'ocmod.net, une description plus détaillée peut être trouvée ici):



<?xml version="1.0" encoding="utf-8"?>
<modification>
    <name>   </name>
    <code>product-page-views</code>
    <version>1.0</version>
    <author>https://ocmod.net</author>
    <link>https://ocmod.net</link>
    <file path="catalog/controller/product/product.php">
        <operation>
            <search>
                <![CDATA[$data['images'] = array();]]>
            </search>
            <add position="after">
                <![CDATA[
                	$data['view'] = $product_info['viewed'];
                ]]>
            </add>
        </operation>
    </file>
    <file path="catalog/language/en-gb/product/product.php">
        <operation>
            <search>
                <![CDATA[$_['text_search']]]>
            </search>
            <add position="before">
                <![CDATA[
                	$_['text_view']              = 'View: ';
                ]]>
            </add>
        </operation>
    </file>
</modification>


En termes généraux, nous définissons les éléments suivants:



<file path="  ">
        <operation>
            <search><![CDATA[ ]]></search>
            <add position=" – ,   ">
                <![CDATA[    ]]>
            </add>
        </operation>
    </file>


Bien que le principe soit assez transparent, la question se pose: est-il possible d'automatiser le processus de sa création ou faudra-t-il l'écrire à la main, car le programmeur doit être engagé dans la programmation, et ne pas se masturber avec des activités de routine stupides.



Idéalement, écrire une modification pour Opencart ressemblerait à ceci: nous avons téléchargé la version "immaculée" de la boutique, apporté quelques modifications directement dans son code source et exécuté un script "magique" qui a généré tout l'ocmod sur place. En fait, tout est un peu plus compliqué, mais nous allons essayer de nous rapprocher de ce schéma. Le problème principal est de déterminer l'emplacement dans le fichier à insérer (ce qui se trouve entre <search> ... </search>). Cela doit être fait par le programmeur. En règle générale, ils essaient de le rendre aussi universel que possible afin de couvrir plus de versions potentielles de la source, et en même temps pour qu'il change exactement là où il est nécessaire. Ceci est clairement fait à la main. Tout le reste est automatisé.



Une petite digression: la recherche s'effectue dans la chaîne entière, et l'insertion n'est possible qu'avant, après ou à la place de celle-ci, mais pas à l'intérieur (dans le package OCMOD classique pour Opencart). C'est une limitation qui m'est incompréhensible personnellement. De plus, je ne comprends pas pourquoi il est impossible de définir plusieurs balises <search> pour trouver le point d'insertion souhaité, qui serait traité de manière cohérente - après tout, la recherche serait beaucoup plus flexible. Par exemple, si dans le code PHP, alors, disons, trouvez le nom de la fonction, puis trouvez le bon endroit dedans, ou d'une autre manière à la discrétion du programmeur. Mais je n'ai pas trouvé cela, si je me trompe, veuillez le corriger.



Et maintenant, le plus important: vous pouvez automatiser le processus de création d'un fichier ocmod et il vous suffit de respecter le schéma souhaité. Tout d'abord, dans le fichier source, nous devons indiquer en quelque sorte le lieu de nos modifications - à la fois juste pour l'ordre, et pour que notre générateur ocmod sache tout de manière adressable. Disons que notre nom est Pyotr Nikolaevich Ivanov (les coïncidences sont accidentelles). Plaçons tous nos changements entre les balises <PNI> ... </PNI>, et pour que les balises ne gâchent pas la source, nous placerons ces balises dans les commentaires de la langue sur laquelle nous travaillons actuellement. Entre les balises, bien en place, nous définirons la chaîne de recherche entre <search> </search> et le code ajouté entre <add> </add>. Ce sera plus clair avec un exemple:



Pour les changements de PHP:




(   opencart)
// <PNI>
//             -
//      ,    (   )
// <search> public function index() {</search>
// <add position=”after”>
$x = 5;
$y = 6;
//</add> </PNI>


Ou comme ça:




(   opencart)
/* <PNI>
     <search> public function index() {</search>
     <add position=”after”> */
$x = 5;
$y = 6;
/*</add> </PNI> */


Si <search> ou <add> a des attributs, par exemple, <search index = ”1”>, ils seront transférés «tels quels» dans notre fichier ocmod. Ce que nous écrivons entre les deux ne nécessite aucun échappement XML, nous écrivons simplement la chaîne de recherche et le code.



Autre exemple, déjà pour le fichier twig que nous modifions:



            {# <PNI>
            <search><li><span style="text-decoration: line-through;">{{ price }}</span></li></search>
            <add position="replace">
            #}
            <li><span class="combination-base-price" style="text-decoration: line-through;">{{ price }}</span></li>
            {# </add></PNI> #}


Après avoir stylisé tous nos changements de cette manière, nous avons besoin d'un script qui gérera tout cela, ainsi que d'un archiveur. Je partage avec vous ma version: elle se compose d'un fichier de configuration et d'un script lui-même.



Le fichier de configuration make-ocmod.opencart.local.cfg.php (encodage UTF-8, ceci est un exemple, chacun le fait pour lui-même):



<?php

define("ROOT_PATH", "../../opencart.local");

define("ENCODING", "utf-8");
define("NAME", " ocmod");
define("CODE", "product-page-views");
define("VERSION", "1.0");
define("AUTHOR", "AS");
define("LINK", "");

define("TAG_OPERATION_BEGIN", "<PNI>");
define("TAG_OPERATION_END", "</PNI>");
define("TAG_SEARCH_BEGIN", "<search"); // !!  >
define("TAG_SEARCH_END", "</search>");
define("TAG_ADD_BEGIN", "<add"); // !!  >
define("TAG_ADD_END", "</add>");

//     </add>      
// ( ,   , ).
//    ,   , 
//   </add> ( , \t, \r, \n  ,  )
$commentsBegin = [ '//', '/*', '<!--', '{#' ];
//     <add>      
// ( ,   , ).
//    ,   , 
//   <add> ( , \t, \r, \n  ,  )
$commentsEnd = [ '*/', '-->', '#}' ];

//       ,     
//  .
$exclude = [ '/cache/', '/cache-/' ];

//      upload.
//     ,   .
$upload = [
  'admin/view/stylesheet/combined-options.css',
  'admin/view/javascript/combined-options.js',
  'catalog/view/theme/default/stylesheet/combined-options.css',
  'admin/view/image/combined-options/cross.png',
  'catalog/view/javascript/combined-options/combined.js',
  'catalog/view/javascript/combined-options/aswmultiselect.js',
  'admin/view/image/combined-options/select.png'
];

//     install.sql.
// (   Opencart  )
$sql = "";

?>


Maintenant, l'essentiel est le générateur de fichiers xml ocmod.

Script Make-ocmod.php (encodage UTF-8):



<?php

include_once ($argv[1]);

function processFile($fileName, $relativePath) {
  global $commentsBegin, $commentsEnd, $xml, $exclude;

  if ($exclude)
    foreach ($exclude as $ex)
      if (false !== strpos($relativePath, $ex))
        return;

  $text = file_get_contents($fileName);
  $end = -1;
  while (false !== ($begin = strpos($text, TAG_OPERATION_BEGIN, $end + 1))) {
    $end = strpos($text, TAG_OPERATION_END, $begin + 1);
    if (false === $end)
      die ("No close operation tag in ".$fileName);
    $search = false;
    $searchEnd = $begin;
    while (false !== ($searchBegin = strpos($text, TAG_SEARCH_BEGIN, $searchEnd + 1)) and $searchBegin < $end) {
      $searchBeginR = strpos($text, '>', $searchBegin + 1);
      $searchAttributes = substr($text, $searchBegin + strlen(TAG_SEARCH_BEGIN), $searchBeginR - $searchBegin - strlen(TAG_SEARCH_BEGIN));
      if (false === $searchBeginR or $searchBeginR >= $end)
        die ("Invalid search tag in ".$fileName);
      $searchEnd = strpos($text, TAG_SEARCH_END, $searchBeginR + 1);
      if (false === $searchEnd or $searchEnd >= $end)
        die ("No close search tag in ".$fileName);
      //  
      $search = substr($text, $searchBeginR + 1, $searchEnd - $searchBeginR - 1);
    }
    $addBegin = strpos($text, TAG_ADD_BEGIN, $begin + 1);
    if (false === $addBegin or $addBegin >= $end)
      die ("No begin add tag in ".$fileName);
    $addBeginR = strpos($text, '>', $addBegin + 1);
    $addAttributes = substr($text, $addBegin + strlen(TAG_ADD_BEGIN), $addBeginR - $addBegin - strlen(TAG_ADD_BEGIN));
    if (false === $addBeginR or $addBeginR >= $end)
      die ("Invalid add tag in ".$fileName);
    $addEnd = strpos($text, TAG_ADD_END, $addBeginR + 1);
    if (false === $addEnd or $addEnd >= $end)
      die ("No close add tag in ".$fileName);
    $codeBegin = $addBeginR + 1;
    $codeEnd = $addEnd;
    //       ,
    //    - .        .
    $p = $codeBegin;
    while (@$text[$p] === " " or @$text[$p] === "\t" or @$text[$p] === "\r" or @$text[$p] === "\n")
      $p ++;
    if ($p < $addEnd) {
      foreach ($commentsEnd as $tag)
        if (substr($text, $p, strlen($tag)) === $tag)
          $codeBegin = $p + strlen($tag);
    }
    $p = $codeEnd - 1;
    while (@$text[$p] === " " or @$text[$p] === "\t" or @$text[$p] === "\r" or @$text[$p] === "\n")
      $p --;
    if ($p >= $codeBegin) {
      foreach ($commentsBegin as $tag)
        if (substr($text, $p - strlen($tag) + 1, strlen($tag)) === $tag)
          $codeEnd = $p - strlen($tag) + 1;
    }
    $code = substr($text, $codeBegin, $codeEnd - $codeBegin - 1);

    $xml .= "
    <file path=\"".str_replace('"', '\"', $relativePath)."\">
        <operation>".(false !== $search ? "
            <search{$searchAttributes}>
                <![CDATA[{$search}]]>
            </search>" : "")."
            <add{$addAttributes}>
                <![CDATA[{$code}]]>
            </add>
        </operation>
    </file>";
  }
}

function processDir($dir, $relativePath = '') {
  global $exclude;

  $cdir = scandir($dir);
  foreach ($cdir as $key => $value) {
    if (!in_array($value,array(".", ".."))) {
      $fileName = $dir . DIRECTORY_SEPARATOR . $value;
      $newRelativePath = ($relativePath ? $relativePath.'/' : '').$value;
      $excluded = false;
      if ($exclude)
        foreach ($exclude as $ex)
          $excluded = $excluded or false !== strpos($newRelativePath, $ex);
      if ($excluded)
        continue;
      if (is_dir($fileName)) {
        processDir($fileName, $newRelativePath);
      } else {
        processFile($fileName, $newRelativePath);
      }
    }
  }
}

function delTree($dir, $delRoot = false) {
  $files = array_diff(scandir($dir), array('.','..'));
  foreach ($files as $file) {
    (is_dir("$dir/$file")) ? delTree("$dir/$file", true) : unlink("$dir/$file");
  }
  return $delRoot ? rmdir($dir) : true;
}

$xml = "<?xml version=\"1.0\" encoding=\"".ENCODING."\"?>
<modification>
    <name>".NAME."</name>
    <code>".CODE."</code>
    <version>".VERSION."</version>
    <author>".AUTHOR."</author>
    <link>".LINK."</link>";

processDir(ROOT_PATH);

$xml .= "
</modification>";

file_put_contents('publish/install.xml', $xml);
file_put_contents('publish/install.sql', $sql);

delTree('publish/upload');
foreach ($upload as $file) {
  $srcfile = ROOT_PATH.(@$file[0] === '/' ? '' : '/').$file;
  $dstfile = 'publish/upload'.(@$file[0] === '/' ? '' : '/').$file;
  mkdir(dirname($dstfile), 0777, true);
  copy($srcfile, $dstfile);
}

?>


La ligne de commande make-ocmod.cmd qui exécute tout cela:



del /f/q/s publish.ocmod.zip
php make-ocmod.php make-ocmod.opencart.local.cfg.php
cd publish
..\7z.exe a -r -tZip ..\publish.ocmod.zip *.*


J'utilise 7zip, donc 7z.exe doit être au même endroit que notre ligne de commande. Quiconque souhaite l'utiliser peut le télécharger sur https://www.7-zip.org/ .



Ceci est un gestionnaire de commandes pour Windows. Qui sur Linux, je pense, réécrira sans problème.



Résumé: À mon avis, il est beaucoup plus facile de travailler de cette façon que d'éditer manuellement ocmod à chaque fois. Lorsque nous ajoutons le code, nous définissons nos balises de recherche pour ce morceau de code directement, puis nous nous concentrons uniquement sur notre travail. Nous ne nous soucions plus de la structure du fichier xml, et nous effectuons toute correction de notre modification sur place, vérifions immédiatement son fonctionnement puis générons un nouveau fichier ocmod en un clic.



All Articles