<?php
/*
Plugin Name: Page Optimize
Plugin URI: https://wordpress.org/plugins/page-optimize/
Description: Optimizes JS and CSS for faster page load and render in the browser.
Author: Automattic
Version: 0.6.3
Author URI: http://automattic.com/
*/

// Default cache directory
if ( ! defined( 'PAGE_OPTIMIZE_CACHE_DIR' ) ) {
	define( 'PAGE_OPTIMIZE_CACHE_DIR', WP_CONTENT_DIR . '/cache/page_optimize' );
}

if ( ! defined( 'PAGE_OPTIMIZE_ABSPATH' ) ) {
	define( 'PAGE_OPTIMIZE_ABSPATH', ABSPATH );
}

if ( ! defined( 'PAGE_OPTIMIZE_CSS_MINIFY' ) ) {
	define( 'PAGE_OPTIMIZE_CSS_MINIFY', false );
}

define( 'PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB', 'page_optimize_cron_cache_cleanup' );

// TODO: Copy tests from nginx-http-concat and/or write them

// TODO: Make concat URL dir configurable
if ( isset( $_SERVER['REQUEST_URI'] ) && '/_static/' === substr( $_SERVER['REQUEST_URI'], 0, 9 ) ) {
	require_once __DIR__ . '/service.php';
	exit;
}

function page_optimize_cache_cleanup( $cache_folder = false, $file_age = DAY_IN_SECONDS ) {
	if ( ! is_dir( $cache_folder ) ) {
		return;
	}

	// If cache is disabled when the cleanup runs, purge it
	$using_cache = defined( 'PAGE_OPTIMIZE_CACHE_DIR' ) && ! empty( PAGE_OPTIMIZE_CACHE_DIR );
	if ( ! $using_cache ) {
		$file_age = 0;
	}
	// If the cache folder changed since queueing, purge it
	if ( $using_cache && $cache_folder !== PAGE_OPTIMIZE_CACHE_DIR ) {
		$file_age = 0;
	}

	// Grab all files in the cache directory
	$cache_files = glob( $cache_folder . '/page-optimize-cache-*' );

	// Cleanup all files older than $file_age
	foreach ( $cache_files as $cache_file ) {
		if ( ! is_file( $cache_file ) ) {
			continue;
		}

		if ( ( time() - $file_age ) > filemtime( $cache_file ) ) {
			unlink( $cache_file );
		}
	}
}
add_action( PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB, 'page_optimize_cache_cleanup' );

// Unschedule cache cleanup, and purge cache directory
function page_optimize_deactivate() {
	$cache_folder = false;
	if ( defined( 'PAGE_OPTIMIZE_CACHE_DIR' ) && ! empty( PAGE_OPTIMIZE_CACHE_DIR ) ) {
		$cache_folder = PAGE_OPTIMIZE_CACHE_DIR;
	}

	page_optimize_cache_cleanup( $cache_folder, 0 /* max file age in seconds */ );

	wp_clear_scheduled_hook( PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB, [ $cache_folder ] );
}
register_deactivation_hook( __FILE__, 'page_optimize_deactivate' );

function page_optimize_uninstall() {
	// Run cleanup on uninstall. You can uninstall an active plugin w/o deactivation.
	page_optimize_deactivate();

	// JS
	delete_option( 'page_optimize-js' );
	delete_option( 'page_optimize-load-mode' );
	delete_option( 'page_optimize-js-exclude' );
	// CSS
	delete_option( 'page_optimize-css' );
	delete_option( 'page_optimize-css-exclude' );

}
register_uninstall_hook( __FILE__, 'page_optimize_uninstall' );

function page_optimize_get_text_domain() {
	return 'page-optimize';
}

function page_optimize_should_concat_js() {
	// Support query param for easy testing
	if ( isset( $_GET['concat-js'] ) ) {
		return $_GET['concat-js'] !== '0';
	}

	return !! get_option( 'page_optimize-js', page_optimize_js_default() );
}

// TODO: Support JS load mode regardless of whether concat is enabled
function page_optimize_load_mode_js() {
	// Support query param for easy testing
	if ( ! empty( $_GET['load-mode-js'] ) ) {
		$load_mode = page_optimize_sanitize_js_load_mode( $_GET['load-mode-js'] );
	} else {
		$load_mode = page_optimize_sanitize_js_load_mode( get_option( 'page_optimize-load-mode', page_optimize_js_load_mode_default() ) );
	}

	return $load_mode;
}

function page_optimize_should_concat_css() {
	// Support query param for easy testing
	if ( isset( $_GET['concat-css'] ) ) {
		return $_GET['concat-css'] !== '0';
	}

	return !! get_option( 'page_optimize-css', page_optimize_css_default() );
}

function page_optimize_js_default() {
	return true;
}

function page_optimize_css_default() {
	return true;
}

function page_optimize_js_load_mode_default() {
	return '';
}

function page_optimize_js_exclude_list() {
	$exclude_list = get_option( 'page_optimize-js-exclude' );
	if ( false === $exclude_list ) {
		// Use the default since the option is not set
		return page_optimize_js_exclude_list_default();
	}
	if ( '' === $exclude_list ) {
		return [];
	}

	return explode( ',', $exclude_list );
}

function page_optimize_js_exclude_list_default() {
	// WordPress core stuff, a lot of other plugins depend on it.
	return [ 'jquery', 'jquery-core', 'underscore', 'backbone' ];
}

function page_optimize_css_exclude_list() {
	$exclude_list = get_option( 'page_optimize-css-exclude' );
	if ( false === $exclude_list ) {
		// Use the default since the option is not set
		return page_optimize_css_exclude_list_default();
	}
	if ( '' === $exclude_list ) {
		return [];
	}

	return explode( ',', $exclude_list );
}

function page_optimize_css_exclude_list_default() {
	// WordPress Core and known conflicting plugins
	return [ 'admin-bar', 'dashicons', 'elementor-app' ];
}

function page_optimize_sanitize_js_load_mode( $value ) {
	switch ( $value ) {
		case 'async':
		case 'defer':
			break;
		default:
			$value = '';
			break;
	}

	return $value;
}

function page_optimize_sanitize_exclude_field( $value ) {
	if ( empty( $value ) ) {
		return '';
	}

	$excluded_strings = explode( ',', sanitize_text_field( $value ) );
	$sanitized_values = [];
	foreach ( $excluded_strings as $excluded_string ) {
		if ( ! empty( $excluded_string ) ) {
			$sanitized_values[] = trim( $excluded_string );
		}
	}

	return implode( ',', $sanitized_values );
}

/**
 * Determines whether a string starts with another string.
 */
function page_optimize_starts_with( $prefix, $str ) {
	$prefix_length = strlen( $prefix );
	if ( strlen( $str ) < $prefix_length ) {
		return false;
	}

	return substr( $str, 0, $prefix_length ) === $prefix;
}

/**
 * Answers whether the plugin should provide concat resource URIs
 * that are relative to a common ancestor directory. Assuming a common ancestor
 * allows us to skip resolving resource URIs to filesystem paths later on.
 */
function page_optimize_use_concat_base_dir() {
	return defined( 'PAGE_OPTIMIZE_CONCAT_BASE_DIR' ) && file_exists( PAGE_OPTIMIZE_CONCAT_BASE_DIR );
}

/**
 * Get a filesystem path relative to a configured base path for resources
 * that will be concatenated. Assuming a common ancestor allows us to skip
 * resolving resource URIs to filesystem paths later on.
 */
function page_optimize_remove_concat_base_prefix( $original_fs_path ) {
	// Always check longer path first
	if ( strlen( PAGE_OPTIMIZE_ABSPATH ) > strlen( PAGE_OPTIMIZE_CONCAT_BASE_DIR ) ) {
		$longer_path = PAGE_OPTIMIZE_ABSPATH;
		$shorter_path = PAGE_OPTIMIZE_CONCAT_BASE_DIR;
	} else {
		$longer_path = PAGE_OPTIMIZE_CONCAT_BASE_DIR;
		$shorter_path = PAGE_OPTIMIZE_ABSPATH;
	}

	$prefix_abspath = trailingslashit( $longer_path );
	if ( page_optimize_starts_with( $prefix_abspath, $original_fs_path ) ) {
		return substr( $original_fs_path, strlen( $prefix_abspath ) );
	}

	$prefix_basedir = trailingslashit( $shorter_path );
	if ( page_optimize_starts_with( $prefix_basedir, $original_fs_path ) ) {
		return substr( $original_fs_path, strlen( $prefix_basedir ) );
	}

	// If we end up here, this is a resource we shouldn't have tried to concat in the first place
	return '/page-optimize-resource-outside-base-path/' . basename( $original_fs_path );
}

function page_optimize_schedule_cache_cleanup() {
	$cache_folder = false;
	if ( defined( 'PAGE_OPTIMIZE_CACHE_DIR' ) && ! empty( PAGE_OPTIMIZE_CACHE_DIR ) ) {
		$cache_folder = PAGE_OPTIMIZE_CACHE_DIR;
	}
	$args = [ $cache_folder ];

	// If caching is on, and job isn't queued for current cache folder
	if( false !== $cache_folder && false === wp_next_scheduled( PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB, $args ) ) {
		wp_schedule_event( time(), 'daily', PAGE_OPTIMIZE_CRON_CACHE_CLEANUP_JOB, $args );
	}
}

// Cases when we don't want to concat
function page_optimize_bail() {
	// Bail if we're in customizer
	global $wp_customize;
	if ( isset( $wp_customize ) ) {
		return true;
	}

	// Bail if we're in any of the excluded pages.
	global $pagenow;
	$excluded_pages = array(
		'post.php',
		'post-new.php',
		'site-editor.php',
	);
	if ( isset( $pagenow ) && in_array( $pagenow, $excluded_pages ) ) {
		return true;
	}

	// Bail if Divi theme is active, and we're in the Divi Front End Builder
	if ( ! empty( $_GET['et_fb'] ) && 'Divi' === wp_get_theme()->get_template() ) {
		return true;
	}

	// Bail if we're editing pages in Brizy Editor
	if ( class_exists( 'Brizy_Editor' ) && method_exists( 'Brizy_Editor', 'prefix' ) && ( isset( $_GET[ Brizy_Editor::prefix( '-edit-iframe' ) ] ) || isset( $_GET[ Brizy_Editor::prefix( '-edit' ) ] ) ) ) {
		return true;
	}

	return false;
}

function page_optimize_init() {
	if ( page_optimize_bail() ) {
		return;
	}

	page_optimize_schedule_cache_cleanup();

	require_once __DIR__ . '/settings.php';
	require_once __DIR__ . '/concat-css.php';
	require_once __DIR__ . '/concat-js.php';

	// Disable Jetpack photon-cdn for static JS/CSS
	add_filter( 'jetpack_force_disable_site_accelerator', '__return_true' );
}
add_action( 'plugins_loaded', 'page_optimize_init' );
