diff --git a/alloy/Module/Generator/Controller.php b/alloy/Module/Generator/Controller.php new file mode 100644 index 0000000..734d619 --- /dev/null +++ b/alloy/Module/Generator/Controller.php @@ -0,0 +1,134 @@ +request()->isCli()) { + throw new Alloy\Exception\FileNotFound("Requested file or page not found. Please check the URL and try again."); + } + } + + + /** + * Scaffold module + */ + public function scaffoldAction(Request $request) + { + $kernel = \Kernel(); + + /** + * Variables we expect in CLI request: + * + * $name string Name of module + */ + $name = preg_replace('/[^a-zA-Z0-9_ ]/', '', $kernel->formatUnderscoreWord($request->name)); + $name_table = preg_replace('/\s+/', '_', strtolower($name)); + + // URL-usable name + $name_url = preg_replace('/\s+/', '_', $name); + + // Directory path + $name_dir = preg_replace('/\s+/', '/', $name); + + // Valid PHP namespace + $namespace = preg_replace('/\s+/', '\\', $name); + + // TODO: Make this dynamic and generated (allow user field definitions) + $fields = array( + 'name' => array('type' => 'string', 'required' => true) + ); + $field_string = ""; + foreach($fields as $fieldName => $fieldInfo) { + // Flattens field definitions for writing in Entity.php file + // str_replace calls to remove some of the prettyprinting and odd formats var_export does by default + $field_string .= "'" . $fieldName . "' => " . str_replace( + array("array (", "\n", ",)"), + array("array(", "", ")"), + var_export($fieldInfo, true) + ) . ",\n"; + } + + // Set tag variables + $generatorTagNames = compact('name', 'name_table', 'name_sanitized', 'name_url', 'name_dir', 'namespace', 'fields', 'field_string'); + + echo PHP_EOL; + + // File paths + $scaffoldPath = __DIR__ . '/scaffold/'; + $modulePath = $kernel->config('app.path.root') .'/Module/' . $name_dir . '/'; + + // Output + echo 'Generator Module Info' . PHP_EOL; + echo '-----------------------------------------------------------------------' . PHP_EOL; + echo 'Name = ' . $name . PHP_EOL; + echo 'Namespace = ' . $namespace . PHP_EOL; + echo 'Datasource (Table) = ' . $name_table . PHP_EOL; + echo 'Path = ' . $modulePath . PHP_EOL; + echo '-----------------------------------------------------------------------' . PHP_EOL; + echo PHP_EOL; + + // Variables (in 'generator' namespace): + // * name + // * name_table + // * $fields + // + // Other variables + // * $fields (from Entity::fields()) + + // Build tag replecement set for scaffold-generated files + $generator_tag_start = '{$generator.'; + $generator_tag_end = '}'; + $generatorTags = array(); + foreach($generatorTagNames as $tag => $val) { + if(is_array($val)) { + $val = var_export($val, true); + } + $generatorTags[$generator_tag_start . $tag . $generator_tag_end] = $val; + } + + // Copy files and replace tokens + $scaffoldFiles = array( + 'Controller.php', + 'Entity.php', + 'views/indexAction.html.php', + 'views/newAction.html.php', + 'views/editAction.html.php', + 'views/deleteAction.html.php', + 'views/viewAction.html.php' + ); + foreach($scaffoldFiles as $sFile) { + $tmpl = file_get_contents($scaffoldPath . $sFile); + + // Replace template tags + $tmpl = str_replace(array_keys($generatorTags), array_values($generatorTags), $tmpl); + + // Ensure destination directory exists + $sfDir = dirname($modulePath . $sFile); + if(!is_dir($sfDir)) { + mkdir($sfDir, 0755, true); + } + + // Write file to destination module directory + $result = file_put_contents($modulePath . $sFile, $tmpl); + if($result) { + echo "+ Generated '" . $sFile . "'"; + } else { + echo "[ERROR] Unable to generate '" . $sFile . "'"; + } + echo PHP_EOL; + } + + echo PHP_EOL; + } +} \ No newline at end of file diff --git a/alloy/Module/Generator/scaffold/Controller.php b/alloy/Module/Generator/scaffold/Controller.php new file mode 100644 index 0000000..e09120d --- /dev/null +++ b/alloy/Module/Generator/scaffold/Controller.php @@ -0,0 +1,241 @@ +mapper()->all(self::ENTITY); + $fields = $kernel->mapper()->entityManager()->fields(self::ENTITY); + + // Return 404 if no items + if(!$items) { + return false; + } + + // HTML template + if('html' == $request->format) { + return $this->template(__FUNCTION__) + ->set(compact('items', 'fields')); + } + // Resource object (JSON/XML, etc) + return $kernel->resource($items); + } + + + /** + * View single item + * @method GET + */ + public function viewAction(Request $request) + { + $kernel = \Kernel(); + $mapper = $kernel->mapper(); + + $item = $mapper->get(self::ENTITY, (int) $request->item); + if(!$item) { + return false; + } + + if('html' == $request->format) { + return $this->template(__FUNCTION__) + ->set(compact('item')); + } else { + return $kernel->resource($item); + } + } + + + /** + * Create new item form + * @method GET + */ + public function newAction(Request $request) + { + $kernel = \Kernel(); + $item = $kernel->mapper()->get(self::ENTITY); + + return $this->template(__FUNCTION__) + ->set(array( + 'item' => $item, + 'form' => $this->formView($item)->data($request->query()) + )); + } + + + /** + * Helper to save item + */ + public function saveItem(Entity $item) + { + $kernel = \Kernel(); + $mapper = $kernel->mapper(); + $request = $kernel->request(); + + // Attempt save + if($mapper->save($item)) { + $itemUrl = $kernel->url(array('module' => '{$generator.name_url}', 'item' => $item->id), 'module_item'); + + // HTML + if('html' == $request->format) { + return $kernel->redirect($itemUrl); + // Others (XML, JSON) + } else { + return $kernel->resource($item->data()) + ->created($itemUrl); + } + // Handle errors + } else { + // HTML + if('html' == $request->format) { + // Re-display form + $res = $kernel->spotForm($item); + // Others (XML, JSON) + } else { + $res = $kernel->resource(); + } + + // Set HTTP status and errors + return $res->status(400) + ->errors($item->errors()); + } + } + + + /** + * New item creation + * @method POST + */ + public function postMethod(Request $request) + { + $kernel = \Kernel(); + $mapper = $kernel->mapper(); + + $item = $mapper->get(self::ENTITY); + $item->data($request->post()); + + // Common save functionality + return $this->saveItem($item); + } + + + /** + * Edit form for item + * @method GET + */ + public function editAction(Request $request) + { + $kernel = \Kernel(); + $mapper = $kernel->mapper(); + + $item = $mapper->get(self::ENTITY, (int) $request->item); + if(!$item) { + return false; + } + + return $this->template(__FUNCTION__) + ->set(array( + 'item' => $item, + 'form' => $this->formView($item) + )); + } + + + /** + * Edit existing item + * @method PUT + */ + public function putMethod(Request $request) + { + $kernel = \Kernel(); + $mapper = $kernel->mapper(); + + $item = $mapper->get(self::ENTITY, (int) $request->item); + if(!$item) { + return false; + } + + // Set all POST data that can be set + $item->data($request->post()); + + // Ensure 'id' cannot be modified + $item->id = (int) $request->item; + + // Update 'last modified' date + $item->date_modified = new \DateTime(); + + // Common save functionality + return $this->saveItem($item); + } + + + /** + * Delete confirmation + * @method GET + */ + public function deleteAction(Request $request) + { + $kernel = \Kernel(); + $mapper = $kernel->mapper(); + + $item = $mapper->get(self::ENTITY, (int) $request->item); + if(!$item) { + return false; + } + + return $this->template(__FUNCTION__) + ->set(compact('item')); + } + + + /** + * Delete post + * @method DELETE + */ + public function deleteMethod(Request $request) + { + $kernel = \Kernel(); + $mapper = $kernel->mapper(); + + $item = $mapper->get(self::ENTITY, (int) $request->item); + if(!$item) { + return false; + } + + $mapper->delete($item); + + return $kernel->redirect($kernel->url(array('module' => '{$generator.name_url}'), 'module')); + } + + + /** + * Entity form with Alloy form generic + */ + protected function formView($entity = null) + { + return \Kernel()->spotForm($entity); + } + + + /** + * Automatic install/migrate method + */ + public function install() + { + $mapper = \Kernel()->mapper(); + $mapper->migrate(self::ENTITY); + } +} \ No newline at end of file diff --git a/alloy/Module/Generator/scaffold/Entity.php b/alloy/Module/Generator/scaffold/Entity.php new file mode 100644 index 0000000..83ee402 --- /dev/null +++ b/alloy/Module/Generator/scaffold/Entity.php @@ -0,0 +1,18 @@ + array('type' => 'int', 'primary' => true, 'serial' => true), + {$generator.field_string} + 'date_created' => array('type' => 'datetime', 'default' => new \DateTime()), + 'date_modified' => array('type' => 'datetime', 'default' => new \DateTime()) + ); + } +} \ No newline at end of file diff --git a/alloy/Module/Generator/scaffold/views/deleteAction.html.php b/alloy/Module/Generator/scaffold/views/deleteAction.html.php new file mode 100644 index 0000000..5a5cf14 --- /dev/null +++ b/alloy/Module/Generator/scaffold/views/deleteAction.html.php @@ -0,0 +1,13 @@ +head()->title('Delete Item'); ?> + +

Really delete this item?

+ +generic('Form'); + +$form->action($kernel->url(array('module' => '{$generator.name_url}', 'item' => $item->id), 'module_item')) + ->method('delete') + ->submit('Delete'); +echo $form->content(); +?> \ No newline at end of file diff --git a/alloy/Module/Generator/scaffold/views/editAction.html.php b/alloy/Module/Generator/scaffold/views/editAction.html.php new file mode 100644 index 0000000..28687f3 --- /dev/null +++ b/alloy/Module/Generator/scaffold/views/editAction.html.php @@ -0,0 +1,8 @@ +head()->title('Edit Item'); ?> + +action($kernel->url(array('module' => '{$generator.name_url}', 'item' => $item->id), 'module_item')) + ->method('put'); +echo $form->content(); +?> \ No newline at end of file diff --git a/alloy/Module/Generator/scaffold/views/indexAction.html.php b/alloy/Module/Generator/scaffold/views/indexAction.html.php new file mode 100644 index 0000000..4420e66 --- /dev/null +++ b/alloy/Module/Generator/scaffold/views/indexAction.html.php @@ -0,0 +1,32 @@ +head()->title('Listing Items'); ?> + +

New {$generator.name}

+ +generic('datagrid'); + +// Set data collection to use for rows +$table->data($items); + +// Add each column heading and cell output callback +foreach($fields as $field => $info) { + $table->column($kernel->formatUnderscoreWord($field), function($item) use($field, $info) { + return $item->$field; + }); +} + +// Edit/delete links +$table->column('View', function($item) use($view) { + return $view->link('View', array('module' => '{$generator.name_url}', 'item' => $item->id), 'module_item'); +}); +$table->column('Edit', function($item) use($view) { + return $view->link('Edit', array('module' => '{$generator.name_url}', 'item' => $item->id, 'action' => 'edit'), 'module_item_action'); +}); +$table->column('Delete', function($item) use($view) { + return $view->link('Delete', array('module' => '{$generator.name_url}', 'item' => $item->id, 'action' => 'delete'), 'module_item_action'); +}); + +// Output table +echo $table->content(); +?> diff --git a/alloy/Module/Generator/scaffold/views/newAction.html.php b/alloy/Module/Generator/scaffold/views/newAction.html.php new file mode 100644 index 0000000..098b151 --- /dev/null +++ b/alloy/Module/Generator/scaffold/views/newAction.html.php @@ -0,0 +1,8 @@ +head()->title('New Item'); ?> + +action($kernel->url(array('module' => '{$generator.name_url}'), 'module')) + ->method('post'); +echo $form->content(); +?> \ No newline at end of file diff --git a/alloy/Module/Generator/scaffold/views/viewAction.html.php b/alloy/Module/Generator/scaffold/views/viewAction.html.php new file mode 100644 index 0000000..7cd1298 --- /dev/null +++ b/alloy/Module/Generator/scaffold/views/viewAction.html.php @@ -0,0 +1,9 @@ +head()->title('View Item'); ?> + +data() as $field => $value): ?> +

+ : +

+ + +link('< Listing', array('module' => '{$generator.name_url}'), 'module', array('class' => 'btn')); ?> \ No newline at end of file diff --git a/alloy/Plugin/Spot/Plugin.php b/alloy/Plugin/Spot/Plugin.php index a86554a..2bd54ee 100644 --- a/alloy/Plugin/Spot/Plugin.php +++ b/alloy/Plugin/Spot/Plugin.php @@ -134,8 +134,14 @@ public function autoinstallOnException($content) if($content instanceof \Spot\Exception_Datasource_Missing ||'42S02' == $content->getCode() || false !== stripos($content->getMessage(), 'Base table or view not found')) { - // Table not found - auto-install module to cause Entity migrations + // Last dispatch attempt $ld = $kernel->lastDispatch(); + + // Debug trace message + $mName = is_object($ld['module']) ? get_class($ld['module']) : $ld['module']; + $kernel->trace("PDO Exception on module '" . $mName . "' when dispatching '" . $ld['action'] . "' Attempting auto-install in Spot plugin at " . __METHOD__ . "", $content); + + // Table not found - auto-install module to cause Entity migrations $content = $kernel->dispatch($ld['module'], 'install'); } } diff --git a/alloy/Plugin/Spot/lib/Spot/Adapter/AdapterAbstract.php b/alloy/Plugin/Spot/lib/Spot/Adapter/AdapterAbstract.php index d555d67..f3ea4a2 100644 --- a/alloy/Plugin/Spot/lib/Spot/Adapter/AdapterAbstract.php +++ b/alloy/Plugin/Spot/lib/Spot/Adapter/AdapterAbstract.php @@ -150,17 +150,17 @@ protected function dateTimeObject($format) * * The format of the supplied DSN is in its fullest form: * - * adapter(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true + * adapter(dbsyntax)://username:password@protocol+host/database?option=8&another=true * * * Most variations are allowed: * - * adapter://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 - * adapter://username:password@hostspec/database_name - * adapter://username:password@hostspec - * adapter://username@hostspec - * adapter://hostspec/database - * adapter://hostspec + * adapter://username:password@protocol+host:110//usr/db_file.db?mode=0644 + * adapter://username:password@host/database_name + * adapter://username:password@host + * adapter://username@host + * adapter://host/database + * adapter://host * adapter(dbsyntax) * adapter * @@ -173,7 +173,7 @@ protected function dateTimeObject($format) * + adapter: Database backend used in PHP (mysql, odbc etc.) * + dbsyntax: Database used with regards to SQL syntax etc. * + protocol: Communication protocol to use (tcp, unix etc.) - * + hostspec: Host specification (hostname[:port]) + * + host: Host specification (hostname[:port]) * + database: Database to use on the DBMS server * + username: User name for login * + password: Password for login @@ -191,7 +191,7 @@ public static function parseDSN( $dsn ) 'username' => FALSE, 'password' => FALSE, 'protocol' => FALSE, - 'hostspec' => FALSE, + 'host' => FALSE, 'port' => FALSE, 'socket' => FALSE, 'database' => FALSE, @@ -238,7 +238,7 @@ public static function parseDSN( $dsn ) } // Get (if found): username and password - // $dsn => username:password@protocol+hostspec/database + // $dsn => username:password@protocol+host/database if ( ( $at = strrpos( (string) $dsn, '@' ) ) !== FALSE ) { $str = substr( $dsn, 0, $at ); @@ -254,7 +254,7 @@ public static function parseDSN( $dsn ) } } - // Find protocol and hostspec + // Find protocol and host if ( preg_match( '|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match ) ) { @@ -265,7 +265,7 @@ public static function parseDSN( $dsn ) } else { - // $dsn => protocol+hostspec/database (old format) + // $dsn => protocol+host/database (old format) if ( strpos( $dsn, '+' ) !== FALSE ) { list( $proto, $dsn ) = explode( '+', $dsn, 2 ); @@ -288,11 +288,11 @@ public static function parseDSN( $dsn ) { if ( strpos( $proto_opts, ':' ) !== FALSE ) { - list( $parsed['hostspec'], $parsed['port'] ) = explode( ':', $proto_opts ); + list( $parsed['host'], $parsed['port'] ) = explode( ':', $proto_opts ); } else { - $parsed['hostspec'] = $proto_opts; + $parsed['host'] = $proto_opts; } } elseif ( $parsed['protocol'] == 'unix' ) diff --git a/alloy/Plugin/Spot/lib/Spot/Adapter/AdapterInterface.php b/alloy/Plugin/Spot/lib/Spot/Adapter/AdapterInterface.php index ca8f21f..4e04810 100644 --- a/alloy/Plugin/Spot/lib/Spot/Adapter/AdapterInterface.php +++ b/alloy/Plugin/Spot/lib/Spot/Adapter/AdapterInterface.php @@ -79,6 +79,12 @@ public function create($source, array $data, array $options = array()); public function read(\Spot\Query $query, array $options = array()); + /* + * Count number of rows in source based on conditions + */ + public function count(\Spot\Query $query, array $options = array()); + + /** * Update entity */ @@ -117,4 +123,4 @@ public function createDatabase($database); * Will throw errors if user does not have proper permissions */ public function dropDatabase($database); -} +} \ No newline at end of file diff --git a/alloy/Plugin/Spot/lib/Spot/Adapter/Mongodb.php b/alloy/Plugin/Spot/lib/Spot/Adapter/Mongodb.php index f82647d..b661f4d 100644 --- a/alloy/Plugin/Spot/lib/Spot/Adapter/Mongodb.php +++ b/alloy/Plugin/Spot/lib/Spot/Adapter/Mongodb.php @@ -206,6 +206,24 @@ public function read(\Spot\Query $query, array $options = array()) return $this->toCollection($query, $cursor); } + /* + * Count number of rows in source based on criteria + */ + public function count(\Spot\Query $query, array $options = array()) + { + // Load criteria + $criteria = $this->queryConditions($query); + + //find and return count + $count = $this->mongoCollection($query->datasource)->find($criteria)->count(); + + // Add query to log + Spot_Log::addQuery($this, $criteria); + + // Return count + return is_numeric($count) ? (int)$count : 0; + } + /** * Update entity */ @@ -437,4 +455,4 @@ public function mongoCollection($collectionName) { return $this->mongoDatabase()->$collectionName; } -} +} \ No newline at end of file diff --git a/alloy/Plugin/Spot/lib/Spot/Adapter/Mysql.php b/alloy/Plugin/Spot/lib/Spot/Adapter/Mysql.php index 6c0226a..309b4b2 100644 --- a/alloy/Plugin/Spot/lib/Spot/Adapter/Mysql.php +++ b/alloy/Plugin/Spot/lib/Spot/Adapter/Mysql.php @@ -128,12 +128,14 @@ public function migrateSyntaxFieldCreate($fieldName, array $fieldInfo) if(!isset($this->_fieldTypeMap[$fieldInfo['type']])) { throw new \Spot\Exception("Field type '" . $fieldInfo['type'] . "' not supported"); } - - $fieldInfo = array_merge($fieldInfo, $this->_fieldTypeMap[$fieldInfo['type']]); + //Ensure this class will choose adapter type + unset($fieldInfo['adapter_type']); + + $fieldInfo = array_merge($this->_fieldTypeMap[$fieldInfo['type']],$fieldInfo); $syntax = "`" . $fieldName . "` " . $fieldInfo['adapter_type']; // Column type and length - $syntax .= ($fieldInfo['length']) ? '(' . $fieldInfo['length'] . ')' : ''; + $syntax .= is_int($fieldInfo['length']) ? '(' . $fieldInfo['length'] . ')' : ''; // Unsigned $syntax .= ($fieldInfo['unsigned']) ? ' unsigned' : ''; // Collate @@ -355,7 +357,7 @@ public function migrateSyntaxTableUpdate($table, array $formattedFields, array $ } // Extra - $syntax .= "\n) ENGINE=" . $options['engine'] . " DEFAULT CHARSET=" . $options['charset'] . " COLLATE=" . $options['collate'] . ";"; + $syntax .= ",\n ENGINE=" . $options['engine'] . " DEFAULT CHARSET=" . $options['charset'] . " COLLATE=" . $options['collate'] . ";"; return $syntax; } diff --git a/alloy/Plugin/Spot/lib/Spot/Adapter/PDO/Abstract.php b/alloy/Plugin/Spot/lib/Spot/Adapter/PDO/Abstract.php index a5f193e..d1dc04a 100644 --- a/alloy/Plugin/Spot/lib/Spot/Adapter/PDO/Abstract.php +++ b/alloy/Plugin/Spot/lib/Spot/Adapter/PDO/Abstract.php @@ -28,7 +28,7 @@ public function connection() // Establish connection try { - $dsn = $dsnp['adapter'].':host='.$dsnp['hostspec'].';dbname='.$dsnp['database']; + $dsn = $dsnp['adapter'].':host='.$dsnp['host'].';dbname='.$dsnp['database']; $this->_connection = new \PDO($dsn, $dsnp['username'], $dsnp['password'], $this->_options); // Throw exceptions by default $this->_connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); @@ -180,11 +180,13 @@ public function migrateTableUpdate($table, array $formattedFields, array $option // Run SQL $this->connection()->exec($sql); } catch(\PDOException $e) { - // Table does not exist + // Table does not exist - special Exception case if($e->getCode() == "42S02") { throw new \Spot\Exception_Datasource_Missing("Table '" . $table . "' does not exist"); } - return false; + + // Re-throw exception + throw $e; } } return true; @@ -248,7 +250,9 @@ public function create($datasource, array $data, array $options = array()) if($stmt) { // Execute if($stmt->execute($binds)) { - $result = $this->connection()->lastInsertId(); + // Use 'id' if PK exists, otherwise returns true + $id = $this->connection()->lastInsertId(); + $result = $id ? $id : true; } else { $result = false; } @@ -260,7 +264,9 @@ public function create($datasource, array $data, array $options = array()) if($e->getCode() == "42S02") { throw new \Spot\Exception_Datasource_Missing("Table or datasource '" . $datasource . "' does not exist"); } - return false; + + // Re-throw exception + throw $e; } return $result; @@ -320,17 +326,69 @@ public function read(\Spot\Query $query, array $options = array()) } else { $result = false; } - } catch(PDOException $e) { + } catch(\PDOException $e) { // Table does not exist if($e->getCode() == "42S02") { throw new \Spot\Exception_Datasource_Missing("Table or datasource '" . $query->datasource . "' does not exist"); } + + // Re-throw exception throw $e; } return $result; } + /* + * Count number of rows in source based on conditions + */ + public function count(\Spot\Query $query, array $options = array()) + { + $conditions = $this->statementConditions($query->conditions); + $binds = $this->statementBinds($query->params()); + $sql = " + SELECT COUNT(*) as count + FROM " . $query->datasource . " + " . ($conditions ? 'WHERE ' . $conditions : '') . " + " . ($query->group ? 'GROUP BY ' . implode(', ', $query->group) : ''); + + // Unset any NULL values in binds (compared as "IS NULL" and "IS NOT NULL" in SQL instead) + if($binds && count($binds) > 0) { + foreach($binds as $field => $value) { + if(null === $value) { + unset($binds[$field]); + } + } + } + + // Add query to log + \Spot\Log::addQuery($this, $sql,$binds); + + $result = false; + try { + // Prepare count query + $stmt = $this->connection()->prepare($sql); + + //if prepared, execute + if($stmt && $stmt->execute($binds)) { + //the count is returned in the first column + $result = (int) $stmt->fetchColumn(); + } else { + $result = false; + } + } catch(\PDOException $e) { + // Table does not exist + if($e->getCode() == "42S02") { + throw new \Spot\Exception_Datasource_Missing("Table or datasource '" . $query->datasource . "' does not exist"); + } + + // Re-throw exception + throw $e; + } + + return $result; + } + /** * Update entity */ @@ -378,7 +436,9 @@ public function update($datasource, array $data, array $where = array(), array $ if($e->getCode() == "42S02") { throw new \Spot\Exception_Datasource_Missing("Table or datasource '" . $datasource . "' does not exist"); } - return false; + + // Re-throw exception + throw $e; } } else { $result = false; @@ -422,7 +482,9 @@ public function delete($datasource, array $data, array $options = array()) if($e->getCode() == "42S02") { throw new \Spot\Exception_Datasource_Missing("Table or datasource '" . $datasource . "' does not exist"); } - return false; + + // Re-throw exception + throw $e; } } @@ -444,7 +506,9 @@ public function truncateDatasource($datasource) { if($e->getCode() == "42S02") { throw new \Spot\Exception_Datasource_Missing("Table or datasource '" . $datasource . "' does not exist"); } - return false; + + // Re-throw exception + throw $e; } } @@ -466,7 +530,9 @@ public function dropDatasource($datasource) { if($e->getCode() == "42S02") { throw new \Spot\Exception_Datasource_Missing("Table or datasource '" . $datasource . "' does not exist"); } - return false; + + // Re-throw exception + throw $e; } } @@ -745,4 +811,4 @@ protected function bindValues($stmt, array $binds) } return true; } -} +} \ No newline at end of file diff --git a/alloy/Plugin/Spot/lib/Spot/Config.php b/alloy/Plugin/Spot/lib/Spot/Config.php index 6151a2c..6b1aabe 100644 --- a/alloy/Plugin/Spot/lib/Spot/Config.php +++ b/alloy/Plugin/Spot/lib/Spot/Config.php @@ -5,95 +5,109 @@ * @package Spot * @link http://spot.os.ly */ -class Config +class Config implements \Serializable { - protected $_defaultConnection; - protected $_connections = array(); - - - /** - * Add database connection - * - * @param string $name Unique name for the connection - * @param string $dsn DSN string for this connection - * @param array $options Array of key => value options for adapter - * @param boolean $defaut Use this connection as the default? The first connection added is automatically set as the default, even if this flag is false. - * @return Spot_Adapter_Interface Spot adapter instance - * @throws Spot_Exception - */ - public function addConnection($name, $dsn, array $options = array(), $default = false) - { - // Connection name must be unique - if(isset($this->_connections[$name])) { - throw new Exception("Connection for '" . $name . "' already exists. Connection name must be unique."); - } - - $dsnp = \Spot\Adapter\AdapterAbstract::parseDSN($dsn); - $adapterClass = "\\Spot\\Adapter\\" . ucfirst($dsnp['adapter']); - $adapter = new $adapterClass($dsn, $options); - - // Set as default connection? - if(true === $default || null === $this->_defaultConnection) { - $this->_defaultConnection = $name; - } - - // Store connection and return adapter instance - $this->_connections[$name] = $adapter; - return $adapter; - } - - - /** - * Get connection by name - * - * @param string $name Unique name of the connection to be returned - * @return Spot_Adapter_Interface Spot adapter instance - * @throws Spot_Exception - */ - public function connection($name = null) - { - if(null === $name) { - return $this->defaultConnection(); - } - - // Connection name must be unique - if(!isset($this->_connections[$name])) { - return false; - } - - return $this->_connections[$name]; - } - - - /** - * Get default connection - * - * @return Spot_Adapter_Interface Spot adapter instance - * @throws Spot_Exception - */ - public function defaultConnection() - { - return $this->_connections[$this->_defaultConnection]; - } - - - /** - * Class loader - * - * @param string $className Name of class to load - */ - public static function loadClass($className) - { - $loaded = false; - - // Require Spot namespaced files by assumed folder structure (naming convention) - if(false !== strpos($className, "Spot\\")) { - $classFile = trim(str_replace("\\", "/", str_replace("_", "/", str_replace('Spot\\', '', $className))), '\\'); - $loaded = require_once(__DIR__ . "/" . $classFile . ".php"); - } - - return $loaded; - } + protected $_defaultConnection; + protected $_connections = array(); + + + /** + * Add database connection + * + * @param string $name Unique name for the connection + * @param string $dsn DSN string for this connection + * @param array $options Array of key => value options for adapter + * @param boolean $defaut Use this connection as the default? The first connection added is automatically set as the default, even if this flag is false. + * @return Spot_Adapter_Interface Spot adapter instance + * @throws Spot_Exception + */ + public function addConnection($name, $dsn, array $options = array(), $default = false) + { + // Connection name must be unique + if(isset($this->_connections[$name])) { + throw new Exception("Connection for '" . $name . "' already exists. Connection name must be unique."); + } + + $dsnp = \Spot\Adapter\AdapterAbstract::parseDSN($dsn); + $adapterClass = "\\Spot\\Adapter\\" . ucfirst($dsnp['adapter']); + $adapter = new $adapterClass($dsn, $options); + + // Set as default connection? + if(true === $default || null === $this->_defaultConnection) { + $this->_defaultConnection = $name; + } + + // Store connection and return adapter instance + $this->_connections[$name] = $adapter; + return $adapter; + } + + + /** + * Get connection by name + * + * @param string $name Unique name of the connection to be returned + * @return Spot_Adapter_Interface Spot adapter instance + * @throws Spot_Exception + */ + public function connection($name = null) + { + if(null === $name) { + return $this->defaultConnection(); + } + + // Connection name must be unique + if(!isset($this->_connections[$name])) { + return false; + } + + return $this->_connections[$name]; + } + + + /** + * Get default connection + * + * @return Spot_Adapter_Interface Spot adapter instance + * @throws Spot_Exception + */ + public function defaultConnection() + { + return $this->_connections[$this->_defaultConnection]; + } + + + /** + * Class loader + * + * @param string $className Name of class to load + */ + public static function loadClass($className) + { + $loaded = false; + + // Require Spot namespaced files by assumed folder structure (naming convention) + if(false !== strpos($className, "Spot\\")) { + $classFile = trim(str_replace("\\", "/", str_replace("_", "/", str_replace('Spot\\', '', $className))), '\\'); + $loaded = require_once(__DIR__ . "/" . $classFile . ".php"); + } + + return $loaded; + } + + + /** + * Default serialization behavior is to not attempt to serialize stored + * adapter connections at all (thanks @TheSavior re: Issue #7) + */ + public function serialize() + { + return serialize(array()); + } + + public function unserialize($serialized) + { + } } diff --git a/alloy/Plugin/Spot/lib/Spot/Entity.php b/alloy/Plugin/Spot/lib/Spot/Entity.php index 36c5322..1f39a39 100644 --- a/alloy/Plugin/Spot/lib/Spot/Entity.php +++ b/alloy/Plugin/Spot/lib/Spot/Entity.php @@ -17,6 +17,9 @@ abstract class Entity protected $_data = array(); protected $_dataModified = array(); + // Entity error messages (may be present after save attempt) + protected $_errors = array(); + /** * Constructor - allows setting of object properties with array on construct @@ -62,10 +65,10 @@ public static function datasource($ds = null) /** * Datasource options getter/setter */ - public static function datasourceOptions($ds = null) + public static function datasourceOptions($dsOpts = null) { - if(null !== $ds) { - static::$_datasourceOptions = $ds; + if(null !== $dsOpts) { + static::$_datasourceOptions = $dsOpts; return $this; } return static::$_datasourceOptions; @@ -156,6 +159,61 @@ public function toArray() { return $this->data(); } + + + /** + * Check if any errors exist + * + * @param string $field OPTIONAL field name + * @return boolean + */ + public function hasErrors($field = null) + { + if(null !== $field) { + return isset($this->_errors[$field]) ? count($this->_errors[$field]) > 0 : false; + } + return count($this->_errors) > 0; + } + + + /** + * Error message getter/setter + * + * @param $field string|array String return errors with field key, array sets errors + * @return self|array|boolean Setter return self, getter returns array or boolean if key given and not found + */ + public function errors($msgs = null) + { + // Return errors for given field + if(is_string($msgs)) { + return isset($this->_errors[$msgs]) ? $this->_errors[$msgs] : array(); + + // Set error messages from given array + } elseif(is_array($msgs)) { + $this->_errors = $msgs; + } + return $this->_errors; + } + + + /** + * Add an error to error messages array + * + * @param string $field Field name that error message relates to + * @param mixed $msg Error message text - String or array of messages + */ + public function error($field, $msg) + { + if(is_array($msg)) { + // Add array of error messages about field + foreach($msg as $msgx) { + $this->_errors[$field][] = $msgx; + } + } else { + // Add to error array + $this->_errors[$field][] = $msg; + } + } /** diff --git a/alloy/Plugin/Spot/lib/Spot/Entity/Manager.php b/alloy/Plugin/Spot/lib/Spot/Entity/Manager.php index 439e0dc..3ac15d0 100644 --- a/alloy/Plugin/Spot/lib/Spot/Entity/Manager.php +++ b/alloy/Plugin/Spot/lib/Spot/Entity/Manager.php @@ -110,7 +110,7 @@ public function fields($entityName) } // Format field will full set of default options - if(isset($fieldInfo['type']) && isset($fieldTypeDefaults[$fieldOpts['type']])) { + if(isset($fieldOpts['type']) && isset($fieldTypeDefaults[$fieldOpts['type']])) { // Include type defaults $fieldOpts = array_merge($fieldDefaults, $fieldTypeDefaults[$fieldOpts['type']], $fieldOpts); } else { diff --git a/alloy/Plugin/Spot/lib/Spot/Mapper.php b/alloy/Plugin/Spot/lib/Spot/Mapper.php index 01793e5..8cd3d4f 100644 --- a/alloy/Plugin/Spot/lib/Spot/Mapper.php +++ b/alloy/Plugin/Spot/lib/Spot/Mapper.php @@ -210,6 +210,12 @@ public function collection($entityName, $cursor) $results = array(); $resultsIdentities = array(); + // Ensure PDO only gives key => value pairs, not index-based fields as well + // Raw PDOStatement objects generally only come from running raw SQL queries or other custom stuff + if($cursor instanceof \PDOStatement) { + $cursor->setFetchMode(\PDO::FETCH_ASSOC); + } + // Fetch all results into new entity class // @todo Move this to collection class so entities will be lazy-loaded by Collection iteration foreach($cursor as $data) { @@ -310,21 +316,17 @@ public function create($entityClass, array $data) /** * Find records with custom query * - * @throws \Spot\Exception + * @param string $entityName Name of the entity class + * @param string $sql Raw query or SQL to run against the datastore + * @param array Optional $conditions Array of binds in column => value pairs to use for prepared statement */ - public function query() - { - $args = func_get_args(); - - // Remove entityName (first element) - $entityName = array_shift($args); - - $result = $this->connection($entityName)->query($args); + public function query($entityName, $sql, array $params = array()) + { + $result = $this->connection($entityName)->query($sql, $params); if($result) { - return $this->collection($result); - } else { - return false; + return $this->collection($entityName, $result); } + return false; } @@ -599,18 +601,14 @@ public function validate($entity) foreach($this->fields($entityName) as $field => $fieldAttrs) { if(isset($fieldAttrs['required']) && true === $fieldAttrs['required']) { // Required field - if(empty($entity->$field)) { - $this->error($field, "Required field '" . $field . "' was left blank"); + if($this->isEmpty($entity->$field)) { + $entity->error($field, "Required field '" . $field . "' was left blank"); } } } - // Check for errors - if($this->hasErrors()) { - return false; - } else { - return true; - } + // Return error result + return !$entity->hasErrors(); } @@ -622,18 +620,21 @@ public function validate($entity) */ public function isEmpty($value) { - return (empty($value) && 0 !== $value); + return empty($value) && !is_numeric($value); } /** * Check if any errors exist * + * @deprecated Please use Entity::hasErrors instead * @param string $field OPTIONAL field name * @return boolean */ public function hasErrors($field = null) { + trigger_error('Error checks at the Mapper level have been deprecated in favor of checking error at the Entity level. Please use Entity::hasErrors instead. Mapper error methods will be completely removed in v1.0.', E_DEPRECATED); + if(null !== $field) { return isset($this->_errors[$field]) ? count($this->_errors[$field]) : false; } @@ -644,13 +645,16 @@ public function hasErrors($field = null) /** * Get array of error messages * + * @deprecated Please use Entity::errors instead * @return array */ public function errors($msgs = null) { + trigger_error('Error checks at the Mapper level have been deprecated in favor of checking error at the Entity level. Please use Entity::errors instead. Mapper error methods will be completely removed in v1.0.', E_DEPRECATED); + // Return errors for given field if(is_string($msgs)) { - return isset($this->_errors[$field]) ? $this->_errors[$field] : array(); + return isset($this->_errors[$msgs]) ? $this->_errors[$msgs] : array(); // Set error messages from given array } elseif(is_array($msgs)) { @@ -670,6 +674,9 @@ public function errors($msgs = null) */ public function error($field, $msg) { + // Deprecation warning + trigger_error('Adding errors at the Mapper level have been deprecated in favor of adding errors at the Entity level. Please use Entity::error instead. Mapper error methods will be completely removed in v1.0.', E_DEPRECATED); + if(is_array($msg)) { // Add array of error messages about field foreach($msg as $msgx) { diff --git a/alloy/Plugin/Spot/lib/Spot/Query.php b/alloy/Plugin/Spot/lib/Spot/Query.php index 4471de5..65d9ea4 100644 --- a/alloy/Plugin/Spot/lib/Spot/Query.php +++ b/alloy/Plugin/Spot/lib/Spot/Query.php @@ -280,9 +280,11 @@ public function params() */ public function count() { - // Execute query and return count - $result = $this->execute(); - return ($result !== false) ? count($result) : 0; + // Execute query + $result = $this->mapper()->connection($this->entityName())->count($this); + + //return count + return is_numeric($result) ? $result : 0; } diff --git a/alloy/Plugin/Spot/lib/Spot/Relation/RelationAbstract.php b/alloy/Plugin/Spot/lib/Spot/Relation/RelationAbstract.php index a610ad9..a559053 100644 --- a/alloy/Plugin/Spot/lib/Spot/Relation/RelationAbstract.php +++ b/alloy/Plugin/Spot/lib/Spot/Relation/RelationAbstract.php @@ -38,7 +38,7 @@ public function __construct(\Spot\Mapper $mapper, \Spot\Entity $entity, array $r // Checks ... if(null === $this->_entityName) { - throw new \InvalidArgumentException("Relation description key 'entity' not set."); + throw new \InvalidArgumentException("Relation description key 'entity' must be set to an Entity class name."); } } @@ -119,8 +119,8 @@ public function relationOrder() public function __toString() { // Load related records for current row - $success = $this->findAllRelation(); - return ($success) ? "1" : "0"; + $res = $this->execute(); + return ($res) ? "1" : "0"; } diff --git a/alloy/Plugin/Spot/lib/Spot/tests/Test/CRUD.php b/alloy/Plugin/Spot/lib/Spot/tests/Test/CRUD.php index 0d9814d..029467e 100644 --- a/alloy/Plugin/Spot/lib/Spot/tests/Test/CRUD.php +++ b/alloy/Plugin/Spot/lib/Spot/tests/Test/CRUD.php @@ -77,6 +77,6 @@ public function testSampleNewsDelete() $post = $mapper->first('Entity_Post', array('title' => "Test Post Modified")); $result = $mapper->delete($post); - $this->assertTrue($result); + $this->assertTrue((boolean) $result); } } \ No newline at end of file diff --git a/alloy/Plugin/Spot/lib/Spot/tests/Test/Config.php b/alloy/Plugin/Spot/lib/Spot/tests/Test/Config.php index 204e030..7181f83 100644 --- a/alloy/Plugin/Spot/lib/Spot/tests/Test/Config.php +++ b/alloy/Plugin/Spot/lib/Spot/tests/Test/Config.php @@ -11,6 +11,22 @@ public function testAddConnectionWithDSNString() { $cfg = new \Spot\Config(); $adapter = $cfg->addConnection('test_mysql', 'mysql://test:password@localhost/test'); - $this->assertTrue($adapter instanceof \Spot\Adapter\Mysql); + $this->assertInstanceOf('\Spot\Adapter\Mysql', $adapter); } + + public function testConfigCanSerialize() + { + $cfg = new \Spot\Config(); + $adapter = $cfg->addConnection('test_mysql', 'mysql://test:password@localhost/test'); + + $this->assertInternalType('string', serialize($cfg)); + } + + public function testConfigCanUnserialize() + { + $cfg = new \Spot\Config(); + $adapter = $cfg->addConnection('test_mysql', 'mysql://test:password@localhost/test'); + + $this->assertInstanceOf('\Spot\Config', unserialize(serialize($cfg))); + } } \ No newline at end of file diff --git a/alloy/Plugin/Spot/lib/Spot/tests/Test/Entity.php b/alloy/Plugin/Spot/lib/Spot/tests/Test/Entity.php index 949ce95..0bedb91 100644 --- a/alloy/Plugin/Spot/lib/Spot/tests/Test/Entity.php +++ b/alloy/Plugin/Spot/lib/Spot/tests/Test/Entity.php @@ -54,4 +54,30 @@ public function testEntitySetDataConstruct() $this->assertEquals($testData, $data); } + + public function testEntityErrors() + { + $post = new Entity_Post(array( + 'title' => 'My Awesome Post', + 'body' => '

Body

' + )); + $postErrors = array( + 'title' => array('Title cannot contain the word awesome') + ); + + // Has NO errors + $this->assertTrue(!$post->hasErrors()); + + // Set errors + $post->errors($postErrors); + + // Has errors + $this->assertTrue($post->hasErrors()); + + // Full error array + $this->assertEquals($postErrors, $post->errors()); + + // Errors for one key only + $this->assertEquals($postErrors['title'], $post->errors('title')); + } } \ No newline at end of file diff --git a/alloy/Plugin/Spot/lib/Spot/tests/Test/Relations.php b/alloy/Plugin/Spot/lib/Spot/tests/Test/Relations.php index 95824c7..6f23c2d 100644 --- a/alloy/Plugin/Spot/lib/Spot/tests/Test/Relations.php +++ b/alloy/Plugin/Spot/lib/Spot/tests/Test/Relations.php @@ -54,12 +54,12 @@ public function testBlogCommentsRelationInsertByObject($postId) 'name' => 'Testy McTester', 'email' => 'test@test.com', 'body' => 'This is a test comment. Yay!', - 'date_created' => $mapper->connection('Entity_Post_Comment')->dateTime() + 'date_created' => new \DateTime() )); try { $commentSaved = $mapper->save($comment); if(!$commentSaved) { - print_r($mapper->errors()); + print_r($comment->errors()); $this->fail("Comment NOT saved"); } } catch(Exception $e) { diff --git a/alloy/config/routes.php b/alloy/config/routes.php index 27e4076..013e173 100644 --- a/alloy/config/routes.php +++ b/alloy/config/routes.php @@ -16,7 +16,8 @@ ->post(array('action' => 'post')); $router->route('module', '/<:module>(.<:format>)') // :format optional - ->defaults(array('action' => 'index', 'format' => 'html')); + ->defaults(array('action' => 'index', 'format' => 'html')) + ->post(array('action' => 'post')); $router->route('default', '/') - ->defaults(array('module' => 'Home', 'action' => 'index', 'format' => 'html')); + ->defaults(array('module' => 'Home', 'action' => 'index', 'format' => 'html')); \ No newline at end of file diff --git a/alloy/lib/Alloy/Client.php b/alloy/lib/Alloy/Client.php deleted file mode 100644 index 6b3c07d..0000000 --- a/alloy/lib/Alloy/Client.php +++ /dev/null @@ -1,141 +0,0 @@ - value parameters to pass - */ - public function get($url, array $params = array()) - { - return $this->_fetch($url, $params, 'GET'); - } - - - /** - * POST - * - * @param string $url URL to perform action on - * @param optional array $params Array of key => value parameters to pass - */ - public function post($url, array $params = array()) - { - return $this->_fetch($url, $params, 'POST'); - } - - - /** - * PUT - * - * @param string $url URL to perform action on - * @param optional array $params Array of key => value parameters to pass - */ - public function put($url, array $params = array()) - { - return $this->_fetch($url, $params, 'PUT'); - } - - - /** - * DELETE - * - * @param string $url URL to perform action on - * @param optional array $params Array of key => value parameters to pass - */ - public function delete($url, array $params = array()) - { - return $this->_fetch($url, $params, 'DELETE'); - } - - - /** - * Fetch a URL with given parameters - */ - protected function _fetch($url, array $params = array(), $method = 'GET') - { - $method = strtoupper($method); - - $urlParts = parse_url($url); - $queryString = http_build_query($params); - - // Append params to URL as query string if not a POST - if(strtoupper($method) != 'POST') { - $url = $url . "?" . $queryString; - } - - //echo $url; - //var_dump("Fetching External URL: [" . $method . "] " . $url, $params); - - // Use cURL - if(function_exists('curl_init')) { - $ch = curl_init($urlParts['host']); - - // METHOD differences - switch($method) { - case 'GET': - curl_setopt($ch, CURLOPT_URL, $url . "?" . $queryString); - break; - case 'POST': - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $queryString); - break; - - case 'PUT': - curl_setopt($ch, CURLOPT_URL, $url); - $putData = file_put_contents("php://memory", $queryString); - curl_setopt($ch, CURLOPT_PUT, true); - curl_setopt($ch, CURLOPT_INFILE, $putData); - curl_setopt($ch, CURLOPT_INFILESIZE, strlen($queryString)); - break; - - case 'DELETE': - curl_setopt($ch, CURLOPT_URL, $url . "?" . $queryString); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); - break; - } - - - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the data - curl_setopt($ch, CURLOPT_HEADER, false); // Get headers - - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_MAXREDIRS, 5); - - // HTTP digest authentication - if(isset($urlParts['user']) && isset($urlParts['pass'])) { - $authHeaders = array("Authorization: Basic ".base64_encode($urlParts['user'].':'.$urlParts['pass'])); - curl_setopt($ch, CURLOPT_HTTPHEADER, $authHeaders); - } - - $response = curl_exec($ch); - $responseInfo = curl_getinfo($ch); - curl_close($ch); - - // Use sockets... (eventually) - } else { - throw new Exception(__METHOD__ . " Requres the cURL library to work."); - } - - // Only return false on 404 or 500 errors for now - if($responseInfo['http_code'] == 404 || $responseInfo['http_code'] == 500) { - $response = false; - } - - return $response; - } -} \ No newline at end of file diff --git a/alloy/lib/Alloy/Kernel.php b/alloy/lib/Alloy/Kernel.php index 3dc07bc..add7f10 100644 --- a/alloy/lib/Alloy/Kernel.php +++ b/alloy/lib/Alloy/Kernel.php @@ -15,6 +15,8 @@ */ class Kernel { + const VERSION = '0.8.0'; + protected static $self; protected static $cfg = array(); @@ -63,10 +65,22 @@ protected function __construct(array $config = array()) // Save memory starting point static::$traceMemoryStart = memory_get_usage(); static::$traceTimeStart = microtime(true); + // Set last as current starting for good zero-base static::$traceMemoryLast = static::$traceMemoryStart; static::$traceTimeLast = static::$traceTimeStart; } + + + /** + * Return current Alloy version string + * + * @return string Current Alloy version in dot-notation + */ + public function version() + { + return static::VERSION; + } /** @@ -127,7 +141,7 @@ public function config($value = null, $default = false) */ public function factory($className, array $params = array()) { - $instanceHash = md5($className . var_export($params, true)); + $instanceHash = md5($className . serialize($params)); // Return already instantiated object instance if set if(isset($this->instances[$instanceHash])) { @@ -214,15 +228,6 @@ public function request() } - /** - * Get HTTP REST client - */ - public function client() - { - return $this->factory(__NAMESPACE__ . '\Client'); - } - - /** * Send HTTP response header * @@ -397,7 +402,7 @@ public function module($module, $init = true, $dispatchAction = null) * @throws \InvalidArgumentException When plugin is not found by name * @return object */ - public function plugin($plugin, $init = true) + public function plugin($plugin, array $pluginConfig = array(), $init = true) { // Module plugin // ex: 'Module\User' @@ -413,17 +418,42 @@ public function plugin($plugin, $init = true) } // Ensure class exists / can be loaded - if(!class_exists($sPluginClass, (boolean)$init)) { - if ($init) { - throw new \InvalidArgumentException("Unable to load plugin '" . $sPluginClass . "'. Remove from app config or ensure plugin files exist in 'app' or 'alloy' load paths."); + if(!class_exists($sPluginClass, (boolean) $init)) { + if($init) { + throw new \InvalidArgumentException("Unable to load plugin '" . $sPluginClass . "'. Remove from app configuration file or ensure plugin files exist in 'app' or 'alloy' load paths."); } return false; } // Instantiate module class - $sPluginObject = new $sPluginClass($this); - return $sPluginObject; + //$sPluginObject = new $sPluginClass($this); + return $this->factory($sPluginClass, array($this, $pluginConfig)); + } + + + /** + * Load plugins if provided + * + * @param array $plugins Array of plugin names to load or key => config array format + * Example: + * array('plugin1', 'plugin2', 'plugin3' => array('foo' => 'bar', 'bar' => 'baz'), 'plugin4') + */ + public function loadPlugins(array $plugins) + { + // Load plugins + if($plugins) { + foreach($plugins as $pluginName => $pluginConfig) { + // Config name supplied without config array - need to shift params + if(is_numeric($pluginName)) { + $pluginName = $pluginConfig; + $pluginConfig = array(); + } + + // Load plugin + $plugin = $this->plugin($pluginName, $pluginConfig); + } + } } @@ -800,4 +830,13 @@ public function __call($method, $args) throw new \BadMethodCallException("Method '" . __CLASS__ . "::" . $method . "' not found or the command is not a valid callback type."); } } + + + /** + * Prevent PHP from trying to serialize cached object instances on Kernel + */ + public function __sleep() + { + return array(); + } } \ No newline at end of file diff --git a/alloy/lib/Alloy/PluginAbstract.php b/alloy/lib/Alloy/PluginAbstract.php new file mode 100644 index 0000000..810d089 --- /dev/null +++ b/alloy/lib/Alloy/PluginAbstract.php @@ -0,0 +1,37 @@ +kernel = $kernel; + $this->config = $config; + + // Initialize plugin code + $this->init(); + } + + + /** + * Initialization work for plugin + */ + abstract public function init(); +} diff --git a/alloy/lib/Alloy/Request.php b/alloy/lib/Alloy/Request.php index 15ba86b..afcce9f 100644 --- a/alloy/lib/Alloy/Request.php +++ b/alloy/lib/Alloy/Request.php @@ -13,9 +13,12 @@ */ class Request { + // Request URL + protected $_url; + // Request parameters protected $_params = array(); - + /** * Ensure magic quotes are not mucking up request data @@ -32,16 +35,61 @@ public function __construct() array_walk_recursive($_COOKIE, $stripslashes_gpc); array_walk_recursive($_REQUEST, $stripslashes_gpc); } + + // Properly handle PUT and DELETE request params + if($this->isPut() || $this->isDelete()) { + parse_str(file_get_contents('php://input'), $params); + $this->params($params); + } } + + + /** + * Return requested URL path + * + * Works for HTTP(S) requests and CLI requests using the -u flag for URL dispatch emulation + * + * @return string Requested URL path segement + */ + public function url() + { + if(null === $this->_url) { + if($this->isCli()) { + // CLI request + $cliArgs = getopt("u:"); + + $requestUrl = isset($cliArgs['u']) ? $cliArgs['u'] : '/'; + $qs = parse_url($requestUrl, PHP_URL_QUERY); + $cliRequestParams = array(); + parse_str($qs, $cliRequestParams); + + // Set parsed query params back on request object + $this->setParams($cliRequestParams); + + // Set requestUrl and remove query string if present so router can parse it as expected + if($qsPos = strpos($requestUrl, '?')) { + $requestUrl = substr($requestUrl, 0, $qsPos); + } + + } else { + // HTTP request + $requestUrl = $this->get('u', '/'); + } + $this->_url = $requestUrl; + } + + return $this->_url; + } + /** - * Access values contained in the superglobals as public members - * Order of precedence: 1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV - * - * @see http://msdn.microsoft.com/en-us/library/system.web.httprequest.item.aspx - * @param string $key - * @return mixed - */ + * Access values contained in the superglobals as public members + * Order of precedence: 1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV + * + * @see http://msdn.microsoft.com/en-us/library/system.web.httprequest.item.aspx + * @param string $key + * @return mixed + */ public function get($key, $default = null) { switch (true) { @@ -219,7 +267,8 @@ public function param($key = null, $default = null) public function query($key = null, $default = null) { if (null === $key) { - return $_GET; + // Return _GET params without routing param or other params set by Alloy or manually on the request object + return array_diff_key($_GET, $this->param() + array('u' => 1)); } return (isset($_GET[$key])) ? $_GET[$key] : $default; @@ -333,20 +382,64 @@ public function header($header) /** - * Return the method by which the request was made + * Return the method by which the request was made. Always returns HTTP_METHOD in UPPERCASE. * - * @return string + * @return string HTTP Request method in UPPERCASE */ public function method() { - $method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET'; + $sm = strtoupper($this->server('REQUEST_METHOD', 'GET')); - // Emulate REST for browsers - if($method == "POST" && $this->post('_method')) { - $method = strtoupper($this->post('_method')); + // POST + '_method' override to emulate REST behavior in browsers that do not support it + if('POST' == $sm && $this->get('_method')) { + return strtoupper($this->get('_method')); } - return $method; + return $sm; + } + + + /** + * Request URI, as seen by PHP + * + * @return string request URI from $_SERVER superglobal + */ + public function uri() + { + return $this->server('REQUEST_URI'); + } + + + /** + * Request scheme (http, https, or cli) + * + * @return string 'http', 'https', or 'cli' + */ + public function scheme() + { + return ($this->isCli() ? 'cli' : ($this->isSecure() ? 'https' : 'http' )); + } + + + /** + * Request HTTP_HOST + * + * @return String request host from $_SERVER superglobal + */ + public function host() + { + return $this->server('HTTP_HOST'); + } + + + /** + * Request port + * + * @return integer request port + */ + public function port() + { + return $this->server('SERVER_PORT'); } @@ -435,6 +528,17 @@ public function isHead() { return ($this->method() == "HEAD"); } + + + /** + * Determine is incoming request is OPTIONS + * + * @return boolean + */ + public function isOptions() + { + return ($this->method() == "OPTIONS"); + } /** diff --git a/alloy/lib/Alloy/Response.php b/alloy/lib/Alloy/Response.php index 025b7e1..7249adb 100644 --- a/alloy/lib/Alloy/Response.php +++ b/alloy/lib/Alloy/Response.php @@ -93,6 +93,9 @@ public function encoding($encoding = null) */ public function content($content = null) { + if(null === $content) { + return (string) $this->_content; + } $this->_content = $content; } public function appendContent($content) diff --git a/alloy/lib/Alloy/Router.php b/alloy/lib/Alloy/Router.php index 0bfae8c..76efb6b 100644 --- a/alloy/lib/Alloy/Router.php +++ b/alloy/lib/Alloy/Router.php @@ -145,14 +145,17 @@ protected function routeMatch(Router_Route $route, $method, $url) throw new \InvalidArgumentException("Error matching URL to route params: matched(" . count($matches) . ") != named(" . count($namedParamsMatched) . ")"); } $params = array_combine(array_keys($namedParamsMatched), $matches); - - + if(strtoupper($method) != "GET") { - // Default REST behavior is to be 'greedy' and always use the REST method defaults if supplied - $params = array_merge($route->namedParams(), $route->defaults(), $params, $route->methodDefaults($method)); + // 1) Determine which actions are set in $params that are also in 'methodDefaults' + // 2) Override the 'methodDefaults' with the explicitly set $params + $setParams = array_filter(array_intersect_key($params, $route->methodDefaults($method))); + $methodParams = array_merge($route->namedParams(), $route->defaults(), $params, $route->methodDefaults($method), $setParams); + $params = $methodParams; } else { $params = array_merge($route->namedParams(), $route->defaults(), $route->methodDefaults($method), $params); } + //$params = array_merge($route->namedParams(), $route->defaults(), $route->methodDefaults($method), $params); } } return array_map('urldecode', $params); diff --git a/alloy/lib/Alloy/View/Generic/Treeview.php b/alloy/lib/Alloy/View/Generic/Treeview.php index 50932bf..ad092d8 100644 --- a/alloy/lib/Alloy/View/Generic/Treeview.php +++ b/alloy/lib/Alloy/View/Generic/Treeview.php @@ -198,6 +198,17 @@ public function filter($callback) } + /** + * Get currnet level + * + * @param int $level Current level + */ + public function level() + { + return self::$_level; + } + + /** * Set minimum level at which to begin item display * @@ -220,17 +231,6 @@ public function levelMax($level = null) $this->set('levelMax', $level); return $this; } - - - /** - * Get currnet level - * - * @param int $level Current level - */ - public function level() - { - return self::$_level; - } /** diff --git a/alloy/lib/Alloy/View/Generic/templates/cellgrid.html.php b/alloy/lib/Alloy/View/Generic/templates/cellgrid.html.php index a306e5c..875ae43 100644 --- a/alloy/lib/Alloy/View/Generic/templates/cellgrid.html.php +++ b/alloy/lib/Alloy/View/Generic/templates/cellgrid.html.php @@ -23,11 +23,11 @@ $ri = 0; endif; endforeach; - else: + elseif(isset($noDataCallback)): ?> - + diff --git a/alloy/lib/Alloy/View/Generic/templates/datagrid.html.php b/alloy/lib/Alloy/View/Generic/templates/datagrid.html.php index b309dfe..bdafa53 100644 --- a/alloy/lib/Alloy/View/Generic/templates/datagrid.html.php +++ b/alloy/lib/Alloy/View/Generic/templates/datagrid.html.php @@ -3,7 +3,7 @@ $ci = 0; ?> - +
$colOpts): $ci++; ?> diff --git a/alloy/lib/Alloy/View/Generic/templates/form.html.php b/alloy/lib/Alloy/View/Generic/templates/form.html.php index 49430ec..cf01289 100644 --- a/alloy/lib/Alloy/View/Generic/templates/form.html.php +++ b/alloy/lib/Alloy/View/Generic/templates/form.html.php @@ -21,14 +21,13 @@ $fieldData = (isset($fieldOpts['default']) && is_scalar($fieldOpts['default'])) ? $fieldOpts['default'] : null; } ?> -
+
+
' . $fieldOpts['before'] . '' : ''; - ?> - - ' . $fieldOpts['before'] . '' : ''; + // Adjust field depending on field type switch($fieldType) { case 'text': @@ -65,12 +64,12 @@ default: echo $form->input($fieldType, $fieldName, $fieldData); } - ?> - - ' . $fieldOpts['after'] . '' : ''; + echo isset($fieldOpts['after']) ? '' . $fieldOpts['after'] . '' : ''; + echo isset($fieldOpts['help']) ? '' . $fieldOpts['help'] . '' : ''; ?> +
@@ -88,13 +87,13 @@ endif; endforeach; ?> - +
submit()): ?> -
- +
+
diff --git a/alloy/lib/Alloy/View/Generic/templates/treeview.html.php b/alloy/lib/Alloy/View/Generic/templates/treeview.html.php index ea9f2ac..a2c2df5 100644 --- a/alloy/lib/Alloy/View/Generic/templates/treeview.html.php +++ b/alloy/lib/Alloy/View/Generic/templates/treeview.html.php @@ -2,10 +2,10 @@ $currentLevel = $view::$_level; // Check if we're okay to display against min level set -$levelMinCheck = (!$levelMin || $currentLevel > $levelMin); +$levelMinCheck = (!$levelMin || $currentLevel >= $levelMin); // Check if we're okay to display against max level set -$levelMaxCheck = (!$levelMax || $currentLevel < $levelMax); +$levelMaxCheck = (!$levelMax || $currentLevel <= $levelMax); if($levelMaxCheck): @@ -38,7 +38,7 @@ // Ensure we can go to next level // Don't show children if current level is equal to max - if(!$levelMax || ($levelMaxCheck && $currentLevel != $levelMax)): + if($levelMaxCheck && $currentLevel != $levelMax): // Item children (hierarchy) if(isset($itemChildrenCallback)): $children = $itemChildrenCallback($item); diff --git a/alloy/lib/Alloy/View/Template.php b/alloy/lib/Alloy/View/Template.php index 4089980..66e07e7 100644 --- a/alloy/lib/Alloy/View/Template.php +++ b/alloy/lib/Alloy/View/Template.php @@ -120,13 +120,12 @@ public function block($name, $closure = null) * @return mixed value if the key is found * @return null if key is not found */ - public function get($var) + public function get($var, $default = null) { if(isset($this->_vars[$var])) { return $this->_vars[$var]; - } else { - return null; } + return $default; } diff --git a/alloy/lib/Zend/Console/Getopt.php b/alloy/lib/Zend/Console/Getopt.php new file mode 100644 index 0000000..d603566 --- /dev/null +++ b/alloy/lib/Zend/Console/Getopt.php @@ -0,0 +1,970 @@ + self::MODE_ZEND, + self::CONFIG_DASHDASH => true, + self::CONFIG_IGNORECASE => false, + self::CONFIG_PARSEALL => true, + ); + + /** + * Stores the command-line arguments for the calling applicaion. + * + * @var array + */ + protected $_argv = array(); + + /** + * Stores the name of the calling applicaion. + * + * @var string + */ + protected $_progname = ''; + + /** + * Stores the list of legal options for this application. + * + * @var array + */ + protected $_rules = array(); + + /** + * Stores alternate spellings of legal options. + * + * @var array + */ + protected $_ruleMap = array(); + + /** + * Stores options given by the user in the current invocation + * of the application, as well as parameters given in options. + * + * @var array + */ + protected $_options = array(); + + /** + * Stores the command-line arguments other than options. + * + * @var array + */ + protected $_remainingArgs = array(); + + /** + * State of the options: parsed or not yet parsed? + * + * @var boolean + */ + protected $_parsed = false; + + /** + * The constructor takes one to three parameters. + * + * The first parameter is $rules, which may be a string for + * gnu-style format, or a structured array for Zend-style format. + * + * The second parameter is $argv, and it is optional. If not + * specified, $argv is inferred from the global argv. + * + * The third parameter is an array of configuration parameters + * to control the behavior of this instance of Getopt; it is optional. + * + * @param array $rules + * @param array $argv + * @param array $getoptConfig + * @return void + */ + public function __construct($rules, $argv = null, $getoptConfig = array()) + { + if (!isset($_SERVER['argv'])) { + require_once 'Zend/Console/Getopt/Exception.php'; + if (ini_get('register_argc_argv') == false) { + throw new Zend_Console_Getopt_Exception( + "argv is not available, because ini option 'register_argc_argv' is set Off" + ); + } else { + throw new Zend_Console_Getopt_Exception( + '$_SERVER["argv"] is not set, but Zend_Console_Getopt cannot work without this information.' + ); + } + } + + $this->_progname = $_SERVER['argv'][0]; + $this->setOptions($getoptConfig); + $this->addRules($rules); + if (!is_array($argv)) { + $argv = array_slice($_SERVER['argv'], 1); + } + if (isset($argv)) { + $this->addArguments((array)$argv); + } + } + + /** + * Return the state of the option seen on the command line of the + * current application invocation. This function returns true, or the + * parameter to the option, if any. If the option was not given, + * this function returns null. + * + * The magic __get method works in the context of naming the option + * as a virtual member of this class. + * + * @param string $key + * @return string + */ + public function __get($key) + { + return $this->getOption($key); + } + + /** + * Test whether a given option has been seen. + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + return isset($this->_options[$key]); + } + return false; + } + + /** + * Set the value for a given option. + * + * @param string $key + * @param string $value + * @return void + */ + public function __set($key, $value) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + $this->_options[$key] = $value; + } + } + + /** + * Return the current set of options and parameters seen as a string. + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Unset an option. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + $this->parse(); + if (isset($this->_ruleMap[$key])) { + $key = $this->_ruleMap[$key]; + unset($this->_options[$key]); + } + } + + /** + * Define additional command-line arguments. + * These are appended to those defined when the constructor was called. + * + * @param array $argv + * @throws Zend_Console_Getopt_Exception When not given an array as parameter + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function addArguments($argv) + { + if(!is_array($argv)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Parameter #1 to addArguments should be an array"); + } + $this->_argv = array_merge($this->_argv, $argv); + $this->_parsed = false; + return $this; + } + + /** + * Define full set of command-line arguments. + * These replace any currently defined. + * + * @param array $argv + * @throws Zend_Console_Getopt_Exception When not given an array as parameter + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setArguments($argv) + { + if(!is_array($argv)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Parameter #1 to setArguments should be an array"); + } + $this->_argv = $argv; + $this->_parsed = false; + return $this; + } + + /** + * Define multiple configuration options from an associative array. + * These are not program options, but properties to configure + * the behavior of Zend_Console_Getopt. + * + * @param array $getoptConfig + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setOptions($getoptConfig) + { + if (isset($getoptConfig)) { + foreach ($getoptConfig as $key => $value) { + $this->setOption($key, $value); + } + } + return $this; + } + + /** + * Define one configuration option as a key/value pair. + * These are not program options, but properties to configure + * the behavior of Zend_Console_Getopt. + * + * @param string $configKey + * @param string $configValue + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setOption($configKey, $configValue) + { + if ($configKey !== null) { + $this->_getoptConfig[$configKey] = $configValue; + } + return $this; + } + + /** + * Define additional option rules. + * These are appended to the rules defined when the constructor was called. + * + * @param array $rules + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function addRules($rules) + { + $ruleMode = $this->_getoptConfig['ruleMode']; + switch ($this->_getoptConfig['ruleMode']) { + case self::MODE_ZEND: + if (is_array($rules)) { + $this->_addRulesModeZend($rules); + break; + } + // intentional fallthrough + case self::MODE_GNU: + $this->_addRulesModeGnu($rules); + break; + default: + /** + * Call addRulesModeFoo() for ruleMode 'foo'. + * The developer should subclass Getopt and + * provide this method. + */ + $method = '_addRulesMode' . ucfirst($ruleMode); + $this->$method($rules); + } + $this->_parsed = false; + return $this; + } + + /** + * Return the current set of options and parameters seen as a string. + * + * @return string + */ + public function toString() + { + $this->parse(); + $s = array(); + foreach ($this->_options as $flag => $value) { + $s[] = $flag . '=' . ($value === true ? 'true' : $value); + } + return implode(' ', $s); + } + + /** + * Return the current set of options and parameters seen + * as an array of canonical options and parameters. + * + * Clusters have been expanded, and option aliases + * have been mapped to their primary option names. + * + * @return array + */ + public function toArray() + { + $this->parse(); + $s = array(); + foreach ($this->_options as $flag => $value) { + $s[] = $flag; + if ($value !== true) { + $s[] = $value; + } + } + return $s; + } + + /** + * Return the current set of options and parameters seen in Json format. + * + * @return string + */ + public function toJson() + { + $this->parse(); + $j = array(); + foreach ($this->_options as $flag => $value) { + $j['options'][] = array( + 'option' => array( + 'flag' => $flag, + 'parameter' => $value + ) + ); + } + + /** + * @see Zend_Json + */ + require_once 'Zend/Json.php'; + $json = Zend_Json::encode($j); + + return $json; + } + + /** + * Return the current set of options and parameters seen in XML format. + * + * @return string + */ + public function toXml() + { + $this->parse(); + $doc = new DomDocument('1.0', 'utf-8'); + $optionsNode = $doc->createElement('options'); + $doc->appendChild($optionsNode); + foreach ($this->_options as $flag => $value) { + $optionNode = $doc->createElement('option'); + $optionNode->setAttribute('flag', utf8_encode($flag)); + if ($value !== true) { + $optionNode->setAttribute('parameter', utf8_encode($value)); + } + $optionsNode->appendChild($optionNode); + } + $xml = $doc->saveXML(); + return $xml; + } + + /** + * Return a list of options that have been seen in the current argv. + * + * @return array + */ + public function getOptions() + { + $this->parse(); + return array_keys($this->_options); + } + + /** + * Return the state of the option seen on the command line of the + * current application invocation. + * + * This function returns true, or the parameter value to the option, if any. + * If the option was not given, this function returns false. + * + * @param string $flag + * @return mixed + */ + public function getOption($flag) + { + $this->parse(); + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + if (isset($this->_ruleMap[$flag])) { + $flag = $this->_ruleMap[$flag]; + if (isset($this->_options[$flag])) { + return $this->_options[$flag]; + } + } + return null; + } + + /** + * Return the arguments from the command-line following all options found. + * + * @return array + */ + public function getRemainingArgs() + { + $this->parse(); + return $this->_remainingArgs; + } + + /** + * Return a useful option reference, formatted for display in an + * error message. + * + * Note that this usage information is provided in most Exceptions + * generated by this class. + * + * @return string + */ + public function getUsageMessage() + { + $usage = "Usage: {$this->_progname} [ options ]\n"; + $maxLen = 20; + $lines = array(); + foreach ($this->_rules as $rule) { + $flags = array(); + if (is_array($rule['alias'])) { + foreach ($rule['alias'] as $flag) { + $flags[] = (strlen($flag) == 1 ? '-' : '--') . $flag; + } + } + $linepart['name'] = implode('|', $flags); + if (isset($rule['param']) && $rule['param'] != 'none') { + $linepart['name'] .= ' '; + switch ($rule['param']) { + case 'optional': + $linepart['name'] .= "[ <{$rule['paramType']}> ]"; + break; + case 'required': + $linepart['name'] .= "<{$rule['paramType']}>"; + break; + } + } + if (strlen($linepart['name']) > $maxLen) { + $maxLen = strlen($linepart['name']); + } + $linepart['help'] = ''; + if (isset($rule['help'])) { + $linepart['help'] .= $rule['help']; + } + $lines[] = $linepart; + } + foreach ($lines as $linepart) { + $usage .= sprintf("%s %s\n", + str_pad($linepart['name'], $maxLen), + $linepart['help']); + } + return $usage; + } + + /** + * Define aliases for options. + * + * The parameter $aliasMap is an associative array + * mapping option name (short or long) to an alias. + * + * @param array $aliasMap + * @throws Zend_Console_Getopt_Exception + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setAliases($aliasMap) + { + foreach ($aliasMap as $flag => $alias) + { + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + $alias = strtolower($alias); + } + if (!isset($this->_ruleMap[$flag])) { + continue; + } + $flag = $this->_ruleMap[$flag]; + if (isset($this->_rules[$alias]) || isset($this->_ruleMap[$alias])) { + $o = (strlen($alias) == 1 ? '-' : '--') . $alias; + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$o\" is being defined more than once."); + } + $this->_rules[$flag]['alias'][] = $alias; + $this->_ruleMap[$alias] = $flag; + } + return $this; + } + + /** + * Define help messages for options. + * + * The parameter $help_map is an associative array + * mapping option name (short or long) to the help string. + * + * @param array $helpMap + * @return Zend_Console_Getopt Provides a fluent interface + */ + public function setHelp($helpMap) + { + foreach ($helpMap as $flag => $help) + { + if (!isset($this->_ruleMap[$flag])) { + continue; + } + $flag = $this->_ruleMap[$flag]; + $this->_rules[$flag]['help'] = $help; + } + return $this; + } + + /** + * Parse command-line arguments and find both long and short + * options. + * + * Also find option parameters, and remaining arguments after + * all options have been parsed. + * + * @return Zend_Console_Getopt|null Provides a fluent interface + */ + public function parse() + { + if ($this->_parsed === true) { + return; + } + $argv = $this->_argv; + $this->_options = array(); + $this->_remainingArgs = array(); + while (count($argv) > 0) { + if ($argv[0] == '--') { + array_shift($argv); + if ($this->_getoptConfig[self::CONFIG_DASHDASH]) { + $this->_remainingArgs = array_merge($this->_remainingArgs, $argv); + break; + } + } + if (substr($argv[0], 0, 2) == '--') { + $this->_parseLongOption($argv); + } else if (substr($argv[0], 0, 1) == '-' && ('-' != $argv[0] || count($argv) >1)) { + $this->_parseShortOptionCluster($argv); + } else if($this->_getoptConfig[self::CONFIG_PARSEALL]) { + $this->_remainingArgs[] = array_shift($argv); + } else { + /* + * We should put all other arguments in _remainingArgs and stop parsing + * since CONFIG_PARSEALL is false. + */ + $this->_remainingArgs = array_merge($this->_remainingArgs, $argv); + break; + } + } + $this->_parsed = true; + return $this; + } + + /** + * Parse command-line arguments for a single long option. + * A long option is preceded by a double '--' character. + * Long options may not be clustered. + * + * @param mixed &$argv + * @return void + */ + protected function _parseLongOption(&$argv) + { + $optionWithParam = ltrim(array_shift($argv), '-'); + $l = explode('=', $optionWithParam, 2); + $flag = array_shift($l); + $param = array_shift($l); + if (isset($param)) { + array_unshift($argv, $param); + } + $this->_parseSingleOption($flag, $argv); + } + + /** + * Parse command-line arguments for short options. + * Short options are those preceded by a single '-' character. + * Short options may be clustered. + * + * @param mixed &$argv + * @return void + */ + protected function _parseShortOptionCluster(&$argv) + { + $flagCluster = ltrim(array_shift($argv), '-'); + foreach (str_split($flagCluster) as $flag) { + $this->_parseSingleOption($flag, $argv); + } + } + + /** + * Parse command-line arguments for a single option. + * + * @param string $flag + * @param mixed $argv + * @throws Zend_Console_Getopt_Exception + * @return void + */ + protected function _parseSingleOption($flag, &$argv) + { + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + if (!isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" is not recognized.", + $this->getUsageMessage()); + } + $realFlag = $this->_ruleMap[$flag]; + switch ($this->_rules[$realFlag]['param']) { + case 'required': + if (count($argv) > 0) { + $param = array_shift($argv); + $this->_checkParameterType($realFlag, $param); + } else { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires a parameter.", + $this->getUsageMessage()); + } + break; + case 'optional': + if (count($argv) > 0 && substr($argv[0], 0, 1) != '-') { + $param = array_shift($argv); + $this->_checkParameterType($realFlag, $param); + } else { + $param = true; + } + break; + default: + $param = true; + } + $this->_options[$realFlag] = $param; + } + + /** + * Return true if the parameter is in a valid format for + * the option $flag. + * Throw an exception in most other cases. + * + * @param string $flag + * @param string $param + * @throws Zend_Console_Getopt_Exception + * @return bool + */ + protected function _checkParameterType($flag, $param) + { + $type = 'string'; + if (isset($this->_rules[$flag]['paramType'])) { + $type = $this->_rules[$flag]['paramType']; + } + switch ($type) { + case 'word': + if (preg_match('/\W/', $param)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires a single-word parameter, but was given \"$param\".", + $this->getUsageMessage()); + } + break; + case 'integer': + if (preg_match('/\D/', $param)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"$flag\" requires an integer parameter, but was given \"$param\".", + $this->getUsageMessage()); + } + break; + case 'string': + default: + break; + } + return true; + } + + /** + * Define legal options using the gnu-style format. + * + * @param string $rules + * @return void + */ + protected function _addRulesModeGnu($rules) + { + $ruleArray = array(); + + /** + * Options may be single alphanumeric characters. + * Options may have a ':' which indicates a required string parameter. + * No long options or option aliases are supported in GNU style. + */ + preg_match_all('/([a-zA-Z0-9]:?)/', $rules, $ruleArray); + foreach ($ruleArray[1] as $rule) { + $r = array(); + $flag = substr($rule, 0, 1); + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flag = strtolower($flag); + } + $r['alias'][] = $flag; + if (substr($rule, 1, 1) == ':') { + $r['param'] = 'required'; + $r['paramType'] = 'string'; + } else { + $r['param'] = 'none'; + } + $this->_rules[$flag] = $r; + $this->_ruleMap[$flag] = $flag; + } + } + + /** + * Define legal options using the Zend-style format. + * + * @param array $rules + * @throws Zend_Console_Getopt_Exception + * @return void + */ + protected function _addRulesModeZend($rules) + { + foreach ($rules as $ruleCode => $helpMessage) + { + // this may have to translate the long parm type if there + // are any complaints that =string will not work (even though that use + // case is not documented) + if (in_array(substr($ruleCode, -2, 1), array('-', '='))) { + $flagList = substr($ruleCode, 0, -2); + $delimiter = substr($ruleCode, -2, 1); + $paramType = substr($ruleCode, -1); + } else { + $flagList = $ruleCode; + $delimiter = $paramType = null; + } + if ($this->_getoptConfig[self::CONFIG_IGNORECASE]) { + $flagList = strtolower($flagList); + } + $flags = explode('|', $flagList); + $rule = array(); + $mainFlag = $flags[0]; + foreach ($flags as $flag) { + if (empty($flag)) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Blank flag not allowed in rule \"$ruleCode\"."); + } + if (strlen($flag) == 1) { + if (isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"-$flag\" is being defined more than once."); + } + $this->_ruleMap[$flag] = $mainFlag; + $rule['alias'][] = $flag; + } else { + if (isset($this->_rules[$flag]) || isset($this->_ruleMap[$flag])) { + require_once 'Zend/Console/Getopt/Exception.php'; + throw new Zend_Console_Getopt_Exception( + "Option \"--$flag\" is being defined more than once."); + } + $this->_ruleMap[$flag] = $mainFlag; + $rule['alias'][] = $flag; + } + } + if (isset($delimiter)) { + switch ($delimiter) { + case self::PARAM_REQUIRED: + $rule['param'] = 'required'; + break; + case self::PARAM_OPTIONAL: + default: + $rule['param'] = 'optional'; + } + switch (substr($paramType, 0, 1)) { + case self::TYPE_WORD: + $rule['paramType'] = 'word'; + break; + case self::TYPE_INTEGER: + $rule['paramType'] = 'integer'; + break; + case self::TYPE_STRING: + default: + $rule['paramType'] = 'string'; + } + } else { + $rule['param'] = 'none'; + } + $rule['help'] = $helpMessage; + $this->_rules[$mainFlag] = $rule; + } + } + +} diff --git a/alloy/lib/Zend/Console/Getopt/Exception.php b/alloy/lib/Zend/Console/Getopt/Exception.php new file mode 100644 index 0000000..cabb406 --- /dev/null +++ b/alloy/lib/Zend/Console/Getopt/Exception.php @@ -0,0 +1,66 @@ +usage = $usage; + parent::__construct($message); + } + + /** + * Returns the usage + * + * @return string + */ + public function getUsageMessage() + { + return $this->usage; + } +} diff --git a/alloy/lib/Zend/Exception.php b/alloy/lib/Zend/Exception.php new file mode 100644 index 0000000..e838f51 --- /dev/null +++ b/alloy/lib/Zend/Exception.php @@ -0,0 +1,96 @@ +_previous = $previous; + } else { + parent::__construct($msg, (int) $code, $previous); + } + } + + /** + * Overloading + * + * For PHP < 5.3.0, provides access to the getPrevious() method. + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, array $args) + { + if ('getprevious' == strtolower($method)) { + return $this->_getPrevious(); + } + return null; + } + + /** + * String representation of the exception + * + * @return string + */ + public function __toString() + { + if (version_compare(PHP_VERSION, '5.3.0', '<')) { + if (null !== ($e = $this->getPrevious())) { + return $e->__toString() + . "\n\nNext " + . parent::__toString(); + } + } + return parent::__toString(); + } + + /** + * Returns previous Exception + * + * @return Exception|null + */ + protected function _getPrevious() + { + return $this->_previous; + } +} diff --git a/alloy/tests/Test/Router/Url/Rest.php b/alloy/tests/Test/Router/Url/Rest.php index 33ffb9f..9ed7b9b 100644 --- a/alloy/tests/Test/Router/Url/Rest.php +++ b/alloy/tests/Test/Router/Url/Rest.php @@ -64,4 +64,54 @@ public function testRouteRestWithOptionalParam() $this->assertEquals('164', $params['id']); $this->assertEquals('html', $params['format']); } + + public function testRouteRestWithAction() + { + $router = $this->router; + $route = $router->route('test', '/<:module>/<:action>') + ->defaults(array('format' => 'html', 'action' => 'index')) + ->get(array('action' => 'view')); + + // Match URL + $params = $router->match("GET", "/user/list"); + + // Check matched params + $this->assertEquals('user', $params['module']); + $this->assertEquals('list', $params['action']); + $this->assertEquals('html', $params['format']); + } + + public function testRouteRestWithActionPost() + { + $router = $this->router; + $route = $router->route('test', '/<:module>(/<:action>)') + ->defaults(array('format' => 'html', 'action' => 'index')) + ->post(array('action' => 'new')); + + // Match URL with action + $params = $router->match("POST", "/user/list"); + + // Check matched params + $this->assertEquals('user', $params['module']); + // Expect to preserve action POSTED to + $this->assertEquals('list', $params['action']); + $this->assertEquals('html', $params['format']); + } + + public function testRouteRestWithoutActionPost() + { + $router = $this->router; + $route = $router->route('test', '/<:module>(/<:action>)') + ->defaults(array('format' => 'html', 'action' => 'index')) + ->post(array('action' => 'new')); + + // Match URL without action + $params = $router->match("POST", "/user"); + + // Check matched params + $this->assertEquals('user', $params['module']); + // Expect to fill-in with supplied method action 'new' + $this->assertEquals('new', $params['action']); + $this->assertEquals('html', $params['format']); + } } \ No newline at end of file diff --git a/app/Module/Filebrowser/Controller.php b/app/Module/Filebrowser/Controller.php index fc67081..5ef9f5a 100755 --- a/app/Module/Filebrowser/Controller.php +++ b/app/Module/Filebrowser/Controller.php @@ -115,7 +115,7 @@ public function imageSizeAction(Alloy\Request $request) // Resize to requested width/height $image = $kernel->imagine() ->open($imagePath . '/' . $image) - ->thumbnail(new Imagine\Image\Box($width, $height), Imagine\ImageInterface::THUMBNAIL_INSET) + ->thumbnail(new Imagine\Image\Box($width, $height), Imagine\Image\ImageInterface::THUMBNAIL_INSET) ->save($resizeDir . $image); // Send image content to browser @@ -199,28 +199,11 @@ public function postMethod(Alloy\Request $request) // =========================================================================== if($saveResult) { - // CKEditor custom response - // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/File_Browser_(Uploader)/Custom_File_Browser - if($request->get('CKEditor')) { - $callback = $request->get('CKEditorFuncNum'); - $url = $kernel->config('cms.url.files') . $subDir . '/' . $fileName; - $err = ''; - - // CKEditor relies on receiving this custom callback after successful upload - return ' - - '; - } - // Redirect to images or files if($subDir = 'images') { - return $kernel->redirect($kernel->url(array('action' => 'images'), 'filebrowser')); + return $kernel->redirect($kernel->url(array('action' => 'images'), 'filebrowser', $request->query())); } - return $kernel->redirect($kernel->url(array('action' => 'files'), 'filebrowser')); + return $kernel->redirect($kernel->url(array('action' => 'files'), 'filebrowser', $request->query())); } else { return $kernel->resource() ->status(400) @@ -228,6 +211,14 @@ public function postMethod(Alloy\Request $request) 'file' => array('Unable to upload file') )); } + return $kernel->redirect($kernel->url(array('action' => 'images'), 'filebrowser', $request->query())); + } + public function postAction($request) + { + if($request->isPost()) { + return $this->postMethod($request); + } + return false; } diff --git a/app/Module/Filebrowser/File.php b/app/Module/Filebrowser/File.php index 937517e..3e914c1 100644 --- a/app/Module/Filebrowser/File.php +++ b/app/Module/Filebrowser/File.php @@ -58,11 +58,11 @@ public function getUrl() public function getSizeUrl($width, $height) { $kernel = \Kernel(); - $siteId = $kernel->site()->id; + $site = $kernel->site(); $filesDir = $kernel->config('cms.path.files') . 'images/'; $imageFile = str_replace($filesDir, '', $this->getPathname()); return $kernel->url(array( - 'site_id' => $siteId, + 'site' => $site->shortname(), 'width' => $width, 'height' => $height, 'image' => $imageFile diff --git a/app/Module/Filebrowser/Plugin.php b/app/Module/Filebrowser/Plugin.php index c5b0a34..49923ea 100644 --- a/app/Module/Filebrowser/Plugin.php +++ b/app/Module/Filebrowser/Plugin.php @@ -19,13 +19,13 @@ public function __construct(Alloy\Kernel $kernel) $this->kernel = $kernel; // Link to FileBrowser module to get an image link - $kernel->addMethod('filebrowserSelectImageLink', function($fieldId = null) use($kernel) { - return 'Select Image...'; + $kernel->addMethod('filebrowserSelectImageLink', function($field_fill_id = null, $size = null) use($kernel) { + return 'Select Image...'; }); // Link to FileBrowser module to get an image link - $kernel->addMethod('filebrowserSelectFileLink', function($fieldId = null) use($kernel) { - return 'Select File...'; + $kernel->addMethod('filebrowserSelectFileLink', function($fieldId = null, $size = null) use($kernel) { + return 'Select File...'; }); } } \ No newline at end of file diff --git a/app/Module/Filebrowser/views/directoryList.html.php b/app/Module/Filebrowser/views/directoryList.html.php index 5029a31..7ddb36e 100644 --- a/app/Module/Filebrowser/views/directoryList.html.php +++ b/app/Module/Filebrowser/views/directoryList.html.php @@ -2,6 +2,14 @@ use Module\Filebrowser\File; $request = $kernel->request(); +$size = null; +$width = $height = 100; +if($request->size) { + // Requested image size dimensions + $size = explode('x', $request->size, 2); + $width = $size[0]; + $height = isset($size[1]) ? $size[1] : $width; +} ?>
@@ -39,12 +47,13 @@ $grid = $view->generic('cellgrid'); $grid->data($files) ->columns(6) - ->cell(function($mFile) { + ->cell(function($mFile) use($size, $width, $height) { // Get into File object $file = new File($mFile); ?> isImage()): // Image ?>
+ - <?php echo $file->getFilename(); ?> + + <?php echo $file->getFilename(); ?>
diff --git a/app/Module/Filebrowser/views/newAction.html.php b/app/Module/Filebrowser/views/newAction.html.php index bb4aa85..305b2ef 100644 --- a/app/Module/Filebrowser/views/newAction.html.php +++ b/app/Module/Filebrowser/views/newAction.html.php @@ -1,7 +1,7 @@ generic('form') ->type('upload') - ->action($view->url(array('action' => 'new'), 'filebrowser')) + ->action($view->url(array('action' => 'post'), 'filebrowser', \Kernel()->request()->query())) ->method('post') ->fields(array( 'upload' => array('type' => 'file') diff --git a/app/Module/Page/Controller.php b/app/Module/Page/Controller.php index 2dfcb7b..3d7ebd2 100755 --- a/app/Module/Page/Controller.php +++ b/app/Module/Page/Controller.php @@ -9,7 +9,7 @@ class Controller extends Stackbox\Module\ControllerAbstract { protected $_path = __DIR__; - + /** * @method GET */ @@ -19,8 +19,8 @@ public function indexAction(Request $request) return $this->viewUrl($request->page); } - - + + /** * View page by URL */ @@ -30,7 +30,7 @@ public function viewUrl($pageUrl) $request = $kernel->request(); $user = $kernel->user(); $site = $kernel->site(); - + // Ensure page exists $mapper = $kernel->mapper(); $pageMapper = $kernel->mapper('Module\Page\Mapper'); @@ -40,11 +40,12 @@ public function viewUrl($pageUrl) if($pageUrl == '/') { // Create new page for the homepage automatically if it does not exist $page = $pageMapper->get('Module\Page\Entity'); - $page->site_id = $kernel->config('cms.site.id'); + $page->site_id = $site->id; $page->parent_id = 0; $page->title = "Home"; $page->url = $pageUrl; - $page->date_created = $pageMapper->connection('Module\Page\Entity')->dateTime(); + $page->template = $site->theme . '/index'; + $page->date_created = new \DateTime(); $page->date_modified = $page->date_created; if(!$pageMapper->save($page)) { throw new Alloy\Exception("Unable to automatically create homepage at '" . $pageUrl . "' - Please check data source permissions"); @@ -54,6 +55,13 @@ public function viewUrl($pageUrl) } } + // Force HTTPS if set + if($page->setting('force_https', false) && !$request->isSecure() && !$request->no_redirect) { + $uri = $request->server('REQUEST_URI'); + \Kernel()->redirect('https://' . $request->server('HTTP_HOST') . $uri); + exit; + } + // Single module call? // @todo Check against matched route name instead of general request params (? - may restict query string params from being used) $mainContent = false; @@ -61,44 +69,44 @@ public function viewUrl($pageUrl) $moduleId = (int) $request->module_id; $moduleName = $request->module_name; $moduleAction = $request->module_action; - - if($moduleId) { + + $module = false; + if($moduleName != 'page' && $moduleName != 'site' && $moduleId) { // Get module by ID $module = $mapper->first('Module\Page\Module\Entity', array('id' => $moduleId)); - } else { - // Get new module entity, no ID supplied - // @todo Possibly restrict callable action with ID of '0' to 'new', etc. because other functions may depend on saved and valid module record - $module = $mapper->get('Module\Page\Module\Entity'); - $module->id = $moduleId; - $module->name = $moduleName; } - + // Setup dummy module object if there is none loaded - if(!$module) { + // @todo Possibly restrict callable action with ID of '0' to 'new', etc. because other functions may depend on saved and valid module record + if(false === $module) { $module = $mapper->get('Module\Page\Module\Entity'); $module->id = $moduleId; $module->name = $moduleName; } - + // Load requested module - $moduleObject = $kernel->module($moduleName); + // Try 'Site' module first + $moduleObject = $kernel->module('Site_' . $moduleName); + if(false === $moduleObject) { + $moduleObject = $kernel->module($moduleName); + } // Ensure module is a placeable module on the page if(!($moduleObject instanceof Stackbox\Module\ControllerAbstract)) { - throw new Alloy\Exception("Module '" . $moduleName . "' must extend 'Stackbox\Module\ControllerAbstract' to be a placeable Stackbox module"); + throw new Alloy\Exception("Module '" . $moduleName . "' must extend 'Stackbox\Module\ControllerAbstract' or 'Stackbox\Site\Module\ControllerAbstract' to be a placeable Stackbox module"); } // Ensure user can execute requested action if(!$moduleObject->userCanExecute($user, $moduleAction)) { throw new Alloy\Exception\Auth("User does not have sufficient permissions to execute requested action. Please login and try again."); } - + // Emulate REST for browsers $requestMethod = $request->method(); if($request->isPost() && $request->post('_method')) { $requestMethod = $request->post('_method'); } - + // Append 'Action' or 'Method' depending on HTTP method if(strtolower($requestMethod) == strtolower($moduleAction)) { $moduleAction = $moduleAction . (false === strpos($moduleAction, 'Method') ? 'Method' : ''); @@ -110,8 +118,13 @@ public function viewUrl($pageUrl) if(!is_callable(array($moduleObject, $moduleAction))) { throw new \BadMethodCallException("Module '" . $moduleName ."' does not have a callable method '" . $moduleAction . "'"); } - $moduleResponse = $kernel->dispatch($moduleObject, $moduleAction, array($request, $page, $module)); - + + try { + $moduleResponse = $kernel->dispatch($moduleObject, $moduleAction, array($request, $page, $module)); + } catch(\Exception $e) { + // Catch module exeptions and pass them through filter + $moduleResponse = $kernel->events('cms')->filter('module_dispatch_exception', $e); + } // Set content as main content (detail view) $mainContent = $this->regionModuleFormat($request, $page, $module, $user, $moduleResponse); @@ -120,14 +133,14 @@ public function viewUrl($pageUrl) return $mainContent; } } - + // Load page template $activeTheme = ($page->theme) ? $page->theme : $kernel->config('cms.site.theme', $kernel->config('cms.default.theme')); // Default template or page template if(!$page->template) { $activeTemplate = $activeTheme . '/' . $kernel->config('cms.default.theme_template'); } else { - $activeTemplate = $page->template; + $activeTemplate = $page->template; } $activeTheme = current(explode('/', $activeTemplate)); $template = new Template($activeTemplate); @@ -154,7 +167,7 @@ public function viewUrl($pageUrl) // Add jQuery as the first item $templateHead = $template->head(); $templateHead->script($kernel->config('cms.url.assets') . 'jquery.min.js'); - + // Template Region Defaults $regionModules = array(); $mainRegion = $template->regionMain(); @@ -162,22 +175,36 @@ public function viewUrl($pageUrl) foreach($template->regions() as $regionName => $regionData) { $regionModules[$regionName] = $regionData['content']; } - + // Modules $modules = $page->modules; $unusedModules = array(); - + // Also include modules in global template regions if global regions are present if($template->regionsType('global')) { $modules->orWhere(array('site_id' => $kernel->config('cms.site.id'), 'region' => $template->regionsType('global'))); } foreach($modules as $module) { - // Loop over modules, building content for each region - $moduleResponse = $kernel->dispatch($module->name, 'indexAction', array($request, $page, $module)); + // Try 'Site' module first, then normal module ('Site' modules override global ones) + $moduleObject = $kernel->module('Site_' . $module->name); + if(false === $moduleObject) { + $moduleObject = $kernel->module($module->name); + } + + // Dispatch to module's 'indexAction' to display module content on page + try { + $moduleResponse = $kernel->dispatch($moduleObject, 'indexAction', array($request, $page, $module)); + } catch(\Exception $e) { + // Catch module exeptions and render them inside region where module would have gone + // (allows users to delete and edit bad modules instead of killing the whole page and forcing manual database modification) + $moduleResponse = $kernel->events('cms')->filter('module_dispatch_exception', $e); + } + + // Setup named region array for module content if(!isset($regionModules[$module->region]) || !is_array($regionModules[$module->region])) { $regionModules[$module->region] = array(); } - + // If we have a 'main' module render, don't dispatch/render other content in main region if(false !== $mainContent) { // If module goes in 'main' region, skip it @@ -194,7 +221,7 @@ public function viewUrl($pageUrl) if(false !== $mainContent) { $regionModules[$mainRegionName] = array($mainContent); } - + // Replace region content $regionModules = $kernel->events('cms')->filter('module_page_template_regions', $regionModules); foreach($regionModules as $region => $modules) { @@ -207,17 +234,17 @@ public function viewUrl($pageUrl) } $template->regionContent($region, $regionContent); } - + // Replace template tags $tags = $mapper->data($page); $tags = $kernel->events('cms')->filter('module_page_template_data', $tags); foreach($tags as $tagName => $tagValue) { $template->replaceTag($tagName, $tagValue); } - + // Template string content $template->clean(); // Remove all unmatched tokens - + // Admin stuff for HTML format if($template->format() == 'html') { @@ -225,7 +252,7 @@ public function viewUrl($pageUrl) if($user && $user->isAdmin()) { // Admin toolbar, javascript, styles, etc. $templateHead->script($kernel->config('cms.url.assets') . 'jquery-ui.min.js'); - + // Setup javascript variables for use $templateHead->prepend(' + kernel->mapper('Module\Text\Mapper')->currentEntity($module); if(!$item) { @@ -31,7 +35,7 @@ public function indexAction($request, $page, $module) /** * @method GET */ - public function newAction($request, $page, $module) + public function newAction(Request $request, Page $page, Module $module) { $form = $this->formView() ->method('post') @@ -44,7 +48,7 @@ public function newAction($request, $page, $module) /** * @method GET */ - public function editlistAction($request, $page, $module) + public function editlistAction(Request $request, Page $page, Module $module) { $form = $this->formView() ->action($this->kernel->url(array('page' => $page->url, 'module_name' => $this->name(), 'module_id' => $module->id), 'module'), 'module') @@ -71,7 +75,7 @@ public function editlistAction($request, $page, $module) * Create a new resource with the given parameters * @method POST */ - public function postMethod($request, $page, $module) + public function postMethod(Request $request, Page $page, Module $module) { $mapper = $this->kernel->mapper('Module\Text\Mapper'); $item = $mapper->get('Module\Text\Entity')->data($request->post()); @@ -98,7 +102,7 @@ public function postMethod($request, $page, $module) * Save over existing entry (from edit) * @method PUT */ - public function putMethod($request, $page, $module) + public function putMethod(Request $request, Page $page, Module $module) { $mapper = $this->kernel->mapper('Module\Text\Mapper'); $item = $mapper->currentEntity($module); @@ -129,7 +133,7 @@ public function putMethod($request, $page, $module) /** * @method DELETE */ - public function deleteMethod($request, $page, $module) + public function deleteMethod(Request $request, Page $page, Module $module) { $mapper = $this->kernel->mapper('Module\Text\Mapper'); $item = $mapper->get('Module\Text\Entity', $request->module_item); @@ -166,9 +170,9 @@ public function uninstall() /** * Return view object for the add/edit form */ - protected function formView() + protected function formView($entityName = null) { - $view = parent::formView(); + $view = parent::formView($entityName); $fields = $view->fields(); // Set text 'content' as type 'editor' to get WYSIWYG diff --git a/app/www/content/Module/Text/Mapper.php b/app/www/content/Module/Text/Mapper.php index c400db7..4c055d3 100755 --- a/app/www/content/Module/Text/Mapper.php +++ b/app/www/content/Module/Text/Mapper.php @@ -18,4 +18,4 @@ public function currentEntity(\Module\Page\Module\Entity $module) } return $item; } -} \ No newline at end of file +} diff --git a/app/www/content/Module/Text/_module.php b/app/www/content/Module/Text/_module.php new file mode 100644 index 0000000..6152ddb --- /dev/null +++ b/app/www/content/Module/Text/_module.php @@ -0,0 +1,4 @@ + substr(strrchr(__DIR__, '/'), 1) +); \ No newline at end of file diff --git a/app/www/content/Module/Text/views/indexAction.html.php b/app/www/content/Module/Text/views/indexAction.html.php index d64e9d8..c2fe432 100755 --- a/app/www/content/Module/Text/views/indexAction.html.php +++ b/app/www/content/Module/Text/views/indexAction.html.php @@ -2,22 +2,24 @@ // Note if('note' == $item->type): ?> -
content; ?>
+
content; ?>
type): ?> -
content; ?>
+
content; ?>
type): ?> -
+  
+
     content, ENT_QUOTES, 'UTF-8'); ?>
-    
+
+
content; + echo $item->content; endif; ?> diff --git a/app/www/index.php b/app/www/index.php index eb7e058..f6bf630 100644 --- a/app/www/index.php +++ b/app/www/index.php @@ -22,9 +22,7 @@ throw new \InvalidArgumentException("Plugin configuration from app config must be an array. Given (" . gettype($plugins) . ")."); } - foreach($plugins as $pluginName) { - $plugin = $kernel->plugin($pluginName); - } + $kernel->loadPlugins($plugins); } $kernel->events()->trigger('boot_start'); @@ -177,9 +175,14 @@ // Debugging on? if($kernel->config('app.debug')) { + // Request Data + echo "

Request Data

"; + echo $kernel->dump($kernel->request()->params()); + echo "
"; echo "

Event Trace

"; echo $kernel->dump($kernel->trace()); + } // Notify that response has been sent @@ -192,4 +195,4 @@ header("HTTP/1.0 500 Internal Server Error"); echo "

Internal Server Error

"; echo $content; -} \ No newline at end of file +}