sendNotFound(); } } /** * Attachment pages are core's to switch off, not ours. * * Since WP 6.4 core owns this: `wp_attachment_pages_enabled` decides whether * an attachment URL renders a page or 301s to the file, and * `redirect_canonical()` performs the redirect. Implementing a second * redirect here would mean two plugins racing to send different Location * headers for the same URL, so Crownify's setting simply drives core's option * and lets core do the work. */ public function syncMediaPagesToCore() { $enabled = 'on' === Utils::getSetting('mediaPages') ? 1 : 0; if ((int) get_option('wp_attachment_pages_enabled') !== $enabled) { update_option('wp_attachment_pages_enabled', $enabled); } } /** * Whether attachment pages currently render, read from core's option so the * settings toggle shows the truth even if something else changed it. */ public static function mediaPagesEnabled() { return (bool) get_option('wp_attachment_pages_enabled'); } /** * Whether this view has been switched off. * * Singular content is never disableable — the setting is about generated * archive pages, and a switch that could 404 the site's actual posts would * be a foot-gun with no legitimate use. */ public static function isDisabled(Context $context) { if (Context::TYPE_SINGULAR === $context->type || Context::TYPE_HOME === $context->type) { return false; } if (!in_array($context->type, [Context::TYPE_TAXONOMY, Context::TYPE_AUTHOR, Context::TYPE_DATE, Context::TYPE_POST_TYPE_ARCHIVE], true)) { return false; } $disabled = 'on' === Utils::getSetting('titles[' . $context->settingsKey . '][disabled]'); /** * A single-author site has no use for author archives at all — they are a * byte-for-byte duplicate of the blog index — so the UI offers a shortcut * that does not require finding the author entity. */ if (!$disabled && Context::TYPE_AUTHOR === $context->type && 'on' === Utils::getSetting('disableAuthorArchivesSingleAuthor') && self::hasSingleAuthor()) { $disabled = true; } return (bool) apply_filters('rankify_archive_disabled', $disabled, $context); } /** * Does the site have exactly one author with published content? * * Counted rather than assumed, and cached for a day: it is a COUNT over the * posts table and the answer changes roughly never. */ public static function hasSingleAuthor() { $cached = get_transient('rankify_author_count'); if (false !== $cached) { return 1 === (int) $cached; } global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The count is stored in a transient for a day. $count = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(DISTINCT post_author) FROM %i WHERE post_status = 'publish' AND post_type = %s", $wpdb->posts, 'post')); set_transient('rankify_author_count', $count, DAY_IN_SECONDS); return 1 === $count; } /** * Blank out links to archives that no longer resolve. * * Returning '' rather than the home URL is deliberate: a theme that prints * the link unconditionally will render an empty href, which is visibly wrong * to whoever set this up, where a silent redirect home would hide it. */ public function filterArchiveLink($link, $postType) { if ('on' === Utils::getSetting('titles[pta_' . $postType . '][disabled]')) { return ''; } return $link; } protected function sendNotFound() { global $wp_query; $wp_query->set_404(); status_header(404); nocache_headers(); // Without this the canonical redirect still fires on the now-404 query and // turns a clean 404 into a 301 to somewhere arbitrary. remove_action('template_redirect', 'redirect_canonical'); } /** * Every archive entity that can be switched off, for the settings UI. * * @return array settings key => human label */ public static function disableableEntities() { $entities = []; foreach (get_post_types(['public' => true], 'objects') as $postType) { if ('attachment' === $postType->name || empty($postType->has_archive)) { continue; } /* translators: %s: post type plural name. */ $entities['pta_' . $postType->name] = sprintf(__('%s archive', 'crownify'), $postType->labels->name); } foreach (get_taxonomies(['public' => true], 'objects') as $taxonomy) { /* translators: %s: taxonomy singular name. */ $entities['tax_' . $taxonomy->name] = sprintf(__('%s archives', 'crownify'), $taxonomy->labels->singular_name); } $entities['author'] = __('Author archives', 'crownify'); $entities['date'] = __('Date archives', 'crownify'); return $entities; } }