<?php

/**
 * @package         Advanced Custom Fields
 * @version         2.8.9 Pro
 * 
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            http://www.tassos.gr
 * @copyright       Copyright © 2023 Tassos Marinos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/

defined('_JEXEC') or die;

use NRFramework\Conditions\Conditions\Component\ContentBase;
use Joomla\CMS\Language\Text;
use Joomla\Registry\Registry;
use Joomla\CMS\Factory;
use NRFramework\Cache;

JLoader::register('ACF_Field', JPATH_PLUGINS . '/system/acf/helper/plugin.php');

if (!class_exists('ACF_Field'))
{
	Factory::getApplication()->enqueueMessage('Advanced Custom Fields System Plugin is missing', 'error');
	return;
}

class PlgFieldsACFArticles extends ACF_Field
{
	/**
	 * Update the label of the field in filters.
     * 
     * @param \Bluecoder\Component\Jfilters\Administrator\Model\Filter\Option\Collection $options
	 * 
     * @return \Bluecoder\Component\Jfilters\Administrator\Model\Filter\Option\Collection
     */
    public function onJFiltersOptionsAfterCreation(\Bluecoder\Component\Jfilters\Administrator\Model\Filter\Option\Collection $options) 
    {
		// Make sure it is a field of that type
        if ($options->getFilterItem()->getAttributes()->get('type') !== $this->_name)
		{
            return $options;
        }

        $contentAssignment = new ContentBase();

        foreach ($options as $option)
		{
			if (!$article = $contentAssignment->getItem($option->getLabel()))
			{
				continue;
			}

			$option->setLabel($article->title);
        }

        return $options;
	}

	
	public function onContentBeforeSave($context, $item, $isNew, $data = [])
	{
		if (!is_array($data))
		{
			return true;
		}
		
		if (!isset($data['com_fields']))
		{
			return true;
		}
		
		// Create correct context for category
		if ($context == 'com_categories.category')
		{
			$context = $item->get('extension') . '.categories';
		}

        // Load Fields Component Helper class
		JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');

		// Check the context
		$parts = FieldsHelper::extract($context, $item);

		if (!$parts)
		{
			return true;
		}

		// Compile the right context for the fields
		$context = $parts[0] . '.' . $parts[1];

		// Loading the fields
		$fields = FieldsHelper::getFields($context, $item);

		if (!$fields)
		{
			return true;
		}

		// Get the fields data
		$fieldsData = !empty($data['com_fields']) ? $data['com_fields'] : [];

		// Whether we should clean up the temp folder at the end of this process
		$should_clean = false;

		// Get the Fields Model
		if (!defined('nrJ4'))
		{
			$model = JModelLegacy::getInstance('Field', 'FieldsModel', ['ignore_request' => true]);
		}
		else
		{
			$model = Factory::getApplication()->bootComponent('com_fields')->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]);
		}

		// Cache subform fields
		$subform_fields = [];

		$error = false;

		// Loop over the fields
		foreach ($fields as $field)
		{
			$field_type = $field->type;

			/**
			 * Check whether a Gallery field is used within the Subform field.
			 */
			if ($field_type === 'subform')
			{
				// Ensure it has a value
				if (!$subform_value = json_decode($field->value, true))
				{
					continue;
				}
				
				foreach ($subform_value as $key => $value)
				{
					if (!is_array($value))
					{
						continue;
					}
					
					foreach ($value as $_key => $_value)
					{
						// Get Field ID
						$field_id = str_replace('field', '', $_key);
						
						// Get Field by ID
						$subform_field = isset($subform_fields[$field_id]) ? $subform_fields[$field_id] : $model->getItem($field_id);

						// Only proceed for this field type
						if ($subform_field->type !== $this->_name)
						{
							continue;
						}

						// Cache field
						if (!isset($subform_fields[$field_id]))
						{
							$subform_fields[$field_id] = $subform_field;
						}

						$check_value = isset($fieldsData[$field->name][$key][$_key]) ? $fieldsData[$field->name][$key][$_key] : false;

						if (!$check_value)
						{
							break;
						}
						
						$fieldParams = new Registry($subform_field->fieldparams);

						$check_value = is_array($check_value) ? $check_value : [$check_value];

						if ($min_articles = (int) $fieldParams->get('min_articles', 0))
						{
							if (count($check_value) < $min_articles)
							{
								Factory::getApplication()->enqueueMessage(sprintf(Text::_('ACF_ARTICLES_MIN_ITEMS_REQUIRED'), $subform_field->title, $min_articles), 'error');
								$error = true;
								break;
							}
						}
		
						if ($max_articles = (int) $fieldParams->get('max_articles', 0))
						{
							if (count($check_value) > $max_articles)
							{
								Factory::getApplication()->enqueueMessage(sprintf(Text::_('ACF_ARTICLES_MAX_ITEMS_REQUIRED'), $subform_field->title, $max_articles), 'error');
								$error = true;
								break;
							}
						}
					}
				}
			}
			else
			{
				// Only proceed for this field type
				if ($field_type !== $this->_name)
				{
					continue;
				}
	
				// Determine the value if it is available from the data
				$value = array_key_exists($field->name, $fieldsData) ? $fieldsData[$field->name] : null;
	
				if (!$value)
				{
					continue;
				}

				$value = is_array($value) ? $value : [$value];

				if ($min_articles = (int) $field->fieldparams->get('min_articles', 0))
				{
					if (count($value) < $min_articles)
					{
						Factory::getApplication()->enqueueMessage(sprintf(Text::_('ACF_ARTICLES_MIN_ITEMS_REQUIRED'), $field->title, $min_articles), 'error');
						$error = true;
						break;
					}
				}

				if ($max_articles = (int) $field->fieldparams->get('max_articles', 0))
				{
					if (count($value) > $max_articles)
					{
						Factory::getApplication()->enqueueMessage(sprintf(Text::_('ACF_ARTICLES_MAX_ITEMS_REQUIRED'), $field->title, $max_articles), 'error');
						$error = true;
						break;
					}
				}
			}
		}

		return !$error;
	}
	
	
	/**
	 * Transforms the field into a DOM XML element and appends it as a child on the given parent.
	 *
	 * @param   stdClass    $field   The field.
	 * @param   DOMElement  $parent  The field node parent.
	 * @param   Form        $form    The form.
	 *
	 * @return  DOMElement
	 *
	 * @since   3.7.0
	 */
	public function onCustomFieldsPrepareDom($field, DOMElement $parent, Joomla\CMS\Form\Form $form)
	{
		if (!$fieldNode = parent::onCustomFieldsPrepareDom($field, $parent, $form))
		{
			return $fieldNode;
		}

		$field_type = $field->fieldparams->get('articles_type', 'default');

		$fieldNode->setAttribute('multiple', true);

		
		$max_articles = (int) $field->fieldparams->get('max_articles', 0);
		if ($max_articles === 1)
		{
			$fieldNode->setAttribute('multiple', false);
		}
		
		// Linked
		if ($field_type === 'linked')
		{
			$fieldNode->setAttribute('multiple', false);
			$fieldNode->setAttribute('type', 'NRToggle');
			// Default state
			$fieldNode->setAttribute('default', 'false');

			// Checked if Default Value = 1/true
			if ((isset($field->value) && in_array($field->value, ['1', 'true'])) || (!isset($field->value) && isset($field->default_value) && in_array($field->default_value, ['1', 'true'])))
			{
				$fieldNode->setAttribute('checked', 'true');
			}
		}
		

		return $fieldNode;
	}

	/**
	 * Prepares the field value for the (front-end) layout
	 *
	 * @param   string    $context  The context.
	 * @param   stdclass  $item     The item.
	 * @param   stdclass  $field    The field.
	 *
	 * @return  string
	 */
	public function onCustomFieldsPrepareField($context, $item, $field)
	{
		// Check if the field should be processed by us
		if (!$this->isTypeSupported($field->type))
		{
			return parent::onCustomFieldsPrepareField($context, $item, $field);
		}

		$value = array_filter((array) $field->value);

		
		$type = $field->fieldparams->get('articles_type', 'default');
		
		// Linked articles
		if ($type === 'linked')
		{
			// Abort if no ACF Articles fields are selected
			if (!$acf_articles = array_filter($field->fieldparams->get('articles_fields', [])))
			{
				$field->value = [];
				return parent::onCustomFieldsPrepareField($context, $item, $field);
			}

			// Proceed only if Field Value = 1/true
			if (!in_array($field->value, ['1', 'true']))
			{
				$field->value = [];
				return parent::onCustomFieldsPrepareField($context, $item, $field);
			}

			$cache_key = 'ACFArticles_' . $item->id . '_' . md5(implode(',', $acf_articles));
			
			if (Cache::has($cache_key))
			{
				$field->value = Cache::get($cache_key);
			}
			else
			{
				// Grab all linked articles
				$db = Factory::getDbo();
				$query = $db->getQuery(true);
				$query->select('a.*')
					->from($db->quoteName('#__fields_values', 'fv'))
					->join('LEFT', $db->quoteName('#__content', 'a') . ' ON a.id = fv.item_id')
					->where($db->quoteName('fv.field_id') . ' IN (' . implode(',', $acf_articles) . ')')
					->where($db->quoteName('fv.value') . ' = ' . $db->q($item->id));
	
				$this->prepareArticles($context, $query, $field, $value, true);

				Cache::set($cache_key, $field->value);
			}
			
			return parent::onCustomFieldsPrepareField($context, $item, $field);
		}
		

		if (!$value)
		{
			return parent::onCustomFieldsPrepareField($context, $item, $field);
		}

		$cache_key = 'ACFArticles_Auto_' . $item->id . '_' . md5(implode(',', $value));
			
		if (Cache::has($cache_key))
		{
			$field->value = Cache::get($cache_key);
		}
		else
		{
			// Default articles
			$db = Factory::getDbo();
			$query = $db->getQuery(true);
			$query->select('a.*')
				->from($db->quoteName('#__content', 'a'))
				->where($db->quoteName('a.id') . ' IN (' . implode(',', array_map('intval', $value)) . ')');
	
			$this->prepareArticles($context, $query, $field, $value);

			Cache::set($cache_key, $field->value);
		}

		return parent::onCustomFieldsPrepareField($context, $item, $field);
	}

	/**
	 * Prepares the articles.
	 * 
	 * @param   string    $context
	 * @param   object    $query
	 * @param   object    $field
	 * @param   array     $articles
	 * @param   bool      $all_filters
	 * 
	 * @return  void
	 */
	private function prepareArticles($context, $query, &$field, $articles, $all_filters = true)
	{
		$db = Factory::getDbo();

		// Filter results
        require_once 'fields/acfarticlesfilters.php';
		$payload = $all_filters ? $field->fieldparams : ['order' => $field->fieldparams->get('order')];
        $filters = new ACFArticlesFilters($query, $payload);
		$query = $filters->apply();

		// Set query
		$db->setQuery($query);

		// Get articles
		if (!$articles = $db->loadAssocList())
		{
			$field->value = [];
			return;
		}

		
		// Get and set the articles values
		foreach ($articles as &$article)
		{
			$article['custom_fields'] = $this->fetchCustomFields($article);
		}
		

		$field->value = $articles;
	}

	
    /**
     * Returns an article's custom fields.
     *
     * @return  array
     */
    private function fetchCustomFields($article = null)
    {
        if (!$article)
        {
            return [];
        }

        $callback = function() use ($article)
        {
			\JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');

            if (!$fields = \FieldsHelper::getFields('com_content.article', $article, false))
            {
                return [];
            }

            $fieldsAssoc = [];

            foreach ($fields as $field)
            {
                $fieldsAssoc[$field->name] = $field->value;
            }

            return $fieldsAssoc;
        };

        return Cache::memo('ACFArticlesFetchCustomFields' . $article['id'], $callback);
    }
	
}