base = rtrim( $base, '/' ) . '/'; } /** * Render a template. * * @param string $template Template name, without extension, relative to the base. * @param array $data Variables made available to the template. * @return void */ public function render( string $template, array $data = array() ): void { $path = $this->path( $template ); if ( '' === $path ) { return; } $this->include_template( $path, $data ); } /** * Load a template with the given data in scope. * * Kept in its own method so the only variables the template can collide with are the two * parameters below. Doing this inline in render() meant a data key called "value" was assigned to * the foreach loop variable of the same name and then lost, which is not a mistake that announces * itself: the field simply renders empty. * * It stays a method rather than a closure because templates call $this->render() to compose * partials, and $this is only bound inside an object scope. * * @param string $adpix_template_path Absolute path to a verified template file. * @param array $adpix_template_data Variables made available to the template. * @return void */ private function include_template( string $adpix_template_path, array $adpix_template_data ): void { // Extracted rather than passed as one array purely for readability in the templates. The keys // come from controllers, never from a request, and the templates escape on output regardless. foreach ( $adpix_template_data as $adpix_template_key => $adpix_template_value ) { // The two parameters of this method are the only names a template must not overwrite. if ( 0 === strpos( $adpix_template_key, 'adpix_template_' ) ) { continue; } ${$adpix_template_key} = $adpix_template_value; } unset( $adpix_template_data, $adpix_template_key, $adpix_template_value ); require $adpix_template_path; } /** * Render a template to a string. * * @param string $template Template name. * @param array $data Variables. * @return string */ public function capture( string $template, array $data = array() ): string { ob_start(); $this->render( $template, $data ); $output = ob_get_clean(); return is_string( $output ) ? $output : ''; } /** * Resolve a template name to a readable file inside the template directory. * * @param string $template Template name. * @return string Empty when the name does not resolve to a template. */ private function path( string $template ): string { $relative = preg_replace( '/[^a-z0-9\/_-]/', '', strtolower( $template ) ); $relative = is_string( $relative ) ? $relative : ''; if ( '' === $relative ) { return ''; } $path = realpath( $this->base . $relative . '.php' ); $base = realpath( $this->base ); // Belt and braces alongside the character filter above: a template is only ever loaded from // inside the plugin's own template directory. if ( false === $path || false === $base || 0 !== strpos( $path, $base ) ) { return ''; } return $path; } }