/home/edulekha/crm.edulekha.com/application/libraries/merge_fields/App_merge_fields.php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

class App_merge_fields
{
    /**
     * Codeigniter instance
     * @var object
     */
    protected $ci;

    /**
     * The actual registered fields from the classes that extends
     * @var array
     */
    protected $fields = [];

    /**
     * Paths to load the classes
     * e.q. merge_fields/client
     * @var array
     */
    protected $registered = [];

    /**
     * Merge fields relation
     * @var mixed
     */
    protected $for = null;

    /**
     * All merge fields are stored here
     * @var array
     */
    protected $all_merge_fields = null;

    /**
     * Helper property
     * @var boolean
     */
    private $classes_for_merge_fields_initialized = false;

    public function __construct()
    {
        $this->ci = &get_instance();

        // Method exists only if App_merge_fields is extended by another merge fields class
        if (method_exists($this, 'build')) {
            $this->set($this->build());
        } else {
            // Run only once when class is autoloaded
            $this->registered = hooks()->apply_filters('register_merge_fields', []);
        }
    }

    public function get_by_name($name)
    {
        foreach ($this->all() as $key => $feature) {
            if (isset($feature[$name])) {
                return $feature[$name];
            }
        }

        return [];
    }

    public function format_feature($name, ...$params)
    {
        // Initialize all merge fields and load classes
        if ($this->classes_for_merge_fields_initialized === false) {
            $this->all();
            $this->classes_for_merge_fields_initialized = true;
        }

        $baseName = basename($name);

        $merge_fields = $this->get_by_name($this->ci->{$baseName}->name());

        $uniqueFormatters = [];
        $uniqueClassLoad  = [];

        foreach ($merge_fields as $field) {
            if ($baseName === $field['format']['base_name']) {
                $uniqueFormatters[]                             = $field['format']['base_name'];
                $uniqueClassLoad[$field['format']['base_name']] = $field['format']['file'];
            }
        }

        $uniqueFormatters = array_unique($uniqueFormatters);

        $formatted = [];

        foreach ($uniqueFormatters as $classFormatter) {
            if (method_exists($this->ci->{$classFormatter}, 'format')) {
                if (!class_exists($classFormatter, false)) {
                    $this->ci->load->library($uniqueClassLoad[$classFormatter]);
                }

                $newFormatted = $this->ci->{$classFormatter}->format(...$params);

                if (is_array($newFormatted)) {
                    $formatted = array_merge($newFormatted, $formatted);
                }
            }
        }

        return $formatted;
    }

    /**
     * Get the registered class fields
     * @return array
     */
    public function get($name = null)
    {
        $for = !$name ? $this->name() : $name;

        return isset($this->fields[$for]) ? $this->fields[$for] : [];
    }

    /**
     * Set merge fields
     * @param array $fields
     */
    public function set($fields)
    {
        $for = $this->name();

        if (!isset($this->fields[$for])) {
            $this->fields[$for] = $fields;
        } else {
            $this->fields[$for][] = $fields;
        }

        return $this;
    }

    /**
     * Register merge field path class
     * @param  mixed $loadPath
     * @return $this
     */
    public function register($loadPath)
    {
        if (is_array($loadPath)) {
            foreach ($loadPath as $merge_fields) {
                $this->register($merge_fields);
            }

            return $this;
        }

        $this->registered[] = $loadPath;

        return $this;
    }

    /**
     * Get all registered paths
     * @return array
     */
    public function get_registered()
    {
        return $this->registered;
    }

    /**
     * Get all merge fields
     * @return array
     */
    public function all($reBuild = false)
    {
        if ($reBuild !== true && !is_null($this->all_merge_fields)) {
            return $this->all_merge_fields;
        }

        $registered = $this->get_registered();

        $available = [];

        foreach ($registered as $merge_field) {
            $baseName = $this->load($merge_field);

            $fields = $this->ci->{$baseName}->get();

            $name  = $this->ci->{$baseName}->name();
            $index = $this->merge_field_exists_by_name($available, $name);

            $format = [
                    'base_name' => $baseName,
                    'file'      => $merge_field,
                ];
            foreach ($fields as $key => $field) {
                $fields[$key]['format'] = $format;
            }

            if ($index !== false) {
                $index                    = (int) $index;
                $available[$index][$name] = array_merge($available[$index][$name], $fields);
            } else {
                $available[][$name] = $fields;
            }
        }

        $available = $this->apply_custom_fields($available, $format);

        $this->all_merge_fields = $available;

        return hooks()->apply_filters('available_merge_fields', $available);
    }

    public function name()
    {
        if (is_null($this->for)) {
            $this->for = strtolower(strbefore(get_class($this), '_merge_fields'));
        }

        return $this->for;
    }

    public function load($merge_field)
    {
        $baseName = basename($merge_field);

        if (!class_exists($baseName, false)) {
            $this->ci->load->library($merge_field);
        }

        return $baseName;
    }

    public function get_flat($primary, $additional = [], $exclude_keys = [])
    {
        if (!is_array($primary)) {
            $primary = [$primary];
        }

        if (!is_array($additional)) {
            $additional = [$additional];
        }

        if (!is_array($exclude_keys)) {
            $exclude_keys = [$exclude_keys];
        }

        $registered = $this->all();
        $flat       = [];
        foreach ($registered as $key => $val) {
            foreach ($val as $type => $fields) {
                if (in_array($type, $primary)) {
                    if ($availableFields = $this->check_availability($fields, $type, $exclude_keys)) {
                        array_push($flat, $availableFields);
                    }
                } elseif (in_array($type, $additional)) {
                    if ($type == 'other') {
                        $other = [];
                        foreach ($fields as $field) {
                            if (!in_array($field['key'], $exclude_keys)) {
                                $other[] = $field;
                            }
                        }
                        array_push($flat, $other);
                    } else {
                        if ($availableFields = $this->check_availability($fields, $type, $exclude_keys)) {
                            array_push($flat, $availableFields);
                        }
                    }
                }
            }
        }

        return $flat;
    }

    private function merge_field_exists_by_name($available, $name)
    {
        foreach ($available as $key => $merge_fields) {
            if (array_key_exists($name, $merge_fields)) {
                return (string) $key;
            }
        }

        return false;
    }

    private function check_availability($fields, $type, $exclude_keys)
    {
        $retVal = [];
        foreach ($fields as $available) {
            foreach ($available['available'] as $av) {
                // Check also if the name is not empty in case conditional merge field e.q. like GDPR
                if ($av == $type && !empty($available['name']) && !in_array($available['key'], $exclude_keys)) {
                    array_push($retVal, $available);
                }
            }
        }

        return count($retVal) > 0 ? $retVal : false;
    }

    private function apply_custom_fields($registered, $format)
    {
        $i = 0;
        foreach ($registered as $fields) {
            $f = 0;
            // Fix for merge fields as custom fields not matching the names
            foreach ($fields as $key => $_fields) {
                switch ($key) {
                case 'client':
                    $_key = 'customers';

                    break;
                case 'proposals':
                    $_key = 'proposal';

                    break;
                case 'contract':
                    $_key = 'contracts';

                    break;
                case 'ticket':
                    $_key = 'tickets';

                    break;
                default:
                    $_key = $key;

                    break;
            }

                $custom_fields = get_custom_fields($_key, [], true);

                foreach ($custom_fields as $field) {
                    array_push($registered[$i][$key], [
                        'name'      => $field['name'],
                        'key'       => '{' . $field['slug'] . '}',
                        'available' => $registered[$i][$key][$f]['available'],
                        'format'    => $format,
                    ]);
                }
                $f++;
            }
            $i++;
        }

        return $registered;
    }
}