This document is an attempt to track the flow of code through WordPress. It stays fairly high-level and tracks the flow, rather than explaining how a specific block of code works. It assumes a familiarity with PHP.
This document deals with the 1.5 branch.
NB: Line numbers refer to how the code was in 188.8.131.52, these may change with subsequent releases.
The request initially comes in to index.php. This is an extremely short file that defines a constant, WP_USE_THEMES, to tell WordPress to use theme redirection, then hands control over to wp-blog-header.php.
The first important line in wp-blog-header.php is line 12, where wp-config.php is included.
Again, a short but crucial file. You'll probably recognise this file if you've already set up your WordPress blog: this is where the constants are defined like username, password and host that allow WordPress to connect to your database. It is also the place where you define your locale if you're not running an English blog. See Editing wp-config.php and WordPress Localization. Control is then handed over to wp-settings.php.
This is again a pretty important file. Its roles are to initialise the WordPress global variables and singletons, and include a lot of vital files. Files that wp-settings.php includes:
These are all just files that define functions or classes the are used by WordPress later on. Important files are functions.php (and derivatives like functions-formatting.php and comment-functions.php) that define useful functions for internal WordPress usage. Also, the template-functions-*.php family, which define the Template Tags.
Another role of wp-settings.php is to load the plugins from the wp-content/plugins directory. This is done with the block of code starting on line 128. It pulls an array of the active plugin from the option 'active_plugins' and loads each one. Thus, any code before this line cannot be affected by plugins.
After the plugins are loaded, wp-includes/pluggable-functions.php is loaded, which load some more important internal WordPress functions, but each function is surrounded by an if () statement that checks to see if any file has already defined a function with that name. If so, that function is not defined. All this gives plugins the chance to use their own functions in place of some core WordPress ones. (This is available with WordPress 1.5.1+). See Pluggable Functions for more detail and a reference.
(beginning line 16)
The next big block in wp-blog-header.php deals with $_SERVER['PATH_INFO']. This is a predefined PHP variable. If you request a file, and then append, for example, /bob/charlie (so your request would be http://example.com/file.php/bob/charlie), $_SERVER['PATH_INFO'] gets filled with whatever's after the filename. If you are using the nice permalinks feature of WordPress, WordPress will understand a URL like http://example.com/index.php/category/sausages. This is useful when writing .htaccess RewriteRules.
(beginning line 88)
wp-blog-header.php then defines an array of 'known query variables', so to speak: the query variables that WordPress understands: $wpvarstoreset (poor name, yes). wp-blog-header.php then iterates over this array, and for each query variable, sees if there is an associated variable in either $_GET or $_POST. If there is, this variable is filled with the value from $_GET or $_POST. For example, if for the query variable 'category_name' there is a value for $_GET['category_name'] or $_POST['category_name'], $category_name will be filled with that value.
(beginning line 109)
The next thing wp-blog-header.php is send out some important HTTP headers. The if () statement here has three branches, corresponding to three different scenarios:
(beginning line 160)
wp-blog-header.php must now start to figure out what the client has requested. First, a new array, $more_wpvars is defined which contains a list of variables that aren't to be passed in the query string but are still going to be useful for what we're about to do.
A loop begins on line 164 that constructs a new query string that completely defines this request: every query variable that was specified in the query string is collected into a query string. Also, if any of the variables listed in $more_wpvars have values, they're added to query string as well. So we may end up with a query srting that looks something like the following:
That string would be for the category archive for the 'tech' category, page two, displaying 10 posts on every page. The query string is then passed into query_posts() (line 177). This calls $wp_query->query (the WP_Query is defined in wp-includes/classes.php), which calls $wp_query->parse_query. This function disects the query string and determines what kind of query we're looking at: a category page, a dated archive page, a feed? $wp_query->parse_query() sets a load of variables in the $wp_query singleton that allow the is_feed(), is_home(), is_category() etc. functions to work. They will not work before line 177.
$wp_query->query() has a second role as well: it also calls $wp_query->get_posts(), which looks at the query variables and retrieves the correct set of posts. This is assigned to the global $posts variable right back in wp-blog-header.php.
(beginning line 190)
There is now a second chance to issue a '404 Not Modified' header: if no posts were found by $wp_query->get_posts() and we haven't already issued a 404 header, then one is sent here.
(beginning line 206)
So, the request has been parsed and all posts have been retrieved. The next thing to do is to display them. The next role for wp-blog-header.php is to give control over to the active theme. This block of code simply determines which theme file to load, and does so. The constant TEMPLATEPATH, defined in wp-settings.php on line 153, contains the path to the active theme. See Theme Development for information on which file is loaded in which scenarios.
Note: WordPress uses its own template files for displaying feeds. If the request is a feed, then no theme is loaded, but control is passed over to wp-feed.php, which loads the correct feed template file (wp-rss.php, wp-rss2.php or wp-atom.php).
Themes have the important responsibility to display the posts. Typically, there's two scenarios themes might be asked to deal with: display one post, or display a list of posts. The first comes about when the request is a permalink or a Page, the second is normal for any other archive.
A call to get_header() is typical at the top of theme files: it fetches the header.php from the active theme.
You've probably heard of The Loop. It's a crucial part of any theme. It is, in essence, a PHP loop which loops over the array of posts ($wp_query->posts is used instead of the global $posts), and displays each post using the Template Tags. The loop typically looks like this:
if (have_posts()): while (have_posts()): the_post(); ... endwhile; endif;
(If the syntax looks unfamiliar, that's because it's using the alternative syntax for PHP control structures). have_posts() and the_posts() are both wrapper functions for WP_Query functions, head over to that page for more information on them.
There should be a loop in each theme file which isn't designed for showing a single post (which would be single.php and page.php).
After The Loop has completed, sidebar.php is included via get_sidebar() (unless the theme is a one-column design), and then footer.php with get_footer().
And thus ends our story of a WordPress request.