Generador de archivos Ocmod para una tienda online en Opencart

¿Es realista centrarse en sus algoritmos al desarrollar modificaciones para el popular motor de tienda en línea Opencart y dejar la preparación de un archivo para cargarlo en este CMS a merced de scripts especiales? En realidad, esto es lo que facilitaría mucho la vida a los desarrolladores bajo Opencart, y en este artículo ofrezco mi solución.



Una pequeña introducción:



el formato ocmod es una solución bastante elegante para definir modificaciones a los archivos fuente, independientemente de su formato. Una de las partes del formato es un archivo XML, en el que está escrito en qué archivo y en qué lugar de este archivo deben realizarse ciertos cambios. Aquí hay un ejemplo de un archivo ocmod (tomado de ocmod.net, se puede encontrar una descripción más detallada allí):



<?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 términos generales, establecemos lo siguiente:



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


Si bien el principio es bastante transparente, surge la pregunta: ¿es posible automatizar el proceso de su creación o será necesario escribirlo a mano, porque el programador debe dedicarse a la programación, y no masturbarse con tonterías rutinarias?



Idealmente, escribir una modificación para Opencart se vería así: descargamos la versión "inmaculada" de la tienda, hicimos algunos cambios en su código fuente y ejecutamos un script "mágico" que generó todo el ocmod en el acto. De hecho, todo es un poco más complicado, pero intentaremos acercarnos a este esquema. El principal problema es determinar la ubicación en el archivo para insertar (lo que está entre <search> ... </search>). El programador debe hacer esto. Como regla, intentan hacerlo lo más universal posible para cubrir más versiones potenciales de la fuente y, al mismo tiempo, para que cambie exactamente donde se necesita. Esto es claramente hecho a mano. Todo lo demás está automatizado.



Una pequeña digresión: la búsqueda ocurre en toda la cadena, y la inserción es posible solo antes, después o en lugar de ella, pero no adentro (en el paquete OCMOD clásico para Opencart). Esta es una limitación que personalmente me resulta incomprensible. Además, no entiendo por qué es imposible establecer varias etiquetas <search> para encontrar el punto de inserción deseado, que se procesaría de forma coherente; después de todo, la búsqueda sería mucho más flexible. Por ejemplo, si está en el código PHP, entonces, digamos, busque el nombre de la función, luego busque el lugar correcto en ella, o de alguna otra manera a discreción del programador. Pero no encontré esto, si me equivoco, corríjalo.



Y ahora lo más importante: puede automatizar el proceso de creación de un archivo ocmod, y solo necesita adherirse al esquema deseado. Primero, en el archivo fuente, necesitamos indicar de alguna manera el lugar de nuestros cambios, tanto solo por orden, como para que nuestro generador de ocmod sepa todo de manera direccionable. Digamos que nuestro nombre es Peter Nikolaevich Ivanov (las coincidencias son aleatorias). Encerremos todos nuestros cambios entre las etiquetas <PNI> ... </PNI>, y para que las etiquetas no estropeen la fuente, colocaremos estas etiquetas en los comentarios del idioma en el que estamos trabajando actualmente. Entre las etiquetas, en su lugar, estableceremos la cadena de búsqueda entre <search> </search> y el código agregado entre <add> </add>. Será más claro con un ejemplo:



Para cambios en PHP:




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


O así:




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


Si <search> o <add> tiene algún atributo, por ejemplo, <search index = ”1”>, entonces se transferirán “tal cual” a nuestro archivo ocmod. Lo que escribimos en el medio no requiere ningún escape XML, solo escribimos la cadena de búsqueda y el código.



Otro ejemplo, ya para el archivo twig que estamos modificando:



            {# <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> #}


Después de haber formalizado todos nuestros cambios de esta manera, necesitamos un script que se encargue de todo esto, así como un archivador. Estoy compartiendo con ustedes mi versión: consta de un archivo de configuración y un script en sí.



El archivo de configuración make-ocmod.opencart.local.cfg.php (codificación UTF-8, este es un ejemplo, todos lo hacen por sí mismos):



<?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 = "";

?>


Ahora lo principal es el generador de archivos ocmod xml.

Script Make-ocmod.php (codificación 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 línea de comando make-ocmod.cmd que ejecuta todo esto:



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 *.*


Yo uso 7zip, por lo que 7z.exe debería estar en el mismo lugar que nuestra línea de comando. Quien quiera usarlo puede descargarlo desde https://www.7-zip.org/ .



Este es un administrador de comandos para Windows. Quién en Linux, creo, reescribirá sin problemas.



Resumen: En mi opinión, es mucho más fácil trabajar de esta manera que editar ocmod manualmente cada vez. Cuando agregamos código, configuramos nuestras etiquetas de búsqueda para este fragmento de código en el lugar y luego nos enfocamos solo en nuestro trabajo. Ya no nos preocupamos por la estructura del archivo xml, y hacemos cualquier corrección de nuestra modificación en el acto, verificamos inmediatamente su trabajo y luego generamos un nuevo archivo ocmod con un clic.



All Articles