Gerador de arquivo OCmod para uma loja online no Opencart

É realista se concentrar em seus algoritmos ao desenvolver modificações para o popular mecanismo de loja online Opencart e deixar a preparação de um arquivo para upload para este CMS à mercê de scripts especiais? Na verdade, é isso que tornaria a vida muito mais fácil para os desenvolvedores no Opencart, e neste artigo eu ofereço minha solução.



Uma pequena introdução:



o formato ocmod é uma solução bastante elegante para definir modificações nos arquivos de origem, independentemente do formato. Uma das partes do formato é um arquivo XML, que especifica em qual arquivo e onde nesse arquivo certas alterações precisam ser feitas. Aqui está um exemplo de um arquivo ocmod (retirado de ocmod.net, uma descrição mais detalhada pode ser encontrada lá):



<?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>


Em termos gerais, definimos o seguinte:



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


Embora o princípio seja bastante transparente, surge a pergunta: é possível automatizar o processo de sua criação ou será necessário escrevê-lo à mão, porque o programador deve estar engajado na programação, e não se masturbar com atividades rotineiras bobas.



Idealmente, escrever uma modificação para o Opencart ficaria assim: baixamos a versão "imaculada" da loja, fizemos algumas alterações no código-fonte e rodamos um script "mágico" que gerou todo o ocmod na hora. Na verdade, tudo é um pouco mais complicado, mas vamos tentar nos aproximar desse esquema. O principal problema é determinar a localização no arquivo a ser inserido (o que está entre <search> ... </search>). O programador deve fazer isso. Como regra, eles tentam torná-lo o mais versátil possível para cobrir mais versões potenciais da fonte e, ao mesmo tempo, para que mude exatamente onde é necessário. Isso é claramente feito à mão. Todo o resto é automatizado.



Uma pequena digressão: a busca ocorre em toda a string, e a inserção só é possível antes, depois ou no lugar dela, mas não dentro (no pacote OCMOD clássico para Opencart). Esta é uma limitação incompreensível para mim pessoalmente. Além disso, não entendo por que é impossível definir várias tags <search> para encontrar o ponto de inserção desejado, que seria processado de forma consistente - afinal, a pesquisa seria muito mais flexível. Por exemplo, se no código PHP, então, digamos, encontre o nome da função, então encontre o lugar certo nele, ou de alguma outra forma a critério do programador. Mas não encontrei isso, se me engano, corrija.



E agora o mais importante: você pode automatizar o processo de criação de um arquivo ocmod, bastando seguir o esquema desejado. Primeiro, no arquivo de origem, precisamos indicar de alguma forma o local de nossas alterações - tanto apenas para fazer o pedido, como para que nosso gerador de ocmod conheça tudo de forma endereçável. Digamos que nosso nome seja Pyotr Nikolaevich Ivanov (as coincidências são aleatórias). Vamos colocar todas as nossas alterações entre as tags <PNI> ... </PNI>, e para que as tags não estraguem a fonte, colocaremos essas tags nos comentários do idioma em que estamos trabalhando no momento. Entre as tags, bem no lugar, definiremos a string de pesquisa entre <search> </search> e o código adicionado entre <add> </add>. Será mais claro com um exemplo:



Para alterações no PHP:




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


Ou assim:




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


Se <search> ou <add> tiver algum atributo, por exemplo, <search index = ”1”>, eles serão transferidos “como estão” para nosso arquivo ocmod. O que escrevemos no meio não requer nenhum escape XML, apenas escrevemos a string de pesquisa e o código.



Outro exemplo, já para o arquivo 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> #}


Depois de definir o estilo de todas as nossas alterações dessa forma, precisamos de um script que irá lidar com tudo isso, bem como um arquivador. Estou compartilhando com vocês minha versão: consiste em um arquivo de configuração e um script próprio.



O arquivo de configuração make-ocmod.opencart.local.cfg.php (codificação UTF-8, este é um exemplo, cada um faz isso por si):



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

?>


Agora, o principal é o gerador de arquivo xml ocmod.

Script Make-ocmod.php (codificação 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);
}

?>


A linha de comando make-ocmod.cmd que executa tudo isso:



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


Eu uso 7zip, então 7z.exe deve estar no mesmo lugar que nossa linha de comando. Quem quiser pode fazer o download em https://www.7-zip.org/ .



Este é um gerenciador de comandos para Windows. Quem no Linux, eu acho, reescreverá sem problemas.



Resumo: Na minha opinião, é muito mais fácil trabalhar dessa maneira do que editar manualmente o ocmod todas as vezes. Quando adicionamos código, definimos nossas tags de pesquisa para esse trecho de código no local e então nos concentramos apenas em nosso trabalho. Não nos importamos mais com a estrutura do arquivo xml, e fazemos qualquer correção de nossa modificação no local, verificamos imediatamente o seu funcionamento e geramos um novo arquivo ocmod com um clique.



All Articles