public function send() { $this->sendHeaders(); $this->sendContent(); if (function_exists('fastcgi_finish_request')) { Log::flush(); //fastcgi_finish_request(); } elseif ('cli' !== PHP_SAPI) { static::closeOutputBuffers(0, true); } return $this; }
/** * Constructor * * Throws an exception if config/app.json file is missing. * * @param $rootPath string The root path to the theme * * @throws \Exception */ public function __construct($rootPath) { $this->siteHost = parse_url(site_url(), PHP_URL_HOST); $this->httpHost = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null; // Load the config if (file_exists($rootPath . '/config/app.php')) { $this->config = (include $rootPath . '/config/app.php'); } else { if (file_exists($rootPath . '/config/app.json')) { $this->config = JSONParser::parse(file_get_contents($rootPath . '/config/app.json')); } else { throw new \Exception('Missing app.json or app.php configuration for theme.'); } } // Create the request object $this->request = Request::createFromGlobals(); $this->environment = getenv('WP_ENV') ?: 'development'; $this->debug = defined(WP_DEBUG) || $this->environment == 'development'; // Initialize logging $loggingConfig = null; if (isset($this->config['logging'])) { if (isset($this->config['logging'][$this->environment])) { $loggingConfig = $this->config['logging'][$this->environment]; } else { if (isset($this->config['logging']['development'])) { $loggingConfig = $this->config['logging']['development']; } else { if (isset($this->config['logging']['other'])) { $loggingConfig = $this->config['logging']['other']; } } } } Log::initialize($loggingConfig); // Set our text domain, not really used though. $this->textdomain = $this->config['text-domain']; // Setup our paths $this->rootPath = $rootPath; $this->classPath = $rootPath . '/classes/'; if (!file_exists($this->classPath)) { $this->classPath = $rootPath . '/Classes/'; if (!file_exists($this->classPath)) { throw new \xception("Missing 'classes' directory in Stem application directory: {$rootPath}"); } } // Create the router for extra routes $this->router = new Router($this); $this->namespace = $this->config['namespace']; // Enable/disable XML RPC if ($this->setting('options/disable-xml-rpc', false)) { add_filter('xmlrpc_enabled', '__return_false', 10000); } // Enable/disable WordPress JSON API if ($this->setting('options/disable-wp-json-api', false)) { add_filter('json_enabled', '__return_false', 10000); add_filter('json_jsonp_enabled', '__return_false', 10000); add_filter('rest_enabled', '__return_false', 10000); add_filter('rest_jsonp_enabled', '__return_false', 10000); remove_action('xmlrpc_rsd_apis', 'rest_output_rsd'); remove_action('wp_head', 'rest_output_link_wp_head', 10); remove_action('template_redirect', 'rest_output_link_header', 11); } // Enable/disable WordPress emoji crap if ($this->setting('options/disable-wp-json-api', false)) { add_action('init', function () { remove_action('admin_print_styles', 'print_emoji_styles'); remove_action('wp_head', 'print_emoji_detection_script', 7); remove_action('admin_print_scripts', 'print_emoji_detection_script'); remove_action('wp_print_styles', 'print_emoji_styles'); remove_filter('wp_mail', 'wp_staticize_emoji_for_email'); remove_filter('the_content_feed', 'wp_staticize_emoji'); remove_filter('comment_text_rss', 'wp_staticize_emoji'); add_filter('disable-emoji', function ($plugins) { if (is_array($plugins)) { return array_diff($plugins, ['wpemoji']); } else { return []; } }, 10000, 1); }); } // Enable/disable RSS Feeds if ($this->setting('options/disable-rss', false)) { add_action('wp_loaded', function () { remove_action('wp_head', 'feed_links', 2); remove_action('wp_head', 'feed_links_extra', 3); }); } // Create the controller/template dispatcher $this->dispatcher = new Dispatcher($this); // Autoload function for theme classes spl_autoload_register(function ($class) { if ('\\' == $class[0]) { $class = substr($class, 1); } $class = strtr($class, '\\', DIRECTORY_SEPARATOR); $classParts = explode(DIRECTORY_SEPARATOR, $class); if (count($classParts) > 1) { array_shift($classParts); $shortClass = implode(DIRECTORY_SEPARATOR, $classParts); if (file_exists($this->classPath . $shortClass . '.php')) { require_once $this->classPath . $shortClass . '.php'; return true; } } if (file_exists($this->classPath . $class . '.php')) { require_once $this->classPath . $class . '.php'; return true; } return false; }); // Theme setup action hook add_action('after_setup_theme', function () { $this->setup(); }); // This does the actual dispatching to Stem controllers // and templates. add_filter('template_include', function ($template) { $this->dispatch(); }); // Build the controller map that maps the templates that // wordpress is trying to "include" to Controller classes // in the stem app/theme. Additionally, we surface these // as "page templates" in the wordpress admin UI. if (isset($this->config['page-controllers'])) { $this->templates = $this->config['page-controllers']; foreach ($this->config['page-controllers'] as $key => $controller) { $this->controllerMap[strtolower(preg_replace('|[^aA-zZ0-9_]+|', "-", $key))] = $controller; } add_filter('theme_page_templates', function ($page_templates, $theme, $post) { foreach ($this->config['page-controllers'] as $key => $controller) { $page_templates[preg_replace("/\\s+/", "-", $key) . '.php'] = $key; } return $page_templates; }, 10, 3); } // Load/save ACF Pro JSON fields to our config directory add_filter('acf/settings/save_json', function ($path) use($rootPath) { $newpath = $rootPath . '/config/fields'; if (file_exists($newpath)) { return $newpath; } Log::error("Saving ACF fields, missing {$newpath} directory."); return $path; }); add_filter('acf/settings/load_json', function ($paths) use($rootPath) { $newpath = $rootPath . '/config/fields'; if (!file_exists($newpath)) { Log::error("Loading ACF fields, missing {$newpath} directory."); return $paths; } unset($paths[0]); $paths[] = $newpath; return $paths; }); // Load our custom post types add_action('init', [$this, 'installCustomPostTypes'], 10000); // Require our plugins $this->setupRequiredPlugins(); $this->cacheControl = new CacheControl($this); $this->ui = new UI($this); $this->admin = new Admin($this); }
/** * @param $templateName string|array * @param $pageType string * @return bool * @throws \Exception */ private function dispatchTemplate($templateName, $pageType) { if (is_array($templateName)) { foreach ($templateName as $name) { if ($this->dispatchTemplate($name, $pageType)) { return true; } } } else { Log::debug("Looking for template '{$templateName}' for page type '{$pageType}'.", ['templateName' => $templateName, 'pageType' => $pageType]); $templateName = strtolower($templateName); // normalize the name, eg front_page becomes front-page $name = preg_replace('|[^a-z0-9_]+|', '-', $templateName); // camel case the the controller class name, eg front_page becomes FrontPage $nameparts = explode('-', $name); array_walk($nameparts, function (&$value, $index) { $value = ucfirst($value); }); $classname = implode('', $nameparts); if (is_numeric($classname)) { $classname = 'Error' . $classname; } // Interpolate the class name $class = $this->context->namespace . '\\Controllers\\' . $classname . 'Controller'; Log::debug("Looking for class.", ['class' => $class]); // Determine the action and controller method $action = $this->context->request->query->has('_action') ? ucfirst($this->context->request->query->get('_action')) : 'Index'; $method = strtolower($this->context->request->getMethod()) . $action; $controller = null; // If the controller exists, create it ... if (class_exists($class)) { $controller = new $class($this->context); } else { // Otherwise, we check to see if the template exists. if ($this->context->ui->viewExists('templates/' . $name)) { if ($pageType == 'none') { Log::debug("Found template.", ['templateName' => $templateName]); $this->context->cacheControl->sendHTTPHeaders(); // Template exists but page type doesn't map to a built-in // controller, so we just render the template as is. echo $this->context->ui->render('templates/' . $name, [$this->context]); return true; } $controller = $this->context->createController($pageType, $name); } else { Log::debug("Trying to mapping controller for class.", ['name' => $name]); $controller = $this->context->mapController($name); } } // if we found a controller, then invoke the method and return it's output if ($controller) { Log::debug("Found controller.", ['templateName' => $templateName]); if (method_exists($controller, $method)) { $response = call_user_func([$controller, $method], $this->context->request); } else { // Try GET if method was something other ... $method = 'get' . $action; if (method_exists($controller, $method)) { $response = call_user_func([$controller, $method], $this->context->request); } else { throw new \Exception("Missing method '{$method}' on class '{$class}'."); } } if (is_object($response) && $response instanceof Response) { $this->context->cacheControl->addResponseHeaders($response); $response->send(); } else { if (is_string($response)) { $this->context->cacheControl->sendHTTPHeaders(); echo $response; } } return true; } else { Log::debug("Controller not found.", ['templateName' => $templateName]); } return false; } return false; }
/** * Load custom image sizes */ private function loadImageSizes() { // configure image sizes if (file_exists($this->context->rootPath . '/config/sizes.php')) { $sizesConfig = (include $this->context->rootPath . '/config/sizes.php'); } else { if (file_exists($this->context->rootPath . '/config/sizes.json')) { $sizesConfig = JSONParser::parse(file_get_contents($this->context->rootPath . '/config/sizes.json')); } else { return; } } if (isset($sizesConfig['srcset'])) { $this->srcsetConfig = $sizesConfig['srcset']; } if (isset($sizesConfig['sizes'])) { $customSizes = []; foreach ($sizesConfig['sizes'] as $key => $info) { if ($key == 'original') { Log::warning("Your sizes config has specified a size for 'original'. This is a keyword and cannot be used. Skipping for now."); continue; } if ($key == 'post-thumbnail') { set_post_thumbnail_size($info['width'], $info['height'], $info['crop']); } else { add_image_size($key, $info['width'], $info['height'], $info['crop']); } if (isset($info['display']) && $info['display']) { $customSizes[] = $key; } } if (count($customSizes) > 0) { add_filter('image_size_names_choose', function ($sizes) use($customSizes) { foreach ($customSizes as $size) { $sizes[$size] = ucwords(str_replace('_', ' ', str_replace('-', ' ', $size))); } return $sizes; }); } } if (isset($sizesConfig['disable-wp-sizes'])) { $disabled = $sizesConfig['disable-wp-sizes']; add_filter('intermediate_image_sizes_advanced', function ($sizes) use($disabled) { foreach ($disabled as $size) { unset($sizes[$size]); } return $sizes; }, 10000); } }