'el' ); private static $roots = null; private static $locale = null; public static function boot() { add_action( 'created_category', array( __CLASS__, 'forget' ) ); add_action( 'edited_category', array( __CLASS__, 'forget' ) ); add_action( 'delete_category', array( __CLASS__, 'forget' ) ); add_filter( 'rooster/docs/chapters', array( __CLASS__, 'order_chapters' ), 10, 2 ); add_filter( 'rooster/docs/download', array( __CLASS__, 'download_url' ), 10, 2 ); if ( ! self::roots() ) { return; } add_filter( 'locale', array( __CLASS__, 'manual_locale' ) ); add_action( 'parse_request', array( __CLASS__, 'resolve_full_path' ) ); add_action( 'pre_get_posts', array( __CLASS__, 'keep_manuals_out_of_the_blog' ) ); add_action( 'init', array( __CLASS__, 'rewrite_rules' ) ); add_filter( 'do_redirect_guess_404_permalink', array( __CLASS__, 'no_guess' ) ); if ( ! is_admin() ) { add_action( 'template_redirect', array( __CLASS__, 'redirect_short_archive' ) ); add_action( 'template_redirect', array( __CLASS__, 'redirect_moved_page' ) ); add_action( 'template_redirect', array( __CLASS__, 'redirect_moved_404' ) ); } add_filter( 'wp_handle_upload_prefilter', array( __CLASS__, 'remember_upload' ), 1 ); add_filter( 'wp_handle_sideload_prefilter', array( __CLASS__, 'remember_upload' ), 1 ); add_filter( 'intermediate_image_sizes_advanced', array( __CLASS__, 'no_sizes' ), 10, 2 ); add_filter( 'big_image_size_threshold', array( __CLASS__, 'no_scaled' ) ); require_once __DIR__ . '/class-aieo-docs-viewer.php'; AIEO_Docs_Viewer::boot(); } public static function keep_manuals_out_of_the_blog( $query ) { if ( ! $query instanceof WP_Query || ! $query->is_main_query() || is_admin() || ! $query->is_home() ) { return; } $roots = self::roots(); if ( empty( $roots ) ) { return; } $tax = (array) $query->get( 'tax_query' ); $tax[] = array( 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => $roots, 'operator' => 'NOT IN', 'include_children' => true, ); $query->set( 'tax_query', $tax ); } public static function roots() { if ( null !== self::$roots ) { return self::$roots; } $stored = get_option( self::OPTION, null ); if ( is_array( $stored ) ) { self::$roots = array_map( 'intval', $stored ); return self::$roots; } $roots = array(); if ( taxonomy_exists( 'category' ) ) { $terms = get_terms( array( 'taxonomy' => 'category', 'hide_empty' => false, 'fields' => 'all', 'number' => 0 ) ); foreach ( is_array( $terms ) ? $terms : array() as $term ) { if ( ! $term instanceof WP_Term || ! $term->parent || ! preg_match( '/^documentation(-|$)/', $term->slug ) ) { continue; } $parent = get_term( (int) $term->parent, 'category' ); if ( $parent instanceof WP_Term && 0 === (int) $parent->parent ) { $roots[] = (int) $term->term_id; } } } self::$roots = array_values( array_unique( array_map( 'intval', (array) apply_filters( 'rooster/docs/roots', $roots ) ) ) ); update_option( self::OPTION, self::$roots, true ); return self::$roots; } public static function forget() { self::$roots = null; self::$locale = null; delete_option( self::OPTION ); delete_option( self::LOCALES_OPTION ); } public static function locales() { $stored = get_option( self::LOCALES_OPTION, null ); if ( is_array( $stored ) ) { return $stored; } $map = (array) apply_filters( 'rooster/docs/locales', self::LOCALES ); $out = array(); foreach ( self::roots() as $root_id ) { $root = get_term( (int) $root_id, 'category' ); $lang = self::language_of_root( $root_id ); if ( ! $root instanceof WP_Term || ! $lang instanceof WP_Term ) { continue; } $code = isset( $map[ $lang->slug ] ) ? $lang->slug : ''; foreach ( '' === $code ? array_keys( $map ) : array() as $suffix ) { if ( str_ends_with( $root->slug, '-' . $suffix ) ) { $code = (string) $suffix; break; } } if ( '' !== $code ) { $out[ $lang->slug . '/' . $root->slug ] = (string) $map[ $code ]; } } update_option( self::LOCALES_OPTION, $out, true ); return $out; } public static function manual_locale( $locale ) { if ( null === self::$locale ) { self::$locale = ''; $path = self::request_path(); if ( '' !== $path && ! is_admin() && ! wp_doing_ajax() && ! wp_doing_cron() && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) { $path = 0 === strpos( $path, 'category/' ) ? substr( $path, 9 ) : $path; foreach ( self::locales() as $prefix => $manual_locale ) { if ( $path === $prefix || 0 === strpos( $path, $prefix . '/' ) ) { self::$locale = (string) $manual_locale; break; } } } } return '' !== self::$locale ? self::$locale : $locale; } public static function language_of_root( $root_id ) { $root = get_term( (int) $root_id, 'category' ); return $root instanceof WP_Term && $root->parent ? get_term( (int) $root->parent, 'category' ) : null; } public static function manual_of( WP_Term $term ) { $roots = self::roots(); if ( ! $roots ) { return null; } $chain = array_merge( array( (int) $term->term_id ), get_ancestors( (int) $term->term_id, 'category', 'taxonomy' ) ); foreach ( $chain as $id ) { $node = get_term( $id, 'category' ); if ( ! $node instanceof WP_Term ) { return null; } if ( in_array( (int) $node->parent, $roots, true ) ) { return $node; } } return null; } public static function current_term() { $object = get_queried_object(); if ( $object instanceof WP_Term ) { return 'category' === $object->taxonomy ? $object : null; } if ( ! $object instanceof WP_Post ) { return null; } $deepest = null; $depth = -1; foreach ( (array) get_the_category( $object->ID ) as $term ) { $d = count( get_ancestors( (int) $term->term_id, 'category', 'taxonomy' ) ); if ( $d > $depth ) { $depth = $d; $deepest = $term; } } return $deepest; } public static function manual() { $term = self::current_term(); return $term ? self::manual_of( $term ) : null; } public static function in_manual( $post_id ) { foreach ( wp_get_post_categories( (int) $post_id ) as $cat_id ) { $term = get_term( (int) $cat_id, 'category' ); if ( $term instanceof WP_Term && self::manual_of( $term ) ) { return true; } } return false; } public static function is_greek( WP_Term $manual ) { $root = get_term( (int) $manual->parent, 'category' ); if ( ! $root instanceof WP_Term ) { return false; } $lang = self::language_of_root( (int) $root->term_id ); return str_ends_with( $root->slug, '-el' ) || ( $lang instanceof WP_Term && 'el' === $lang->slug ); } public static function chapters( WP_Term $parent ) { $terms = get_terms( array( 'taxonomy' => 'category', 'parent' => (int) $parent->term_id, 'hide_empty' => false, 'orderby' => 'term_id', 'order' => 'ASC' ) ); $kept = array(); foreach ( is_array( $terms ) ? $terms : array() as $term ) { if ( $term instanceof WP_Term && self::has_pages( $term ) ) { $kept[] = $term; } } return $kept; } private static function has_pages( WP_Term $term ) { if ( (int) $term->count > 0 ) { return true; } foreach ( (array) get_term_children( (int) $term->term_id, 'category' ) as $child_id ) { $child = get_term( (int) $child_id, 'category' ); if ( $child instanceof WP_Term && (int) $child->count > 0 ) { return true; } } return false; } public static function order_chapters( $terms, $parent = null ) { $terms = array_values( array_filter( (array) $terms, static function ( $t ) { return $t instanceof WP_Term; } ) ); usort( $terms, static function ( WP_Term $a, WP_Term $b ) { return (int) $a->term_id <=> (int) $b->term_id; } ); return $terms; } public static function pages( WP_Term $term ) { return get_posts( array( 'post_type' => 'post', 'post_status' => 'publish', 'category__in' => array( (int) $term->term_id ), 'numberposts' => 200, 'orderby' => array( 'menu_order' => 'ASC', 'title' => 'ASC' ), 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); } public static function card( WP_Term $manual ) { $el = self::is_greek( $manual ); $slug = (string) $manual->slug; $of = static function ( $product ) use ( $slug ) { return $slug === $product || 0 === strpos( $slug, $product . '-' ); }; $card = null; if ( $of( 'rooster-one' ) ) { $card = array( 'kind' => 'download', 'title' => 'Rooster One', 'text' => __( 'The e-shop Manager for WooCommerce', 'ai-eshop-optimizer' ), 'cta' => __( 'Download free', 'ai-eshop-optimizer' ), 'sub' => __( 'from WordPress.org', 'ai-eshop-optimizer' ), 'href' => $el ? 'https://el.wordpress.org/plugins/ai-eshop-optimizer/' : 'https://wordpress.org/plugins/ai-eshop-optimizer/', 'badge' => __( 'Free & open source', 'ai-eshop-optimizer' ), 'external' => true, ); } elseif ( $of( 'rooster-lms' ) ) { $card = array( 'kind' => 'download', 'title' => 'Rooster LMS', 'text' => __( 'The WordPress LMS for training many organisations', 'ai-eshop-optimizer' ), 'cta' => __( 'Download free', 'ai-eshop-optimizer' ), 'sub' => __( 'from WordPress.org', 'ai-eshop-optimizer' ), 'href' => $el ? 'https://el.wordpress.org/plugins/rooster-lms/' : 'https://wordpress.org/plugins/rooster-lms/', 'badge' => __( 'Free & open source', 'ai-eshop-optimizer' ), 'external' => true, ); } elseif ( $of( 'lansa' ) ) { $card = array( 'kind' => 'quote', 'title' => 'Evit for LANSA', 'text' => "Your whole LANSA application, analysed like a compiler and published on CM First's EvolveIT.", 'cta' => 'Request a quote', 'sub' => 'Census, assessment or continuous analysis', 'href' => home_url( '/lansa/#quote' ), 'badge' => 'Your repository, never your business data', 'external' => false, ); } $card = apply_filters( 'rooster/docs/card', $card, $manual ); return is_array( $card ) && '' !== (string) ( $card['href'] ?? '' ) ? $card : null; } public static function download_url( $url, $post_id = 0 ) { $manual = self::manual(); if ( ! $manual ) { return (string) $url; } $card = self::card( $manual ); return $card && 'download' === $card['kind'] ? (string) $card['href'] : (string) $url; } public static function resolve_full_path( $wp ) { if ( ! $wp instanceof WP || empty( $wp->query_vars['category_name'] ) ) { return; } $path = trim( (string) $wp->query_vars['category_name'], '/' ); if ( false === strpos( $path, '/' ) ) { return; } $term = get_category_by_path( $path, true ); if ( ! $term instanceof WP_Term ) { return; } unset( $wp->query_vars['category_name'] ); $wp->query_vars['cat'] = (int) $term->term_id; } public static function languages() { $out = array(); foreach ( self::roots() as $root ) { $lang = self::language_of_root( $root ); if ( $lang instanceof WP_Term ) { $out[] = $lang->slug; } } return array_values( array_unique( $out ) ); } public static function prefixes() { $out = array(); foreach ( self::roots() as $root_id ) { $root = get_term( (int) $root_id, 'category' ); $lang = self::language_of_root( $root_id ); if ( $root instanceof WP_Term && $lang instanceof WP_Term ) { $out[] = $lang->slug . '/' . $root->slug; } } return array_values( array_unique( $out ) ); } public static function rewrite_rules() { foreach ( self::prefixes() as $prefix ) { $regex = preg_quote( $prefix, '#' ); add_rewrite_rule( '^' . $regex . '/?$', 'index.php?category_name=' . $prefix, 'top' ); add_rewrite_rule( '^' . $regex . '/([^/]+)/?$', 'index.php?category_name=' . $prefix . '/$matches[1]', 'top' ); } } public static function redirect_short_archive() { if ( ! is_category() || is_feed() ) { return; } $term = get_queried_object(); if ( ! $term instanceof WP_Term || 0 === (int) $term->parent || ! self::manual_of( $term ) && ! in_array( (int) $term->term_id, self::roots(), true ) ) { return; } $path = self::request_path(); $canonical = (string) get_category_link( (int) $term->term_id ); $canon_path = trim( (string) wp_parse_url( $canonical, PHP_URL_PATH ), '/' ); if ( '' === $path || $path === $canon_path || substr_count( $path, '/' ) >= substr_count( $canon_path, '/' ) ) { return; } wp_safe_redirect( $canonical, 301 ); exit; } private static function request_uri() { return isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( (string) $_SERVER['REQUEST_URI'] ) ) : ''; } private static function request_path() { return trim( (string) wp_parse_url( self::request_uri(), PHP_URL_PATH ), '/' ); } public static function no_guess( $do ) { $uri = self::request_uri(); $prefixes = self::prefixes(); if ( $prefixes && preg_match( '#^/(category/)?(' . implode( '|', array_map( static function ( $p ) { return preg_quote( $p, '#' ); }, $prefixes ) ) . ')(/|$)#', $uri ) ) { return false; } return $do; } public static function redirect_moved_page() { if ( ! is_singular( 'post' ) || is_feed() || is_preview() ) { return; } $id = (int) get_queried_object_id(); if ( ! $id || ! self::in_manual( $id ) ) { return; } $target = self::canonical_for( self::request_path(), $id ); if ( '' !== $target ) { wp_safe_redirect( $target, 301 ); exit; } } public static function canonical_for( $path, $post_id ) { $canonical = (string) get_permalink( (int) $post_id ); $canon = trim( (string) wp_parse_url( $canonical, PHP_URL_PATH ), '/' ); return '' === $path || '' === $canon || $path === $canon ? '' : $canonical; } public static function redirect_moved_404() { if ( ! is_404() || is_feed() ) { return; } $target = self::moved_target( self::request_path() ); if ( '' !== $target ) { wp_safe_redirect( $target, 301 ); exit; } } public static function moved_target( $path ) { $parts = explode( '/', (string) $path ); if ( 'category' === ( $parts[0] ?? '' ) ) { array_shift( $parts ); } if ( 5 !== count( $parts ) || ! in_array( $parts[0] . '/' . $parts[1], self::prefixes(), true ) ) { return ''; } $posts = get_posts( array( 'name' => sanitize_title( $parts[4] ), 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => true ) ); if ( ! $posts ) { return ''; } $manual = get_category_by_path( $parts[0] . '/' . $parts[1] . '/' . $parts[2], true ); if ( ! $manual instanceof WP_Term ) { return ''; } $id = (int) $posts[0]; foreach ( wp_get_post_categories( $id ) as $cat_id ) { if ( (int) $cat_id === (int) $manual->term_id || cat_is_ancestor_of( (int) $manual->term_id, (int) $cat_id ) ) { $target = (string) get_permalink( $id ); return trim( (string) wp_parse_url( $target, PHP_URL_PATH ), '/' ) === $path ? '' : $target; } } return ''; } public static function is_manual_upload( $filename ) { $base = strtolower( basename( (string) $filename ) ); foreach ( (array) apply_filters( 'rooster/docs/media_prefixes', self::MEDIA_PREFIXES ) as $prefix ) { if ( 0 === strpos( $base, (string) $prefix ) ) { return true; } } return false; } public static function remember_upload( $file ) { $GLOBALS['aieo_docs_current_upload'] = isset( $file['name'] ) ? (string) $file['name'] : ''; return $file; } private static function current_upload( $metadata = null ) { $name = (string) ( $GLOBALS['aieo_docs_current_upload'] ?? '' ); if ( '' === $name && is_array( $metadata ) && ! empty( $metadata['file'] ) ) { $name = (string) $metadata['file']; } return $name; } public static function no_sizes( $sizes, $metadata = null ) { return self::is_manual_upload( self::current_upload( $metadata ) ) ? array() : $sizes; } public static function no_scaled( $threshold ) { return self::is_manual_upload( self::current_upload() ) ? false : $threshold; } } }