# ali2woo-lite/trunk/includes/classes/utils/Helper.php

AliNext – WooCommerce Dropshipping Plugin for AliExpress, version trunk. 659 lines.

- Page: https://pluginprobe.com/plugins/ali2woo-lite/trunk/code/includes/classes/utils/Helper.php
- Raw: https://pluginprobe.com/plugins/ali2woo-lite/trunk/raw/includes/classes/utils/Helper.php
- Modified: 2026-09-07T19:12:00+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/ali2woo-lite/trunk/code/includes/classes/utils/Helper.php#L10-L20`.

```php
<?php
/**
 * Description of Helper
 *
 * @author Ali2Woo Team
 */

namespace AliNext_Lite;;

class Helper {
    public function image_http_to_https($image_url) {
        //return preg_replace("/http:\/\/g(\d+)\.a\./i", "https://ae$1.", strval($image_url));
        return str_replace("http://", "https://", $image_url);
    }

    public function image_https_to_http($image_url) {
        return preg_replace("/https:\/\/ae(\d+)\./i", "http://g$1.a.", strval($image_url));
    }

    public function clear_html($in_html) {
        if (!$in_html)
            return "";
        $html = preg_replace('~<(?:!DOCTYPE|/?(?:html|body))[^>]*>\s*~i', '', $in_html);
        $html = preg_replace('/(<[^>]+) style=".*?"/i', '$1', $html);
        $html = preg_replace('/(<[^>]+) class=".*?"/i', '$1', $html);
        $html = preg_replace('/(<[^>]+) width=".*?"/i', '$1', $html);
        $html = preg_replace('/(<[^>]+) height=".*?"/i', '$1', $html);
        $html = preg_replace('/(<[^>]+) alt=".*?"/i', '$1', $html);
        $html = preg_replace('/^<!DOCTYPE.+?>/', '$1', str_replace(array('<html>', '</html>', '<body>', '</body>'), '', $html));
        $html = preg_replace("/<\/?div[^>]*\>/i", "", $html);

        $html = preg_replace('#(<a.*?>).*?(</a>)#', '$1$2', $html);
        $html = preg_replace('/<a[^>]*>(.*)<\/a>/iU', '', $html);
        $html = preg_replace("/<\/?h1[^>]*\>/i", "", $html);
        $html = preg_replace("/<\/?strong[^>]*\>/i", "", $html);
        $html = preg_replace("/<\/?span[^>]*\>/i", "", $html);

        //$html = str_replace(' &nbsp; ', '', $html);
        $html = str_replace('&nbsp;', ' ', $html);
        $html = str_replace('\t', ' ', $html);
        $html = str_replace('  ', ' ', $html);


        $html = preg_replace("/http:\/\/g(\d+)\.a\./i", "https://ae$1.", $html);

        $pattern = "/<[^\/>]*>([\s]?)*<\/[^>]*>/";
        $html = preg_replace($pattern, '', $html);

        $html = str_replace(array('<img', '<table'), array('<img class="img-responsive"', '<table class="table table-bordered'), $html);
        $html = force_balance_tags($html);

        return $html;
    }

    public function clean_woocommerce_product_attributes($product_id, $used_attributes) {
        global $wpdb;
        $escAttrs = array();
        foreach($used_attributes as $a){
            $escAttrs[] = "'".esc_sql($a)."'";
        }

        if(!empty($escAttrs)){
            $sql = "DELETE tr FROM {$wpdb->term_relationships} tr INNER JOIN {$wpdb->term_taxonomy} tt " .
                   "ON (tt.term_taxonomy_id=tr.term_taxonomy_id) " .
                   "INNER JOIN {$wpdb->prefix}woocommerce_attribute_taxonomies wat " .
                   "ON(CONCAT('pa_',wat.attribute_name)=tt.taxonomy) " .
                   "WHERE tr.object_id = %s AND tt.taxonomy NOT IN (". implode(',', $escAttrs) .")";
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $wpdb->query($wpdb->prepare($sql, $product_id));
        }
    }

    public function set_woocommerce_attributes($post_id, $attributes = array()) {
        global $wpdb;
        global $woocommerce;

        // convert Amazon attributes into woocommerce attributes
        $_product_attributes = array();
        $position = 0;

        foreach ($attributes as $attr) {
            $key = $attr['name'];
            $value = $attr['value'];


            if (!is_object($value)) {
                // change dimension name as woocommerce attribute name
                $attribute_name = $this->cleanTaxonomyName(strtolower($key));

                // Clean
                if (is_array($value)) {
                    foreach($value as $k=>$v){
                        $value[$k] = $this->cleanValue($v);
                    }
                }else{
                    $value = $this->cleanValue($value);
                }

                // if is empty attribute don't import
                if (empty($value)){
                    continue;
                }

                $_product_attributes[$attribute_name] = array(
                    'name' => $attribute_name,
                    'value' => is_array($value)?implode(", ", $value):$value,
                    'position' => $position++,
                    'is_visible' => 1,
                    'is_variation' => 0,
                    'is_taxonomy' => 1
                );

                $this->add_attribute($post_id, $key, $value);
            }
        }

        // update product attribute
        update_post_meta($post_id, '_product_attributes', $_product_attributes);

        $this->attrclean_clean_all('array'); // delete duplicate attributes
        // refresh attribute cache
        //$dmtransient_name = 'wc_attribute_taxonomies';
        //$dmattribute_taxonomies = $wpdb->get_results("SELECT * FROM " . $wpdb->prefix . "woocommerce_attribute_taxonomies");
        //set_transient($dmtransient_name, $dmattribute_taxonomies);

        flush_rewrite_rules();
        a2wl_delete_transient('wc_attribute_taxonomies');
    }


    public function add_attribute($post_id, $key, $value): void
    {
        global $wpdb;

        // avoid object to be inserted in terms
        if (is_object($value)) {
            return;
        }

        // Empty attribute values are expected for sold-out/disabled products (the attribute
        // itself is still registered in advance). Not a bug; keep as a case to verify with
        // a dedicated test in the future.
        if (empty($value) || (is_array($value) && count(array_filter($value)) === 0)) {
            a2wl_info_log(sprintf(
                '[add_attribute] Empty value detected for product_id=%d, key=%s',
                $post_id,
                $key
            ));
        }

        $taxonomy = $this->cleanTaxonomyName($key);

        $attribute_label = $key;
        //attribute name should be the same as taxonomy just without pa_
        $attribute_name = str_replace('pa_', '', $taxonomy);
        $attribute_type = 'select';

        $attribute_taxonomies = $wpdb->get_var(
            "SELECT * FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_name = '" . esc_sql($attribute_name) . "'"
        );

        if ($attribute_taxonomies) {
            // Update existing global attribute
            // Set type to 'select' since this attribute is used for variations
            $wpdb->update(
                $wpdb->prefix . 'woocommerce_attribute_taxonomies',
                [
                    'attribute_type' => $attribute_type
                ],
                ['attribute_name' => $attribute_name]
            );
        } else {
            $wpdb->insert(
                    $wpdb->prefix . 'woocommerce_attribute_taxonomies', [
                        'attribute_label' => $attribute_label,
                        'attribute_name' => $attribute_name,
                        'attribute_type' => $attribute_type,
                        'attribute_orderby' => 'name'
                    ]
            );
        }

        // add attribute values if not exist

        $values = is_array($value) ? $value : [$value];

        // check taxonomy
        if (!taxonomy_exists($taxonomy)) {
            // add attribute value
            foreach ($values as $attribute_value) {
                $attribute_value = (string) $attribute_value;

                if (is_string($attribute_value)) {
                    // add term
                    $name = $this->cleanValue($attribute_value);
                    $slug = sanitize_title($name);

                    if (!term_exists($name)) {
                        $term_taxonomy_id = $this->createTermAndLinkTaxonomy($name, $taxonomy, $slug);
                    } else {
                        $term_id = $wpdb->get_var(
                            $wpdb->prepare(
                                "SELECT term_id FROM {$wpdb->terms} WHERE name = %s",
                                $name
                            )
                        );

                        $existing_tt_id = $wpdb->get_var(
                            $wpdb->prepare(
                                "SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE term_id = %d AND taxonomy = %s",
                                $term_id,
                                $taxonomy
                            )
                        );

                        if ($existing_tt_id) {
                            //term is connected to the taxonomy, do nothing
                            $term_taxonomy_id = $existing_tt_id;
                        } else {
                            //need create a new token and connect it to the taxonomy
                            $term_taxonomy_id = $this->createTermAndLinkTaxonomy($name, $taxonomy, $slug);

                        }
                    }
                }
            }
        }

        if (!empty($values)) {
            $attribute_slugs = array();
            $terms = $this->load_terms($taxonomy);
            foreach ($terms as $term) {
                $attribute_slugs[] = strval($term->slug);
            }
            // Add terms
            foreach ($values as $attribute_value) {
                $attribute_value = $this->cleanValue((string) $attribute_value);
                $attribute_slug = sanitize_title(htmlspecialchars($attribute_value, ENT_NOQUOTES));
                if (!in_array(strval($attribute_slug), $attribute_slugs, true)) {
                    wp_insert_term($attribute_value, $taxonomy, array('slug'=>$attribute_slug));
                }
            }
        }
        // wp_term_relationships (object_id to term_taxonomy_id)
        if (!empty($values)) {
            foreach ($values as $term) {

                if (!is_array($term) && !is_object($term)) {
                    $term = sanitize_title(htmlspecialchars($term, ENT_NOQUOTES));

                    $term_taxonomy_id = $wpdb->get_var("SELECT tt.term_taxonomy_id FROM {$wpdb->terms} AS t INNER JOIN {$wpdb->term_taxonomy} as tt ON tt.term_id = t.term_id WHERE t.slug = '" . esc_sql($term) . "' AND tt.taxonomy = '" . esc_sql($taxonomy) . "'");

                    if ($term_taxonomy_id) {
                        $checkSql = "SELECT * FROM {$wpdb->term_relationships} WHERE object_id = {$post_id} AND term_taxonomy_id = {$term_taxonomy_id}";
                        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
                        if (!$wpdb->get_var($checkSql)) {
                            $wpdb->insert(
                                $wpdb->term_relationships,
                                array('object_id' => $post_id, 'term_taxonomy_id' => $term_taxonomy_id)
                            );
                        }
                    }
                }
            }
        }

        // Update WPML translates
        global $woocommerce_wpml;
        if (isset($woocommerce_wpml) && property_exists($woocommerce_wpml, 'attributes') && $woocommerce_wpml->attributes) {
            $woocommerce_wpml->attributes->set_attribute_config_in_settings( $taxonomy, 1 );
        }
    }

    public function attrclean_clean_all($retType = 'die') {
        // :: get duplicates list
        $duplicates = $this->attrclean_getDuplicateList();

        if (empty($duplicates) || !is_array($duplicates)) {
            $ret['status'] = 'valid';
            $ret['msg_html'] = 'no duplicate terms found!';
            if ($retType == 'die')
                die(wp_json_encode($ret));
            else
                return $ret;
        }
        // html message
        $__duplicates = array();
        $__duplicates[] = '0 : name, slug, term_taxonomy_id, taxonomy, count';
        foreach ($duplicates as $key => $value) {
            $__duplicates[] = $value->name . ' : ' . implode(', ', (array) $value);
        }
        $ret['status'] = 'valid';
        $ret['msg_html'] = implode('<br />', $__duplicates);
        // if ( $retType == 'die' ) die(wp_json_encode($ret));
        // else return $ret;
        // :: get terms per duplicate
        $__removeStat = array();
        $__terms = array();
        $__terms[] = '0 : term_id, name, slug, term_taxonomy_id, taxonomy, count';
        foreach ($duplicates as $key => $value) {
            $terms = $this->attrclean_getTermPerDuplicate($value->name, $value->taxonomy);
            if (empty($terms) || !is_array($terms) || count($terms) < 2)
                continue 1;

            $first_term = array_shift($terms);

            // html message
            foreach ($terms as $k => $v) {
                $__terms[] = $key . ' : ' . implode(', ', (array) $v);
            }

            // :: remove duplicate term
            $removeStat = $this->attrclean_removeDuplicate($first_term->term_id, $terms, false);

            // html message
            $__removeStat[] = '-------------------------------------- ' . $key;
            $__removeStat[] = '---- term kept';
            $__removeStat[] = 'term_id, term_taxonomy_id';
            $__removeStat[] = $first_term->term_id . ', ' . $first_term->term_taxonomy_id;
            foreach ($removeStat as $k => $v) {
                $__removeStat[] = '---- ' . $k;
                if (!empty($v) && is_array($v)) {
                    foreach ($v as $k2 => $v2) {
                        $__removeStat[] = implode(', ', (array) $v2);
                    }
                } else if (!is_array($v)) {
                    $__removeStat[] = (int) $v;
                } else {
                    $__removeStat[] = 'empty!';
                }
            }
        }

        $ret['status'] = 'valid';
        $ret['msg_html'] = implode('<br />', $__removeStat);
        if ($retType == 'die')
            die(wp_json_encode($ret));
        else
            return $ret;
    }

    /**
     * Attributes clean duplicate
     */
    public function attrclean_getDuplicateList() {
        global $wpdb;

        // $q = "SELECT COUNT(a.term_id) AS nb, a.name, a.slug FROM {$wpdb->terms} AS a WHERE 1=1 GROUP BY a.name HAVING nb > 1;";
        $q = "SELECT COUNT(a.term_id) AS nb, a.name, a.slug, b.term_taxonomy_id, b.taxonomy, b.count FROM {$wpdb->terms} AS a " .
             "LEFT JOIN {$wpdb->term_taxonomy} AS b ON a.term_id = b.term_id " .
             "WHERE b.taxonomy REGEXP '^pa_' GROUP BY a.name, b.taxonomy HAVING nb > 1 " .
             "ORDER BY a.name ASC";

        $res = $wpdb->get_results(
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $wpdb->prepare($q)
        );
        if (!$res || !is_array($res))
            return false;

        $ret = array();
        foreach ($res as $key => $value) {
            $name = $value->name;
            $taxonomy = $value->taxonomy;
            $ret["$name@@$taxonomy"] = $value;
        }
        return $ret;
    }

    public function attrclean_getTermPerDuplicate($term_name, $taxonomy) {
        global $wpdb;

        $q = "SELECT a.term_id, a.name, a.slug, b.term_taxonomy_id, b.taxonomy, b.count FROM {$wpdb->terms} AS a " .
             "LEFT JOIN {$wpdb->term_taxonomy} AS b ON a.term_id = b.term_id " .
             "WHERE a.name=%s AND b.taxonomy=%s ORDER BY a.slug ASC";

        $res = $wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $wpdb->prepare($q, $term_name, $taxonomy)
        );
        if (!$res || !is_array($res))
            return false;

        $ret = array();
        foreach ($res as $key => $value) {
            $ret[$value->term_taxonomy_id] = $value;
        }
        return $ret;
    }

    public function attrclean_removeDuplicate($first_term, $terms = array(), $debug = false) {
        if (empty($terms) || !is_array($terms))
            return false;

        $term_id = array();
        $term_taxonomy_id = array();
        foreach ($terms as $k => $v) {
            $term_id[] = $v->term_id;
            $term_taxonomy_id[] = $v->term_taxonomy_id;
            $taxonomy = $v->taxonomy;
        }
        // var_dump('<pre>',$first_term, $term_id, $term_taxonomy_id, $taxonomy,'</pre>');  

        $ret = array();
        $ret['term_relationships'] = $this->attrclean_remove_term_relationships($first_term, $term_taxonomy_id, $debug);
        $ret['terms'] = $this->attrclean_remove_terms($term_id, $debug);
        $ret['term_taxonomy'] = $this->attrclean_remove_term_taxonomy($term_taxonomy_id, $taxonomy, $debug);
        // var_dump('<pre>',$ret,'</pre>');  
        return $ret;
    }

    /**
     * Create a new term and link it to the given taxonomy.
     *
     * Normally each taxonomy should have its own terms, even if names/slugs match.
     * This function uses wp_unique_term_slug() as a safeguard in case another process
     * already inserted the same slug for this taxonomy, ensuring uniqueness and avoiding conflicts.
     *
     * @param string $termName  Term name (e.g. "white")
     * @param string $taxonomy  Taxonomy name (e.g. "pa_color")
     * @param string $slug      Base slug for the term
     * @return int|false        term_taxonomy_id or false on failure
     */
    private function createTermAndLinkTaxonomy($termName, $taxonomy, $slug) {
        global $wpdb;

        if (trim($slug) !== '' && trim($termName) !== '') {

            $term = (object) [
                'taxonomy' => $taxonomy,
                'parent'   => 0,
                'term_id'  => 0,
            ];
            $unique_slug = wp_unique_term_slug($slug, $term);

            $this->db_custom_insert(
                $wpdb->terms,
                [
                    'values' => ['name' => $termName, 'slug' => $unique_slug],
                    'format' => ['%s', '%s']
                ],
                true
            );

            $term_id = $wpdb->insert_id;

            $this->db_custom_insert(
                $wpdb->term_taxonomy,
                [
                    'values' => ['term_id' => $term_id, 'taxonomy' => $taxonomy],
                    'format' => ['%d', '%s']
                ],
                true
            );

            return $wpdb->insert_id;
        }

        return false;
    }

    private function attrclean_remove_term_relationships($first_term, $term_taxonomy_id, $debug = false) {
        global $wpdb;

        $idList = (is_array($term_taxonomy_id) && count($term_taxonomy_id) > 0 ?
            implode(', ', array_map(array($this, 'prepareForInList'), $term_taxonomy_id)) :
            0);

        if ($debug) {
            $q = "SELECT a.object_id, a.term_taxonomy_id FROM {$wpdb->term_relationships} AS a " .
                 "WHERE a.term_taxonomy_id IN ({$idList}) ORDER BY a.object_id ASC, a.term_taxonomy_id";
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $res = $wpdb->get_results($q);
            if (!$res || !is_array($res))
                return false;

            $ret = array();
            $ret[] = 'object_id, term_taxonomy_id';
            foreach ($res as $key => $value) {
                $term_taxonomy_id = $value->term_taxonomy_id;
                $ret["$term_taxonomy_id"] = $value;
            }
            return $ret;
        }

        // execution/ update
        $q = "UPDATE {$wpdb->term_relationships} AS a SET a.term_taxonomy_id = %s " .
             "WHERE a.term_taxonomy_id IN ({$idList})";

        $res = $wpdb->query(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $wpdb->prepare($q, $first_term)
        );
        $ret = $res;

        return $ret;
    }

    private function attrclean_remove_terms($term_id, $debug = false) {
        global $wpdb;

        $idList = (is_array($term_id) && count($term_id) > 0 ? implode(', ', array_map(array($this, 'prepareForInList'), $term_id)) : 0);

        if ($debug) {
            $q = "SELECT a.term_id, a.name FROM {$wpdb->terms} AS a " .
                 "WHERE a.term_id IN (%s) ORDER BY a.name ASC";

            $res = $wpdb->get_results(
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
                $wpdb->prepare($q, $idList)
            );
            if (!$res || !is_array($res))
                return false;

            $ret = array();
            $ret[] = 'term_id, name';
            foreach ($res as $key => $value) {
                $term_id = $value->term_id;
                $ret["$term_id"] = $value;
            }
            return $ret;
        }

        // execution/ update
        $q = "DELETE FROM a USING {$wpdb->terms} as a WHERE a.term_id IN ({$idList})";
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
        return $wpdb->query($q);
    }

    private function attrclean_remove_term_taxonomy($term_taxonomy_id, $taxonomy, $debug = false) {
        global $wpdb;

        $idList = (
            is_array($term_taxonomy_id) && count($term_taxonomy_id) > 0 ?
                implode(', ', array_map(array($this, 'prepareForInList'), $term_taxonomy_id)) :
                0
        );

        if ($debug) {
            $q = "SELECT a.term_id, a.taxonomy, a.term_taxonomy_id FROM {$wpdb->term_taxonomy} AS a " .
                 "WHERE a.term_taxonomy_id IN ({$idList}) AND a.taxonomy = %s ORDER BY a.term_taxonomy_id ASC";

            $res = $wpdb->get_results(
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
                $wpdb->prepare($q, esc_sql($taxonomy))
            );
            if (!$res || !is_array($res))
                return false;

            $ret = array();
            $ret[] = 'term_id, taxonomy, term_taxonomy_id';
            foreach ($res as $key => $value) {
                $term_taxonomy_id = $value->term_taxonomy_id;
                $ret["$term_taxonomy_id"] = $value;
            }
            return $ret;
        }

        // execution/ update
        $q = "DELETE FROM a USING {$wpdb->term_taxonomy} as a WHERE a.term_taxonomy_id IN ({$idList}) AND a.taxonomy = %s";

        return $wpdb->query(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $wpdb->prepare($q, esc_sql($taxonomy))
        );
    }

    public function load_terms(string $taxonomy) {
        global $wpdb;

        $query = "SELECT DISTINCT t.name, t.slug FROM {$wpdb->terms} AS t " .
                 "INNER JOIN {$wpdb->term_taxonomy} as tt ON tt.term_id = t.term_id " .
                 "WHERE tt.taxonomy = %s";

        return $wpdb->get_results($wpdb->prepare($query, $taxonomy), OBJECT);
    }

    public function db_custom_insert(string $table, array $fields, bool $ignore = false, bool $wp_way = false): int
    {
        global $wpdb;

        if ($wp_way && !$ignore) {
            $wpdb->insert($table, $fields['values'], $fields['format']);
        } else {
            $columns = [];
            foreach ($fields['values'] as $k => $v) {
                $columns[] = $k;
            }

            $query = "INSERT " . ($ignore ? "IGNORE" : "") .
                " INTO $table (" . implode(', ', $columns) . ")" .
                " VALUES (" . implode(', ', $fields['format']) . ");";

            $valuesForPrepare = array_values($fields['values']);
            $wpdb->query($wpdb->prepare($query, $valuesForPrepare));
        }

        return $wpdb->insert_id;
    }

    public function prepareForInList($v): string
    {
        return "'" . $v . "'";
    }

    public function cleanTaxonomyName(string $value, bool $withPrefix = true, bool $checkSize = true): string
    {
        // Sanitize taxonomy names. Slug format (no spaces, lowercase) - uses sanitize_title
        if ($withPrefix) {
            $ret = wc_attribute_taxonomy_name($value); // return 'pa_' . $value
        } else {
            $ret = wc_sanitize_taxonomy_name($value); // return $value
        }

        if ($checkSize) {
            // limit to 32 characters (database/ table wp_term_taxonomy/ field taxonomy/ is limited to varchar(32) )
            if (wp_is_valid_utf8($ret)) {
                $limit_max = $withPrefix ? 18 : 15; // utf8: 3 + 29/2
                if (function_exists('mb_substr')) {
                    $ret = mb_substr($ret, 0, $limit_max);

                    // Ensure generated name doesn't exceed byte length using strlen (because Woocommerce use strlen)
                    while (strlen($ret) > ($withPrefix ? 32 : 28)) {
                        $ret = mb_substr($ret, 0, -1, 'UTF-8'); // remove last character
                    }
                }
            } else {
                $limit_max = $withPrefix ? 32 : 29; // 29 = 32 - strlen('pa_')
                $ret = substr($ret, 0, $limit_max);
            }
        }
        
        // IMPORTANT, if not sure do not need sanitize_title!
        return $ret;
    }

    public function cleanValue(string $value): string
    {
        // Format Camel Case
        //$value = trim( preg_replace('/([A-Z])/', ' $1', $value) );
        // Clean / from value
        return trim(preg_replace('/(\/)/', '-', $value));
    }

    public function multi_implode($array, $glue) {
        $ret = '';
        foreach ($array as $item) {
            if (is_array($item)) {
                $ret .= $this->multi_implode($item, $glue) . $glue;
            } else {
                $ret .= $item . $glue;
            }
        }
        $ret = substr($ret, 0, 0 - strlen($glue));
        return $ret;
    }

}

```
