├── .gitignore ├── App ├── App.php ├── Config │ ├── config.dist.json │ └── routes.json ├── Controller │ ├── DashboardController.php │ ├── DefaultController.php │ └── NewsController.php ├── Model │ ├── DashboardModel.php │ ├── DefaultModel.php │ └── NewsModel.php ├── Router │ └── AppRouter.php ├── Service │ ├── ConfigServiceProvider.php │ └── DatabaseServiceProvider.php ├── Setup │ ├── InstallScript.php │ └── sampleData.sql ├── Table │ ├── DefaultDatabaseTable.php │ └── NewsTable.php ├── Templates │ ├── dashboard │ │ └── dashboard.index.twig │ ├── index.twig │ └── news │ │ ├── news.edit.twig │ │ ├── news.index.twig │ │ └── news.view.twig └── View │ ├── Dashboard │ └── DashboardHtmlView.php │ ├── DefaultHtmlView.php │ ├── News │ └── NewsHtmlView.php │ └── Renderer │ ├── RendererInterface.php │ ├── Twig.php │ └── TwigExtension.php ├── LICENSE ├── README.md ├── composer.json ├── composer.lock └── www ├── .htaccess ├── index.php └── themes └── awesome ├── css └── theme.css └── images └── logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE based files and folders # 2 | .buildpath 3 | .project 4 | .settings 5 | .DS_Store 6 | .idea 7 | 8 | # Files and folders that should not be pushed to the repo # 9 | App/Config/config.json 10 | www/assets/css 11 | www/assets/js 12 | www/assets/img 13 | 14 | # Apple Files # 15 | .DS_STORE 16 | 17 | # COMPOSER Files # 18 | vendor/* 19 | composer.phar 20 | -------------------------------------------------------------------------------- /App/App.php: -------------------------------------------------------------------------------- 1 | setContainer($container); 62 | 63 | // Merge the config into the application 64 | $config = $container->get('config'); 65 | $this->config->merge($config); 66 | 67 | $this->theme = $this->config->get('theme.default'); 68 | 69 | define('BASE_URL', $this->get('uri.base.full')); 70 | define('DEFAULT_THEME', BASE_URL . 'themes/' . $this->theme); 71 | } 72 | 73 | /** 74 | * Method to run the Web application routines. 75 | * 76 | * @return void 77 | * 78 | * @since 1.0 79 | */ 80 | protected function doExecute() 81 | { 82 | try 83 | { 84 | // Instantiate the router 85 | $router = new AppRouter($this->input, $this); 86 | $maps = json_decode(file_get_contents(JPATH_CONFIGURATION . '/routes.json')); 87 | 88 | if (!$maps) 89 | { 90 | throw new \RuntimeException('Invalid router file.', 500); 91 | } 92 | 93 | $router->addMaps($maps, true); 94 | $router->setControllerPrefix('\\App'); 95 | $router->setDefaultController('\\Controller\\DefaultController'); 96 | 97 | // Fetch the controller 98 | /* @type ControllerInterface $controller */ 99 | $controller = $router->getController($this->get('uri.route')); 100 | $content = $controller->execute(); 101 | } 102 | catch (\Exception $exception) 103 | { 104 | header('HTTP/1.1 500 Internal Server Error', true, 500); 105 | 106 | $content = $exception->getMessage(); 107 | } 108 | 109 | $this->setBody($content); 110 | } 111 | 112 | /** 113 | * Enqueue a system message. 114 | * 115 | * @param string $msg The message to enqueue. 116 | * @param string $type The message type. Default is message. 117 | * 118 | * @return $this Method allows chaining 119 | * 120 | * @since 1.0 121 | */ 122 | public function enqueueMessage($msg, $type = 'message') 123 | { 124 | $this->getSession()->getFlashBag()->add($type, $msg); 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * Get the DI container. 131 | * 132 | * @return Container 133 | * 134 | * @since 1.0 135 | */ 136 | public function getContainer() 137 | { 138 | return $this->container; 139 | } 140 | 141 | /** 142 | * Get a session object. 143 | * 144 | * @return Session 145 | * 146 | * @since 1.0 147 | */ 148 | public function getSession() 149 | { 150 | if (is_null($this->newSession)) 151 | { 152 | $this->newSession = new Session; 153 | $this->newSession->start(); 154 | } 155 | 156 | return $this->newSession; 157 | } 158 | 159 | /** 160 | * Clear the system message queue. 161 | * 162 | * @return void 163 | * 164 | * @since 1.0 165 | */ 166 | public function clearMessageQueue() 167 | { 168 | $this->getSession()->getFlashBag()->clear(); 169 | } 170 | 171 | /** 172 | * Get the system message queue. 173 | * 174 | * @return array The system message queue. 175 | * 176 | * @since 1.0 177 | */ 178 | public function getMessageQueue() 179 | { 180 | return $this->getSession()->getFlashBag()->peekAll(); 181 | } 182 | 183 | /** 184 | * Set the DI container. 185 | * 186 | * @param Container $container The DI container. 187 | * 188 | * @return $this Method allows chaining 189 | * 190 | * @since 1.0 191 | */ 192 | public function setContainer(Container $container) 193 | { 194 | $this->container = $container; 195 | 196 | return $this; 197 | } 198 | 199 | /** 200 | * Set the system message queue for a given type. 201 | * 202 | * @param string $type The type of message to set 203 | * @param mixed $message Either a single message or an array of messages 204 | * 205 | * @return void 206 | * 207 | * @since 1.0 208 | */ 209 | public function setMessageQueue($type, $message = '') 210 | { 211 | $this->getSession()->getFlashBag()->set($type, $message); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /App/Config/config.dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "driver": "mysql", 4 | "host": "localhost", 5 | "user": "root", 6 | "password": "root", 7 | "name": "framework-app", 8 | "prefix": "app_" 9 | }, 10 | "renderer": { 11 | "type": "twig" 12 | }, 13 | "theme": { 14 | "default": "awesome" 15 | }, 16 | "system": { 17 | "list_limit": "20", 18 | "gzip": "0", 19 | "offset": "UTC" 20 | }, 21 | "languages": [ 22 | "en-GB" 23 | ] 24 | } -------------------------------------------------------------------------------- /App/Config/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "dashboard": "\\Controller\\DashboardController", 3 | "dashboard/:task": "\\Controller\\DashboardController", 4 | "news": "\\Controller\\NewsController", 5 | "news/:task": "\\Controller\\NewsController", 6 | "news/:task/:id": "\\Controller\\NewsController" 7 | } -------------------------------------------------------------------------------- /App/Controller/DashboardController.php: -------------------------------------------------------------------------------- 1 | getInput(), $this->getContainer()->get('db')); 34 | $dashboardModel->updateDatabase($this->getContainer()->get('config')); 35 | 36 | $this->getInput()->set('success', true); 37 | $this->getApplication()->redirect($this->getApplication()->get('uri.base.path')); 38 | } 39 | catch (\Exception $e) 40 | { 41 | throw new \RuntimeException(sprintf('Error: ' . $e->getMessage())); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /App/Controller/DefaultController.php: -------------------------------------------------------------------------------- 1 | getInput(); 52 | 53 | $task = $input->get('task','view'); 54 | 55 | // Get some data from the request 56 | $vName = $input->getWord('view', $this->defaultView); 57 | $vFormat = $input->getWord('format', 'html'); 58 | 59 | //TODO 60 | if (is_null($input->get('layout'))) 61 | { 62 | if ($task == 'view' && $input->get('id') == null) 63 | { 64 | $input->set('layout', 'index'); 65 | } 66 | elseif ($task == 'view') 67 | { 68 | $input->set('layout', 'view'); 69 | } 70 | elseif ($task != null) 71 | { 72 | $this->$task(); 73 | } 74 | } 75 | 76 | $lName = $input->get('layout'); 77 | 78 | $input->set('view', $vName); 79 | 80 | $base = '\\App'; 81 | 82 | $vClass = $base . '\\View\\' . ucfirst($vName) . '\\' . ucfirst($vName) . ucfirst($vFormat) . 'View'; 83 | $mClass = $base . '\\Model\\' . ucfirst($vName) . 'Model'; 84 | 85 | // If a model doesn't exist for our view, revert to the default model 86 | if (!class_exists($mClass)) 87 | { 88 | $mClass = $base . '\\Model\\DefaultModel'; 89 | 90 | // If there still isn't a class, panic. 91 | if (!class_exists($mClass)) 92 | { 93 | throw new \RuntimeException(sprintf('No model found for view %s', $vName)); 94 | } 95 | } 96 | 97 | // Make sure the view class exists, otherwise revert to the default 98 | if (!class_exists($vClass)) 99 | { 100 | $vClass = '\\App\\View\\DefaultHtmlView'; 101 | 102 | // If there still isn't a class, panic. 103 | if (!class_exists($vClass)) 104 | { 105 | throw new \RuntimeException(sprintf('Class %s not found', $vClass)); 106 | } 107 | } 108 | 109 | // Register the templates paths for the view 110 | $paths = array(); 111 | 112 | $path = JPATH_TEMPLATES . '/' . $vName . '/'; 113 | 114 | if (is_dir($path)) 115 | { 116 | $paths[] = $path; 117 | } 118 | 119 | /* @type DefaultHtmlView $view */ 120 | $view = new $vClass($this->getApplication(), new $mClass($this->getInput(), $this->getContainer()->get('db')), $paths); 121 | $view->setLayout($vName . '.' . $lName); 122 | 123 | try 124 | { 125 | // Render our view. 126 | return $view->render(); 127 | } 128 | catch (\Exception $e) 129 | { 130 | throw new \RuntimeException(sprintf('Error: ' . $e->getMessage())); 131 | } 132 | 133 | return; 134 | } 135 | 136 | /** 137 | * Get the DI container. 138 | * 139 | * @return Container 140 | * 141 | * @since 1.0 142 | */ 143 | public function getContainer() 144 | { 145 | return $this->container; 146 | } 147 | 148 | /** 149 | * Set the DI container. 150 | * 151 | * @param Container $container The DI container. 152 | * 153 | * @return $this Method allows chaining 154 | * 155 | * @since 1.0 156 | */ 157 | public function setContainer(Container $container) 158 | { 159 | $this->container = $container; 160 | 161 | return $this; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /App/Controller/NewsController.php: -------------------------------------------------------------------------------- 1 | database->driver = $this->input->get('driver', $oldConfig->get('database.driver')); 42 | $config->database->user = $this->input->get('user', $oldConfig->get('database.user')); 43 | $config->database->password = $this->input->get('password', $oldConfig->get('database.password')); 44 | $config->database->name = $this->input->get('name', $oldConfig->get('database.name')); 45 | $config->database->host = $this->input->get('host', $oldConfig->get('database.host')); 46 | $config->database->prefix = $this->input->get('prefix', $oldConfig->get('database.prefix')); 47 | 48 | file_put_contents($file, json_encode($config)); 49 | 50 | if ($this->input->get('install_sample_data')) 51 | { 52 | $this->installSampleData(); 53 | } 54 | 55 | return true; 56 | } 57 | 58 | /** 59 | * Install sample data for the application 60 | * 61 | * @return boolean 62 | * 63 | * @since 1.0 64 | * @throws \RuntimeException 65 | * @throws \UnexpectedValueException 66 | */ 67 | public function installSampleData() 68 | { 69 | $sampleData = JPATH_SETUP . '/sampleData.sql'; 70 | 71 | if (!is_readable($sampleData)) 72 | { 73 | throw new \RuntimeException('Sample Data file does not exist or is unreadable'); 74 | } 75 | 76 | $sql = file_get_contents($sampleData); 77 | 78 | if (false == $sql) 79 | { 80 | throw new \UnexpectedValueException('SQL file corrupted.'); 81 | } 82 | 83 | foreach ($this->db->splitSql($sql) as $query) 84 | { 85 | $q = trim($this->db->replacePrefix($query)); 86 | 87 | if ('' == trim($q)) 88 | { 89 | continue; 90 | } 91 | 92 | $this->db->setQuery($q)->execute(); 93 | } 94 | 95 | return true; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /App/Model/DefaultModel.php: -------------------------------------------------------------------------------- 1 | input = $input; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /App/Model/NewsModel.php: -------------------------------------------------------------------------------- 1 | input->getUint('id'); 34 | $alias = $this->input->get('id'); 35 | 36 | if (!$id && !$alias) 37 | { 38 | throw new \UnexpectedValueException('No news identifier provided.'); 39 | } 40 | 41 | $query = $this->db->getQuery(true) 42 | ->select('a.*') 43 | ->from($this->db->quoteName('#__news','a')); 44 | 45 | if ($id) 46 | { 47 | $query->where($this->db->quoteName('a.news_id') . ' = ' . (int) $id); 48 | } 49 | elseif ($alias) 50 | { 51 | $query->where($this->db->quoteName('a.alias') . ' = ' . $this->db->quote($alias)); 52 | } 53 | 54 | return $this->db->setQuery($query)->loadObject(); 55 | } 56 | 57 | /** 58 | * Retrieve all news items 59 | * 60 | * @return object Container with news items 61 | * 62 | * @since 1.0 63 | */ 64 | public function getItems() 65 | { 66 | $query = $this->db->getQuery(true) 67 | ->select('a.*') 68 | ->from($this->db->quoteName('#__news','a')); 69 | 70 | return $this->db->setQuery($query)->loadObjectList(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /App/Router/AppRouter.php: -------------------------------------------------------------------------------- 1 | input); 42 | 43 | $this->app = $app; 44 | } 45 | 46 | /** 47 | * Get a ControllerInterface object for a given name. 48 | * 49 | * @param string $name The controller name (excluding prefix) for which to fetch and instance. 50 | * 51 | * @return ControllerInterface 52 | * 53 | * @since 1.0 54 | * @throws \RuntimeException 55 | */ 56 | protected function fetchController($name) 57 | { 58 | // Derive the controller class name. 59 | $class = $this->controllerPrefix . ucfirst($name); 60 | 61 | // If the controller class does not exist panic. 62 | if (!class_exists($class) || !is_subclass_of($class, 'Joomla\\Controller\\ControllerInterface')) 63 | { 64 | throw new \RuntimeException(sprintf('Unable to locate controller `%s`.', $class), 404); 65 | } 66 | 67 | // Instantiate the controller. 68 | $controller = new $class($this->input, $this->app); 69 | 70 | if ($controller instanceof ContainerAwareInterface) 71 | { 72 | $controller->setContainer($this->app->getContainer()); 73 | } 74 | 75 | return $controller; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /App/Service/ConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadObject($configObject); 58 | 59 | $this->config = $config; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function register(Container $container) 66 | { 67 | $config = $this->config; 68 | 69 | $container->share( 70 | 'config', 71 | function () use ($config) 72 | { 73 | return $config; 74 | } 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /App/Service/DatabaseServiceProvider.php: -------------------------------------------------------------------------------- 1 | set('Joomla\\Database\\DatabaseDriver', 26 | function () use ($container) 27 | { 28 | $config = $container->get('config'); 29 | 30 | $options = array( 31 | 'driver' => $config->get('database.driver'), 32 | 'host' => $config->get('database.host'), 33 | 'user' => $config->get('database.user'), 34 | 'password' => $config->get('database.password'), 35 | 'database' => $config->get('database.name'), 36 | 'prefix' => $config->get('database.prefix') 37 | ); 38 | 39 | $db = DatabaseDriver::getInstance($options); 40 | $db->setDebug($config->get('debug.database', false)); 41 | 42 | return $db; 43 | }, true, true 44 | ); 45 | 46 | // Alias the database 47 | $container->alias('db', 'Joomla\\Database\\DatabaseDriver'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /App/Setup/InstallScript.php: -------------------------------------------------------------------------------- 1 | tableName = $table; 75 | $this->db = $db; 76 | $this->tableFields = new \stdClass; 77 | 78 | // Set the key to be an array. 79 | if (is_string($keys)) 80 | { 81 | $keys = array($keys); 82 | } 83 | elseif (is_object($keys)) 84 | { 85 | $keys = (array) $keys; 86 | } 87 | 88 | $this->tableKeys = $keys; 89 | 90 | $this->autoIncrement = (count($keys) == 1) ? true : false; 91 | 92 | // Initialise the table properties. 93 | $fields = $this->getFields(); 94 | 95 | if ($fields) 96 | { 97 | foreach ($fields as $name => $v) 98 | { 99 | // Add the field if it is not already present. 100 | $this->tableFields->$name = null; 101 | } 102 | } 103 | } 104 | 105 | /** 106 | * Magic setter to set a table field. 107 | * 108 | * @param string $key The key name. 109 | * @param mixed $value The value to set. 110 | * 111 | * @return void 112 | * 113 | * @since 1.0 114 | * @throws \InvalidArgumentException 115 | */ 116 | public function __set($key, $value) 117 | { 118 | if (isset($this->tableFields->$key) || is_null($this->tableFields->$key)) 119 | { 120 | $this->tableFields->$key = $value; 121 | } 122 | else 123 | { 124 | throw new \InvalidArgumentException(__METHOD__ . ' - Set unknown property: ' . $key); 125 | } 126 | } 127 | 128 | /** 129 | * Magic getter to get a table field. 130 | * 131 | * @param string $key The key name. 132 | * 133 | * @return mixed 134 | * 135 | * @since 1.0 136 | * @throws \InvalidArgumentException 137 | */ 138 | public function __get($key) 139 | { 140 | if (isset($this->tableFields->$key) || is_null($this->tableFields->$key)) 141 | { 142 | return $this->tableFields->$key; 143 | } 144 | 145 | throw new \InvalidArgumentException(__METHOD__ . ' - Get unknown property: ' . $key); 146 | } 147 | 148 | /** 149 | * Method to provide a shortcut to binding, checking and storing a AbstractDatabaseTable 150 | * instance to the database table. The method will check a row in once the 151 | * data has been stored and if an ordering filter is present will attempt to 152 | * reorder the table rows based on the filter. The ordering filter is an instance 153 | * property name. The rows that will be reordered are those whose value matches 154 | * the AbstractDatabaseTable instance for the property specified. 155 | * 156 | * @param mixed $src An associative array or object to bind to the AbstractDatabaseTable instance. 157 | * @param mixed $ignore An optional array or space separated list of properties 158 | * to ignore while binding. 159 | * 160 | * @return $this Method allows chaining 161 | * 162 | * @since 1.0 163 | */ 164 | public function save($src, $ignore = '') 165 | { 166 | $this 167 | // Attempt to bind the source to the instance. 168 | ->bind($src, $ignore) 169 | // Run any sanity checks on the instance and verify that it is ready for storage. 170 | ->check() 171 | // Attempt to store the properties to the database table. 172 | ->store(); 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Method to bind an associative array or object to the AbstractDatabaseTable instance.This 179 | * method only binds properties that are publicly accessible and optionally 180 | * takes an array of properties to ignore when binding. 181 | * 182 | * @param mixed $src An associative array or object to bind to the AbstractDatabaseTable instance. 183 | * @param mixed $ignore An optional array or space separated list of properties to ignore while binding. 184 | * 185 | * @return $this Method allows chaining 186 | * 187 | * @since 1.0 188 | * @throws \InvalidArgumentException 189 | */ 190 | public function bind($src, $ignore = array()) 191 | { 192 | // If the source value is not an array or object return false. 193 | if (!is_object($src) && !is_array($src)) 194 | { 195 | throw new \InvalidArgumentException(sprintf('%s::bind(*%s*)', get_class($this), gettype($src))); 196 | } 197 | 198 | // If the source value is an object, get its accessible properties. 199 | if (is_object($src)) 200 | { 201 | $src = get_object_vars($src); 202 | } 203 | 204 | // If the ignore value is a string, explode it over spaces. 205 | if (!is_array($ignore)) 206 | { 207 | $ignore = explode(' ', $ignore); 208 | } 209 | 210 | // Bind the source value, excluding the ignored fields. 211 | foreach ($this->tableFields as $k => $v) 212 | { 213 | // Only process fields not in the ignore array. 214 | if (!in_array($k, $ignore)) 215 | { 216 | if (isset($src[$k])) 217 | { 218 | $this->tableFields->$k = $src[$k]; 219 | } 220 | } 221 | } 222 | 223 | return $this; 224 | } 225 | 226 | /** 227 | * Method to load a row from the database by primary key and bind the fields 228 | * to the AbstractDatabaseTable instance properties. 229 | * 230 | * @param mixed $keys An optional primary key value to load the row by, or an array of fields to match. If not 231 | * set the instance property value is used. 232 | * @param boolean $reset True to reset the default values before loading the new row. 233 | * 234 | * @return $this Method allows chaining 235 | * 236 | * @since 1.0 237 | * @throws \RuntimeException 238 | * @throws \UnexpectedValueException 239 | * @throws \InvalidArgumentException 240 | */ 241 | public function load($keys = null, $reset = true) 242 | { 243 | if (empty($keys)) 244 | { 245 | $empty = true; 246 | $keys = array(); 247 | 248 | // If empty, use the value of the current key 249 | foreach ($this->tableKeys as $key) 250 | { 251 | $empty = $empty && empty($this->$key); 252 | $keys[$key] = $this->$key; 253 | } 254 | 255 | // If empty primary key there's is no need to load anything 256 | if ($empty) 257 | { 258 | return $this; 259 | } 260 | } 261 | elseif (!is_array($keys)) 262 | { 263 | // Load by primary key. 264 | $keyCount = count($this->tableKeys); 265 | 266 | if ($keyCount) 267 | { 268 | if ($keyCount > 1) 269 | { 270 | throw new \InvalidArgumentException('Table has multiple primary keys specified, only one primary key value provided.'); 271 | } 272 | 273 | $keys = array($this->getKeyName() => $keys); 274 | } 275 | else 276 | { 277 | throw new \RuntimeException('No table keys defined.'); 278 | } 279 | } 280 | 281 | if ($reset) 282 | { 283 | $this->reset(); 284 | } 285 | 286 | // Initialise the query. 287 | $query = $this->db->getQuery(true) 288 | ->select('*') 289 | ->from($this->db->quoteName($this->tableName)); 290 | 291 | foreach ($keys as $field => $value) 292 | { 293 | // Check that $field is in the table. 294 | if (isset($this->tableFields->$field) || is_null($this->tableFields->$field)) 295 | { 296 | // Add the search tuple to the query. 297 | $query->where($this->db->quoteName($field) . ' = ' . $this->db->quote($value)); 298 | } 299 | else 300 | { 301 | throw new \UnexpectedValueException(sprintf('Missing field in database: %s %s.', get_class($this), $field)); 302 | } 303 | } 304 | 305 | $this->db->setQuery($query); 306 | 307 | $row = $this->db->loadAssoc(); 308 | 309 | // Check that we have a result. 310 | if (empty($row)) 311 | { 312 | throw new \RuntimeException(__METHOD__ . ' can not bind.'); 313 | } 314 | 315 | // Bind the object with the row and return. 316 | return $this->bind($row); 317 | } 318 | 319 | /** 320 | * Method to delete a row from the database table by primary key value. 321 | * 322 | * @param mixed $pKey An optional primary key value to delete. If not set the instance property value is used. 323 | * 324 | * @return $this Method allows chaining 325 | * 326 | * @since 1.0 327 | * @throws \UnexpectedValueException 328 | */ 329 | public function delete($pKey = null) 330 | { 331 | $key = $this->getKeyName(); 332 | 333 | $pKey = (is_null($pKey)) ? $this->$key : $pKey; 334 | 335 | // If no primary key is given, return false. 336 | if ($pKey === null) 337 | { 338 | throw new \UnexpectedValueException('Null primary key not allowed.'); 339 | } 340 | 341 | // Delete the row by primary key. 342 | $this->db->setQuery( 343 | $this->db->getQuery(true) 344 | ->delete($this->db->quoteName($this->tableName)) 345 | ->where($this->db->quoteName($key) . ' = ' . $this->db->quote($pKey)) 346 | ) 347 | ->execute(); 348 | 349 | return $this; 350 | } 351 | 352 | /** 353 | * Method to reset class properties to the defaults set in the class 354 | * definition. It will ignore the primary key as well as any private class 355 | * properties. 356 | * 357 | * @return void 358 | * 359 | * @since 1.0 360 | */ 361 | public function reset() 362 | { 363 | // Get the default values for the class from the table. 364 | foreach ($this->getFields() as $k => $v) 365 | { 366 | // If the property is not the primary key, reset it. 367 | if (!in_array($k, $this->tableKeys)) 368 | { 369 | $this->$k = $v->Default; 370 | } 371 | } 372 | } 373 | 374 | /** 375 | * Method to perform sanity checks on the AbstractDatabaseTable instance properties to ensure 376 | * they are safe to store in the database. Child classes should override this 377 | * method to make sure the data they are storing in the database is safe and 378 | * as expected before storage. 379 | * 380 | * @return $this Method allows chaining 381 | * 382 | * @since 1.0 383 | */ 384 | public function check() 385 | { 386 | return $this; 387 | } 388 | 389 | /** 390 | * Method to store a row in the database from the DefaultDatabaseTable instance properties. 391 | * If a primary key value is set the row with that primary key value will be 392 | * updated with the instance property values. If no primary key value is set 393 | * a new row will be inserted into the database with the properties from the 394 | * DefaultDatabaseTable instance. 395 | * 396 | * @param boolean $updateNulls True to update fields even if they are null. 397 | * 398 | * @return $this Method allows chaining 399 | * 400 | * @since 1.0 401 | */ 402 | public function store($updateNulls = false) 403 | { 404 | // If a primary key exists update the object, otherwise insert it. 405 | if ($this->hasPrimaryKey()) 406 | { 407 | $this->db->updateObject($this->tableName, $this->tableFields, $this->tableKeys, $updateNulls); 408 | } 409 | else 410 | { 411 | $this->db->insertObject($this->tableName, $this->tableFields, $this->tableKeys[0]); 412 | } 413 | 414 | return $this; 415 | } 416 | 417 | /** 418 | * Validate that the primary key has been set. 419 | * 420 | * @return boolean True if the primary key(s) have been set. 421 | * 422 | * @since 1.0 423 | */ 424 | public function hasPrimaryKey() 425 | { 426 | if ($this->autoIncrement) 427 | { 428 | $empty = true; 429 | 430 | foreach ($this->tableKeys as $key) 431 | { 432 | $empty = $empty && !$this->$key; 433 | } 434 | } 435 | else 436 | { 437 | $query = $this->db->getQuery(true) 438 | ->select('COUNT(*)') 439 | ->from($this->tableName); 440 | $this->appendPrimaryKeys($query); 441 | 442 | $this->db->setQuery($query); 443 | $count = $this->db->loadResult(); 444 | 445 | if ($count == 1) 446 | { 447 | $empty = false; 448 | } 449 | else 450 | { 451 | $empty = true; 452 | } 453 | } 454 | 455 | return !$empty; 456 | } 457 | 458 | /** 459 | * Method to append the primary keys for this table to a query. 460 | * 461 | * @param DatabaseQuery $query A query object to append. 462 | * @param mixed $pk Optional primary key parameter. 463 | * 464 | * @return $this Method allows chaining 465 | * 466 | * @since 1.0 467 | */ 468 | public function appendPrimaryKeys($query, $pk = null) 469 | { 470 | if (is_null($pk)) 471 | { 472 | foreach ($this->tableKeys as $k) 473 | { 474 | $query->where($this->db->quoteName($k) . ' = ' . $this->db->quote($this->$k)); 475 | } 476 | } 477 | else 478 | { 479 | if (is_string($pk)) 480 | { 481 | $pk = array($this->tableKeys[0] => $pk); 482 | } 483 | 484 | $pk = (object) $pk; 485 | 486 | foreach ($this->tableKeys AS $k) 487 | { 488 | $query->where($this->db->quoteName($k) . ' = ' . $this->db->quote($pk->$k)); 489 | } 490 | } 491 | 492 | return $this; 493 | } 494 | 495 | /** 496 | * Method to get the primary key field name for the table. 497 | * 498 | * @param boolean $multiple True to return all primary keys (as an array) or false to return just the first one (as a string). 499 | * 500 | * @return mixed Array of primary key field names or string containing the first primary key field. 501 | * 502 | * @since 1.0 503 | */ 504 | public function getKeyName($multiple = false) 505 | { 506 | // Count the number of keys 507 | if (count($this->tableKeys)) 508 | { 509 | if ($multiple) 510 | { 511 | // If we want multiple keys, return the raw array. 512 | return $this->tableKeys; 513 | } 514 | else 515 | { 516 | // If we want the standard method, just return the first key. 517 | return $this->tableKeys[0]; 518 | } 519 | } 520 | 521 | return ''; 522 | } 523 | 524 | /** 525 | * Get the columns from database table. 526 | * 527 | * @return mixed An array of the field names, or false if an error occurs. 528 | * 529 | * @since 1.0 530 | * @throws \UnexpectedValueException 531 | */ 532 | public function getFields() 533 | { 534 | static $cache = null; 535 | 536 | if ($cache === null) 537 | { 538 | // Lookup the fields for this table only once. 539 | $fields = $this->db->getTableColumns($this->tableName, false); 540 | 541 | if (empty($fields)) 542 | { 543 | throw new \UnexpectedValueException(sprintf('No columns found for %s table', $this->tableName)); 544 | } 545 | 546 | $cache = $fields; 547 | } 548 | 549 | return $cache; 550 | } 551 | 552 | /** 553 | * Get the table name. 554 | * 555 | * @return string 556 | * 557 | * @since 1.0 558 | */ 559 | public function getTableName() 560 | { 561 | return $this->tableName; 562 | } 563 | 564 | /** 565 | * Get an iterator object. 566 | * 567 | * @return \ArrayIterator 568 | * 569 | * @since 1.0 570 | */ 571 | public function getIterator() 572 | { 573 | return new \ArrayIterator($this->tableFields); 574 | } 575 | 576 | /** 577 | * Clone the table. 578 | * 579 | * @return \ArrayIterator 580 | * 581 | * @since 1.0 582 | */ 583 | public function __clone() 584 | { 585 | return $this->getIterator(); 586 | } 587 | } 588 | -------------------------------------------------------------------------------- /App/Table/NewsTable.php: -------------------------------------------------------------------------------- 1 | title) 51 | { 52 | throw new \UnexpectedValueException('A title is required'); 53 | } 54 | 55 | if (!$this->alias) 56 | { 57 | $this->alias = $this->title; 58 | } 59 | 60 | $this->alias = OutputFilter::stringURLSafe($this->alias); 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Method to store a row in the database from the DefaultDatabaseTable instance properties. 67 | * If a primary key value is set the row with that primary key value will be 68 | * updated with the instance property values. If no primary key value is set 69 | * a new row will be inserted into the database with the properties from the 70 | * DefaultDatabaseTable instance. 71 | * 72 | * @param boolean $updateNulls True to update fields even if they are null. 73 | * 74 | * @return $this Method allows chaining 75 | * 76 | * @since 1.0 77 | */ 78 | public function store($updateNulls = false) 79 | { 80 | $oldId = $this->{$this->getKeyName()}; 81 | 82 | return parent::store($updateNulls); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /App/Templates/dashboard/dashboard.index.twig: -------------------------------------------------------------------------------- 1 | {% extends "index.twig" %} 2 | 3 | {% block content %} 4 |
You've now got the Joomla! Framework installed and you're ready to get started coding. If you have questions, be sure to check out the docs.
8 | Documentation 9 |This install comes preloaded with the Twitter Bootstrap package pre-installed, symlinked and ready to use.
17 | 18 |This install comes preloaded with the latest jQuery packages pre-installed, symlinked and ready to use.
20 | 21 |This install comes preloaded with Twig as the templating package pre-installed and ready to use.
23 |This install has a sample theme called "Awesome". Yes, you're looking at it. Create your own theme and set as the default in config.json
28 | 29 |This install has a database setup included. (Be sure to checkout the config.json file)
31 | 32 |This install uses a static routing file (routes.json) and also includes minor code to demo automatic routing.
34 |