<?php

/**
 * The admin settings handler of the plugin.
 *
 *
 * @since      1.1.0
 * @package    LiteSpeed
 * @subpackage LiteSpeed/src
 * @author     LiteSpeed Technologies <info@litespeedtech.com>
 */

namespace LiteSpeed;

defined('WPINC') || exit();

class Admin_Settings extends Base
{
	const ENROLL = '_settings-enroll';

	/**
	 * Save settings
	 *
	 * Both $_POST and CLI can use this way
	 *
	 * Import will directly call conf.cls
	 *
	 * @since  3.0
	 * @access public
	 */
	public function save($raw_data)
	{
		Debug2::debug('[Settings] saving');

		if (empty($raw_data[self::ENROLL])) {
			exit('No fields');
		}

		$raw_data = Admin::cleanup_text($raw_data);

		// Convert data to config format
		$the_matrix = array();
		foreach (array_unique($raw_data[self::ENROLL]) as $id) {
			$child = false;
			// Drop array format
			if (strpos($id, '[') !== false) {
				if (strpos($id, self::O_CDN_MAPPING) === 0 || strpos($id, self::O_CRAWLER_COOKIES) === 0) {
					// CDN child | Cookie Crawler settings
					$child = substr($id, strpos($id, '[') + 1, strpos($id, ']') - strpos($id, '[') - 1);
					$id = substr($id, 0, strpos($id, '[')); // Drop ending []; Compatible with xx[0] way from CLI
				} else {
					$id = substr($id, 0, strpos($id, '[')); // Drop ending []
				}
			}

			if (!array_key_exists($id, self::$_default_options)) {
				continue;
			}

			// Validate $child
			if ($id == self::O_CDN_MAPPING) {
				if (!in_array($child, array(self::CDN_MAPPING_URL, self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS, self::CDN_MAPPING_FILETYPE))) {
					continue;
				}
			}
			if ($id == self::O_CRAWLER_COOKIES) {
				if (!in_array($child, array(self::CRWL_COOKIE_NAME, self::CRWL_COOKIE_VALS))) {
					continue;
				}
			}

			$data = false;

			if ($child) {
				$data = !empty($raw_data[$id][$child]) ? $raw_data[$id][$child] : false; // []=xxx or [0]=xxx
			} else {
				$data = !empty($raw_data[$id]) ? $raw_data[$id] : false;
			}

			/**
			 * Sanitize the value
			 */
			if ($id == self::O_CDN_MAPPING || $id == self::O_CRAWLER_COOKIES) {
				// Use existing in queue data if existed (Only available when $child != false)
				$data2 = array_key_exists($id, $the_matrix) ? $the_matrix[$id] : (defined('WP_CLI') && WP_CLI ? $this->conf($id) : array());
			}
			switch ($id) {
				case self::O_CRAWLER_ROLES: // Don't allow Editor/admin to be used in crawler role simulator
					$data = Utility::sanitize_lines($data);
					if ($data) {
						foreach ($data as $k => $v) {
							if (user_can($v, 'edit_posts')) {
								$msg = sprintf(
									__('The user with id %s has editor access, which is not allowed for the role simulator.', 'litespeed-cache'),
									'<code>' . $v . '</code>'
								);
								Admin_Display::error($msg);
								unset($data[$k]);
							}
						}
					}
					break;
				case self::O_CDN_MAPPING:
					/**
					 * CDN setting
					 *
					 * Raw data format:
					 * 		cdn-mapping[url][] = 'xxx'
					 * 		cdn-mapping[url][2] = 'xxx2'
					 * 		cdn-mapping[inc_js][] = 1
					 *
					 * Final format:
					 * 		cdn-mapping[ 0 ][ url ] = 'xxx'
					 * 		cdn-mapping[ 2 ][ url ] = 'xxx2'
					 */
					if ($data) {
						foreach ($data as $k => $v) {
							if ($child == self::CDN_MAPPING_FILETYPE) {
								$v = Utility::sanitize_lines($v);
							}
							if ($child == self::CDN_MAPPING_URL) {
								# If not a valid URL, turn off CDN
								if (strpos($v, 'https://') !== 0) {
									self::debug('❌ CDN mapping set to OFF due to invalid URL');
									$the_matrix[self::O_CDN] = false;
								}
								$v = trailingslashit($v);
							}
							if (in_array($child, array(self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS))) {
								// Because these can't be auto detected in `config->update()`, need to format here
								$v = $v === 'false' ? 0 : (bool) $v;
							}

							if (empty($data2[$k])) {
								$data2[$k] = array();
							}

							$data2[$k][$child] = $v;
						}
					}

					$data = $data2;
					break;

				case self::O_CRAWLER_COOKIES:
					/**
					 * Cookie Crawler setting
					 * Raw Format:
					 * 		crawler-cookies[name][] = xxx
					 * 		crawler-cookies[name][2] = xxx2
					 * 		crawler-cookies[vals][] = xxx
					 *
					 * todo: need to allow null for values
					 *
					 * Final format:
					 * 		crawler-cookie[ 0 ][ name ] = 'xxx'
					 * 		crawler-cookie[ 0 ][ vals ] = 'xxx'
					 * 		crawler-cookie[ 2 ][ name ] = 'xxx2'
					 *
					 * empty line for `vals` use literal `_null`
					 */
					if ($data) {
						foreach ($data as $k => $v) {
							if ($child == self::CRWL_COOKIE_VALS) {
								$v = Utility::sanitize_lines($v);
							}

							if (empty($data2[$k])) {
								$data2[$k] = array();
							}

							$data2[$k][$child] = $v;
						}
					}

					$data = $data2;
					break;

				case self::O_CACHE_EXC_CAT: // Cache exclude cat
					$data2 = array();
					$data = Utility::sanitize_lines($data);
					foreach ($data as $v) {
						$cat_id = get_cat_ID($v);
						if (!$cat_id) {
							continue;
						}

						$data2[] = $cat_id;
					}
					$data = $data2;
					break;

				case self::O_CACHE_EXC_TAG: // Cache exclude tag
					$data2 = array();
					$data = Utility::sanitize_lines($data);
					foreach ($data as $v) {
						$term = get_term_by('name', $v, 'post_tag');
						if (!$term) {
							// todo: can show the error in admin error msg
							continue;
						}

						$data2[] = $term->term_id;
					}
					$data = $data2;
					break;

				default:
					break;
			}

			$the_matrix[$id] = $data;
		}

		// Special handler for CDN/Crawler 2d list to drop empty rows
		foreach ($the_matrix as $id => $data) {
			/**
			 * 		cdn-mapping[ 0 ][ url ] = 'xxx'
			 * 		cdn-mapping[ 2 ][ url ] = 'xxx2'
			 *
			 * 		crawler-cookie[ 0 ][ name ] = 'xxx'
			 * 		crawler-cookie[ 0 ][ vals ] = 'xxx'
			 * 		crawler-cookie[ 2 ][ name ] = 'xxx2'
			 */
			if ($id == self::O_CDN_MAPPING || $id == self::O_CRAWLER_COOKIES) {
				// Drop this line if all children elements are empty
				foreach ($data as $k => $v) {
					foreach ($v as $v2) {
						if ($v2) {
							continue 2;
						}
					}
					// If hit here, means all empty
					unset($the_matrix[$id][$k]);
				}
			}

			// Don't allow repeated cookie name
			if ($id == self::O_CRAWLER_COOKIES) {
				$existed = array();
				foreach ($the_matrix[$id] as $k => $v) {
					if (!$v[self::CRWL_COOKIE_NAME] || in_array($v[self::CRWL_COOKIE_NAME], $existed)) {
						// Filter repeated or empty name
						unset($the_matrix[$id][$k]);
						continue;
					}

					$existed[] = $v[self::CRWL_COOKIE_NAME];
				}
			}

			// CDN mapping allow URL values repeated
			// if ( $id == self::O_CDN_MAPPING ) {}

			// tmp fix the 3rd part woo update hook issue when enabling vary cookie
			if ($id == 'wc_cart_vary') {
				if ($data) {
					add_filter('litespeed_vary_cookies', function ($list) {
						$list[] = 'woocommerce_cart_hash';
						return array_unique($list);
					});
				} else {
					add_filter('litespeed_vary_cookies', function ($list) {
						if (in_array('woocommerce_cart_hash', $list)) {
							unset($list[array_search('woocommerce_cart_hash', $list)]);
						}
						return array_unique($list);
					});
				}
			}
		}

		// id validation will be inside
		$this->cls('Conf')->update_confs($the_matrix);

		$msg = __('Options saved.', 'litespeed-cache');
		Admin_Display::success($msg);
	}

	/**
	 * Parses any changes made by the network admin on the network settings.
	 *
	 * @since 3.0
	 * @access public
	 */
	public function network_save($raw_data)
	{
		Debug2::debug('[Settings] network saving');

		if (empty($raw_data[self::ENROLL])) {
			exit('No fields');
		}

		$raw_data = Admin::cleanup_text($raw_data);

		foreach (array_unique($raw_data[self::ENROLL]) as $id) {
			// Append current field to setting save
			if (!array_key_exists($id, self::$_default_site_options)) {
				continue;
			}

			$data = !empty($raw_data[$id]) ? $raw_data[$id] : false;

			// id validation will be inside
			$this->cls('Conf')->network_update($id, $data);
		}

		// Update related files
		Activation::cls()->update_files();

		$msg = __('Options saved.', 'litespeed-cache');
		Admin_Display::success($msg);
	}

	/**
	 * Hooked to the wp_redirect filter.
	 * This will only hook if there was a problem when saving the widget.
	 *
	 * @since 1.1.3
	 * @access public
	 * @param string $location The location string.
	 * @return string the updated location string.
	 */
	public static function widget_save_err($location)
	{
		return str_replace('?message=0', '?error=0', $location);
	}

	/**
	 * Hooked to the widget_update_callback filter.
	 * Validate the LiteSpeed Cache settings on edit widget save.
	 *
	 * @since 1.1.3
	 * @access public
	 * @param array $instance The new settings.
	 * @param array $new_instance
	 * @param array $old_instance The original settings.
	 * @param WP_Widget $widget The widget
	 * @return mixed Updated settings on success, false on error.
	 */
	public static function validate_widget_save($instance, $new_instance, $old_instance, $widget)
	{
		if (empty($new_instance)) {
			return $instance;
		}

		if (!isset($new_instance[ESI::WIDGET_O_ESIENABLE]) || !isset($new_instance[ESI::WIDGET_O_TTL])) {
			return $instance;
		}

		$esi = intval($new_instance[ESI::WIDGET_O_ESIENABLE]) % 3;
		$ttl = (int) $new_instance[ESI::WIDGET_O_TTL];

		if ($ttl != 0 && $ttl < 30) {
			add_filter('wp_redirect', __CLASS__ . '::widget_save_err');
			return false; // invalid ttl.
		}

		if (empty($instance[Conf::OPTION_NAME])) {
			// todo: to be removed
			$instance[Conf::OPTION_NAME] = array();
		}
		$instance[Conf::OPTION_NAME][ESI::WIDGET_O_ESIENABLE] = $esi;
		$instance[Conf::OPTION_NAME][ESI::WIDGET_O_TTL] = $ttl;

		$current = !empty($old_instance[Conf::OPTION_NAME]) ? $old_instance[Conf::OPTION_NAME] : false;
		if (!strpos($_SERVER['HTTP_REFERER'], '/wp-admin/customize.php')) {
			if (!$current || $esi != $current[ESI::WIDGET_O_ESIENABLE]) {
				Purge::purge_all('Widget ESI_enable changed');
			} elseif ($ttl != 0 && $ttl != $current[ESI::WIDGET_O_TTL]) {
				Purge::add(Tag::TYPE_WIDGET . $widget->id);
			}

			Purge::purge_all('Widget saved');
		}
		return $instance;
	}
}
