Text::data(), 'existingAdminMenuTitles' => static::notUsedExistingMenuTitles(), 'ajaxNonce' => wp_create_nonce(SubpageImportExport::AJAX_NONCE), 'rest' => [ 'root' => rest_url(), 'url' => rest_url(Rest\PagesController::NAMESPACE . '/'), 'nonce' => wp_create_nonce('wp_rest'), ], 'data' => static::pageData() ]); } /** * Forget everything this class cached for the current request (request copies and table reads). */ public static function forgetRequestCaches(): void { foreach (array_keys((new \ReflectionClass(static::class))->getStaticProperties()) as $name) { static::$$name = null; } Access::reset(); } public static function getPostRequest() { $nonceName = SubpageEdit::$nonceName; $nonceAction = SubpageEdit::$nonceAction; if (!isset(Request::post()[$nonceName]) || !wp_verify_nonce(Request::post()[$nonceName], $nonceAction)) { return []; } if (static::$getPostRequest === null) { static::$getPostRequest = Request::post(); } return static::$getPostRequest; } public static function getRequest() { if (static::$getRequest === null) { static::$getRequest = $_REQUEST; } return static::$getRequest; } public static function getGetRequest() { $nonceName = SubpageEdit::$nonceName; $nonceAction = SubpageEdit::$nonceAction; if (!isset(Request::get()[$nonceName]) || !wp_verify_nonce(Request::get()[$nonceName], $nonceAction)) { return []; } if (static::$getGetRequest === null) { static::$getGetRequest = Request::get(); } return static::$getGetRequest; } /** * @return Collection|array */ public static function pageData() { if (Request::get()['page'] === SubpageCreate::SLUG) { return Collection::make([ 'registered_options' => static::getAllOptionNamesExcludeCurrentPageFieldNames() ]); } if (!isset(Request::get()['action']) && !isset(Request::get()['optionpage']) || Request::get()['page'] === SubpageMaster::SLUG) { return []; } $currentPage = static::listTableData()->filter(function ($value) { return Request::get()['optionpage'] === $value['ID']; })->values(); $allRows = Request::post() ? PageRepository::rows() : static::getAllRowsFromPluginTable(); $allRowsFromPluginTable = Collection::make($allRows)->filter(function ($row) use ($currentPage) { return $currentPage[0]['ID'] === $row->id; })->flatten(1)->map(function ($value) { return PageRepository::decode($value->page_value); })->first(); return Collection::make($allRowsFromPluginTable)->merge([ 'id' => $currentPage[0]['ID'], 'nonce' => wp_create_nonce($currentPage[0]['ID']), 'page' => Request::get()['page'], 'registered_options' => static::getAllOptionNamesExcludeCurrentPageFieldNames() ]); } /** * Existing admin menu titles. * * @return object * @global array $GLOBALS ['menu'] */ public static function existingAdminMenuTitles() { return Collection::make($GLOBALS['menu'])->filter(function ($value) { return in_array($value[2], static::defaultWordpressMenuPageSlugs()); })->map(function ($value) { if (strpos($value[0], '')) { $value[0] = substr($value[0], 0, strpos($value[0], ' $value[0], 'slug' => $value[2]]; })->values(); } /** * Existing admin menu title minus the ones who are already used. * * @return object * @global array $GLOBALS ['menu'] */ public static function notUsedExistingMenuTitles() { $parentSlug = static::getParentSlugFromSubpages()->values()->toArray(); if (isset($GLOBALS['menu'])) { global $menu; } else { $menu = get_option(Plugin::PREFIX_ . 'admin_menu_list'); } return Collection::make($menu)->filter(function ($value) use ($parentSlug) { return in_array($value[2], static::defaultWordpressMenuPageSlugs()) && !in_array($value[2], $parentSlug); })->map(function ($value) { if (strpos($value[0], '') !== false) { $value[0] = substr($value[0], 0, strpos($value[0], ' $value[0], 'slug' => $value[2]]; })->values(); } /** * Get all default WordPress menu titles and slugs. * * @return object */ public static function getDefaultWordpressMenus() { if (isset($GLOBALS['menu'])) { global $menu; } else { $menu = get_option(Plugin::PREFIX_ . 'admin_menu_list'); } return Collection::make($menu)->filter(function ($value) { return in_array($value[2], static::defaultWordpressMenuPageSlugs()); })->map(function ($value, $key) { if (strpos($value[0], '') !== false) { $value[0] = substr($value[0], 0, strpos($value[0], ' $value[0], 'slug' => $value[2], 'position' => $key ]; })->values(); } public static function defaultWordpressMenuPageSlugs() { return [ 'index.php', 'edit.php', 'upload.php', 'edit.php?post_type=page', 'edit-comments.php', 'themes.php', 'plugins.php', 'users.php', 'tools.php', 'options-general.php' ]; } /** * @return Collection */ public static function getParentSlugFromSubpages() { return static::getSubpages() ->pluck('parent_slug') ->unique(); } /** * @param null $id * * @return Collection */ public static function getSubpages($id = null) { $value = ($id === null) ? static::getAllFromPageValueColumn() : static::getPageValueFromSpecificId($id); // dump(static::getAllFromPageValueColumn()); // dump($value); return Collection::make($value) ->pluck('pages') ->flatten(1); } /** * @return Collection|void */ public static function requestGetSubpages() { if (!isset(Request::post()['pages'])) { return; } return Collection::make(Request::post()['pages']); } /** * @return Collection */ public static function getFields() { return static::getSubpages() ->pluck('fields') ->flatten(1); } /** * @param $id * * @return Collection */ public static function getFieldsfromId($id) { return static::getSubpages($id) ->pluck('fields') ->flatten(1); } /** * @param $id * * @return Collection */ public static function getOptionNamesFromSettingPagesById($id) { return static::getFieldsfromId($id)->filter(function ($field) { return isset($field['field_name']); })->pluck('field_name')->flatten(1); } /** * @return Collection */ public static function getPageNamesFromOptionsPages() { return static::getSubpages()->map(function ($page) { return $page['menu_slug']; }); } /** * @return Collection */ public static function adminPagesIds() { return Collection::make([ // The screen id depends on the (translatable) menu title, so let WordPress build it. get_plugin_page_hookname(SubpageCreate::SLUG, SubpageMaster::SLUG), 'admin_page_' . SubpageEdit::SLUG, 'toplevel_page_' . SubpageMaster::SLUG ]); } /** * Get all options names from options table. * * @return array */ public static function getAllOptionNames() { if (static::$getAllOptionNames === null) { static::$getAllOptionNames = Collection::make(PageRepository::optionNamesInOptionsTable()) ->pluck('option_name') ->merge(static::getAllCreatedOptionNames()); } return static::$getAllOptionNames; } /** * Get all options names from options table exclude current subpage field names. * * @return array */ public static function getAllOptionNamesExcludeCurrentPageFieldNames() { if (static::$getAllOptionNamesExcludeCurrentPageFieldNames === null) { static::$getAllOptionNamesExcludeCurrentPageFieldNames = Collection::make(static::getAllOptionNames()) ->diff(static::getAllRegisteredFieldNames()) // An import may take over option names that the plugin made itself. ->diff(\AOP\App\ImportExport\AllowedOptionNames::all()) ->all(); } return static::$getAllOptionNamesExcludeCurrentPageFieldNames; } /** * Get all options names from options table created by this plugin. * To be clear: field_name === option_name. * * @return object */ public static function getAllCreatedOptionNames() { if (is_null(static::$getAllFromPageValueColumn)) { return; } return static::$getAllFromPageValueColumn ->pluck('pages')->flatten(1) ->pluck('fields')->flatten(1) ->pluck('field_name'); } /** * Get all field names which already registered. * * @return array */ public static function getAllRegisteredFieldNames() { if (!isset(Request::get()['optionpage'])) { return []; } return static::getFieldsfromId(Request::get()['optionpage'])->pluck('field_name'); } /** * @return Collection */ public static function getFieldsWithFieldNames() { // Fields of unavailable types are not posted but they stay: their names count. $preservedNames = Collection::make(static::unknownTypeFields())->pluck('field.field_name'); return Collection::make(Request::post()['pages']) ->pluck('fields')->flatten(1) ->pluck('field_name') ->merge($preservedNames) ->filter(); } /** * The stored fields of the page that is being edited whose type is not available (an add-on that is not * active). They are kept when the page is saved and their options are never deleted. * * @return array */ public static function unknownTypeFields(): array { $id = Request::get()['optionpage'] ?? null; if (!$id || (Request::get()['page'] ?? '') !== SubpageEdit::SLUG) { return []; } $known = FieldRegistry::instance()->all(); $removed = self::removedFieldIds(); $found = []; foreach (static::getSubpages((int) $id) as $page) { foreach ($page['fields'] ?? [] as $field) { if (!isset($field['type']) || isset($known[$field['type']])) { continue; } // Deleted in the editor: not kept, so its name is gone too and PageSaver deletes its option. if (in_array((string) ($field['id'] ?? ''), $removed, true)) { continue; } $found[] = ['subpage' => (string) ($page['menu_slug'] ?? ''), 'field' => $field]; } } return $found; } /** * The ids the editor posts as `removed_fields[]`: fields of an unavailable type that the user deleted. Only * unknownTypeFields() reads them, so they can only drop such a field of the page being edited, never another. * * @return array */ private static function removedFieldIds(): array { $posted = Request::post()['removed_fields'] ?? []; if (!is_array($posted)) { return []; } $ids = array_map(function ($id) { return is_scalar($id) ? preg_replace('/[^0-9]/', '', (string) $id) : ''; }, $posted); return array_values(array_filter($ids, 'strlen')); } /** * All data from column page_value. * * @return object */ public static function getAllFromPageValueColumn() { if (static::$getAllFromPageValueColumn === null) { static::$getAllFromPageValueColumn = Collection::make(PageRepository::pageValueRows()) ->pluck('page_value') ->map(function ($item) { $item = PageRepository::decode($item); if (!isset($item['menu'])) { return $item; } $pages = Collection::make($item['pages'])->filter(function ($item) { return current_user_can(Access::pageCapability($item)); }); if ($pages->isEmpty()) { return $item; } $firstPage = $pages->first(); if ($item['menu']['menu_slug'] === $firstPage['menu_slug']) { return $item; } $item['menu']['menu_slug'] = $firstPage['menu_slug']; $item['menu']['menu_slug_suffix'] = $firstPage['subpage_slug_suffix']; $item['pages'] = $pages->map(function ($page) use ($item) { $page['parent_slug'] = $item['menu']['menu_slug']; return $page; }); return $item; }); } return static::$getAllFromPageValueColumn; } public static function getAllRowsFromPluginTable() { if (static::$getAllRowsFromPluginTable === null) { static::$getAllRowsFromPluginTable = PageRepository::rows(); } return static::$getAllRowsFromPluginTable; } // public function getIdFromTableRow() // { // return Collection::make(static::getAllRowsFromPluginTable())->pluck('id'); // } public function getIdfromOptionPage($slug) { return Collection::make(static::getAllRowsFromPluginTable()) ->where('menu_slug', $slug) ->pluck('id') ->first(); } /** * The menu_id from all created menus. * * @return object */ public function menuIdFromAllCreatedMenus() { return static::getAllFromPageValueColumn()->map(function ($page) { return [ 'menu_id' => $page['menu_id'], 'menu_type' => $page['menu'] ? 'menu' : 'existing_menu' ]; }); } /** * The menu_id from all created menus. * * @return object */ public function menuIdFromAllExistingMenus() { return static::getAllFromPageValueColumn()->filter(function ($page) { return $page['existing_menu']; })->pluck('menu_id'); } /** * Get page_value from specific id. * * @return object */ private static function getPageValueFromSpecificId($id) { return Collection::make(PageRepository::rowsById((int) $id))->map(function ($row) { return PageRepository::decode($row->page_value); }); } /** * @return Collection */ public static function listTableData() { return Collection::make(static::getAllRowsFromPluginTable())->map(function ($row) { $pageValue = PageRepository::decode($row->page_value); if (isset($pageValue['existing_menu'])) { $menuItems = Collection::make($GLOBALS['menu'])->filter(function ($value) { return in_array($value[2], static::defaultWordpressMenuPageSlugs()); })->filter(function ($item) use ($pageValue) { return $item[2] === $pageValue['existing_menu']['menu_slug']; })->map(function ($item, $key) { return array_merge($item, ['position' => $key]); })->flatten(1); $dashicon = $menuItems[6]; $position = $menuItems->last(); // $menuName = '' . $pageValue['existing_menu']['menu_name']; $menuIcon = $dashicon; $menuName = $pageValue['existing_menu']['menu_name']; } else { $position = $pageValue['menu']['position']; // $menuName = '' . $pageValue['menu']['menu_title']; $menuIcon = $pageValue['menu']['icon_url']; $menuName = $pageValue['menu']['menu_title']; } $totalPages = count($pageValue['pages']); return [ 'ID' => $row->id, 'title' => $menuName, 'icon' => $menuIcon, 'position' => (float)$position, 'pages' => (string)$totalPages ]; }); } /** * Delete an options page and its option values. * * Only from the overview or the edit page, for users with manage_options and with a valid nonce. * * @return void */ public static function deleteRowFromSpecificId() { if (!isset($_GET['action'], $_GET['optionpage'], $_GET['nonce']) || $_GET['action'] !== 'delete') { return; } if (!(SubpageEdit::isCurrentPage() || SubpageMaster::isCurrentPage()) || !current_user_can('manage_options')) { return; } $id = absint($_GET['optionpage']); if (!$id || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['nonce'])), (string) $id)) { wp_die(esc_html__('The link you followed has expired.', 'admin-options-pages'), 403); } static::deletePage($id); wp_safe_redirect(SubpageMaster::url()); exit; } /** * Delete one page and the options of its fields. The overview screen tidies `aop_option_names` and shows * the notice on its next load, from the flash data set here. */ public static function deletePage(int $id): void { static::getOptionNamesFromSettingPagesById($id)->map(function ($optionName) { return delete_option($optionName); }); Flash::add([ 'pages_deleted' => true, 'pages_deleted_multiple' => static::getSubpages($id)->count() > 1 ? true : false ]); PageRepository::delete($id); OptionNames::sync(); } /** * Page ID from specific edit page. * * @return int */ public static function IdEditPage() { if (!isset(Request::get()['optionpage'])) { return; } return static::listTableData()->filter(function ($value) { return Request::get()['optionpage'] === $value['ID']; })->values()[0]['ID']; } }