userfrosting / UserFrosting

Modern PHP user login and management framework
https://www.userfrosting.com
Other
1.64k stars 366 forks source link

Question about directory structure #32

Closed lilfade closed 10 years ago

lilfade commented 10 years ago

This touches on #12 and #14 a bit but is there going to be any kind of structure here soon, i mean with the addition of people's work to the code base (on the end user's side not the repo) eg. @r3wt 's chat and my own person guild app, the directory is going to be huge here soon and to the point where a general user may just overlook the project because of this.

If we could implement some type of (H)MVC framework this would make it alot easier for the end user and for people looking to extend just a small part of the application rather then rewriting chunks on there own. Also this would make it easier to write modules/extension of the app while still using the original codebase rather then having 700 files in the root directory.

alexweissman commented 10 years ago

One of things I was thinking to do with function-level auth is to move backend pages to a separate directory, and have only view-level pages in the main directory. But of course, a more general framework for directory management would be preferable.

As far as implementing an MVC framework, what do you have in mind?

lilfade commented 10 years ago

Well im not sure to be honest I'd like to see the coding separated from the views, I know we have that sort of at the moment but a routing system with controllers for data models for database access and views for html but no logic would be nice.

I've tried doing this in a test case on my own but short of losing all the Ajax I couldn't get it working, and to be honest I rather like the Ajax for loading data.

If we could move to something like this we could use Ajax to load drop in modules such as his chat module or my guild module then we could add other modules as well most likely down the line people will look for a forum module or a news system for starters. But rather then people hacking everything together we can just unzip and say drop the chat folder into the modules folder and enable it via the admin dashboard.

lilfade commented 10 years ago

I guess maybe I should be asking is userfrosting going to be a user system or evolve into a full framework?

alexweissman commented 10 years ago

That's a tough question...the path of evolution is always difficult (impossible?) to predict ;-)

But, what do you mean by "framework" - something like Drupal/Django/CakePHP? Or were you thinking something like Wordpress?

lilfade commented 10 years ago

More like cake php or yii rather then a full cms system but the word press route kind of is what I ment on modules how you can get plugin enable them and bam there working.

For instance we can make a orm class to route db calls through rather then coding all statements which again would make it easier on us as well as the user.

lilfade commented 10 years ago

I guess in the long run it really depends on what route to go with it if its ment to be a full on application from here then starting with another premade base would be easier, if it would be more like a framework, a skeleton so to speak then we could resort files change the directory structure now and start building it up as such.

Most php frameworks i see always have people looking for a few of the usual things on every site but they want to be a bit more hands on with everything and make it there own. The things most people look for (as newer webmasters) are a news system/blog systems, a user system/authentication system, and a forum for there they usually grab other plugins/mods/modules to extend functionality, or build there own to fit that feature they need but cant find.

If we could make this base with the base features and simple to use then a lot of people would use it i bet.

alexweissman commented 10 years ago

Yeah, so I agree that we want to make it as easy as possible for people to add new functionality.

Wordpress is probably the exemplar for that; when you want some plugin, you just run an installer and configure it through the web interface. So, we could try to do something like that, except base it around user-admin systems, rather than blogging. However, as you point out, Wordpress is sort of hands-off to the point where it's hard to make a Wordpress-based site really your own.

I will say though, I think this is preferable to implementing a full-on programming framework. The thing I hate about programmatic frameworks is that there's always a million different choices, and they always require you to learn a special vocabulary. I find them tough to get started with, even following tutorials. Then, you've spent a bunch of time learning the lingo for Framework X only to find that hey, it's not really the best for your needs after all.

I do like the idea of ORM as it does get repetitive to write CRUD functions every time you add a new object. My only concern is that this be easy to extend. For example, sometimes I like to perform complex joins (for my tutor CMS, I have queries like "fetch all the tutors, and for each tutor get all the students they teach as an array, plus the total number of sessions they've completed". This joins data from five tables!) It would be nice to make it easy to extend an ORM system in this fashion.

lilfade commented 10 years ago

Yea and i know this code is originally for your website/business and its great that you feel like sharing it with us and also taking our suggestions unlike some people who are so set in there ways that its ridiculous to even try to suggest something.

Im with you on the framework thing when i start a project i usually look for a nice baseline to get started with and have to learn a bunch of new functions and when you need to ask for help people get all bent out of shape expecting you to know everything about the code even though your just starting.

The one main feature i like is the routing system and controller/view system it makes it alot easier for everyone to see whats going on and not have to mess up a complete template to simply change variables. Also at the moment were reusing html over and over again even though it rarely changes across pages, with some type of controller / view system we could slim down alot of files and save a tiny bandwidth as well. I also really like pretty url's eg. localhost/user/ (for all user related stuff) localhost/home/ (for new and stuff) localhost/admin/ (for admin related utility's').

As for the modules/mods/plugins i think it would make it alot nicer even if say we have {site_dir} = /www/{root_dir} then say for my WoW guild stuff or his chat module maybe it be stored in {site_dir}/guild or {site_dir}/chat respectively for ease of use. Then as you said visit a install script and allow the user to set up the information we need and install it and if they no longer need the module then a simple uninstall plugin button to rip it our while leaving the site fully functional still.

Some simple modules we could make would be the basics most people look for as mentioned earlier. And these would be fully independent of the user system / general website and it would still stay a user system rather then a full on application. If it ends up evolving into a full fledged cms or something then so be it but it would have better handling at that point then it does now.

lilfade commented 10 years ago

On a side note: I've started on a small bare bones system to get routing and controller / view systems working if you want when i get some stuff working i can commit it into a branch and you can check it out. Although i'm no pro by anymeans and php is one of my hobbies i'm always down to help out how i can but my weakness is js really and had you not had that widget-users files i don't think id have as much done as i do now. I basically just used your widgets as templates to create my own(they were very helpful).

r3wt commented 10 years ago

One alternative, and please note this is no small undertaking is to have a single index file that accepts a get request for the page you want to view. instead of fetching this view page from disk via require once, you can generate it from a function. here's an example from my current project.

what the directory structure looks like title

the code


if(isEnabled('maintenance')) {
        if(isset($_GET['p'])) {
            switch(security($_GET['p'])) {
                case 'admin'      : AdminPage(); break;
                case 'support'    : SupportPage(); break;
                case 'home'       : HomePage(); break;
                case 'login'      : LoginPage(); break;
                case 'logout'     : LogoutPage(); break;
                case 'register'   : RegisterPage(); break;
                case 'account'    : AccountPage(); break;
                case 'trade'      : TradePage(); break;
                case 'settings'   : SettingsPage(); break;
                case 'forgot-pass': ForgotPage(); break;
                default           : HomePage();
            }   
        }else{
            $_GET['p'] = '';
            HomePage();
        }
    }else{
        MaintenancePage();
    }

To separate the views from the normal functions, i simply created a file called /models/views.php and added the following to config.php:

require_once __DIR__ . '/views.php';

I can't really say its any easier than working with separate files for each view, but editing and updating views is much simpler, and the main directory no longer needs a trillion files.

You can even get tricky with url rewriting to make the url's more appeasing, but this offers little SEO value, but can be of benefit to your customers who might be confused by a link such as index.php?p=login&action=2fa

for example, i've rewritten mine as so:

www.example.com/?p=login but you could even do www.example.com/login

alexweissman commented 10 years ago

@lilfade yes, I'd love to check it out. Also, do you have any recommended reading about routing schemes and the like? I honestly don't know much about them which is probably one reason I'm being so skittish.

@r3wt that's a cool idea. Like you pointed out, my concern would be the SEO aspects of having everything effectively on one "page". But for backend management systems obviously SEO isn't a concern.

As @lilfade and others point out, just having files organized better would probably go a long way to making this more usable. Maybe we should start with that?

lilfade commented 10 years ago

I was thinking something like this so we dont need to manually code each route to make it a bit easier so everything autoloads.

abstract class UF_Controller {
  protected $controller_path='../app/controllers/'; //with trailing slash
  protected $web_folder='/'; //with trailing slash
  protected $request_uri_parts=array();
  protected $controller;
  protected $action;
  protected $params=array();

  function __construct($controller_path,$web_folder,$default_controller,$default_action)  {
    $this->controller_path=$controller_path;
    $this->web_folder=$web_folder;
    $this->controller=$default_controller;
    $this->action=$default_action;
    $this->explode_http_request()->parse_http_request()->route_request();
  }

  function explode_http_request() {
    $requri = $_SERVER['REQUEST_URI'];
    if (strpos($requri,$this->web_folder)===0)
      $requri=substr($requri,strlen($this->web_folder));
    $this->request_uri_parts = $requri ? explode('/',$requri) : array();
    return $this;
  }

  //This function parses the HTTP request to get the controller name, action name and parameter array.
  function parse_http_request() {
    $this->params = array();
    $p = $this->request_uri_parts;
    if (isset($p[0]) && $p[0] && $p[0][0]!='?')
      $this->controller=$p[0];
    if (isset($p[1]) && $p[1] && $p[1][0]!='?')
      $this->action=$p[1];
    if (isset($p[2]))
      $this->params=array_slice($p,2);
    return $this;
  }

  //This function maps the controller name and action name to the file location of the .php file to include
  function route_request() {
    $controllerfile=$this->controller_path.$this->controller.'/'.$this->action.'.php';
    if (!preg_match('#^[A-Za-z0-9_-]+$#',$this->controller) || !file_exists($controllerfile))
      $this->request_not_found('Controller file not found: '.$controllerfile);
    $function='_'.$this->action;
    if (!preg_match('#^[A-Za-z_][A-Za-z0-9_-]*$#',$function) || function_exists($function))
      $this->request_not_found('Invalid function name: '.$function);
    require($controllerfile);
    if (!function_exists($function))
      $this->request_not_found('Function not found: '.$function);
    call_user_func_array($function,$this->params);
    return $this;
  }

  //Override this function for your own custom 404 page
  function request_not_found($msg='') {
    header("HTTP/1.0 404 Not Found");
    die('<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>'.$msg.'<p>The requested URL was not found on this server.</p><p>Please go <a href="javascript: history.back(1)">back</a> and try again.</p><hr /><p>Powered By: <a href="http://userfrosting.com">UserFrosting</a></p></body></html>');
  }
}

then to load a class just make a file in ./app/controllers/{module_name}/

<?php
function _index() {
   //code here
}

As for the view is where im a little short on experience should we keep all php out of the view or use <? echo $varname; ?> in views to make it easy to edit, although this would require we force ourselves to not do anything other then that in the views such as for each's (if we can help it haha). This would allow a basic htaccess file to make urls like {domain.tld}/index.php/controller/action/ or even better {domain.tld}/controller/action/

Options -MultiViews
RewriteEngine On

Options -Indexes

RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l

RewriteRule ^(.+)$ index.php?url=$1 [QSA,L]
r3wt commented 10 years ago

@lilfade i'm sure you're well aware, but the security of the http_parse function is debatable. hard to exploit, but definitely not impossible. Manually mapping uri's to views is the only 100% sure way to prevent RFI and LFI attacks, which is why i have and continue to do such things manually. the risk is to great in my eyes. Dont get me wrong, your skill level is clearly high, but i think instead of automatically parsing we could simply add another global to config.php. basically we store the array of pages. in the documentation, we show how to add a view to the array. then simply check for "if in_array()". For mission critical sites this is a no brainer, but for this project, i guess i could go either way. Other than the parsing of the uri, i'm game for your proposed model. great work.

lilfade commented 10 years ago

Well one other thing we could do is to hard code our links/pages manually and if we go along with this sort of plan and add in functionally for plugins and such we could grab our plugin links and pages from the db. We could install location links when we install the plugin and just set em up in the config to autoload on when we need them. We can even cache them to lower the query's we need to make as most admins will know how to clear the cache to get new files, for the beginner and ease of use we could have something in the admin panel to clear all cache at once.

Of course this is @alexweissman 's project and im down with whatever route he wants to go. I really like this project and the upgrading of the usercake system as well. I was browsing the forums there the other day and i see so many people waiting on 3.0 (even the moderator Tyson i believe) and new features and upgrades. I've seen other project offshoots that tried to do what UserFrosting is doing but failed right away so i hope we can continue this and help some other people out in the long run as well.

Edit: I did find this on stack over flow on the topic as well to limit security a bit more

Haven't used this in practice, so there might be practicality issues I wasn't counting on, but thought I'd contribute the idea anyway. If I interpret correctly, this is similar to (if not the same as) the idea @Seldaek posted.

Your Server generates a unique ID for each page-serve and embeds the ID in the page.
Server stores the ID and the Client's IP address.
The js on the client places the ID in its request to the Server and sends the request.
When the Server receives the js request from the Client, it only responds if the IP/ID pair matches one that is on-file (see #2).
After some specified time (and/or when the browser session ends), the ID/IP entries expire.
This could perhaps be faked if a person sharing the visitor's IP address (perhaps both are behind the same NAT box) hijacks another visitor's session in real-time, but it will at least prevent someone from making another web page which piggybacks on your server's service.

There could also be issues if, for some reason, your visitor's IP address changes between when the page was served and when the js request was sent.

Basically, your server is saying "I will not service your js request unless you possess the data from a page I recently served and you are coming from (to the best of my knowledge) the place to which I served that page."
r3wt commented 10 years ago

@lilfade I would be game for that approach as well(storing views in db), and i to am down for whatever. i think it would be a great chance for me to extend loggedInUser class and add a guest class so we can introduce uniform, rolling throttle ajax rate limiting.

lilfade commented 10 years ago

I guess we'll see what @alexweissman says about it all i know its quite a read here now haha but some good ideas. As i said im down for whatever, my main reason for wanting the reorganization mainly is so i can keep up to date on the main codebase. So far i find myself editing main files for my own use and i'd rather not, i'd rather still be able to merge changes and stuff automatically. Also it looks nicer not having 700 files in the main directory haha.

alexweissman commented 10 years ago

You guys are clearly putting a lot of thought and effort into this, so thank you! Yes, I definitely want to keep this project going and I think it could be really successful, especially if we improve the code organization. But I have to admit, I'm already starting to get lost with how this would work. Could you give me an example of how, say, user CRUD would be implemented with this? Would there be a "User" class with associated "create", "update", "delete", "view" methods? And then, these methods would be mapped to pages?

lilfade commented 10 years ago

Well the crud would be the easy part basically with the code I posted you would have urls like {domain_Url}/user/{action} so instead of user.php just showing userinfo, instead it would be a full class so /user/ would show users /user/create would trigger our create function /user/register/ would be our register script /user/delete/ would of course delete the user. That would all be contained in the user.php page (user controller)

lilfade commented 10 years ago

Sorry for a better example when im not on my phone a example of the user.php page (the controller) would be like this (minus code)

{base_url} = domain.tld/index.php/ or with rewrite {base_url} = domain.tld/

<?php
function _index() {
  //code here thow show when user goes to {base_url}/user/
}

function _create() {
  //code to show when url is {base_url}/user/create
}

function _delete($id) {
  //shown when url is {base_url}/user/delete/$id
}

//...ect

When we use a code like this we will remove a bunch of pages based on each action so the user stuff would all be contained in the user.php file, the admin stuff would be in the admin.php controller and so on.

alexweissman commented 10 years ago

Ahhh ok. So the controllers would be implemented as pages, not classes. And then each page/controller would support multiple actions...I like!

lilfade commented 10 years ago

If you want to add another codebase maybe a dev codebase that we can fork and be contributors to work off it might make it alot easier for the 3 of us.

alexweissman commented 10 years ago

Done. Ok cool, we can work on this through the dev branch now.

alexweissman commented 10 years ago

Oh, one other thought. We still have the pages table from UserCake. What should we do with that?
One possibility is to eliminate it entirely, and just use my function-level authorization scheme everywhere. Another possibility is to repurpose it, and use it for mapping links (such as /user/create) to their corresponding controllers and actions.

lilfade commented 10 years ago

Well as r3wt was saying we could grab plugin page names from the database and hard code core files into the controller class for security.

lilfade commented 10 years ago

Okay looks like it merged and stuff i work nights that's why it seems like i'm on 18 hours a day xD but if i can get it working a little better tonight ill do a pull again when you get a chance to play around let me know what you think.

lilfade commented 10 years ago

Well i realized the code base i was starting from ... sucked i decided to use a barebones framework to restart and got the main page working as well as the login function. One major issue i found is its not saving sessions across pages and i think its due to the apache mod_rewrite for some reason and im not sure what to do about it at the moment but am going to continue working on it as i have time. If you feel like checking out the most recent commit and testing it out here you go: https://github.com/lilfade/ufmvc/commit/96527e66c0d311db6ae62de5d8e767b0dfe832e9

lilfade commented 10 years ago

Well after further reading and coding i dont think this would be the best route, although i'd still like to see file compaction rather then the mvc model. Have you come up with any ideas in that respect ?

Edit: I've tried to move the admin based files into a directory /admin/ but due to the heavy use of js i've had to modify the base code to make it work (mainly css/js links as well as some includes) but most stuff does not work for example to load the admin dashboard i had to set a complete copy of the js directory into the admin folder as well and even then the widgets don't work anymore for what reason i have no idea ... yet. None the less i feel like my eyes are bleeding time for some sleep i shall work on it more later.

lilfade commented 10 years ago

Closing this as its old now.