diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..21709b7 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,43 @@ +Order allow,deny +Allow from all +Require all granted + +SetEnv COMPUTERNAME demo + +Options -Indexes +IndexIgnore */* + +php_value error_reporting -1 +php_flag display_errors On +php_value max_execution_time 0 +php_value default_charset "utf-8" +php_value session.use_trans_sid 0 +php_value session.gc_maxlifetime 1440 +php_value session.hash_function 0 +php_value session.hash_bits_per_character 4 +php_flag output_buffering Off +php_flag zlib.output_compression On +php_flag magic_quotes_gpc Off +php_flag register_globals Off +php_value sendmail_from no-reply@demo.com +php_value memory_limit 64M +php_value post_max_size 500M +php_value upload_max_filesize 500M + + + Order Deny,Allow + Deny from all + + + + Header unset ETag + + Header set Cache-Control "max-age=31536000, public" + + +FileETag None + +RewriteEngine On + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^ /index.php [L] diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..60c12b6 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..f4af1e6 --- /dev/null +++ b/public/index.php @@ -0,0 +1,4 @@ +execute(); diff --git a/src/config/container.php b/src/config/container.php new file mode 100644 index 0000000..9268235 --- /dev/null +++ b/src/config/container.php @@ -0,0 +1,99 @@ +request($c->db($c->config))' ... but this is just pseudo-code! + * These are anonymous functions that get called when one of the Container properties are accessed + * eg. A statement like `$c->db->query(...)`; will call the 'db' function handler code below (when it tries to resolve `$c->db` and automatically builds the `db` instance. + * The code will create the object instance internally, + * then assign the new instance value to the corresponding (same) location in the Container class at the end of the function, + * effectively overwriting/replacing the array member (anonymous function) with the new object instance, + * essentially building something like a singleton object instance, + * since calling `$c->db` the next time will return the new object and not the original function. + * If you require multiple instances, you can just skip assigning the value at the end and just return a new object each time eg. 'return $obj;' instead of 'return $c->obj = $obj;' + * If you require multiple arguments, like `$c->db('primary_conn')`, or `$c->myFunc($param1)` + * then you can just create a function like `function($c, $param1)`, + * the container object itself ($this) is always inserted as the first parameter in the parameter list before the function is called (with array_unshift()). + * Each one of these `properties` are called by the __get, __set and __call magic methods in the Container class! + * The `__get` method of the Container is actually called when we ask for '$c->db' (eg. $c->__get('db')) ... but the internal value of 'db' is actually the function below. + * Our `__get` method inside the Container class will recognize that the property is actually a callable function, + * and proceed to actually CALL the function, passing it the `$this` (container) value as the first parameter of the function call + */ +return [ + 'execute' => function($c) + { + /** + * Register an Exception Handler + */ + /** + * The Twister Exception Handler is just a slightly improved version of the default internal PHP handler + * new \Twister\ExceptionHandler(); + * or use Whoops below! + */ + $whoops = new \Whoops\Run; + $whoops->pushHandler(new \Whoops\Handler\PrettyPageHandler); + // $whoops->pushHandler(new \Whoops\Handler\JsonResponseHandler); + $whoops->register(); + $c->request->execute_route(); + }, + + 'config' => function($c) + { + $config = require __DIR__ . '/' . strtolower(getenv('COMPUTERNAME')) . '.php'; + $c->is_debug = $c->isDebug = $config['debug']; // shorthand + return $c->config = &$config; + }, + + 'db' => function($c) + { + $dbc = $c->config['db']; + mysqli_report( MYSQLI_REPORT_ALL ); + try { + $db = new DB($dbc['host'], $dbc['username'], $dbc['password'], $dbc['schema'], $dbc['port']); + } catch (mysqli_sql_exception $e) { + header('HTTP/1.1 503 Service Temporarily Unavailable'); + header('Status: 503 Service Temporarily Unavailable'); + header('Retry-After: 7200'); // seconds + die('Website under scheduled maintenance!
' . + 'We are aware of the situation and apologize for the inconvenience!
' . + 'Normal operation will resume shortly, please be patient and try again later!
' . ($c->is_debug ? mysqli_connect_error() : null)); + } + $db->set_charset($dbc['charset']); + $db->real_query('SET NAMES ' . $dbc['charset'] . ' COLLATE ' . $dbc['collation']); + return $c->db = &$db; + }, + + /** + * This Request object will instantiate the 'db' object internally, because the container is Injected into the Request object constructor, + * it includes the 'db' handler above, so when Request calls '$this->container->db->query(...)'; + * it will (unknowingly) instantiate the 'db' object when the container tries to resolve the 'db' handler above, + * the 'db' handler is the function above, which returns the new 'db' object. + * Another way to do this is use a static variable inside the function, but then we have the unecessary function call overhead. + * Upon executing any 'db' queries, the 'db' object is automatically instantiated by the 'db' code above. + * Code like: `$db = new DB(...);` is never actually called anywhere else in code. + */ + 'request' => function($c) + { + return $c->request = new Request($c); + }, + + 'session' => function($c) + { + return $c->session = new Session($c->db); + /* // alternative: check if HTTPS is enabled + if ($c->request->is_https) + return $c->session = new Session($c->db); + else + return $c->session = new Session($c->db); + */ + }, + + 'user' => function($c) + { + return $c->user = new User($c); + } + ]; diff --git a/src/config/demo.php b/src/config/demo.php new file mode 100644 index 0000000..7689fb3 --- /dev/null +++ b/src/config/demo.php @@ -0,0 +1,22 @@ + true, + 'db' => [ 'host' => '127.0.0.1', + 'username' => 'root', + 'password' => '(-Y${K_c,Hg*3H|E2nu^XT?R3>68!@m:U]5eM&2#', + 'schema' => 'fcm', + 'port' => '33306', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'debug' => true + ] + ]); diff --git a/src/config/my_server_name.php b/src/config/my_server_name.php new file mode 100644 index 0000000..b6169ca --- /dev/null +++ b/src/config/my_server_name.php @@ -0,0 +1,31 @@ + false, + 'maintenance' => false, + 'email_from' => 'DEMO ', + 'charset' => 'UTF-8', + 'title' => 'Demo Application', + 'lang' => 'en', + 'languages' => ['en'], + 'db' => [ 'host' => '127.0.0.1', + 'username' => 'root', + 'password' => 'XR7mswQKfgsgdfhgregGEzAedxYUDqk6iHBCtI3pcN', + 'schema' => 'demo', + 'port' => '3306', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci' + ], + 'google' => [ 'places' => '34fgdrrdg343t4dfgdfg34tdfsdfghg3q4tghert', // Google Places API server key ... SECRET!!! + 'recaptcha-site' => 'hj345wehjqwereghjj56io54unety354khngdrg3', // `Use this in the HTML code your site serves to users.` + 'recaptcha-secret' => 'hj345wehjqwereghjj56io54unety354khngdrg3' // `Use this for communication between your site and Google. Be sure to keep it a secret.` + ] + ]; diff --git a/src/config/routes.php b/src/config/routes.php new file mode 100644 index 0000000..7e9b588 --- /dev/null +++ b/src/config/routes.php @@ -0,0 +1,57 @@ + [ // official named patterns + 'alnum' => '[A-Za-z0-9]', + 'word' => '[A-Za-z0-9_]', + 'alpha' => '[A-Za-z]', + 'digit' => '[0-9]', + 'lower' => '[a-z]', + 'xdigit' => '[A-Fa-f0-9]', + + // common + 'any' => '[^/]+', + 'string' => '[A-Za-z0-9_-]+', + 'id' => '[0-9]+', + 'int' => '[0-9]+', + 'num' => '[0-9]+', + 'i' => '[0-9]+', + 'd' => '[0-9]', + 'x' => '[A-Fa-f0-9]', + 'l' => '[a-z]', + 'u' => '[A-Z]', + 'U' => '[A-Z]', + 'hex' => '[A-Fa-f0-9]', + 'date' => '\d{4}-\d{2}-\d{2}', + 'day' => '0[1-9]|[12][0-9]|3[01]', + 'month' => '0[1-9]|1[012]', + 'year' => '[12][0-9]{3}', + 'uuid' => '[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}', + // 'action' => 'index|show|add|create|edit|update|remove|del|delete|view|item', + ], + + 'routes' => [ '' => [ null, '/', function(Container $c) { (new Response($c, 'public', 'index'))->render(); }, 'home' ], + 'admin' => [ [ 'GET', '', 'AdminController::indexAction', 'admin' ], + [ 'GET', 'asset-groups', 'AdminController::assetGroupsAction', 'admin_asset_groups' ], + [ 'POST', 'asset-groups', 'AdminController::assetGroupsPostAction', 'admin_asset_groups_post' ], + [ 'GET', 'asset-servers', 'AdminController::assetServersAction', 'admin_asset_servers' ], + [ 'GET', 'assets', 'AdminController::assetsAction', 'admin_assets' ], + [ 'GET', 'asset/{id}', 'AdminController::assetAction', 'admin_asset' ], + [ 'GET', 'article/{id}', 'AdminController::articleAction', 'admin_article' ], + [ 'GET', 'articles', 'AdminController::articlesAction', 'admin_articles' ], + [ 'GET', 'languages', 'AdminController::languagesAction', 'admin_languages' ], + [ 'GET', 'club/{id}', 'AdminController::clubAction', 'admin_club' ], + ], + + 'login' => [ null, null, 'LoginController::loginAction', 'login' ], + 'register' => [ null, null, 'LoginController::registerAction', 'register' ], + 'forgot-password' => [ null, null, 'LoginController::forgotPasswordAction', 'forgot_password' ], + 'resend-verification' => [ null, null, 'LoginController::resendVerificationAction', 'resend_verification' ], + 'logout' => [ null, null, 'LoginController::logoutAction', 'logout' ], + + 'robots.txt' => 'robots.txt', + 'sitemap.xml' => 'sitemap.xml' + ], + + 404 => function(Container $c) { (new Response($c, 'public', '404'))->render(); } + ]; diff --git a/src/controllers/AdminController.php b/src/controllers/AdminController.php new file mode 100644 index 0000000..e6ee6f5 --- /dev/null +++ b/src/controllers/AdminController.php @@ -0,0 +1,14 @@ + searchAction($q) + * value of $q above will be filled with $_GET['q'] value + */ + +class LoginController +{ + static function loginAction(DB $db, Container $c) + { + if ($c->request->isGet()) + (new Response($c, 'public', 'login'))->render(); + else + throw new \Exception('Process login action here ...'); + } + + static function registerAction(Container $c, $non_existing_param1 = null) + { + (new Response($c, 'public', 'register'))->render(); + } +} diff --git a/src/controllers/robots.txt.php b/src/controllers/robots.txt.php new file mode 100644 index 0000000..351cb38 --- /dev/null +++ b/src/controllers/robots.txt.php @@ -0,0 +1,17 @@ + +User-agent: * +Allow: / +Disallow: /admin/ + +Sitemap: http://request->uri->authority; ?>/sitemap.xml + + + + + + http://example.com/ + 2006-11-18 + daily + 0.8 + + + +Lousma: "This is Houston. Say again, please."
+Lovell: "Uh, Houston, we've had a problem."
+Lovell: "We've had a 404 Page Not Found."
+Lousma: "Roger, 404 Not Found."
+Lousma: "Okay, stand by, Thirteen, we're looking at it."
diff --git a/src/elements/admin/index/init.php b/src/elements/admin/index/init.php new file mode 100644 index 0000000..ba83e3f --- /dev/null +++ b/src/elements/admin/index/init.php @@ -0,0 +1,5 @@ + 'https', 'path' => '/login')), array('message' => 'invalid permissions', 'next' => '/admin/')); +// if (empty($_SESSION['id'])) redirect(request::build_url('https'), 'invalid-permissions'); + if (empty($_SESSION['id'])) request::redirect('https', '/login', ['message' => 'invalid permissions', 'next' => '/admin/']); diff --git a/src/elements/admin/index/view.php b/src/elements/admin/index/view.php new file mode 100644 index 0000000..2a40911 --- /dev/null +++ b/src/elements/admin/index/view.php @@ -0,0 +1,3 @@ +admin index view'; diff --git a/src/elements/footer/init.php b/src/elements/footer/init.php new file mode 100644 index 0000000..e69de29 diff --git a/src/elements/footer/view.php b/src/elements/footer/view.php new file mode 100644 index 0000000..e69de29 diff --git a/src/elements/header/init.php b/src/elements/header/init.php new file mode 100644 index 0000000..52c0315 --- /dev/null +++ b/src/elements/header/init.php @@ -0,0 +1,6 @@ +container->user; + +//echo $response->container->request->uri->getLeftPart(Uri::PARTIAL_AUTHORITY) . '/'; +//echo $response->container->request->uri->getLeftPart(Uri::PARTIAL_PATH) . '/mdfg'; + +//echo $response->container->request->uri->withScheme(null); + + + if ( ! $response->container->request->is_https) // || ! isset($_COOKIE[session_name()]) + $response->container->request->redirect('https'); + + $response->scripts['recaptcha'] = 'https://www.google.com/recaptcha/api.js'; + + // PROBLEM: if we only create session when the session cookie is set, it's only set (retrieved) on the SECOND page view! + // What I mean is, if you go immediately to the login page (no other page views), it will NOT have a session cookie stored yet! + // Actually, the cookie is stored on the client side, but it's not retrieveable yet, until the second page view!?!? + // Therefore, this session challenge will not be stored in the database if we first check to see if the cookie exists!?!? + // Maybe we should `fake` it on this page only, by manually setting the session cookie, so when the page ends, and the session must write, + // it will write the challenge. ie. We need to manually set `$_COOKIE[session_name()] = session_create_id();` + $_SESSION['challenge'] = md5(uniqid(microtime().mt_rand(), true)); // http://php.net/manual/en/function.mcrypt-create-iv.php + + // HACK + // We might need to do this!?!? + // What we are doing here is CREATING a cookie that didn't exist before. + // ALTERNATIVE: We REDIRECT back to this page if the cookie didn't exist before! + // This `might` be necessary on this page only, to FORCE the session_write_close() function to write the session data (which includes the `challenge`) to the database! + $_COOKIE[session_name()] = session_id(); + }; diff --git a/src/elements/login/post.php b/src/elements/login/post.php new file mode 100644 index 0000000..01dd52d --- /dev/null +++ b/src/elements/login/post.php @@ -0,0 +1,199 @@ +close(); + sleep(3); + redirect($from, array('message' => $msg, 'next' => $next)); + } +//print_r($_POST); +//print_r($_SESSION); + + if (empty($_SESSION['challenge']) || $challenge != $_SESSION['challenge']) delay('login-failed1'); + if (empty($email)) delay('login-failed2'); + if (empty($password)) delay('login-failed3'); + if (empty($response)) delay('login-failed4'); + + + // + // Verify reCAPTCHA + // + // https://www.google.com/recaptcha/admin#site/319890180?setup + // https://developers.google.com/recaptcha/docs/verify + + require CODE_PATH . 'lib/curl.php'; + $curl = new curl(); + $curl->post('https://www.google.com/recaptcha/api/siteverify', 'secret=' . env::get('google')['recaptcha-secret'] . '&response=' . $response, array(CURLOPT_SSL_VERIFYHOST => 0, CURLOPT_SSL_VERIFYPEER => 0)); + + //curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 0); // I needed these to pass my self signed certificate. cURL complains about unable to verify local certificate or something! + //curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0); + + if ($curl->response === false && env::isDebug()) + { + var_dump($curl->response); + var_dump($curl->error()); + var_dump($curl->errno()); + var_dump($curl->info); + } + + if ($curl->response === false) + delay('login-captcha-failed'); + $response = json_decode($curl->response, true); + if (!isset($response['success']) || $response['success'] === false) // https://developers.google.com/recaptcha/docs/verify + delay('login-captcha-failed'); + + $user = db::lookup('SELECT id, email_verified, password, salt FROM users WHERE email_hash = 0x' . md5($email) . ' LIMIT 1'); + if (empty($user) || hash_hmac('sha256', $password, $user['salt'], true) !== $user['password']) + delay('login-failed6'); + + unset($_SESSION['challenge']); + +//var_dump($_COOKIE); +//var_dump($_SESSION); + + /* + // Original code in session class + static function login($user_id, $persistent) + { + session_regenerate_id(true); + setcookie(session_name(), session_id(), $persistent ? 0x7fffffff : 0, '/'); + $_SESSION['id'] = $user_id; + self::$_db->real_query('INSERT INTO sessions (id, timestamp, persistent, user_id, data) VALUES (0x' . session_id() . ', UNIX_TIMESTAMP(), ' . ($persistent ? 'UNIX_TIMESTAMP(), ' : '0, ') . $user_id . ', "")'); + } + + // call the function + session::login($user['id'], $persistent); + */ + // We do almost everything here, because I don't want to 'polute' the session class with functionality that's only called/used here! + session_regenerate_id(true); + +//var_dump($_COOKIE); +//var_dump($_SESSION); + + // This cookie is used to `force` (redirect) the browser to the HTTPS url of ALL pages due to a possible session timeout! + setrawcookie('HTTPS_ONLY', time(), 0x7fffffff, '/'); + + setrawcookie(session_name(), session_id(), $persistent ? 0x7fffffff : 0, '/', null, true, true); // $_SERVER["HTTP_HOST"] || null ??? ... This ALSO ensures that the session cookie will ONLY be sent over HTTPS! + $_SESSION['id'] = $user['id']; +// session::login($user['id'], $persistent); // This is the only thing we do in the session class, because it requires the session's DB connection ... HOW RETARDED! + +//var_dump($_COOKIE); +//var_dump($_SESSION); + + // We MUST lock the `sessions` table, just in-case the session garbage collector runs between the scripts and deletes some extra sessions! + db::real_query('LOCK TABLES sessions WRITE, user_sessions WRITE, user_session_history WRITE'); + + // ARCHIVE USER SESSIONS: `user_sessions` => `user_session_history` + db::real_query('INSERT INTO user_session_history (id, user_id, ip, cc, agent_id, forwarded_for_id, via_id, created, modified, last_request, requests, page_views, time_on_site, persistent) SELECT id, user_id, ip, cc, agent_id, forwarded_for_id, via_id, created, modified, last_request, requests, page_views, time_on_site, persistent FROM user_sessions WHERE id NOT IN (SELECT id FROM sessions)'); + db::real_query('DELETE FROM user_sessions WHERE id NOT IN (SELECT id FROM sessions)'); + + // Create new session + if ($persistent) session::create_persistent_session(); // NEW! + session_write_close(); // might as well do this while we have a table lock !?!? + + db::real_query('UNLOCK TABLES'); // I think it's important to unlock these tables early so other scripts can continue to execute !?!? However, this runs so infrequently it shouldn't be an issue!?!? + + // Create a new entry in `user_sessions` ... most of the values can be empty, because we will UPDATE them on each page view! + db::real_query('INSERT INTO user_sessions (id, user_id, ip, cc, agent_id, forwarded_for_id, via_id, created, modified, last_request, requests, page_views, time_on_site, persistent) VALUES (0x' . session_id() . ',' . $user['id'] . ', 0x' . request::$ip2hex . ', "' . request::$cc . '", ' . request::$agent_id . ', ' . request::$forwarded_for_id . ', ' . request::$via_id . ', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 1, 1, 0, ' . (int) $persistent . ')'); + + // ORIGINAL + /* + $sql = 'UPDATE users SET ' . + 'previous_online = last_online,' . + 'last_login = NOW(),' . + 'last_online = NOW(),' . + //'last_domain_id = ' . $GLOBALS['FW']['domain']['id'] . ',' . + 'locale = "' . $GLOBALS['FW']['locale'] . '",' . + 'logins = logins + 1,' . + 'ip = @ip,' . + 'cc = @cc,' . + 'for_id = ' . $logs['forwarder_id'] . ',' . + 'via_id = ' . $logs['proxy_id'] . ',' . + 'agent_id = ' . $logs['agent_id'] . ',' . + 'referer_id = ' . $logs['referer_id'] . ',' . + 'language_id = ' . $logs['language_id'] . + ' WHERE id = ' . $_SESSION['id']; + */ + db::real_query('UPDATE users SET last_login = UNIX_TIMESTAMP(), logins = logins + 1 WHERE id = ' . $user['id']); + +// session_write_close(); // moved to the session table lock above, can be moved back without side-effects! + db::close(); + +// ORIGINAL +// redirect('http://' . $fqdn . $next, array('error' => &$errors, 'warning' => &$warnings, 'message' => &$messages)); + + redirect(empty($next) ? '/dashboard/' : $next, array('message' => 'login-welcome')); + + +/* +// Generate the first password ... +$salt = microtime(); +echo md5($salt) . '
'; +echo hash_hmac('sha256', 'abc123', md5($salt, true)) . '
'; +UPDATE users SET email_hash = UNHEX(MD5(email)), password = 0x9f37ce72aaad946e3a98b9dc6126e7c39855600a9d063058dfe79084cbf3a3e4, salt = 0x4c06ff520bc5198942657313a153ec09; +exit; +*/ + + +/* + // ORIGINAL + $this->update_user_logs(); + session_id(md5(uniqid(microtime().mt_rand(),true))); + //setrawcookie(session_name(), session_id(), 0x7fffffff, '/', '.iedb.net', false, true); // problem with this is that it doesn't override the previous session cookie. This is only applicable if we've deleted the cookies and refreshed the post page. + header('Set-Cookie: PHPSESSID=' . session_id() . ($persistent ? '; expires=Tue, 19-Jan-2038 03:14:07 GMT' : '') . '; path=/; httponly', true); // Later we can modify this to an SSL only cookie! + $DB->real_query('INSERT INTO sessions (id, timestamp, persistent, user_id, data) VALUES (0x' . session_id() . ', UNIX_TIMESTAMP(), ' . ($persistent ? 'UNIX_TIMESTAMP(), ' : '0, ') . $_SESSION['id'] . ', "")'); + return true; + + function update_user_logs() // try to run this in the framework after we have the $FW['domain']['id'] -- problem is: we don't know if the user has just "logged in" + { + $logs = $this->logs(); + + $GLOBALS['DB']->init_ipcc(); + + $sql = 'UPDATE users SET ' . + 'previous_online = last_online,' . + 'last_login = NOW(),' . + 'last_online = NOW(),' . + //'last_domain_id = ' . $GLOBALS['FW']['domain']['id'] . ',' . + 'locale = "' . $GLOBALS['FW']['locale'] . '",' . + 'logins = logins + 1,' . + 'ip = @ip,' . + 'cc = @cc,' . + 'for_id = ' . $logs['forwarder_id'] . ',' . + 'via_id = ' . $logs['proxy_id'] . ',' . + 'agent_id = ' . $logs['agent_id'] . ',' . + 'referer_id = ' . $logs['referer_id'] . ',' . + 'language_id = ' . $logs['language_id'] . + ' WHERE id = ' . $_SESSION['id']; + + $GLOBALS['DB']->real_query($sql); + } +*/ diff --git a/src/elements/login/view.php b/src/elements/login/view.php new file mode 100644 index 0000000..cfe5269 --- /dev/null +++ b/src/elements/login/view.php @@ -0,0 +1,68 @@ + +
+
Login
+
+ +
+ + + +
+ + +
+
+ + +
+ +
+ +
+ +
+
+
+ +
+
+ +
+ +
+ +
+ Loading Loading... +
+ +
+

By logging in you are agreeing to our Terms of Use and Privacy Policy.

+ +
+ Forgot your password?
+ Resend Email Verification
+ +
+

Not a member?

+ Create an account + +
+
+ + diff --git a/src/layouts/admin.php b/src/layouts/admin.php new file mode 100644 index 0000000..bd7d378 --- /dev/null +++ b/src/layouts/admin.php @@ -0,0 +1,60 @@ +title = 'Admin'; + + $response->styles['font-awesome'] = 'https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css'; // http://fontawesome.io/ + + $response->scripts['jquery'] = 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'; + $response->scripts['bootstrap'] = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js'; + + $response->elements['content'] = null; + $response->elements['navbar'] = 'admin/navbar'; + $response->elements['messages'] = 'admin/messages'; + + $response->robots = 'noindex,nofollow,noarchive'; + + $response->renderer = function() use ($response) + { + echo + '' . PHP_EOL . + '' . + '' . + '' . + '' . + '' . + '' . htmlspecialchars($response->title) . '' . + '' . + '' . + '' . + ''; + foreach ($response->styles as &$url) + echo ''; + foreach ($response->scripts as &$url) + echo ''; + echo + ($response->script ? '' : null) . + '' . + '' . + '
'; + // '
'; + // '
'; + require __DIR__ . '/../elements/' . $response->elements['navbar'] . '/view.php'; + echo + // '
' . + '
'; + require __DIR__ . '/../elements/' . $response->elements['messages'] . '/view.php'; + echo + '
' . + '
'; + require __DIR__ . '/../elements/' . $response->elements['content'] . '/view.php'; + echo + '
' . + // '
' . + '
' . + '' . + ''; + }; +}; diff --git a/src/layouts/public.php b/src/layouts/public.php new file mode 100644 index 0000000..781bee4 --- /dev/null +++ b/src/layouts/public.php @@ -0,0 +1,60 @@ +title = $response->container->config['title']; + + $response->styles['font-awesome'] = 'https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css'; // http://fontawesome.io/ + + $response->scripts['jquery'] = 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'; + $response->scripts['bootstrap'] = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js'; + + $response->elements['content'] = null; + $response->elements['header'] = 'header'; + $response->elements['footer'] = 'footer'; + + $response->meta['Play'] = ''; + + $response->renderer = function() use ($response) + { + echo + '' . PHP_EOL . + '' . + '' . + '' . + '' . + '' . + '' . htmlspecialchars($response->title) . '' . + '' . + '' . + '' . + '' . + '' . + '' . + ''; + foreach ($response->styles as &$url) + echo ''; + foreach ($response->scripts as &$url) + echo ''; + foreach ($response->meta as &$meta) + echo $meta; + echo + '' . + '' . + '
'; + + require __DIR__ . '/../elements/' . $response->elements['header'] . '/view.php'; + require __DIR__ . '/../elements/' . $response->elements['content'] . '/view.php'; + require __DIR__ . '/../elements/' . $response->elements['footer'] . '/view.php'; + + echo + '
' . + '' . + ''; + }; +};