vendor/isotope/isotope-core/system/modules/isotope/library/Isotope/Backend/Product/DcaManager.php line 125

Open in your IDE?
  1. <?php
  2. /*
  3.  * Isotope eCommerce for Contao Open Source CMS
  4.  *
  5.  * Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup
  6.  *
  7.  * @link       https://isotopeecommerce.org
  8.  * @license    https://opensource.org/licenses/lgpl-3.0.html
  9.  */
  10. namespace Isotope\Backend\Product;
  11. use Contao\Backend;
  12. use Contao\BackendUser;
  13. use Contao\Controller;
  14. use Contao\CoreBundle\Exception\AccessDeniedException;
  15. use Contao\Database;
  16. use Contao\Environment;
  17. use Contao\Input;
  18. use Contao\Session;
  19. use Contao\StringUtil;
  20. use Haste\Util\Format;
  21. use Isotope\Backend\Group\Breadcrumb;
  22. use Isotope\Interfaces\IsotopeAttribute;
  23. use Isotope\Interfaces\IsotopeAttributeWithOptions;
  24. use Isotope\Interfaces\IsotopeProduct;
  25. use Isotope\Model\Attribute;
  26. use Isotope\Model\Group;
  27. use Isotope\Model\Product;
  28. use Isotope\Model\ProductType;
  29. use Isotope\Model\RelatedCategory;
  30. class DcaManager extends Backend
  31. {
  32.     /**
  33.      * Initialize the tl_iso_product DCA
  34.      *
  35.      * @param string $strTable
  36.      */
  37.     public function initialize($strTable)
  38.     {
  39.         if ($strTable != Product::getTable() || !Database::getInstance()->tableExists(Attribute::getTable())) {
  40.             return;
  41.         }
  42.         $this->addAttributes();
  43.     }
  44.     /**
  45.      * Load DCA configuration (onload_callback)
  46.      */
  47.     public function load()
  48.     {
  49.         $this->checkFeatures();
  50.         $this->addBreadcrumb();
  51.         $this->buildPaletteString();
  52.         $this->changeVariantColumns();
  53.     }
  54.     /**
  55.      * Store initial values when creating a product
  56.      *
  57.      * @param   string $strTable
  58.      * @param   int    $insertID
  59.      * @param   array  $arrSet
  60.      */
  61.     public function updateNewRecord($strTable$insertID$arrSet)
  62.     {
  63.         if (($arrSet['pid'] ?? 0) > 0) {
  64.             Database::getInstance()->prepare("UPDATE $strTable SET dateAdded=? WHERE id=?")->execute(time(), $insertID);
  65.             return;
  66.         }
  67.         $intType  0;
  68.         $intGroup = (int) Session::getInstance()->get('iso_products_gid');
  69.         if (!$intGroup) {
  70.             $intGroup BackendUser::getInstance()->isAdmin : (int) BackendUser::getInstance()->iso_groups[0];
  71.         }
  72.         $objGroup Group::findByPk($intGroup);
  73.         if (null === $objGroup || null === $objGroup->getRelated('product_type')) {
  74.             $objType ProductType::findFallback();
  75.         } else {
  76.             $objType $objGroup->getRelated('product_type');
  77.         }
  78.         if (null !== $objType) {
  79.             $intType $objType->id;
  80.         }
  81.         Database::getInstance()->prepare("UPDATE $strTable SET gid=?, type=?, dateAdded=? WHERE id=?")->execute($intGroup$intTypetime(), $insertID);
  82.     }
  83.     /**
  84.      * Update dateAdded on copy
  85.      *
  86.      * @param int $insertId
  87.      *
  88.      * @link http://www.contao.org/callbacks.html#oncopy_callback
  89.      */
  90.     public function updateDateAdded($insertId)
  91.     {
  92.         Database::getInstance()
  93.             ->prepare("UPDATE tl_iso_product SET dateAdded=? WHERE id=?")
  94.             ->execute(time(), $insertId)
  95.         ;
  96.     }
  97.     /**
  98.      * Add custom attributes to tl_iso_product DCA
  99.      */
  100.     protected function addAttributes()
  101.     {
  102.         $arrData               = &$GLOBALS['TL_DCA'][Product::getTable()];
  103.         $arrData['attributes'] = array();
  104.         // Write attributes from database to DCA
  105.         /** @var Attribute[] $objAttributes */
  106.         if (($objAttributes Attribute::findValid()) !== null) {
  107.             foreach ($objAttributes as $objAttribute) {
  108.                 if (null !== $objAttribute) {
  109.                     $objAttribute->saveToDCA($arrData);
  110.                     $arrData['attributes'][$objAttribute->field_name] = $objAttribute;
  111.                 }
  112.             }
  113.         }
  114.         // Create temporary models for non-database attributes
  115.         foreach (array_diff_key($arrData['fields'], $arrData['attributes']) as $strName => $arrConfig) {
  116.             if (\is_array($arrConfig['attributes'] ?? null)) {
  117.                 if (!empty($arrConfig['attributes']['type'])) {
  118.                     $strClass $arrConfig['attributes']['type'];
  119.                 } else {
  120.                     $strClass Attribute::getClassForModelType($arrConfig['inputType'] ?? '');
  121.                 }
  122.                 if ($strClass != '') {
  123.                     /** @var Attribute $objAttribute */
  124.                     $objAttribute = new $strClass();
  125.                     $objAttribute->loadFromDCA($arrData$strName);
  126.                     $arrData['attributes'][$strName] = $objAttribute;
  127.                 }
  128.             }
  129.         }
  130.     }
  131.     /**
  132.      * Disable features that are not used in the current installation
  133.      */
  134.     protected function checkFeatures()
  135.     {
  136.         $blnDownloads      false;
  137.         $blnVariants       false;
  138.         $blnAdvancedPrices false;
  139.         $blnShowSku        false;
  140.         $blnShowPrice      false;
  141.         $arrAttributes     = array();
  142.         /** @var ProductType[] $objProductTypes */
  143.         if (($objProductTypes ProductType::findAllUsed()) !== null) {
  144.             foreach ($objProductTypes as $objType) {
  145.                 if ($objType->hasDownloads()) {
  146.                     $blnDownloads true;
  147.                 }
  148.                 if ($objType->hasVariants()) {
  149.                     $blnVariants true;
  150.                 }
  151.                 if ($objType->hasAdvancedPrices()) {
  152.                     $blnAdvancedPrices true;
  153.                 }
  154.                 if (\in_array('sku'$objType->getAttributes(), true)) {
  155.                     $blnShowSku true;
  156.                 }
  157.                 if (\in_array('price'$objType->getAttributes(), true)) {
  158.                     $blnShowPrice true;
  159.                 }
  160.                 $arrAttributes array_merge($arrAttributes$objType->getAttributes());
  161.             }
  162.         }
  163.         // If no downloads are enabled in any product type, we do not need the option
  164.         if (!$blnDownloads) {
  165.             unset($GLOBALS['TL_DCA'][Product::getTable()]['list']['operations']['downloads']);
  166.         }
  167.         // Disable all variant related operations
  168.         if (!$blnVariants) {
  169.             unset(
  170.                 $GLOBALS['TL_DCA'][Product::getTable()]['list']['global_operations']['toggleVariants'],
  171.                 $GLOBALS['TL_DCA'][Product::getTable()]['list']['operations']['generate']
  172.             );
  173.         }
  174.         // Disable prices button if not enabled in any product type
  175.         if (!$blnAdvancedPrices) {
  176.             unset($GLOBALS['TL_DCA'][Product::getTable()]['list']['operations']['prices']);
  177.         }
  178.         // Hide SKU column if not enabled in any product type
  179.         if (!$blnShowSku) {
  180.             unset($GLOBALS['TL_DCA'][Product::getTable()]['list']['label']['fields'][2]);
  181.         }
  182.         // Hide price column if not enabled in any product type
  183.         if (!$blnShowPrice) {
  184.             unset($GLOBALS['TL_DCA'][Product::getTable()]['list']['label']['fields'][3]);
  185.         }
  186.         // Disable sort-into-group if no groups are defined
  187.         if (Group::countAll() == 0) {
  188.             unset($GLOBALS['TL_DCA'][Product::getTable()]['list']['operations']['group']);
  189.         }
  190.         // Disable related categories if none are defined
  191.         if (RelatedCategory::countAll() == 0) {
  192.             unset($GLOBALS['TL_DCA'][Product::getTable()]['list']['operations']['related']);
  193.         }
  194.         foreach (array_diff(array_keys($GLOBALS['TL_DCA'][Product::getTable()]['fields']), array_unique($arrAttributes)) as $field) {
  195.             $GLOBALS['TL_DCA'][Product::getTable()]['fields'][$field]['filter'] = false;
  196.             $GLOBALS['TL_DCA'][Product::getTable()]['fields'][$field]['sorting'] = false;
  197.             $GLOBALS['TL_DCA'][Product::getTable()]['fields'][$field]['search'] = false;
  198.         }
  199.     }
  200.     /**
  201.      * Add the breadcrumb menu
  202.      */
  203.     public function addBreadcrumb()
  204.     {
  205.         // Avoid the page node trap (#1701)
  206.         if (\defined('TL_SCRIPT') && TL_SCRIPT === 'contao/page.php') {
  207.             return;
  208.         }
  209.         $strBreadcrumb Breadcrumb::generate(Session::getInstance()->get('iso_products_gid'));
  210.         $strBreadcrumb .= static::getPagesBreadcrumb();
  211.         $GLOBALS['TL_DCA']['tl_iso_product']['list']['sorting']['breadcrumb'] = $strBreadcrumb;
  212.     }
  213.     /**
  214.      * Build palette for the current product type/variant
  215.      */
  216.     public function buildPaletteString()
  217.     {
  218.         Controller::loadDataContainer(Attribute::getTable());
  219.         if ((Input::get('act') == '' && Input::get('key') == '') || 'select' === Input::get('act')) {
  220.             return;
  221.         }
  222.         $arrTypes      = array();
  223.         $arrFields     = &$GLOBALS['TL_DCA']['tl_iso_product']['fields'];
  224.         /** @var IsotopeAttribute[] $arrAttributes */
  225.         $arrAttributes = &$GLOBALS['TL_DCA']['tl_iso_product']['attributes'];
  226.         $blnVariants     false;
  227.         $act             Input::get('act');
  228.         $blnSingleRecord $act === 'edit' || $act === 'show';
  229.         if (Input::get('id') > 0) {
  230.             /** @var object $objProduct */
  231.             $objProduct Database::getInstance()->prepare("SELECT p1.pid, p1.type, p2.type AS parent_type FROM tl_iso_product p1 LEFT JOIN tl_iso_product p2 ON p1.pid=p2.id WHERE p1.id=?")->execute(Input::get('id'));
  232.             if ($objProduct->numRows) {
  233.                 $objType  ProductType::findByPk(($objProduct->pid $objProduct->parent_type $objProduct->type));
  234.                 $arrTypes null === $objType ? array() : array($objType);
  235.                 if ($objProduct->pid || ('edit' !== $act && 'copyFallback' !== $act && 'show' !== $act)) {
  236.                     $blnVariants true;
  237.                 }
  238.             }
  239.         } else {
  240.             $arrTypes ProductType::findAllUsed() ? : array();
  241.         }
  242.         /** @var ProductType $objType */
  243.         foreach ($arrTypes as $objType) {
  244.             // Enable advanced prices
  245.             if ($blnSingleRecord && $objType->hasAdvancedPrices()) {
  246.                 $arrFields['prices']['exclude']    = $arrFields['price']['exclude'];
  247.                 $arrFields['prices']['attributes'] = $arrFields['price']['attributes'];
  248.                 $arrFields['price']                = $arrFields['prices'];
  249.             }
  250.             // Register callback to version/restore a price
  251.             else {
  252.                 $GLOBALS['TL_DCA']['tl_iso_product']['config']['onversion_callback']['iso_product_price'] = array('Isotope\Backend\Product\Price''createVersion');
  253.                 $GLOBALS['TL_DCA']['tl_iso_product']['config']['onrestore_callback']['iso_product_price'] = array('Isotope\Backend\Product\Price''restoreVersion');
  254.             }
  255.             $arrInherit = array();
  256.             $arrPalette = array();
  257.             $arrLegends = array();
  258.             $arrLegendOrder = array();
  259.             $arrCanInherit = array();
  260.             if ($blnVariants) {
  261.                 $arrConfig     $objType->variant_attributes;
  262.                 $arrEnabled    $objType->getVariantAttributes();
  263.                 $arrCanInherit $objType->getAttributes();
  264.             } else {
  265.                 $arrConfig  $objType->attributes;
  266.                 $arrEnabled $objType->getAttributes();
  267.             }
  268.             // Go through each enabled field and build palette
  269.             foreach ($arrFields as $name => $arrField) {
  270.                 if (\in_array($name$arrEnabled)) {
  271.                     if ($arrField['inputType'] == '') {
  272.                         continue;
  273.                     }
  274.                     // Variant fields can only be edited in variant mode
  275.                     if (null !== $arrAttributes[$name]
  276.                         && !$blnVariants
  277.                         && /* @todo in 3.0: $arrAttributes[$name] instanceof IsotopeAttributeForVariants
  278.                         && */$arrAttributes[$name]->isVariantOption()
  279.                     ) {
  280.                         continue;
  281.                     }
  282.                     // Field cannot be edited in variant
  283.                     if ($blnVariants && $arrAttributes[$name]->inherit) {
  284.                         continue;
  285.                     }
  286.                     $arrLegendOrder[$arrConfig[$name]['position']] = $arrConfig[$name]['legend'];
  287.                     $arrPalette[$arrConfig[$name]['legend']][$arrConfig[$name]['position']] = $name;
  288.                     // Apply product type attribute config
  289.                     if ($arrConfig[$name]['tl_class'] != '') {
  290.                         $arrFields[$name]['eval']['tl_class'] = $arrConfig[$name]['tl_class'];
  291.                     }
  292.                     if ('yes' === $arrConfig[$name]['mandatory']) {
  293.                         $arrFields[$name]['eval']['mandatory'] = true;
  294.                     } elseif ('no' === $arrConfig[$name]['mandatory']) {
  295.                         $arrFields[$name]['eval']['mandatory'] = false;
  296.                     }
  297.                     if ($blnVariants
  298.                         && \in_array($name$arrCanInherit)
  299.                         && null !== $arrAttributes[$name]
  300.                         && /* @todo in 3.0: $arrAttributes[$name] instanceof IsotopeAttributeForVariants
  301.                         && */!$arrAttributes[$name]->isVariantOption()
  302.                         && !\in_array($name, ['price''published''start''stop'], true)
  303.                     ) {
  304.                         $arrInherit[$name] = Format::dcaLabel('tl_iso_product'$name);
  305.                     }
  306.                 } else {
  307.                     // Hide field from "show" option
  308.                     if ((!isset($arrField['attributes']) || ($arrField['inputType'] ?? '') != '') && 'inherit' !== $name) {
  309.                         $arrFields[$name]['eval']['doNotShow'] = true;
  310.                     }
  311.                 }
  312.             }
  313.             ksort($arrLegendOrder);
  314.             $arrLegendOrder array_unique($arrLegendOrder);
  315.             // Build
  316.             foreach ($arrLegendOrder as $legend) {
  317.                 $fields $arrPalette[$legend];
  318.                 ksort($fields);
  319.                 $arrLegends[] = '{' $legend '},' implode(','$fields);
  320.             }
  321.             // Set inherit options
  322.             $arrFields['inherit']['options'] = $arrInherit;
  323.             // Add palettes
  324.             $GLOBALS['TL_DCA']['tl_iso_product']['palettes'][($blnVariants 'default' $objType->id)] = ($blnVariants 'inherit,' '') . implode(';'$arrLegends);
  325.         }
  326.         // Remove non-active fields from multi-selection
  327.         if ($blnVariants && !$blnSingleRecord) {
  328.             $arrInclude = empty($arrPalette) ? array() : array_merge(...array_values($arrPalette));
  329.             foreach ($arrFields as $name => $config) {
  330.                 if (($arrFields[$name]['attributes']['legend'] ?? '') != '' && !\in_array($name$arrInclude)) {
  331.                     $arrFields[$name]['exclude'] = true;
  332.                 }
  333.             }
  334.         }
  335.     }
  336.     /**
  337.      * Change the displayed columns in the variants view
  338.      */
  339.     public function changeVariantColumns()
  340.     {
  341.         if ((Input::get('act') != '' && 'select' !== Input::get('act'))
  342.             || Input::get('id') == ''
  343.             || ($objProduct Product::findByPk(Input::get('id'))) === null
  344.         ) {
  345.             return;
  346.         }
  347.         $GLOBALS['TL_DCA']['tl_iso_product']['list']['sorting']['mode']    = 4;
  348.         $GLOBALS['TL_DCA']['tl_iso_product']['list']['sorting']['fields']  = ['id'];
  349.         $GLOBALS['TL_DCA']['tl_iso_product']['fields']['alias']['sorting'] = false;
  350.         $arrFields = array();
  351.         $objType $objProduct->getType();
  352.         $arrVariantFields $objType->getVariantAttributes();
  353.         $arrVariantOptions array_intersect($arrVariantFieldsAttribute::getVariantOptionFields());
  354.         if (\in_array('images'$arrVariantFieldstrue)) {
  355.             $arrFields[] = 'images';
  356.         }
  357.         if (\in_array('name'$arrVariantFieldstrue)) {
  358.             $arrFields[] = 'name';
  359.             $GLOBALS['TL_DCA']['tl_iso_product']['list']['sorting']['fields'] = array('name');
  360.         }
  361.         if (\in_array('sku'$arrVariantFieldstrue)) {
  362.             $arrFields[] = 'sku';
  363.             $GLOBALS['TL_DCA']['tl_iso_product']['list']['sorting']['fields'] = array('sku');
  364.         }
  365.         if (\in_array('price'$arrVariantFieldstrue)) {
  366.             $arrFields[] = 'price';
  367.         }
  368.         // Limit the number of columns if there are more than 2
  369.         if (\count($arrVariantOptions) > 2) {
  370.             $arrFields[] = 'variantFields';
  371.             $GLOBALS['TL_DCA']['tl_iso_product']['list']['label']['variantFields'] = $arrVariantOptions;
  372.         } else {
  373.             foreach (array_merge($arrVariantOptions) as $name) {
  374.                 /** @var Attribute $objAttribute */
  375.                 $objAttribute $GLOBALS['TL_DCA']['tl_iso_product']['attributes'][$name];
  376.                 if ($objAttribute instanceof IsotopeAttributeWithOptions
  377.                     && 'table' === $objAttribute->optionsSource
  378.                 ) {
  379.                     $name .= ':tl_iso_attribute_option.label';
  380.                 }
  381.                 $arrFields[] = $name;
  382.             }
  383.         }
  384.         $GLOBALS['TL_DCA']['tl_iso_product']['list']['label']['fields'] = $arrFields;
  385.         // Make all column fields sortable
  386.         foreach ($GLOBALS['TL_DCA']['tl_iso_product']['fields'] as $name => $arrField) {
  387.             $GLOBALS['TL_DCA']['tl_iso_product']['fields'][$name]['sorting'] = ('price' !== $name && 'variantFields' !== $name && \in_array($name$arrFields));
  388.             $objAttribute $GLOBALS['TL_DCA']['tl_iso_product']['attributes'][$name] ?? null;
  389.             $GLOBALS['TL_DCA']['tl_iso_product']['fields'][$name]['filter'] = $objAttribute && ($objAttribute->be_filter \in_array($name$arrVariantFields) : false);
  390.             $GLOBALS['TL_DCA']['tl_iso_product']['fields'][$name]['search'] = $objAttribute && ($objAttribute->be_search \in_array($name$arrVariantFields) : false);
  391.         }
  392.     }
  393.     /**
  394.      * Add options from attribute to DCA
  395.      *
  396.      * @param array  $arrData
  397.      * @param object $objDca
  398.      *
  399.      * @return array
  400.      */
  401.     public function addOptionsFromAttribute($arrData$objDca)
  402.     {
  403.         if ($arrData['strTable'] == Product::getTable()
  404.             && ($arrData['optionsSource'] ?? '') != ''
  405.             && 'foreignKey' !== $arrData['optionsSource']
  406.         ) {
  407.             /** @var IsotopeAttributeWithOptions|Attribute $objAttribute */
  408.             $objAttribute Attribute::findByFieldName($arrData['strField']);
  409.             if (null !== $objAttribute && $objAttribute instanceof IsotopeAttributeWithOptions) {
  410.                 $arrData['options'] = ($objDca instanceof IsotopeProduct) ? $objAttribute->getOptionsForWidget($objDca) : $objAttribute->getOptionsForWidget();
  411.                 if (!empty($arrData['options'])) {
  412.                     if ($arrData['includeBlankOption']) {
  413.                         array_unshift($arrData['options'], array('value'=>'''label'=>($arrData['blankOptionLabel'] ?: '-')));
  414.                     }
  415.                     if (null !== ($arrData['default'] ?? null)) {
  416.                         $arrDefault array_filter(
  417.                             $arrData['options'],
  418.                             function (&$option) {
  419.                                 return (bool) $option['default'];
  420.                             }
  421.                         );
  422.                         if (!empty($arrDefault)) {
  423.                             array_walk(
  424.                                 $arrDefault,
  425.                                 function (&$value) {
  426.                                     $value $value['value'];
  427.                                 }
  428.                             );
  429.                             $arrData['value'] = ($objAttribute->multiple $arrDefault $arrDefault[0]);
  430.                         }
  431.                     }
  432.                 }
  433.             }
  434.         }
  435.         return $arrData;
  436.     }
  437.     /**
  438.      * Add a breadcrumb menu to the page tree
  439.      *
  440.      * @return string
  441.      */
  442.     protected static function getPagesBreadcrumb()
  443.     {
  444.         $session Session::getInstance()->getData();
  445.         // Set a new gid
  446.         if (isset($_GET['page'])) {
  447.             $session['filter']['tl_iso_product']['iso_page'] = (int) Input::get('page');
  448.             Session::getInstance()->setData($session);
  449.             Controller::redirect(preg_replace('/&page=[^&]*/'''Environment::get('request')));
  450.         }
  451.         $intNode $session['filter']['tl_iso_product']['iso_page'] ?? 0;
  452.         if ($intNode 1) {
  453.             return '';
  454.         }
  455.         $arrIds   = array();
  456.         $arrLinks = array();
  457.         $objUser BackendUser::getInstance();
  458.         // Generate breadcrumb trail
  459.         if ($intNode) {
  460.             $intId       $intNode;
  461.             $objDatabase Database::getInstance();
  462.             do {
  463.                 $objPage $objDatabase->prepare("SELECT * FROM tl_page WHERE id=?")
  464.                     ->limit(1)
  465.                     ->execute($intId);
  466.                 if ($objPage->numRows 1) {
  467.                     // Currently selected page does not exits
  468.                     if ($intId == $intNode) {
  469.                         $session['filter']['tl_iso_product']['iso_page'] = 0;
  470.                         Session::getInstance()->setData($session);
  471.                         return '';
  472.                     }
  473.                     break;
  474.                 }
  475.                 $arrIds[] = $intId;
  476.                 // No link for the active page
  477.                 if ($objPage->id == $intNode) {
  478.                     $arrLinks[] = Backend::addPageIcon($objPage->row(), ''null''true) . ' ' $objPage->title;
  479.                 } else {
  480.                     $arrLinks[] = Backend::addPageIcon($objPage->row(), ''null''true) . ' <a href="' Controller::addToUrl('page=' $objPage->id) . '" title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['selectNode']) . '">' $objPage->title '</a>';
  481.                 }
  482.                 // Do not show the mounted pages
  483.                 if (!$objUser->isAdmin && $objUser->hasAccess($objPage->id'pagemounts')) {
  484.                     break;
  485.                 }
  486.                 $intId $objPage->pid;
  487.             } while ($intId && 'root' !== $objPage->type);
  488.         }
  489.         // Check whether the node is mounted
  490.         if (!$objUser->isAdmin && !$objUser->hasAccess($arrIds'pagemounts')) {
  491.             $session['filter']['tl_iso_product']['iso_page'] = 0;
  492.             Session::getInstance()->setData($session);
  493.             throw new AccessDeniedException('Page ID ' $intNode ' was not mounted');
  494.         }
  495.         // Add root link
  496.         $arrLinks[] = '<img src="' TL_FILES_URL 'system/themes/' Backend::getTheme() . '/images/pagemounts.svg" width="18" height="18" alt=""> <a href="' Controller::addToUrl('page=0') . '" title="' StringUtil::specialchars($GLOBALS['TL_LANG']['MSC']['selectAllNodes']) . '">' $GLOBALS['TL_LANG']['MSC']['filterAll'] . '</a>';
  497.         $arrLinks   array_reverse($arrLinks);
  498.         // Insert breadcrumb menu
  499.         return '
  500. <ul id="tl_breadcrumb">
  501.   <li>' implode(' &gt; </li><li>'$arrLinks) . '</li>
  502. </ul>';
  503.     }
  504. }