}} and post_link_url_* targets from WordPress (rulings P10, Q14): the entry exists, * lives (no trash, auto-draft or revision), is no attachment (whatever its status), its type has a * Divi link option post_link_url_ (never the library et_pb_layout or an internal "_" type) and * the current user may read it (read_post, like select_post settings, S37). Missing, unreadable and * unlinkable entries give the same null (no existence or type oracle). */ final class WpLinkTargets implements LinkTargets { private const DEAD = ['trash', 'auto-draft', 'inherit']; /** Never link targets, whatever Divi lists: attachments and the Divi Library (ruling Q14). */ private const NEVER = ['attachment', 'et_pb_layout']; /** @var Closure(string): bool */ private Closure $linkable; /** * @param (Closure(string): bool)|null $linkable Whether Divi offers post_link_url_ (tests); * null = Divi's et_builder_get_public_post_types(), the list its link option uses. */ public function __construct(?Closure $linkable = null) { $this->linkable = $linkable ?? self::diviLinkable(...); } public function postTypeOf(int $id): ?string { $post = get_post($id); if (! $post instanceof WP_Post || in_array((string) $post->post_status, self::DEAD, true)) { return null; } $type = (string) $post->post_type; if (in_array($type, self::NEVER, true) || str_starts_with($type, '_') || ! ($this->linkable)($type)) { return null; } return current_user_can('read_post', $id) ? $type : null; } /** Fail closed: without Divi's list no type has a link option. */ private static function diviLinkable(string $type): bool { if (! function_exists('et_builder_get_public_post_types')) { return false; } $types = et_builder_get_public_post_types(); return is_array($types) && array_key_exists($type, $types); } }