public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $open_items = PhabricatorSetupCheck::getOpenSetupIssueKeys(); $issues = PhabricatorSetupCheck::runNormalChecks(); PhabricatorSetupCheck::setOpenSetupIssueKeys(PhabricatorSetupCheck::getUnignoredIssueKeys($issues), $update_database = true); if ($issues) { require_celerity_resource('phabricator-notification-menu-css'); $items = array(); foreach ($issues as $issue) { $classes = array(); $classes[] = 'phabricator-notification'; if ($issue->getIsIgnored()) { $classes[] = 'phabricator-notification-read'; } else { $classes[] = 'phabricator-notification-unread'; } $uri = '/config/issue/' . $issue->getIssueKey() . '/'; $title = $issue->getName(); $summary = $issue->getSummary(); $items[] = javelin_tag('div', array('class' => implode(' ', $classes), 'sigil' => 'notification', 'meta' => array('href' => $uri)), $title); } $content = phutil_tag_div('setup-issue-menu', $items); } else { $content = phutil_tag_div('phabricator-notification no-notifications', pht('You have no unresolved setup issues.')); } $content = hsprintf('<div class="phabricator-notification-header">%s</div>' . '%s', phutil_tag('a', array('href' => '/config/issue/'), pht('Unresolved Setup Issues')), $content); $unresolved_count = count($open_items); $json = array('content' => $content, 'number' => (int) $unresolved_count); return id(new AphrontAjaxResponse())->setContent($json); }
protected function getBody() { $user = null; $request = $this->getRequest(); if ($request) { $user = $request->getUser(); } $header_chrome = null; if ($this->getShowChrome()) { $header_chrome = $this->menuContent; } $classes = array(); $classes[] = 'main-page-frame'; $developer_warning = null; if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode') && DarkConsoleErrorLogPluginAPI::getErrors()) { $developer_warning = phutil_tag_div('aphront-developer-error-callout', pht('This page raised PHP errors. Find them in DarkConsole ' . 'or the error log.')); } // Render the "you have unresolved setup issues..." warning. $setup_warning = null; if ($user && $user->getIsAdmin()) { $open = PhabricatorSetupCheck::getOpenSetupIssueKeys(); if ($open) { $classes[] = 'page-has-warning'; $setup_warning = phutil_tag_div('setup-warning-callout', phutil_tag('a', array('href' => '/config/issue/', 'title' => implode(', ', $open)), pht('You have %d unresolved setup issue(s)...', count($open)))); } } $main_page = phutil_tag('div', array('id' => 'phabricator-standard-page', 'class' => 'phabricator-standard-page'), array($developer_warning, $header_chrome, $setup_warning, phutil_tag('div', array('id' => 'phabricator-standard-page-body', 'class' => 'phabricator-standard-page-body'), $this->renderPageBodyContent()))); $durable_column = null; if ($this->getShowDurableColumn()) { $is_visible = $this->getDurableColumnVisible(); $durable_column = id(new ConpherenceDurableColumnView())->setSelectedConpherence(null)->setUser($user)->setQuicksandConfig($this->buildQuicksandConfig())->setVisible($is_visible)->setInitialLoad(true); } Javelin::initBehavior('quicksand-blacklist', array('patterns' => $this->getQuicksandURIPatternBlacklist())); return phutil_tag('div', array('class' => implode(' ', $classes)), array($main_page, $durable_column)); }
private function renderNotificationMenu() { $viewer = $this->getViewer(); require_celerity_resource('phabricator-notification-css'); require_celerity_resource('phabricator-notification-menu-css'); $container_classes = array('alert-notifications'); $aural = array(); $dropdown_query = id(new AphlictDropdownDataQuery())->setViewer($viewer); $dropdown_data = $dropdown_query->execute(); $message_tag = ''; $message_notification_dropdown = ''; $conpherence_app = 'PhabricatorConpherenceApplication'; $conpherence_data = $dropdown_data[$conpherence_app]; if ($conpherence_data['isInstalled']) { $message_id = celerity_generate_unique_node_id(); $message_count_id = celerity_generate_unique_node_id(); $message_dropdown_id = celerity_generate_unique_node_id(); $message_count_number = $conpherence_data['rawCount']; if ($message_count_number) { $aural[] = phutil_tag('a', array('href' => '/conpherence/'), pht('%s unread messages.', new PhutilNumber($message_count_number))); } else { $aural[] = pht('No messages.'); } $message_count_tag = phutil_tag('span', array('id' => $message_count_id, 'class' => 'phabricator-main-menu-message-count'), $conpherence_data['count']); $message_icon_tag = javelin_tag('span', array('class' => 'phabricator-main-menu-message-icon phui-icon-view ' . 'phui-font-fa fa-comments', 'sigil' => 'menu-icon'), ''); if ($message_count_number) { $container_classes[] = 'message-unread'; } $message_tag = phutil_tag('a', array('href' => '/conpherence/', 'class' => implode(' ', $container_classes), 'id' => $message_id), array($message_icon_tag, $message_count_tag)); Javelin::initBehavior('aphlict-dropdown', array('bubbleID' => $message_id, 'countID' => $message_count_id, 'dropdownID' => $message_dropdown_id, 'loadingText' => pht('Loading...'), 'uri' => '/conpherence/panel/', 'countType' => $conpherence_data['countType'], 'countNumber' => $message_count_number, 'unreadClass' => 'message-unread')); $message_notification_dropdown = javelin_tag('div', array('id' => $message_dropdown_id, 'class' => 'phabricator-notification-menu', 'sigil' => 'phabricator-notification-menu', 'style' => 'display: none;'), ''); } $bubble_tag = ''; $notification_dropdown = ''; $notification_app = 'PhabricatorNotificationsApplication'; $notification_data = $dropdown_data[$notification_app]; if ($notification_data['isInstalled']) { $count_id = celerity_generate_unique_node_id(); $dropdown_id = celerity_generate_unique_node_id(); $bubble_id = celerity_generate_unique_node_id(); $count_number = $notification_data['rawCount']; if ($count_number) { $aural[] = phutil_tag('a', array('href' => '/notification/'), pht('%s unread notifications.', new PhutilNumber($count_number))); } else { $aural[] = pht('No notifications.'); } $count_tag = phutil_tag('span', array('id' => $count_id, 'class' => 'phabricator-main-menu-alert-count'), $notification_data['count']); $icon_tag = javelin_tag('span', array('class' => 'phabricator-main-menu-alert-icon phui-icon-view ' . 'phui-font-fa fa-bell', 'sigil' => 'menu-icon'), ''); if ($count_number) { $container_classes[] = 'alert-unread'; } $bubble_tag = phutil_tag('a', array('href' => '/notification/', 'class' => implode(' ', $container_classes), 'id' => $bubble_id), array($icon_tag, $count_tag)); Javelin::initBehavior('aphlict-dropdown', array('bubbleID' => $bubble_id, 'countID' => $count_id, 'dropdownID' => $dropdown_id, 'loadingText' => pht('Loading...'), 'uri' => '/notification/panel/', 'countType' => $notification_data['countType'], 'countNumber' => $count_number, 'unreadClass' => 'alert-unread')); $notification_dropdown = javelin_tag('div', array('id' => $dropdown_id, 'class' => 'phabricator-notification-menu', 'sigil' => 'phabricator-notification-menu', 'style' => 'display: none;'), ''); } // Admin Level Urgent Notification Channel $setup_tag = ''; $setup_notification_dropdown = ''; if ($viewer && $viewer->getIsAdmin()) { $open = PhabricatorSetupCheck::getOpenSetupIssueKeys(); if ($open) { $setup_id = celerity_generate_unique_node_id(); $setup_count_id = celerity_generate_unique_node_id(); $setup_dropdown_id = celerity_generate_unique_node_id(); $setup_count_number = count($open); if ($setup_count_number) { $aural[] = phutil_tag('a', array('href' => '/config/issue/'), pht('%s unresolved issues.', new PhutilNumber($setup_count_number))); } else { $aural[] = pht('No issues.'); } $setup_count_tag = phutil_tag('span', array('id' => $setup_count_id, 'class' => 'phabricator-main-menu-setup-count'), $setup_count_number); $setup_icon_tag = javelin_tag('span', array('class' => 'phabricator-main-menu-setup-icon phui-icon-view ' . 'phui-font-fa fa-exclamation-circle', 'sigil' => 'menu-icon'), ''); if ($setup_count_number) { $container_classes[] = 'setup-unread'; } $setup_tag = phutil_tag('a', array('href' => '/config/issue/', 'class' => implode(' ', $container_classes), 'id' => $setup_id), array($setup_icon_tag, $setup_count_tag)); Javelin::initBehavior('aphlict-dropdown', array('bubbleID' => $setup_id, 'countID' => $setup_count_id, 'dropdownID' => $setup_dropdown_id, 'loadingText' => pht('Loading...'), 'uri' => '/config/issue/panel/', 'countType' => null, 'countNumber' => null, 'unreadClass' => 'setup-unread')); $setup_notification_dropdown = javelin_tag('div', array('id' => $setup_dropdown_id, 'class' => 'phabricator-notification-menu', 'sigil' => 'phabricator-notification-menu', 'style' => 'display: none;'), ''); } } $dropdowns = array($notification_dropdown, $message_notification_dropdown, $setup_notification_dropdown); return array(array($bubble_tag, $message_tag, $setup_tag), $dropdowns, $aural); }
public function buildWelcomeScreen(AphrontRequest $request) { $viewer = $request->getUser(); $this->requireResource('config-welcome-css'); $content = pht("=== Install Phabricator ===\n\n" . "You have successfully installed Phabricator. This screen will guide " . "you through configuration and orientation. " . "These steps are optional, and you can go through them in any order. " . "If you want to get back to this screen later on, you can find it in " . "the **Config** application under **Welcome Screen**."); $setup = array(); $setup[] = $this->newItem($request, 'fa-check-square-o green', $content); $issues_resolved = !PhabricatorSetupCheck::getOpenSetupIssueKeys(); $setup_href = PhabricatorEnv::getURI('/config/issue/'); if ($issues_resolved) { $content = pht("=== Resolve Setup Issues ===\n\n" . "You've resolved (or ignored) all outstanding setup issues. " . "You can review issues in the **Config** application, under " . "**[[ %s | Setup Issues ]]**.", $setup_href); $icon = 'fa-check-square-o green'; } else { $content = pht("=== Resolve Setup Issues ===\n\n" . "You have some unresolved setup issues to take care of. Click " . "the link in the yellow banner at the top of the screen to see " . "them, or find them in the **Config** application under " . "**[[ %s | Setup Issues ]]**. " . "Although most setup issues should be resolved, sometimes an issue " . "is not applicable to an install. " . "If you don't intend to fix a setup issue (or don't want to fix " . "it for now), you can use the \"Ignore\" action to mark it as " . "something you don't plan to deal with.", $setup_href); $icon = 'fa-warning red'; } $setup[] = $this->newItem($request, $icon, $content); $configs = id(new PhabricatorAuthProviderConfigQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->execute(); $auth_href = PhabricatorEnv::getURI('/auth/'); $have_auth = (bool) $configs; if ($have_auth) { $content = pht("=== Login and Registration ===\n\n" . "You've configured at least one authentication provider, so users " . "can register or log in. " . "To configure more providers or adjust settings, use the " . "**[[ %s | Auth Application ]]**.", $auth_href); $icon = 'fa-check-square-o green'; } else { $content = pht("=== Login and Registration ===\n\n" . "You haven't configured any authentication providers yet. " . "Authentication providers allow users to register accounts and " . "log in to Phabricator. You can configure Phabricator to accept " . "credentials like username and password, LDAP, or Google OAuth. " . "You can configure authentication using the " . "**[[ %s | Auth Application ]]**.", $auth_href); $icon = 'fa-warning red'; } $setup[] = $this->newItem($request, $icon, $content); $config_href = PhabricatorEnv::getURI('/config/'); // Just load any config value at all; if one exists the install has figured // out how to configure things. $have_config = (bool) id(new PhabricatorConfigEntry())->loadAllWhere('1 = 1 LIMIT 1'); if ($have_config) { $content = pht("=== Configure Phabricator Settings ===\n\n" . "You've configured at least one setting from the web interface. " . "To configure more settings later, use the " . "**[[ %s | Config Application ]]**.", $config_href); $icon = 'fa-check-square-o green'; } else { $content = pht("=== Configure Phabricator Settings ===\n\n" . 'Many aspects of Phabricator are configurable. To explore and ' . 'adjust settings, use the **[[ %s | Config Application ]]**.', $config_href); $icon = 'fa-info-circle'; } $setup[] = $this->newItem($request, $icon, $content); $settings_href = PhabricatorEnv::getURI('/settings/'); $prefs = $viewer->loadPreferences()->getPreferences(); $have_settings = !empty($prefs); if ($have_settings) { $content = pht("=== Adjust Account Settings ===\n\n" . "You've adjusted at least one setting on your account. " . "To make more adjustments, visit the " . "**[[ %s | Settings Application ]]**.", $settings_href); $icon = 'fa-check-square-o green'; } else { $content = pht("=== Adjust Account Settings ===\n\n" . 'You can configure settings for your account by clicking the ' . 'wrench icon in the main menu bar, or visiting the ' . '**[[ %s | Settings Application ]]** directly.', $settings_href); $icon = 'fa-info-circle'; } $setup[] = $this->newItem($request, $icon, $content); $dashboard_href = PhabricatorEnv::getURI('/dashboard/'); $have_dashboard = (bool) PhabricatorDashboardInstall::getDashboard($viewer, PhabricatorHomeApplication::DASHBOARD_DEFAULT, 'PhabricatorHomeApplication'); if ($have_dashboard) { $content = pht("=== Customize Home Page ===\n\n" . "You've installed a default dashboard to replace this welcome screen " . "on the home page. " . "You can still visit the welcome screen here at any time if you " . "have steps you want to complete later, or if you feel lonely. " . "If you've changed your mind about the dashboard you installed, " . "you can install a different default dashboard with the " . "**[[ %s | Dashboards Application ]]**.", $dashboard_href); $icon = 'fa-check-square-o green'; } else { $content = pht("=== Customize Home Page ===\n\n" . "When you're done setting things up, you can create a custom " . "dashboard and install it. Your dashboard will replace this " . "welcome screen on the Phabricator home page. " . "Dashboards can show users the information that's most important to " . "your organization. You can configure them to display things like: " . "a custom welcome message, a feed of recent activity, or a list of " . "open tasks, waiting reviews, recent commits, and so on. " . "After you install a default dashboard, it will replace this page. " . "You can find this page later by visiting the **Config** " . "application, under **Welcome Page**. " . "To get started building a dashboard, use the " . "**[[ %s | Dashboards Application ]]**. ", $dashboard_href); $icon = 'fa-info-circle'; } $setup[] = $this->newItem($request, $icon, $content); $apps_href = PhabricatorEnv::getURI('/applications/'); $content = pht("=== Explore Applications ===\n\n" . "Phabricator is a large suite of applications that work together to " . "help you develop software, manage tasks, and communicate. A few of " . "the most commonly used applications are pinned to the left navigation " . "bar by default.\n\n" . "To explore all of the Phabricator applications, adjust settings, or " . "uninstall applications you don't plan to use, visit the " . "**[[ %s | Applications Application ]]**. You can also click the " . "**Applications** button in the left navigation menu, or search for an " . "application by name in the main menu bar. ", $apps_href); $explore = array(); $explore[] = $this->newItem($request, 'fa-globe', $content); // TODO: Restore some sort of "Support" link here, but just nuke it for // now as we figure stuff out. $differential_uri = PhabricatorEnv::getURI('/differential/'); $differential_create_uri = PhabricatorEnv::getURI('/differential/diff/create/'); $differential_all_uri = PhabricatorEnv::getURI('/differential/query/all/'); $differential_user_guide = PhabricatorEnv::getDoclink('Differential User Guide'); $differential_vs_uri = PhabricatorEnv::getDoclink('User Guide: Review vs Audit'); $quick = array(); $quick[] = $this->newItem($request, 'fa-gear', pht("=== Quick Start: Code Review ===\n\n" . "Review code with **[[ %s | Differential ]]**. " . "Engineers can use Differential to share, review, and approve " . "changes to source code. " . "To get started with code review:\n\n" . " - **[[ %s | Create a Revision ]]** //(Copy and paste a diff from " . " the command line into the web UI to quickly get a feel for " . " review.)//\n" . " - **[[ %s | View All Revisions ]]**\n\n" . "For more information, see these articles in the documentation:\n\n" . " - **[[ %s | Differential User Guide ]]**, for a general overview " . " of Differential.\n" . " - **[[ %s | User Guide: Review vs Audit ]]**, for a discussion " . " of different code review workflows.", $differential_uri, $differential_create_uri, $differential_all_uri, $differential_user_guide, $differential_vs_uri)); $maniphest_uri = PhabricatorEnv::getURI('/maniphest/'); $maniphest_create_uri = PhabricatorEnv::getURI('/maniphest/task/edit/'); $maniphest_all_uri = PhabricatorEnv::getURI('/maniphest/query/all/'); $quick[] = $this->newItem($request, 'fa-anchor', pht("=== Quick Start: Bugs and Tasks ===\n\n" . "Track bugs and tasks in Phabricator with " . "**[[ %s | Maniphest ]]**. " . "Users in all roles can use Maniphest to manage current and " . "planned work and to track bugs and issues. " . "To get started with bugs and tasks:\n\n" . " - **[[ %s | Create a Task ]]**\n" . " - **[[ %s | View All Tasks ]]**\n", $maniphest_uri, $maniphest_create_uri, $maniphest_all_uri)); $pholio_uri = PhabricatorEnv::getURI('/pholio/'); $pholio_create_uri = PhabricatorEnv::getURI('/pholio/new/'); $pholio_all_uri = PhabricatorEnv::getURI('/pholio/query/all/'); $quick[] = $this->newItem($request, 'fa-camera-retro', pht("=== Quick Start: Design Review ===\n\n" . "Review proposed designs with **[[ %s | Pholio ]]**. " . "Designers can use Pholio to share images of what they're working on " . "and show off things they've made. " . "To get started with design review:\n\n" . " - **[[ %s | Create a Mock ]]**\n" . " - **[[ %s | View All Mocks ]]**", $pholio_uri, $pholio_create_uri, $pholio_all_uri)); $diffusion_uri = PhabricatorEnv::getURI('/diffusion/'); $diffusion_create_uri = PhabricatorEnv::getURI('/diffusion/create/'); $diffusion_all_uri = PhabricatorEnv::getURI('/diffusion/query/all/'); $diffusion_user_guide = PhabricatorEnv::getDoclink('Diffusion User Guide'); $diffusion_setup_guide = PhabricatorEnv::getDoclink('Diffusion User Guide: Repository Hosting'); $quick[] = $this->newItem($request, 'fa-code', pht("=== Quick Start: Repositories ===\n\n" . "Manage and browse source code repositories with " . "**[[ %s | Diffusion ]]**. " . "Engineers can use Diffusion to browse and audit source code. " . "You can configure Phabricator to host repositories, or have it " . "track existing repositories hosted elsewhere (like GitHub, " . "Bitbucket, or an internal server). " . "To get started with repositories:\n\n" . " - **[[ %s | Create a New Repository ]]**\n" . " - **[[ %s | View All Repositories ]]**\n\n" . "For more information, see these articles in the documentation:\n\n" . " - **[[ %s | Diffusion User Guide ]]**, for a general overview of " . " Diffusion.\n" . " - **[[ %s | Diffusion User Guide: Repository Hosting ]]**, " . " for instructions on configuring repository hosting.\n\n" . "Phabricator supports Git, Mercurial and Subversion.", $diffusion_uri, $diffusion_create_uri, $diffusion_all_uri, $diffusion_user_guide, $diffusion_setup_guide)); $header = id(new PHUIHeaderView())->setHeader(pht('Welcome to Phabricator')); $setup_header = PhabricatorMarkupEngine::renderOneObject(id(new PhabricatorMarkupOneOff())->setContent(pht('=Setup and Configuration')), 'default', $viewer); $explore_header = PhabricatorMarkupEngine::renderOneObject(id(new PhabricatorMarkupOneOff())->setContent(pht('=Explore Phabricator')), 'default', $viewer); $quick_header = PhabricatorMarkupEngine::renderOneObject(id(new PhabricatorMarkupOneOff())->setContent(pht('=Quick Start Guides')), 'default', $viewer); return id(new PHUIDocumentView())->setHeader($header)->setFluid(true)->appendChild($setup_header)->appendChild($setup)->appendChild($explore_header)->appendChild($explore)->appendChild($quick_header)->appendChild($quick); }
public function renderModuleStatus(AphrontRequest $request) { $viewer = $request->getViewer(); $guide_items = new PhabricatorGuideListView(); $title = pht('Resolve Setup Issues'); $issues_resolved = !PhabricatorSetupCheck::getOpenSetupIssueKeys(); $href = PhabricatorEnv::getURI('/config/issue/'); if ($issues_resolved) { $icon = 'fa-check'; $icon_bg = 'bg-green'; $description = pht("You've resolved (or ignored) all outstanding setup issues."); } else { $icon = 'fa-warning'; $icon_bg = 'bg-red'; $description = pht('You have some unresolved setup issues to take care of.'); } $item = id(new PhabricatorGuideItemView())->setTitle($title)->setHref($href)->setIcon($icon)->setIconBackground($icon_bg)->setDescription($description); $guide_items->addItem($item); $configs = id(new PhabricatorAuthProviderConfigQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->execute(); $title = pht('Login and Registration'); $href = PhabricatorEnv::getURI('/auth/'); $have_auth = (bool) $configs; if ($have_auth) { $icon = 'fa-check'; $icon_bg = 'bg-green'; $description = pht("You've configured at least one authentication provider."); } else { $icon = 'fa-key'; $icon_bg = 'bg-sky'; $description = pht('Authentication providers allow users to register accounts and ' . 'log in to Phabricator.'); } $item = id(new PhabricatorGuideItemView())->setTitle($title)->setHref($href)->setIcon($icon)->setIconBackground($icon_bg)->setDescription($description); $guide_items->addItem($item); $title = pht('Configure Phabricator'); $href = PhabricatorEnv::getURI('/config/'); // Just load any config value at all; if one exists the install has figured // out how to configure things. $have_config = (bool) id(new PhabricatorConfigEntry())->loadAllWhere('1 = 1 LIMIT 1'); if ($have_config) { $icon = 'fa-check'; $icon_bg = 'bg-green'; $description = pht("You've configured at least one setting from the web interface."); } else { $icon = 'fa-sliders'; $icon_bg = 'bg-sky'; $description = pht('Learn how to configure mail and other options in Phabricator.'); } $item = id(new PhabricatorGuideItemView())->setTitle($title)->setHref($href)->setIcon($icon)->setIconBackground($icon_bg)->setDescription($description); $guide_items->addItem($item); $title = pht('User Account Settings'); $href = PhabricatorEnv::getURI('/settings/'); $preferences = id(new PhabricatorUserPreferencesQuery())->setViewer($viewer)->withUsers(array($viewer))->executeOne(); $have_settings = $preferences && $preferences->getPreferences(); if ($have_settings) { $icon = 'fa-check'; $icon_bg = 'bg-green'; $description = pht("You've adjusted at least one setting on your account."); } else { $icon = 'fa-wrench'; $icon_bg = 'bg-sky'; $description = pht('Configure account settings for all users, or just yourself'); } $item = id(new PhabricatorGuideItemView())->setTitle($title)->setHref($href)->setIcon($icon)->setIconBackground($icon_bg)->setDescription($description); $guide_items->addItem($item); $title = pht('Notification Server'); $href = PhabricatorEnv::getURI('/config/edit/notification.servers/'); $have_notifications = PhabricatorEnv::getEnvConfig('notification.servers'); if ($have_notifications) { $icon = 'fa-check'; $icon_bg = 'bg-green'; $description = pht("You've set up a real-time notification server."); } else { $icon = 'fa-bell'; $icon_bg = 'bg-sky'; $description = pht('Phabricator can deliver notifications in real-time with WebSockets.'); } $item = id(new PhabricatorGuideItemView())->setTitle($title)->setHref($href)->setIcon($icon)->setIconBackground($icon_bg)->setDescription($description); $guide_items->addItem($item); $intro = pht('Phabricator has been successfully installed. These next guides will ' . 'take you through configuration and new user orientation. ' . 'These steps are optional, and you can go through them in any order. ' . 'If you want to get back to this guide later on, you can find it in ' . '{icon globe} **Applications** under {icon map-o} **Guides**.'); $intro = new PHUIRemarkupView($viewer, $intro); $intro = id(new PHUIDocumentViewPro())->appendChild($intro); return array($intro, $guide_items); }