Understanding theme management in OXID eShop from 4.5.0

From OXID eShop 4.5.0 theme handling will be moved from config.inc.php to back-end (Master Settings -> Themes) showing list of all available templates, their descriptions, thumbnails and theme specific settings and also theme switching in back-end. Theme meta data file and thumbnail image laying in theme folder enables template appearance in list. To enable selected theme just click activate button.

Base theme folder structure, containing templates, translations, styles images and javascript files did not changed in this version. Changes are coming exceptionally to smarty template directory stucture, but this is not a subject of this tutorial
800px-EShopBackendWithNewOption.

Theme meta data

Azure theme files

Thumbnail out/azure/theme.jpg

Info out/azure/theme.php

 $aTheme = array(
  'id'           => 'azure',
  'title'        => 'Azure',
  'description'  => 'Azure theme for OXID eShop 4.5.0 Beta',
  'thumbnail'    => 'theme.jpg',
  'version'      => '0.5',
  'author'       => 'OXID',
); 

It is enough to add these 2 files filled with your data to theme folder (/out/your_theme/) to see it in theme list. Theme id must match theme folder name.
Attention – starting from 4.7.x there are following changes:
The theme.php needs to be placed into /application/views/your_theme/ where as well is the right place for your individualised template files (respecting the same folder structure like Azure). Within /out/mein_theme/ you need to place theme.jpg as well as folders /img and /src with all needed files.

Custom themes

To create a custom theme, which extends another one by overriding just some files, it is enough to add the id of the parent theme (parentTheme) together with compatible version numbers (parentVersions) to the $aTheme info array e.g.

 $aTheme = array(
 'title'          => 'Child of Azure',
 'description'    => 'EXTENDED Azure theme for OXID eShop 4.5.0 Beta',
 'thumbnail'      => 'theme.jpg',
 'version'        => '0.5',
 'author'         => 'OXID',
 'parentTheme'    => 'azure',
 'parentVersions' => array('0.5','0.6'),
); 

Such theme can only be activated if the parent theme (‘azure’ in this case) is present in the system and its version matches one of the predefined values (‘0.5’ or ‘0.6’).

Such theme will take all files template file (and config options) from the parent theme . Except files, which are present in both the child and the parent themes, will be used from the child (custom) theme, effectively overriding the parent theme files.

Theme ID is stored in config variable named “sTheme”. If activating custom theme with defined parent, parent theme ID will be saved to config variable “sTheme” and child theme ID will be saved to config variable “sCustomTheme”. All custom theme config variables will override same parent themes config variables and if some option is missing – will take it value from parent.

In case any error happens, the theme can not be activated in the admin tool and instead of the ‘Activate’ button an error message is displayed e.g.

800px-Theme_error_no_parent

Script to copy only modified template-files

Normally it is way easier to work on a complete copy of the basic template-set while developing your own theme. Aggrosoft has developed a script to copy only the modified files afterwards to an extra folder. This new folder can be set as sCustomTheme in config-file then.
Using the script is simple:

  1. create “tploverride.php” in the shop root directory,
  2. paste the code below,
  3. open your browser and go to http://your-shop-url.com/tploverride.php
  4. choose the modified and original theme (there has to be an unmodified version of the original theme),
  5. choose target folder and press “Start”.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
 <head>
  <title>generate Template Override</title>
  <link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap-1.1.1.min.css">
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
 </head>
<?php

    function makeAll($dir, $mode = 0777, $recursive = true) {
        if( is_null($dir) || $dir === "" ){
            return FALSE;
        }
       
        if( is_dir($dir) || $dir === "/" ){
            return TRUE;
        }
        if( makeAll(dirname($dir), $mode, $recursive) ){
            return mkdir($dir, $mode);
        }
        return FALSE;
    }
   
    function smartCopy($source, $dest, $options=array('folderPermission'=>0755,'filePermission'=>0755))
    {
        $result=false;
       
        //For Cross Platform Compatibility
        if (!isset($options['noTheFirstRun'])) {
            $source=str_replace('\\','/',$source);
            $dest=str_replace('\\','/',$dest);
            $options['noTheFirstRun']=true;
        }
       
        if (is_file($source)) {
            if ($dest[strlen($dest)-1]=='/') {
                if (!file_exists($dest)) {
                    makeAll($dest,$options['folderPermission'],true);
                }
                $__dest=$dest."/".basename($source);
            } else {
                $__dest=$dest;
            }
            if (!file_exists($__dest)) {
                $result=copy($source, $__dest);
                chmod($__dest,$options['filePermission']);
            }
        } elseif(is_dir($source)) {
            if ($dest[strlen($dest)-1]=='/') {
                if ($source[strlen($source)-1]=='/') {
                    //Copy only contents
                } else {
                    //Change parent itself and its contents
                    $dest=$dest.basename($source);
                    @mkdir($dest);
                    chmod($dest,$options['filePermission']);
                }
            } else {
                if ($source[strlen($source)-1]=='/') {
                    //Copy parent directory with new name and all its content
                    @mkdir($dest,$options['folderPermission']);
                    chmod($dest,$options['filePermission']);
                } else {
                    //Copy parent directory with new name and all its content
                    @mkdir($dest,$options['folderPermission']);
                    chmod($dest,$options['filePermission']);
                }
            }
   
            $dirHandle=opendir($source);
            while($file=readdir($dirHandle))
            {
                if($file!="." && $file!="..")
                {
                    $__dest=$dest."/".$file;
                    $__source=$source."/".$file;
                    //echo "$__source ||| $__dest<br />";
                    if ($__source!=$dest) {
                        $result=smartCopy($__source, $__dest, $options);
                    }
                }
            }
            closedir($dirHandle);
           
        } else {
            $result=false;
        }
        return $result;
    }

    function compareDirectories( $path = '.', $changePath = '.', $level = 0 ){
       
        $ignore = array( 'cgi-bin', '.', '..' );
        $dh = @opendir( $path );
       
        while( false !== ( $file = readdir( $dh ) ) ){ // Loop through the directory
            if( !in_array( $file, $ignore ) ){
                if( is_dir( "$path/$file" ) ){
                    // Its a directory, so we need to keep reading down…
                    compareDirectories( "$path/$file", "$changePath/$file", ($level+1),$time );
                } else {
                    compareFiles("$path/$file", "$changePath/$file");
                }//elseif
            }//if in array
        }//while
       
        closedir( $dh );
       
    }
   
    function compareFiles( $path = '.', $changePath = '.' ){
        $vanilla_path = $path;
        $branch_path = $changePath;
       
        if( !file_exists($branch_path) || strcmp(file_get_contents($vanilla_path),file_get_contents($branch_path)) ){
            if($_REQUEST["displayonly"] == 1){
                echo "$vanilla_path >> $branch_path<br/>";
            }else{
                $tPath = str_replace($_REQUEST['modifiedtpl']."/",'',$vanilla_path);
           
                makeAll(dirname("out/".$_REQUEST['targetfolder']."/$tPath"));
                smartCopy($vanilla_path, "out/".$_REQUEST['targetfolder']."/$tPath");
            }
        }   
        return true;
    }
   
   

   
    $aTemplates = array();
    foreach ( glob("out/**",GLOB_ONLYDIR) as $tplfolder ) {
        if(file_exists($tplfolder."/theme.php")){
            $aTemplates[] = $tplfolder;
        }
    }
   
    if($_REQUEST['fnc'] == 'create'){
       
        compareDirectories( $_REQUEST['modifiedtpl'], $_REQUEST['originaltpl']);
       
        $blCopied = true;
       
    }
   
?>
 
<body style="padding-top: 60px">

 <div class="topbar-wrapper noprint" style="z-index: 5">
  <div class="topbar">
     <div class="fill">
         <div class="container">
             <h3><a href="#">Aggrosoft Override Generator</a></h3>
         </div>
     </div>
 </div>
 </div>

<div class="container">

        <div class="alert-message success">
        <p><strong>Success!</strong> You will find all modified files now in chosen the target folder.</p>
        </div>

  <form action="tploverride.php" method="post">
      <input type="hidden" name="fnc" value="create">
    <fieldset>
      <legend>generate Custom Theme</legend>
      <div class="clearfix">
        <label for="username">modified Template</label>
        <div class="input">
          <select name="modifiedtpl">
              <?php foreach ($aTemplates as $sTemplate) : ?>
              <option value="<?php echo $sTemplate; ?>"><?php echo $sTemplate; ?></option>
              <?php endforeach; ?>
          </select>
        </div>
      </div><!-- /clearfix -->
      <div class="clearfix">
        <label for="username">original Template</label>
        <div class="input">
          <select name="originaltpl">
              <?php foreach ($aTemplates as $sTemplate) : ?>
              <option value="<?php echo $sTemplate; ?>"><?php echo $sTemplate; ?></option>
              <?php endforeach; ?>
          </select>
        </div>
      </div><!-- /clearfix -->
      <div class="clearfix">
        <label for="username">target folder (in out)</label>
        <div class="input">
          <input type="text" size="30" name="targetfolder" class="xlarge">
        </div>
      </div><!-- /clearfix -->
      <div class="clearfix">
        <label for="username">simulate</label>
        <div class="input">
            <div class="input-prepend">
                  <label class="add-on"><input type="checkbox" name="displayonly" value="1"></label>
                  <input class="small" readonly="readonly" value="Nur anzeigen" type="text">
              </div>
        </div>
      </div><!-- /clearfix -->           
      <div class="actions">
        <button class="btn primary" type="submit">Start</button>&nbsp;<button class="btn" type="reset">Cancel</button>
      </div>
    </fieldset>
  </form>

    </div>
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
  <script type="text/javascript" src="http://autobahn.tablesorter.com/jquery.tablesorter.min.js"></script>
  <script type="text/javascript">
      $(function(){
          $("table").tablesorter({ sortList: [[1,1]] });
      });
  </script>
 </body>
</html>

Thanks to Aggrosoft for sharing this script!

Theme specific config

OXID eShop themes always depended on different configuration parameters set in back-end. While developing new totally revamped Azure theme for OXID eShop 4.5.0 we decided to improve theme specific config handling.

OXID eShop 4.5.0 will come with separated theme config option handling allowing theme developers to add their own theme specific config options, without modifying back-end templates.

Theme config options will be stored in existing oxconfig database table, marked with dedicated oxmodule column containing value “theme:azure”. Additional display parameters has a separate database table oxconfigdisplay allowing config option sorting, grouping and drop-down option constrain passing. Theme config language constants translation text has to be added to language files namedtheme_options.php.

Some theme specific config options are used in eShop source code to generate proper size images or calculate pagination so we recommend copying them from azure theme (search for “theme:azure” sql inserts in setup/sql/database.sql file).

Example

Adding new theme specific config option to azure theme to show/hide slogan in page header can be accomplished in following easy steps

1. Add option to oxconfig table

Column Value Description
OXID azure.blShowSlogan.1 unique id value
OXSHOPID 1 shop id
OXMODULE theme:azure theme:{theme id = folder name}
OXVARNAME blShowSlogan config option name
OXVARTYPE bool config option type (‘bool’,’str’,’arr’,’aarr’)
OXVARVALUE config option value, empty value, will be set on save

execute sql

 INSERT INTO `oxconfig` 
  (`OXID`,`OXSHOPID`,`OXMODULE`,`OXVARNAME`,`OXVARTYPE`,`OXVARVALUE`) VALUES
  ('azure.blShowSlogan.1', 1, 'theme:azure', 'blShowSlogan', 'bool', ''); 

2. Configure option display parameters in oxconfigdisplay table

Column Value Description
OXID azure.blShowSlogan.1 unique id value
OXCFGMODULE theme:azure theme:{theme id = folder name}
OXCFGVARNAME blShowSlogan config option name
OXGROUPING display config option display group
OXVARCONSTRAINT value constrains, used for drop-down selects
OXPOS 0 position in config option group

Config variable display depends on variable type (oxconfig.OXVARTYPE)

Type Display html element
str text input
bool checkbox input
arr textarea
aarr textarea

To add drop-down selects for predefined values, set constraint value(oxconfigdisplay.OXVARCONSTRAINT)

Constraint Display input
0|1 Dropdown with value 0 and 1
a|b|c Dropdown with values a, b and c

You can always invent you own theme option groups, when specifying grouping value (oxconfigdisplay.OXGROUPING) and adding translations to theme_options.php file ( constant name ‘SHOP_THEME_GROUP_mygroup’).

execute sql

 INSERT INTO `oxconfigdisplay` 
  (`OXID,`OXCFGMODULE`,`OXCFGVARNAME`,`OXGROUPING`,`OXVARCONSTRAINT`,`OXPOS`) VALUES
  ('azure.blShowSlogan.1', 'theme:azure', 'blShowSlogan', 'display', '', 0); 

3. Add language translations to out/azure/[en|de]/theme_options.php

 'SHOP_THEME_blShowSlogan'   => 'Display slogan',

4. Add slogan with option check to out/azure/tpl/layout/header.tpl template

 [{if $oViewConf->getViewThemeParam('blShowSlogan') }]
  <h1 style="margin:0 10px 15px;padding:10px;color:#fff;background:#000;">Slogan text</h1>
[{/if}] 

800px-EShopBackendThemes

800px-EShopFrontendWithSlogan

Clone a theme with independent config

To get an own theme with all config-parameters without any relation to a parent theme, you need to copy all files from the initial theme to a new folder ( /myTheme). Then run following script to copy all config-parameters:

Template Structure

out/azure/tpl
├── custom
├── email
│ ├── html
│ │ ├── footer.tpl
│ │ ├── forgotpwd.tpl
│ │ ├── header.tpl
│ │ ├── invite.tpl
│ │ ├── newsletteroptin.tpl
│ │ ├── order_cust.tpl
│ │ ├── order_owner.tpl
│ │ ├── owner_reminder.tpl
│ │ ├── pricealarm_owner.tpl
│ │ ├── register.tpl
│ │ ├── senddownloadlinks.tpl
│ │ ├── sendednow.tpl
│ │ ├── suggest.tpl
│ │ └── wishlist.tpl
│ └── plain
│ ├── forgotpwd.tpl
│ ├── invite.tpl
│ ├── newsletteroptin.tpl
│ ├── order_cust.tpl
│ ├── order_owner.tpl
│ ├── pricealarm_owner.tpl
│ ├── register.tpl
│ ├── senddownloadlinks.tpl
│ ├── sendednow.tpl
│ ├── suggest.tpl
│ └── wishlist.tpl
├── form
│ ├── account_newsletter.tpl
│ ├── contact.tpl
│ ├── fieldset
│ │ ├── order_newsletter.tpl
│ │ ├── order_remark.tpl
│ │ ├── salutation.tpl
│ │ ├── state.tpl
│ │ ├── user_account.tpl
│ │ ├── user_billing.tpl
│ │ ├── user_email.tpl
│ │ ├── user_noaccount.tpl
│ │ └── user_shipping.tpl
│ ├── forgotpwd_change_pwd.tpl
│ ├── forgotpwd_email.tpl
│ ├── formparams.tpl
│ ├── guestbook.tpl
│ ├── login_account.tpl
│ ├── login.tpl
│ ├── newsletter.tpl
│ ├── pricealarm.tpl
│ ├── privatesales
│ │ ├── accept_terms.tpl
│ │ ├── basketexcl.tpl
│ │ └── invite.tpl
│ ├── recommendation_add.tpl
│ ├── recommendation_edit.tpl
│ ├── register.tpl
│ ├── suggest.tpl
│ ├── user_checkout_change.tpl
│ ├── user_checkout_noregistration.tpl
│ ├── user_checkout_registration.tpl
│ ├── user_password.tpl
│ ├── user.tpl
│ ├── wishlist_publish.tpl
│ ├── wishlist_search.tpl
│ └── wishlist_suggest.tpl
├── layout
│ ├── base.tpl
│ ├── footer.tpl
│ ├── header.tpl
│ ├── page.tpl
│ ├── popup.tpl
│ └── sidebar.tpl
├── message
│ ├── err_404.tpl
│ ├── errors.tpl
│ ├── error.tpl
│ ├── err_setup.tpl
│ ├── err_unknown.tpl
│ ├── exception.tpl
│ ├── inputvalidation.tpl
│ ├── notice.tpl
│ ├── success.tpl
│ └── user_blocked.tpl
├── page
│ ├── account
│ │ ├── dashboard.tpl
│ │ ├── downloads.tpl
│ │ ├── forgotpwd.tpl
│ │ ├── inc
│ │ │ ├── account_menu.tpl
│ │ │ └── file_attributes.tpl
│ │ ├── login.tpl
│ │ ├── newsletter.tpl
│ │ ├── noticelist.tpl
│ │ ├── order.tpl
│ │ ├── password.tpl
│ │ ├── recommendationadd.tpl
│ │ ├── recommendationedit.tpl
│ │ ├── recommendationlist.tpl
│ │ ├── register_confirm.tpl
│ │ ├── register_success.tpl
│ │ ├── register.tpl
│ │ ├── user.tpl
│ │ └── wishlist.tpl
│ ├── checkout
│ │ ├── basket.tpl
│ │ ├── inc
│ │ │ ├── basketcontents.tpl
│ │ │ ├── options.tpl
│ │ │ ├── payment_other.tpl
│ │ │ ├── payment_oxidcashondel.tpl
│ │ │ ├── payment_oxidcreditcard.tpl
│ │ │ ├── payment_oxiddebitnote.tpl
│ │ │ ├── steps.tpl
│ │ │ ├── trustedshops.tpl
│ │ │ └── wrapping.tpl
│ │ ├── order.tpl
│ │ ├── payment.tpl
│ │ ├── thankyou.tpl
│ │ └── user.tpl
│ ├── compare
│ │ ├── compare.tpl
│ │ └── inc
│ │ └── compareitem.tpl
│ ├── details
│ │ ├── ajax
│ │ │ ├── fullproductinfo.tpl
│ │ │ └── productmain.tpl
│ │ ├── details.tpl
│ │ └── inc
│ │ ├── attributes.tpl
│ │ ├── compare_links.tpl
│ │ ├── deliverytime.tpl
│ │ ├── editTags.tpl
│ │ ├── fullproductinfo.tpl
│ │ ├── media.tpl
│ │ ├── morepics.tpl
│ │ ├── priceinfo.tpl
│ │ ├── productmain.tpl
│ │ ├── related_products.tpl
│ │ ├── tabs.tpl
│ │ ├── tags.tpl
│ │ └── zoompopup.tpl
│ ├── guestbook
│ │ └── guestbook.tpl
│ ├── info
│ │ ├── contact.tpl
│ │ ├── content_plain.tpl
│ │ ├── content.tpl
│ │ ├── links.tpl
│ │ ├── newsletter.tpl
│ │ ├── news.tpl
│ │ └── suggest.tpl
│ ├── list
│ │ ├── list.tpl
│ │ └── morecategories.tpl
│ ├── privatesales
│ │ ├── invite.tpl
│ │ └── login.tpl
│ ├── recommendations
│ │ ├── inc
│ │ │ └── list.tpl
│ │ └── recommlist.tpl
│ ├── review
│ │ ├── review_login.tpl
│ │ └── review.tpl
│ ├── search
│ │ └── search.tpl
│ ├── shop
│ │ ├── mallstart.tpl
│ │ └── start.tpl
│ ├── tags
│ │ └── tags.tpl
│ └── wishlist
│ └── wishlist.tpl
├── rdfa
│ ├── content
│ │ ├── content.tpl
│ │ └── inc
│ │ ├── business_entity.tpl
│ │ ├── delivery_charge.tpl
│ │ └── payment_charge.tpl
│ ├── details
│ │ ├── details.tpl
│ │ └── inc
│ │ ├── delivery.tpl
│ │ ├── object.tpl
│ │ ├── payment.tpl
│ │ └── price.tpl
│ └── rdfa.tpl
└── widget
├── address
│ ├── billing_address.tpl
│ └── shipping_address.tpl
├── breadcrumb.tpl
├── dynscript.tpl
├── facebook
│ ├── comments.tpl
│ ├── connect.tpl
│ ├── enable.tpl
│ ├── facepile.tpl
│ ├── init.tpl
│ ├── invite.tpl
│ ├── like.tpl
│ └── share.tpl
├── footer
│ ├── categorieslist.tpl
│ ├── info.tpl
│ ├── manufacturers.tpl
│ ├── newsletter.tpl
│ ├── services.tpl
│ └── vendors.tpl
├── header
│ ├── currencies.tpl
│ ├── languages.tpl
│ ├── loginbox.tpl
│ ├── search.tpl
│ ├── servicebox.tpl
│ └── topcategories.tpl
├── locator
│ ├── attributes.tpl
│ ├── itemsperpage.tpl
│ ├── listdisplaytype.tpl
│ ├── listlocator.tpl
│ ├── paging.tpl
│ └── sort.tpl
├── manufacturersslider.tpl
├── minibasket
│ ├── countdown.tpl
│ ├── minibasketmodal.tpl
│ ├── minibasket.tpl
│ └── newbasketitemmsg.tpl
├── product
│ ├── bargainitems.tpl
│ ├── boxproducts.tpl
│ ├── compare_links.tpl
│ ├── listitem_grid.tpl
│ ├── listitem_infogrid.tpl
│ ├── listitem_line.tpl
│ ├── list.tpl
│ └── selectbox.tpl
├── promoslider.tpl
├── reviews
│ ├── rating.tpl
│ └── reviews.tpl
├── rss.tpl
├── shoplupe
│ └── ratings.tpl
├── sidebar
│ ├── adminbanner.tpl
│ ├── categoriestree.tpl
│ ├── news.tpl
│ ├── partners.tpl
│ ├── recommendation.tpl
│ └── tags.tpl
└── trustedshops
├── info.tpl
└── ratings.tpl

 



0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *