/** * 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); }