From 07a95a7508447b5dedb00ada3b22f6329d25d561 Mon Sep 17 00:00:00 2001 From: Josh Sherman Date: Sat, 28 Dec 2013 01:13:02 -0500 Subject: [PATCH] Built out new Display class with tests Not hooked up to the Controller yet, wanted to get Travis setup. --- classes/Config.php | 41 +------ classes/Controller.php | 25 ++--- classes/Display.php | 202 ++++++++++++++++++++++++++++++++++ classes/Module.php | 11 -- pickles.php | 15 ++- tests/bootstrap.php | 16 +++ tests/classes/DisplayTest.php | 149 +++++++++++++++++++++++++ 7 files changed, 386 insertions(+), 73 deletions(-) create mode 100644 classes/Display.php create mode 100644 tests/bootstrap.php create mode 100644 tests/classes/DisplayTest.php diff --git a/classes/Config.php b/classes/Config.php index 866dc00..88e51f6 100644 --- a/classes/Config.php +++ b/classes/Config.php @@ -41,49 +41,12 @@ class Config extends Object * Constructor * * Calls the parent constructor and loads the passed file. - * - * @param string $filename optional Filename of the config */ - public function __construct($filename = null) + public function __construct() { parent::__construct(); - // Try to fine the configuration - if ($filename == null) - { - $filename = 'config.php'; - $loaded = false; - $cwd = getcwd(); - - while ($loaded == false) - { - chdir(dirname($filename)); - - if (getcwd() == '/') - { - throw new Exception('Unable to load configuration.'); - } - - chdir($cwd); - - $filename = '../' . $filename; - $loaded = $this->load($filename); - } - } - else - { - $this->load($filename); - } - } - - /** - * Loads a configuration file - * - * @param string $filename filename of the config file - * @return boolean success of the load process - */ - public function load($filename) - { + $filename = '../config.php'; $environments = false; $environment = false; diff --git a/classes/Controller.php b/classes/Controller.php index 9e83beb..604d6a0 100644 --- a/classes/Controller.php +++ b/classes/Controller.php @@ -236,11 +236,6 @@ class Controller extends Object } } - // Validates the rendering engine - $engines = is_array($module->engine) ? array_values($module->engine) : array($module->engine); - $engines = array_combine($engines, $engines); - $engine = current($engines); - // Possibly overrides the engine with the passed return type if (isset($return_type)) { @@ -256,7 +251,7 @@ class Controller extends Object } // Starts up the display engine - $display_class = 'Display_' . $engine; + $display_class = 'Display_PHP'; $display = new $display_class(); // Assigns the template / template variables @@ -476,18 +471,18 @@ class Controller extends Object $css_class = $module_class; $js_basename = $basename; - if (isset($action)) - { - $module_class .= '_' . $action; - $template_basename .= '/' . $action; - $css_class .= '_' . $action; - $js_basename .= '/' . $action; - } - // Scrubs class names with hyphens + // @todo Unsure this is even relevant anymore if (strpos($module_class, '-') !== false) { - $module_class = preg_replace('/(-(.{1}))/e', 'strtoupper("$2")', $module_class); + $module_class = preg_replace_callback( + '/(-(.{1}))/', + function ($matches) + { + return strtoupper($matches[2]); + }, + $module_class + ); } return array($module_class, $module_filename, $template_basename, $css_class, $js_basename); diff --git a/classes/Display.php b/classes/Display.php new file mode 100644 index 0000000..5695920 --- /dev/null +++ b/classes/Display.php @@ -0,0 +1,202 @@ + + * @copyright Copyright 2007-2013, Joshua Sherman + * @license http://www.opensource.org/licenses/mit-license.html + * @package PICKLES + * @link https://github.com/joshtronic/pickles + */ + +/** + * Display Class + * + * If you can see it then it probably happened in here. + */ +class Display extends Object +{ + /** + * Return Type + * + * This class supports loading a PHP template, displaying JSON, XML and an + * RSS flavored XML. Inside your modules you can specify either a string or + * array. Possible values include "template", "json", "xml" and "rss". + * Default behavior is to try to load a template and fallback to displaying + * JSON. The "template" option always takes precedence when used with the + * other types. + * + * @var mixed string or array to determine how to return + */ + public $return = ['template', 'json']; + + /** + * Template + * + * Templates are found in the ./templates directory of your site. The + * template workflow is to load ./templates/__shared/index.phtml and you + * would set that template up to require $this->template, the path and + * filename for the module template (named based on the structure of the + * requested URI. Inside your module you can specify the basename of the + * parent template you would like to use or false to not use a parent + * template. + * + * @var string or boolean false the basename of the parent template + */ + public $template = 'index'; + + /** + * Meta Data + * + * An array of meta data that you want exposed to the template. Currently + * you set the meta data from inside your module using the class variables + * title, description and keywords. The newer [preferred] method is to + * set an array in your module using the meta variable using title, + * description and keywords as the keys. You can also specify any other + * meta keys in the array that you would like to be exposed to your + * templates. The meta data is only used by TEMPLATE and RSS return types. + */ + public $meta = []; + + /** + * Module Data + * + * Any data the module returns or is assigned inside of the module will + * be available here and exposed to the template. + */ + public $module = null; + + public function render() + { + try + { + // Starts up the buffer so we can capture it + ob_start(); + + if (!is_array($this->return)) + { + $this->return = [ $this->return ]; + } + + $return_json = $return_rss = $return_template = $return_xml = false; + + foreach ($this->return as $return) + { + $variable = 'return_' . $return; + $$variable = true; + } + + // Makes sure the return type is valid + if (!$return_json && !$return_rss && !$return_template && !$return_xml) + { + throw new Exception('Invalid return type.'); + } + + // Checks for the PHPSESSID in the query string + if (stripos($_SERVER['REQUEST_URI'], '?PHPSESSID=') === false) + { + // XHTML compliancy stuff + // @todo Wonder if this could be yanked now that we're in HTML5 land + ini_set('arg_separator.output', '&'); + ini_set('url_rewriter.tags', 'a=href,area=href,frame=src,input=src,fieldset='); + + header('Content-type: text/html; charset=UTF-8'); + } + else + { + // Redirect so Google knows to index the page without the session ID + list($request_uri, $phpsessid) = explode('?PHPSESSID=', $_SERVER['REQUEST_URI'], 2); + header('HTTP/1.1 301 Moved Permanently'); + header('Location: ' . $request_uri); + + throw new Exception('Requested URI contains PHPSESSID, redirecting.'); + } + + // @todo Derrive CSS and JS from _REQUEST['request'] no need to pass around + + $loaded = false; + + if ($return_template) + { + // Determines if we're using a custom class or not + $dynamic_class = (class_exists('CustomDynamic') ? 'CustomDynamic' : 'Dynamic'); + $form_class = (class_exists('CustomForm') ? 'CustomForm' : 'Form'); + $html_class = (class_exists('CustomHTML') ? 'CustomHTML' : 'HTML'); + + // Exposes some objects and variables to the local scope of the template + $this->request = $this->js_file = $_REQUEST['request']; + // @todo replace _ with - as it's more appropriate for CSS naming + $this->css_class = strtr($this->request, '/', '_'); + + // @todo Remove the magic $__variable when all sites are ported + $__config = $this->config; + $__css_class = $this->css_class; + $__js_file = $this->js_file; + $__meta = $this->meta; + $__module = $this->module; + + $__dynamic = $this->dynamic = new $dynamic_class(); + $__form = $this->form = new $form_class(); + $__html = $this->html = new $html_class(); + + // Checks for the parent template and tries to load it + if ($this->template) + { + $parent_file = SITE_TEMPLATE_PATH . '__shared/' . $this->template . '.phtml'; + $child_file = SITE_TEMPLATE_PATH . $_REQUEST['request'] . '.phtml'; + + // Assigns old and new variables + // @todo Remove $__template when all sites are ported + $__template = $this->template = $child_file; + + if (file_exists($parent_file)) + { + $loaded = require_once $parent_file; + } + } + + // Checks for the module template and tries to load it + if (file_exists($child_file)) + { + $loaded = require_once $child_file; + } + } + + if (!$loaded) + { + if ($return_json) + { + echo json_encode($this->module, isset($_REQUEST['pretty']) ? JSON_PRETTY_PRINT : false); + } + elseif ($return_xml) + { + echo Convert::arrayToXML($this->module, isset($_REQUEST['pretty'])); + } + } + + // Grabs the buffer so we can massage it a bit + $buffer = ob_get_clean(); + + // Kills any whitespace and HTML comments in templates + if ($loaded) + { + // The BSA exception is because their system sucks and demands there be comments present + $buffer = preg_replace(['/^[\s]+/m', '//U'], '', $buffer); + } + + return $buffer; + } + catch (Exception $e) + { + return $e->getMessage(); + } + } +} + +?> diff --git a/classes/Module.php b/classes/Module.php index 67e01d4..2eb2def 100644 --- a/classes/Module.php +++ b/classes/Module.php @@ -157,17 +157,6 @@ class Module extends Object */ protected $hash = null; - /** - * Default Display Engine - * - * Defaults to PHP but could be set to JSON, XML or RSS. Value is - * overwritten by the config value if not set by the module. - * - * @access protected - * @var string, null by default - */ - protected $engine = DISPLAY_PHP; - /** * Default Template * diff --git a/pickles.php b/pickles.php index df25119..fc3c003 100644 --- a/pickles.php +++ b/pickles.php @@ -31,8 +31,11 @@ define('PICKLES_PATH', dirname(__FILE__) . '/'); define('PICKLES_CLASS_PATH', PICKLES_PATH . 'classes/'); define('PICKLES_VENDOR_PATH', PICKLES_PATH . 'vendors/'); -// Establishes our site paths -define('SITE_PATH', getcwd() . '/../'); +// Establishes our site paths, sanity check is to allow vfsStream in our tests +if (!defined('SITE_PATH')) +{ + define('SITE_PATH', getcwd() . '/../'); +} define('SITE_CLASS_PATH', SITE_PATH . 'classes/'); define('SITE_MODEL_PATH', SITE_PATH . 'models/'); @@ -42,12 +45,6 @@ define('SITE_TEMPLATE_PATH', SITE_PATH . 'templates/'); define('PRIVATE_PATH', SITE_PATH . 'private/'); define('LOG_PATH', PRIVATE_PATH . 'logs/'); -// Sets up constants for the Display names -define('DISPLAY_JSON', 'JSON'); -define('DISPLAY_PHP', 'PHP'); -define('DISPLAY_RSS', 'RSS'); -define('DISPLAY_XML', 'XML'); - // Creates a variable to flag if we're on the command line define('IS_CLI', !isset($_SERVER['REQUEST_METHOD'])); @@ -168,6 +165,8 @@ function __autoload($class) return $loaded; } +spl_autoload_register('__autoload'); + // }}} // {{{ Error Handler diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..697505e --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,16 @@ + diff --git a/tests/classes/DisplayTest.php b/tests/classes/DisplayTest.php new file mode 100644 index 0000000..23e0c2d --- /dev/null +++ b/tests/classes/DisplayTest.php @@ -0,0 +1,149 @@ +child template'; + + protected function setUp() + { + parent::setUp(); + + $this->shared_templates = SITE_TEMPLATE_PATH . '__shared/'; + + if (!file_exists($this->shared_templates)) + { + mkdir($this->shared_templates, 0644, true); + } + + $_SERVER['REQUEST_URI'] = '/test'; + $_REQUEST['request'] = 'test'; + + $this->display = new Display(); + $this->display->module = [ + 'pickles' => [ + 'yummy' => 'gherkin', + 'delish' => 'kosher dill', + 'yucky' => 'bread & butter' + ] + ]; + } + + protected function tearDown() + { + unlink(SITE_TEMPLATE_PATH . 'test.phtml'); + unlink($this->shared_templates . 'index.phtml'); + } + + public function testInvalidReturnType() + { + $this->display->return = 'invalid'; + $this->assertEquals('Invalid return type.', $this->display->render()); + } + + public function testPHPSESSID() + { + $request_uri = $_SERVER['REQUEST_URI']; + $_SERVER['REQUEST_URI'] .= '?PHPSESSID=session_id'; + $return = $this->display->render(); + + $this->assertTrue(in_array('Location: ' . $request_uri, xdebug_get_headers())); + $this->assertEquals('Requested URI contains PHPSESSID, redirecting.', $return); + } + + public function testNoParentTemplate() + { + file_put_contents(SITE_TEMPLATE_PATH . 'test.phtml', $this->child_html); + + $this->assertEquals($this->child_html, $this->display->render()); + } + + public function testRenderTemplate() + { + file_put_contents(SITE_TEMPLATE_PATH . 'test.phtml', $this->child_html); + + $child = 'template; ?>' . "\n"; + + $html = << + + + +

parent template

+ {$child} + + +HTML; + + file_put_contents($this->shared_templates . 'index.phtml', $html); + + $html = str_replace($child, $this->child_html, $html); + $html = preg_replace(['/^[\s]+/m', '//U'], '', $html); + + $this->assertEquals($html, $this->display->render()); + } + + public function testRenderJSON() + { + $this->assertEquals( + '{"pickles":{"yummy":"gherkin","delish":"kosher dill","yucky":"bread & butter"}}', + $this->display->render() + ); + } + + public function testRenderJSONPrettyPrint() + { + $_REQUEST['pretty'] = 'true'; + + $pretty_json = <<assertEquals($pretty_json, $this->display->render()); + } + + public function testRenderXML() + { + $this->display->return = ['template', 'xml']; + $this->assertEquals( + 'gherkinkosher dill', + $this->display->render() + ); + } + + public function testRenderXMLPrettyPrint() + { + $_REQUEST['pretty'] = 'true'; + + $pretty_xml = <<gherkin +kosher dill + + +XML; + + $this->display->return = ['template', 'xml']; + $this->assertEquals($pretty_xml, $this->display->render()); + } + + /* + public function testRenderRSS() + { + $this->fail('Not yet implemented.'); + } + + public function testRenderRSSPrettyPrint() + { + $this->fail('Not yet implemented.'); + } + */ +} + +?>