first commit

This commit is contained in:
Roman Pyrih
2023-07-24 08:30:51 +02:00
commit c2e100a763
7128 changed files with 1622619 additions and 0 deletions

View File

@@ -0,0 +1,489 @@
<?php
/* * * * * * * * * * * * * * * * * * * * *
*
* ██████╗ ███╗ ███╗ ██████╗ ███████╗
* ██╔═══██╗████╗ ████║██╔════╝ ██╔════╝
* ██║ ██║██╔████╔██║██║ ███╗█████╗
* ██║ ██║██║╚██╔╝██║██║ ██║██╔══╝
* ╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██║
* ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝
*
* @package : OMGF
* @author : Daan van den Bergh
* @copyright: (c) 2021 Daan van den Bergh
* @url : https://daan.dev
* * * * * * * * * * * * * * * * * * * */
defined('ABSPATH') || exit;
class OMGF_API_Download extends WP_REST_Controller
{
const OMGF_GOOGLE_FONTS_API_URL = 'https://google-webfonts-helper.herokuapp.com/api/fonts/';
const OMGF_GOOGLE_FONTS_API_FALLBACK = 'https://omgf-google-fonts-api.herokuapp.com/api/fonts/';
const OMGF_USE_FALLBACK_API_TRANSIENT = 'omgf_use_fallback_api';
/**
* If a font changed names recently, this array will map the old name (key) to the new name (value).
*
* The key of an element should be dashed (no spaces) if necessary, e.g. open-sans.
*/
const OMGF_RENAMED_GOOGLE_FONTS = [
'ek-mukta' => 'mukta',
'muli' => 'mulish'
];
/** @var string */
private $plugin_text_domain = 'host-webfonts-local';
/** @var array */
private $endpoints = ['css', 'css2'];
/** @var string */
protected $namespace = 'omgf/v1';
/** @var string */
protected $rest_base = '/download/';
/** @var string */
private $handle = '';
/** @var string */
private $path = '';
/**
* @return void
*/
public function register_routes()
{
foreach ($this->endpoints as $endpoint) {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . $endpoint,
[
[
'methods' => 'GET',
'callback' => [$this, 'process'],
'permission_callback' => [$this, 'permissions_check']
],
'schema' => null,
]
);
}
}
/**
* Prevent CSRF.
*
* @since v4.5.4
*
* @return bool
*/
public function permissions_check()
{
if (!isset($_REQUEST['_wpnonce'])) {
return false;
}
/**
* This API should only be accessible to users with manage_options capabilities.
*
* @since v4.5.13
*/
return current_user_can('manage_options') && wp_verify_nonce($_REQUEST['_wpnonce'], 'wp_rest') > 0;
}
/**
* @param WP_REST_Request $request
*/
public function process($request)
{
$this->handle = sanitize_title_with_dashes($request->get_param('handle'));
$original_handle = sanitize_title_with_dashes($request->get_param('original_handle'));
if (!$this->handle || !$original_handle) {
wp_die(__('Handle not provided.', $this->plugin_text_domain), 406);
}
$this->path = WP_CONTENT_DIR . OMGF_CACHE_PATH . '/' . $this->handle;
$font_families = explode('|', $request->get_param('family'));
$query['subsets'] = $request->get_param('subset') ?? 'latin,latin-ext';
$fonts = [];
foreach ($font_families as $font_family) {
if (empty($font_family)) {
continue;
}
$fonts[] = $this->grab_font_family($font_family, $query);
}
// Filter out empty elements, i.e. failed requests.
$fonts = array_filter($fonts);
if (empty($fonts)) {
return;
}
foreach ($fonts as $font_key => &$font) {
$fonts_request = $this->build_fonts_request($font_families, $font);
if (strpos($fonts_request, ':') != false) {
list($family, $requested_variants) = explode(':', $fonts_request);
} else {
$family = $fonts_request;
$requested_variants = '';
}
$requested_variants = $this->parse_requested_variants($requested_variants, $font);
if ($unloaded_fonts = OMGF::unloaded_fonts()) {
$font_id = $font->id;
// Now we're sure we got 'em all. We can safely dequeue those we don't want.
if (isset($unloaded_fonts[$original_handle][$font_id])) {
$requested_variants = $this->dequeue_unloaded_variants($requested_variants, $unloaded_fonts[$original_handle], $font->id);
$fonts_request = $family . ':' . implode(',', $requested_variants);
}
}
$font->variants = $this->process_unload_queue($font->id, $font->variants, $fonts_request, $original_handle);
}
/**
* Which file types should we download and include in the stylesheet?
*
* @since v4.5
*/
$file_types = apply_filters('omgf_include_file_types', ['woff2', 'woff', 'eot', 'ttf', 'svg']);
foreach ($fonts as &$font) {
$font_id = $font->id;
/**
* Sanitize font family, because it may contain spaces.
*
* @since v4.5.6
*/
$font->family = rawurlencode($font->family);
foreach ($font->variants as &$variant) {
$filename = strtolower($font_id . '-' . $variant->fontStyle . '-' . $variant->fontWeight);
/**
* Encode font family, because it may contain spaces.
*
* @since v4.5.6
*/
$variant->fontFamily = rawurlencode($variant->fontFamily);
foreach ($file_types as $file_type) {
if (isset($variant->$file_type)) {
$variant->$file_type = OMGF::download($variant->$file_type, $filename, $file_type, $this->path);
}
}
}
}
$local_file = $this->path . '/' . $this->handle . '.css';
/**
* If this $stylesheet doesn't exist yet, let's generate it.
*
* If any modifications are done, e.g. unloads, the cache key ($handle) changes. Therefore it makes no sense to
* continue after this point if $local_file already exists.
*
* @since v4.5.9
*/
if (file_exists($local_file)) {
return;
}
$stylesheet = OMGF::generate_stylesheet($fonts, $original_handle);
file_put_contents($local_file, $stylesheet);
$current_stylesheet = [$original_handle => $fonts];
/**
* $current_stylesheet is added to temporary cache layer, if it isn't present in database.
*
* @since v4.5.7
*/
$optimized_fonts = OMGF::optimized_fonts($current_stylesheet);
/**
* When unload is used, this takes care of rewriting the font style URLs in the database.
*
* @since v4.5.7
*/
$optimized_fonts = $this->rewrite_variants($optimized_fonts, $current_stylesheet);
update_option(OMGF_Admin_Settings::OMGF_OPTIMIZE_SETTING_OPTIMIZED_FONTS, $optimized_fonts);
}
/**
* @param $variants
* @param $unloaded_fonts
* @param $font_id
*
* @return array
*/
private function dequeue_unloaded_variants($variants, $unloaded_fonts, $font_id)
{
return array_filter(
$variants,
function ($value) use ($unloaded_fonts, $font_id) {
if ($value == '400') {
// Sometimes the font is defined as 'regular', so we need to check both.
return !in_array('regular', $unloaded_fonts[$font_id]) && !in_array($value, $unloaded_fonts[$font_id]);
}
if ($value == '400italic') {
// Sometimes the font is defined as 'italic', so we need to check both.
return !in_array('italic', $unloaded_fonts[$font_id]) && !in_array($value, $unloaded_fonts[$font_id]);
}
return !in_array($value, $unloaded_fonts[$font_id]);
}
);
}
/**
* @param $font_family
* @param $url
* @param $query
*
* @return mixed|void
*/
private function grab_font_family($font_family, $query)
{
$url = $this->get_working_service_url();
list($family) = explode(':', $font_family);
/**
* Replace multiple spaces or plus signs (or a combination of, with a single dash)
*
* @since v4.5.10
*/
$family = strtolower(preg_replace("/[\s\+]+/", '-', $family));
/**
* Add fonts to the request's $_GET 'family' parameter. Then pass an array to 'omgf_alternate_fonts'
* filter. Then pass an alternate API url to the 'omgf_alternate_api_url' filter to fetch fonts from
* an alternate API.
*/
$alternate_fonts = apply_filters('omgf_alternate_fonts', []);
if (in_array($family, array_keys($alternate_fonts))) {
$url = apply_filters('omgf_alternate_api_url', $url, $family);
unset($query);
}
$query_string = '';
if (isset($query)) {
$query_string = '?' . http_build_query($query);
}
/**
* If a font changed names recently, map their old name to the new name, before triggering the API request.
*/
if (in_array($family, array_keys(self::OMGF_RENAMED_GOOGLE_FONTS))) {
$family = self::OMGF_RENAMED_GOOGLE_FONTS[$family];
}
$response = wp_remote_get(
sprintf($url . '%s', $family) . $query_string
);
if (is_wp_error($response)) {
OMGF_Admin_Notice::set_notice(sprintf(__('OMGF encountered an error while trying to fetch fonts: %s', $this->plugin_text_domain), $response->get_error_message()), $response->get_error_code(), true, 'error', 500);
}
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code != 200) {
$font_family = str_replace('-', ' ', $family);
$error_body = wp_remote_retrieve_body($response);
$error_message = wp_remote_retrieve_response_message($response);
$message = sprintf(__('OMGF couldn\'t find <strong>%s</strong>. The API returned the following error: %s.', $this->plugin_text_domain), ucwords($font_family), $error_message);
OMGF_Admin_Notice::set_notice($message, 'omgf_api_error', false, 'error');
if ($error_message == 'Service Unavailable') {
$message = __('OMGF\'s Google Fonts API is currently unavailable. Try again later.', $this->plugin_text_domain);
OMGF_Admin_Notice::set_notice($message, 'omgf_api_error', true, 'error', $response_code);
}
if ($error_body == 'Not found') {
$message = sprintf(__('Please verify that %s is available for free at Google Fonts by doing <a href="%s" target="_blank">a manual search</a>. Maybe it\'s a Premium font?', $this->plugin_text_domain), ucwords($font_family), 'https://fonts.google.com/?query=' . str_replace('-', '+', $family));
OMGF_Admin_Notice::set_notice($message, 'omgf_api_info_not_found', false, 'info');
}
if ($error_body == 'Internal Server Error') {
$message = sprintf(__('Try using the Force Subsets option (available in OMGF Pro) to force loading %s in a subset in which it\'s actually available. Use the Language filter <a href="%s" target="_blank">here</a> to verify which subsets are available for %s.', $this->plugin_text_domain), ucwords($font_family), 'https://fonts.google.com/?query=' . str_replace('-', '+', $family), ucwords($font_family));
OMGF_Admin_Notice::set_notice($message, 'omgf_api_info_internal_server_error', false, 'info');
}
return [];
}
return json_decode(wp_remote_retrieve_body($response));
}
/**
* Because the regular Google Webfonts Helper API tends to go offline sometimes, this function allows us
* to use fallback services.
*
* @return string Will return regular API url if fallback API url fails, too. Error handling later on will display a
* proper message to the user.
*/
private function get_working_service_url()
{
/**
* If this transient returns true, then that means that the regular API has failed in the last 24 hours.
*/
if (get_transient(self::OMGF_USE_FALLBACK_API_TRANSIENT)) {
return self::OMGF_GOOGLE_FONTS_API_FALLBACK;
}
$url = self::OMGF_GOOGLE_FONTS_API_URL;
$response = wp_remote_get($url);
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) != 200) {
set_transient(self::OMGF_USE_FALLBACK_API_TRANSIENT, true, DAY_IN_SECONDS);
$response = wp_remote_get(self::OMGF_GOOGLE_FONTS_API_FALLBACK);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) == 200) {
$url = self::OMGF_GOOGLE_FONTS_API_FALLBACK;
}
}
return $url;
}
/**
* @param $font_families
* @param $font
*
* @return mixed
*/
private function build_fonts_request($font_families, $font)
{
$font_request = array_filter(
$font_families,
function ($value) use ($font) {
if (isset($font->early_access)) {
return strpos($value, strtolower(str_replace(' ', '', $font->family))) !== false;
}
return strpos($value, $font->family) !== false;
}
);
return reset($font_request);
}
/**
* @param $request
* @param $font
*
* @return array
*/
private function parse_requested_variants($request, $font)
{
$requested_variants = array_filter(explode(',', $request));
/**
* This means by default all fonts are requested, so we need to fill up the queue, before unloading the unwanted variants.
*/
if (count($requested_variants) == 0) {
foreach ($font->variants as $variant) {
$requested_variants[] = $variant->id;
}
}
return $requested_variants;
}
/**
*
* @param mixed $font_id
* @param mixed $available
* @param mixed $wanted
* @param mixed $stylesheet_handle
* @return mixed
*/
private function process_unload_queue($font_id, $available, $wanted, $stylesheet_handle)
{
if (strpos($wanted, ':') !== false) {
// We don't need the first variable.
list(, $variants) = explode(':', $wanted);
} else {
$variants = '';
}
/**
* Build array and filter out empty elements.
*/
$variants = array_filter(explode(',', $variants));
/**
* If $variants is empty and this is the first run, i.e. there are no unloaded fonts (yet)
* return all available variants.
*/
if (empty($variants) && !isset(OMGF::unloaded_fonts()[$stylesheet_handle][$font_id])) {
return $available;
}
return array_filter(
$available,
function ($font) use ($variants) {
$id = $font->id;
if ($id == 'regular' || $id == '400') {
return in_array('400', $variants) || in_array('regular', $variants);
}
if ($id == 'italic') {
return in_array('400italic', $variants) || in_array('italic', $variants);
}
return in_array($id, $variants);
}
);
}
/**
* When unload is used, insert the cache key in the font URLs for the variants still in use.
*
* @param array $all_stylesheets Contains all font styles, loaded and unloaded.
* @param array $current_stylesheet Contains just the loaded font styles of current stylesheet.
*
* @return mixed
*/
private function rewrite_variants($all_stylesheets, $current_stylesheet)
{
foreach ($all_stylesheets as $stylesheet => &$fonts) {
foreach ($fonts as $index => &$font) {
if (empty((array) $font->variants)) {
continue;
}
foreach ($font->variants as $variant_index => &$variant) {
$replace_variant = $current_stylesheet[$stylesheet][$index]->variants[$variant_index] ?? (object) [];
if (!empty((array) $replace_variant)) {
$variant = $replace_variant;
}
}
}
}
return $all_stylesheets;
}
}