Can't Manage Addons - 500 Internal Error

Hi all

I have 4.1.3 installed for a client. for no real reason, I can't get to the manage addons section of the admin panel.

When I turn error reporting on, I get this

Notice: Undefined index: 3.0 in /shop/app/Tygh/Addons/SchemesManager.php on line 42

Fatal error: Class name must be a valid object or a string in shop/app/Tygh/Addons/SchemesManager.php on line 42

I'm unsure why this is happening - any ideas?

Thanks

In /app/Tygh/Addons/SchemesManager.php, find:

        return array(
            '1.0' => 'Tygh\\Addons\\XmlScheme1',
            '2.0' => 'Tygh\\Addons\\XmlScheme2',
        );

Replace with:

        return array(
            '1.0' => 'Tygh\\Addons\\XmlScheme1',
            '2.0' => 'Tygh\\Addons\\XmlScheme2',
            '3.0' => 'Tygh\\Addons\\XmlScheme3',
        );

You then need to create /app/Tygh/Addons/XmlScheme3.php with the following.

namespace Tygh\Addons;

use Tygh\Registry;
use Tygh\Languages\Languages;
use Tygh\Languages\Po;

class XmlScheme3 extends XmlScheme2
{
protected $poparser = null;
private $parsed_po = array();

/**
 * Install all langvars from addon PO files
 *
 * @param  bool  $only_originals Gets only original values instead of language values
 * @return array List of language value or originals
 */
public function getLanguageValues($only_originals = false)
{
    $addon_id = (string) $this->_xml->id;
    $lang_dir_path = Registry::get('config.dir.addons') . $addon_id . '/lang/';

    $default_lang_pack = $this->getPoPath($this->getDefaultLanguage());

    $language_variables = parent::getLanguageValues($only_originals);

    foreach ($this->getLanguages() as $lang_code => $_v) {
        $lang_data = array();

        $po_path = $this->getPoPath($lang_code);
        if (!empty($po_path)) {
            $lang_data = Po::getValues($po_path, 'Languages');
        }

        if (!empty($default_lang_pack)) {
            $lang_data = array_merge(Po::getValues($default_lang_pack, 'Languages'), $lang_data);
        }

        foreach ($lang_data as $var_name => $var_data) {
            $value = implode('', $var_data['msgstr']);
            $original_value = $var_data['msgid'];
            $value = empty($value) ? $original_value : $value;

            if ($only_originals) {
                $language_variables[] = array(
                    'msgctxt' => $var_name,
                    'msgid' => $original_value,
                );
            } else {
                $language_variables[] = array(
                    'lang_code' => $lang_code,
                    'name' => $var_data['id'],
                    'value' => $value,
                );
            }
        }
    }

    return $language_variables;
}

/**
 * Returns addons text name from xml.
 * @param  string $lang_code
 * @return string
 */
public function getName($lang_code = CART_LANGUAGE)
{
    $addon_id = (string) $this->_xml->id;
    $addon_translations = $this->getPoValues($lang_code, 'Addons');

    if (!empty($addon_translations['Addons' . \I18n_Pofile::DELIMITER . 'name' . \I18n_Pofile::DELIMITER . $addon_id])) {
        $name = $addon_translations['Addons' . \I18n_Pofile::DELIMITER . 'name' . \I18n_Pofile::DELIMITER . $addon_id]['value'];
    } else {
        $name = parent::getName($lang_code);
    }

    return $name;
}

/**
 * Removes original values and values from languages and description tables
 * TODO: Make proper cleanup of PO language variables. Only XML langvars remove now.
 */
public function uninstallLanguageValues()
{
    $addon_id = (string) $this->_xml->id;

    db_query('DELETE FROM ?:original_values WHERE msgctxt IN (?a)', array('Addons' . \I18n_Pofile::DELIMITER . 'name' . \I18n_Pofile::DELIMITER . $addon_id, 'Addons' . \I18n_Pofile::DELIMITER . 'description' . \I18n_Pofile::DELIMITER . $addon_id));

    $originals = $this->getLanguageValues(true);

    if (!empty($originals)) {
        foreach ($originals as $original) {
            $name = explode(\I18n_Pofile::DELIMITER, $original['msgctxt']);

            db_query('DELETE FROM ?:original_values WHERE msgctxt = ?s AND msgid = ?s', $original['msgctxt'], $original['msgid']);
            db_query("DELETE FROM ?:language_values WHERE name = ?s", $name[1]);

            if (fn_allowed_for('ULTIMATE')) {
                db_query("DELETE FROM ?:ult_language_values WHERE name = ?s", $name[1]);
            }
        }
    }

    // Remove settings original language variables
    db_query('DELETE FROM ?:original_values WHERE msgctxt like ?l', 'Settings%::' . $addon_id . '::%');

    parent::uninstallLanguageValues();
}

/**
 * Returns addons text description from xml.
 * @param  string $lang_code
 * @return string
 */
public function getDescription($lang_code = CART_LANGUAGE)
{
    $addon_id = (string) $this->_xml->id;
    $addon_translations = $this->getPoValues($lang_code, 'Addons');

    if (!empty($addon_translations['Addons' . \I18n_Pofile::DELIMITER .'description' . \I18n_Pofile::DELIMITER . $addon_id])) {
        $description = $addon_translations['Addons' . \I18n_Pofile::DELIMITER . 'description' . \I18n_Pofile::DELIMITER . $addon_id]['value'];
    } else {
        $description = parent::getDescription($lang_code);
    }

    fn_set_hook('addons_scheme3_get_description', $this, $lang_code, $addon_id, $addon_translations, $description);

    return $description;
}

public function getSections()
{
    $addon_id = (string) $this->_xml->id;
    $default_lang = $this->getDefaultLanguage();
    $po_sections = $this->getPoValues($default_lang, 'SettingsSections');

    $sections = array();
    if (isset($this->_xml->settings->sections->section)) {
        foreach ($this->_xml->settings->sections->section as $section) {
            $_id = 'SettingsSections' . \I18n_Pofile::DELIMITER . $addon_id . \I18n_Pofile::DELIMITER . (string) $section['id'];
            if (isset($po_sections[$_id])) {
                $name = $po_sections[$_id]['value'];
                $original = $po_sections[$_id]['original'];
            } else {
                $name = (string) $section->name;
                $original = '';
            }

            $_section = array(
                'id' => (string) $section['id'],
                'name' => $name,
                'original' => $original,
                'translations' => $this->_getTranslations($section, 'SettingsSections', $addon_id),
                'edition_type' => $this->_getEditionType($section)
            );

            if (!empty($section['outside_of_form'])) {
                $_section['separate'] = true;
            }

            $sections[] = $_section;
        }
    }

    return $sections;
}

public function getSettings($section_id)
{
    $settings = array();

    $section = $this->_xml->xpath("//section[@id='$section_id']");

    if (!empty($section) && is_array($section)) {
        $section = current($section);

        if (isset($section->items->item)) {
            foreach ($section->items->item as $setting) {
                $settings[] = $this->_getSettingItem($setting);
            }
        }
    }

    return $settings;
}

/**
 * Returns translations of description and addon name.
 * @return array|bool
 */
public function getAddonTranslations()
{
    $name = $this->_getTranslations($this->_xml, 'Addons', 'name');
    $description = $this->_getTranslations($this->_xml, 'Addons', 'description', 'description');

    return fn_array_merge($name, $description);
}

/**
 * Gets original values for language-dependence name/description
 *
 * @return array Original values
 */
public function getOriginals()
{
    $originals = array();

    $addon_id = (string) $this->_xml->id;
    $pack = $this->getPoPath($this->getDefaultLanguage());

    if (file_exists($pack)) {
        $values = Po::getValues($pack, 'Addons');

        foreach ($values as $value) {
            if ($value['parent'] == 'name') {
                $originals['name'] = $value['msgid'];
            } elseif ($value['parent'] == 'description') {
                $originals['description'] = $value['msgid'];
            }
        }
    }

    return $originals;
}

/**
 * Gets path to PO translation for specified language
 *
 * @param  string      $lang_code 2-letters language identifier
 * @return string|bool Path to file if exists of false otherwise
 */
public function getPoPath($lang_code)
{
    $addon_id = (string) $this->_xml->id;
    $po_path = Registry::get('config.dir.lang_packs') . $lang_code . '/addons/' . $addon_id . '.po';

    if (file_exists($po_path)) {
        return $po_path;
    }

    return false;
}

private function getPoValues($lang_code, $section)
{
    if (empty($this->parsed_po[$lang_code][$section])) {
        $addon_id = (string) $this->_xml->id;
        $result = array();
        $pack = $this->getPoPath($lang_code);
        $default_pack = $this->getPoPath($this->getDefaultLanguage());

        if ($default_pack != $pack && file_exists($default_pack)) {
            $result = $this->parsePoContent(Po::getValues($default_pack, $section));
        }

        if (file_exists($pack)) {
            $result = fn_array_merge($result, $this->parsePoContent(Po::getValues($pack, $section)));
        }

        $this->parsed_po[$lang_code][$section] = $result;
    }

    return $this->parsed_po[$lang_code][$section];
}

private function parsePoContent($po_parsed_content)
{
    $formatted_po_values = array();

    foreach ($po_parsed_content as $var_id => $var_data) {
        $value = implode('', $var_data['msgstr']);
        $original_value = $var_data['msgid'];

        $formatted_po_values[$var_id] = array(
            'id' => $var_data['id'],
            'parent' => $var_data['parent'],
            'section' => $var_data['section'],
            'value' => empty($value) ? $original_value : $value,
            'original' => $original_value,
        );
    }

    return $formatted_po_values;
}

/**
 * Returns all translations for xml_node for all installed languages if it is presents in addon xml
 * @param $xml_node
 * @return array|bool
 */
protected function _getTranslations($xml_node, $type = '', $parent_id = '', $value_name = 'value')
{
    $po_values = array();
    $translations = array();

    // Generate id from attribute or property
    if (isset($xml_node['id'])) {
        $id = (string) $xml_node['id'];
    } elseif (isset($xml_node->id)) {
        $id = (string) $xml_node->id;
    } else {
        return false;
    }

    $default_language = $this->getDefaultLanguage();
    $po_values[$default_language] = $this->getPoValues($default_language, $type);
    $po_id = $type . (!empty($parent_id) ? \I18n_Pofile::DELIMITER . $parent_id : '') . \I18n_Pofile::DELIMITER . $id;

    if (isset($po_values[$default_language][$po_id])) {
        $default_value = $po_values[$default_language][$po_id]['value'];
    } else {
        $default_value = (string) $xml_node->name;
    }

    $default_translation = array(
        'lang_code' => $default_language,
        'name' => $id,
        $value_name => $default_value,
    );

    // Fill all languages by default laguage values
    foreach ($this->getLanguages() as $lang_code => $_v) {
        if (empty($po_values[$lang_code][$po_id])) {
            $po_values[$lang_code] = $this->getPoValues($lang_code, $type);
        }

        $value = isset($po_values[$lang_code][$po_id])
            ? $po_values[$lang_code][$po_id]['value']
            : $xml_node->xpath("translations/item[(not(@for) or @for='name') and @lang='$lang_code']");

        if (!empty($value) && is_array($value)) {
            $value = (string) current($value);
        }

        $translations[] = array(
            'lang_code' => $lang_code,
            'name' => $default_translation['name'],
            $value_name => !empty($value) ? $value : $default_translation[$value_name],
        );
    }

    return $translations;
}

/**
 * Returns array of setting item data from xml node
 * @param $xml_node
 * @return array
 */
protected function _getSettingItem($xml_node)
{
    $addon_id = (string) $this->_xml->id;
    $default_language = $this->getDefaultLanguage();

    foreach ($this->getLanguages() as $lang_code => $_v) {
        $items[$lang_code] = $this->getPoValues($lang_code, 'SettingsOptions');
    }

    if (isset($xml_node['id'])) {
        $_types = $this->_getTypes();

        $translations = $this->_getTranslations($xml_node, 'SettingsOptions', $addon_id);
        $tooltip_translations = $this->_getTranslations($xml_node, 'SettingsTooltips', $addon_id, 'tooltip');

        $setting = array(
            'edition_type' =>  $this->_getEditionType($xml_node),
            'id' => (string) $xml_node['id'],
            'name' => isset($items[$default_language]['SettingsOptions::' . $addon_id . '::' . ((string) $xml_node['id'])]) ? $items[$default_language]['SettingsOptions::' . $addon_id . '::' . ((string) $xml_node['id'])]['value'] : (string) $xml_node->name,
            'original' => isset($items[$default_language]['SettingsOptions::' . $addon_id . '::' . ((string) $xml_node['id'])]) ? $items[$default_language]['SettingsOptions::' . $addon_id . '::' . ((string) $xml_node['id'])]['original'] : '',
            'type' => isset($_types[(string) $xml_node->type]) ? $_types[(string) $xml_node->type] : '',
            'translations' => fn_array_merge($translations, $tooltip_translations),
            'default_value' => isset($xml_node->default_value) ? (string) $xml_node->default_value : '',
            'variants' => $this->_getVariants($xml_node),
            'handler' => isset($xml_node->handler) ? (string) $xml_node->handler : '',
            'parent_id' => isset($xml_node['parent_id']) ? (string) $xml_node['parent_id'] : '',
        );

        return $setting;
    } else {
        return array();
    }
}

/**
 * Returns array of variants of setting item from xml node
 * @param $xml_node
 * @return array
 */
protected function _getVariants($xml_node)
{
    $addon_id = (string) $this->_xml->id;
    $option_id = (string) $xml_node['id'];
    $originals = $this->getPoValues($this->getDefaultLanguage(), 'SettingsVariants');

    $variants = array();
    if (isset($xml_node->variants)) {
        foreach ($xml_node->variants->item as $variant) {
            $variants[] = array(
                'id' => (string) $variant['id'],
                'name' => isset($originals['SettingsVariants::' . $addon_id . '::' . $option_id . '::' . ((string) $variant['id'])]) ? $originals['SettingsVariants::' . $addon_id . '::' . $option_id . '::' . ((string) $variant['id'])]['value'] : (string) $variant->name,
                'original' => isset($originals['SettingsVariants::' . $addon_id . '::' . $option_id . '::' . ((string) $variant['id'])]) ? $originals['SettingsVariants::' . $addon_id . '::' . $option_id . '::' . ((string) $variant['id'])]['original'] : '',
                'translations' => $this->_getTranslations($variant, 'SettingsVariants', $addon_id . \I18n_Pofile::DELIMITER . $option_id),
            );
        }
    }

    return $variants;
}

}

Done

Now I get this

Fatal error: Class 'Tygh\Languages\Po' not found in /home1/tbarcoma/rails/tbar/public/shop/app/Tygh/Addons/XmlScheme3.php on line 271

So I found Po.php in a later version of CS-Cart and used that

Now I get

Fatal error: Undefined class constant 'DELIMITER' in /home1/tbarcoma/rails/tbar/public/shop/app/Tygh/Addons/XmlScheme3.php on line 86

Does it make a difference this is CS-Cart 4.1.3 and not more recent?

So I found Po.php in a later version of CS-Cart and used that

Now I get

Fatal error: Undefined class constant 'DELIMITER' in /home1/tbarcoma/rails/tbar/public/shop/app/Tygh/Addons/XmlScheme3.php on line 86

Does it make a difference this is CS-Cart 4.1.3 and not more recent?

Most probably, you have installed an add-on for later versions of CS-Cart. Please check the app/addons/[add-on_name]/addon.xml files for all recently installed add-ons. If you find the following line in this file you should delete this add-on since it is incompatible with your version of CS-Cart: