$input * @return list */ public static function declaredBy(Ability $ability, array $input): array { if ($ability instanceof RequiresDiviFeatures) { return self::merge($ability->diviFeatures($input)); } if (! $ability instanceof DeclaresRequirements) { return []; } $features = []; foreach ($ability->requirements($input) as $id) { $feature = DiviFeature::tryFrom($id); if ($feature !== null) { $features[] = $feature; } } return self::merge($features); } /** * @param list ...$lists * @return list Every capability once, in the order of first appearance. */ public static function merge(array ...$lists): array { $merged = []; foreach ($lists as $list) { foreach ($list as $feature) { $merged[$feature->value] ??= $feature; } } return array_values($merged); } /** * What the entry $postId adds to a tool that loads it. A page or post adds nothing; a library entry needs the * Divi roles (LibraryAccess), written also the library check of global entries and the library save scope * (DiviSaver, D5); a Theme Builder layout needs the roles, written also the template lookup of * ThemeBuilderEdits and the Theme Builder save scope. An ID that names no entry, or one the caller may not edit, * adds nothing – the tool answers "not found" or "forbidden" itself, and a lock never tells which kind of entry * an ID is (ruling P12). * * @return list */ public static function ofEntry(mixed $postId, bool $write): array { $id = PostGuard::id($postId); $type = $id === null ? false : get_post_type($id); if ($id === null || ! is_string($type)) { return []; } $isLibrary = $type === DiviLibraryStore::POST_TYPE; if (! $isLibrary && DiviThemeBuilder::areaOf($type) === null) { return []; } if (! current_user_can('edit_post', $id)) { return []; } if ($isLibrary) { return $write ? [DiviFeature::Roles, DiviFeature::LibraryRead, DiviFeature::LibraryWrite] : [DiviFeature::Roles]; } return $write ? [DiviFeature::Roles, DiviFeature::ThemeBuilderRead, DiviFeature::ThemeBuilderWrite] : [DiviFeature::Roles]; } }