perkiset

I've received a lot of PMs and seen a lot of posts about folks wanting to do more powerful things with

PHP

 , and since NutBalls is <>on the path to salvation

learn

 ing 

PHP

  as well, I figgered now would be a great time to do a demonstration thread on what a completely dynamic

PHP

  site might look like.

The client that I'm using as an example came to me with a reasonably attractive, but inconsistent and completely HTML file based site. The code was horribly FrontPaged and the JS was ... well, it needed some work. The client and I decided that a consistent look for the site was required, better navs, a couple changes to the galleries (he is an artist) and wants to do a couple updates per ear. Since I don't like tooling about in crappy HTML, I decided to give away a couple hours of my time and convert the site completely to a

PHP

 , theme-based site. The graphics and "basic feel" of the site were not to be changed.

Essentially, rather than being a hodge-podge collection of HTML, CSS and JS files, it is now a web application - even though it looks simply like a regular website. The way that it is built gives me a centralized point of entry and exit for every page... giving rise to the possibility of doing "something" for every single page call ie., surfer tracking, cloaking if that's your bag, on the fly language translation, shopping cart control... you name it, this is the first step in getting towards that direction.

From an

SEO

  perspective, I don't want or need anyone seeing that I use

PHP

 , nor the shape and structure of my site, nor any GET parameters - which only opens the door for XSS and SQL injection attacks - even if there's no way someone could get in, it doesn't mean they won't try. So I have the .HTML url converted in

Apache

  to a call to a single script, and the original URL is passed as a GET parameter to me. I can then do anything I want to with it, because I perceive the main.

php

  function to be like a single function call, and the GET parameters to be kind of like function parameters that I am responding to. This makes for a pretty simply programmatic structure and is easy to maintain.

Additionally, you will see in this example the notion of using a theme - I like separating the header and footer from the content. This makes it easy to modify a single file and all pages on the website are modified instantly. Although there are many, many ways to do this, I will present here an easy way that I use often.

There will be opinions about the right way and better ways... I get that. If you have suggestions that's completely cool - I'm only presenting <i>one</i> way to spur the imaginations of n00bs and those that haven't done a bunch of this, so they can get an idea of what this kind of site from a back-end or code perspective looks like. There will almost certainly be thoughts about why I blend traditional table layout with CSS work - this is because, IME, a combination of the LCD of both creates the most robust and stable websites regardless of browser.

A munged version of the new site can be seen here:

http://demo1-after.perkiset.org

You may look at it for reference as to what is going on.

perkiset

The first thing that needs to be done is to have

Apache

  rewrite the URL so that requests for any number of pages (or even incorrect ones) come into my central script instead. I use VirtualHost directives inside of the httpd.conf file, but this work can be done with a tiny amount of modification through the .htaccess file as well.

The original VirtualHost section looked like this (it has since been removed):

<VirtualHost 1.2.3.4:80>
        ServerName      demo1-before.perkiset.org
        DocumentRoot    /www/sites/perkiset/demo1-before
</VirtualHost>


The new one looks like this:

<VirtualHost 1.2.3.4:80>
        ServerName      demo1-after.perkiset.org
        DocumentRoot    /www/sites/perkiset/demo1-after

        RewriteEngine  on
        RewriteCond    %{REQUEST_URI}  /graphics      [OR]
        RewriteCond    %{REQUEST_URI}  /

javascript

 
        RewriteRule    ^(.*)$  -      <>

        RewriteRule    ^(.*)$  /main.

php

 ?uri=$1
</VirtualHost>


The most important change is the mod_rewrite section. The steps as you see them are:
* Turn the rewriting engine on
* Condition: If the requested URI looks like "*/graphics*" OR
* Condition: if the requested URI looks like "*/

javascript

 *" THEN
* RULE: Don't do anything to the request, and stop rewriting.
* RULE: Since there are no conditions, this will always be executed:
rewrite the entire URL from "/afile.html" (for example) to "/main.

php

 ?uri=/afile.html"

That's it! Now every single request, even it is a page that doesn't exist will get shoved into main.

php

  for me to handle.

perkiset

My website structures pretty much look the same all the time, regardless of application or client.
Here is a look at the /www/sites/perkiset/demos/demo1-after directory, then the pages directory and the theme directory:

perkiset


<?

php

 

// Needed vars...
$thisDir = '/www/sites/perkiset/demos/demo1-after';
$pages = 'pages';
$pagePath = "$thisDir/pages";
$content = array();

// Build Translation table...
$transArr['/'] = $transArr['/index.html'] = 'index.

php

 ';
$transArr['/cars.html'] = $transArr['/cars/cars-2.html'] = 'cars.

php

 ';
$transArr['/diners.html'] = $transArr['/diners/diners.html'] = 'diners.

php

 ';
$transArr['/figures.html'] = $transArr['/figures/figures.html'] = 'figures.

php

 ';
$transArr['/jungle.html'] = $transArr['/jungle/jungle.html'] = 'jungle.

php

 ';
$transArr['/misc.html'] = $transArr['/miscellany/miscellaney.html'] = 'misc.

php

 ';
$transArr['/marbles.html'] = $transArr['/marbles/marbles.html'] = 'marbles.

php

 ';
$transArr['/rocks.html'] = $transArr['/rocks/rocks.html'] = 'rocks.

php

 ';
$transArr['/contact.html'] = $transArr['/contact_page/contact.html'] = 'contact.

php

 ';
$transArr['/bio.html'] = $transArr['/artist_bio/bio.html'] = 'bio.

php

 ';

// Get the header/footer...
require("$thisDir/theme/header.

php

 ");

// Attempt translation - if not, then return a not found page...
$inPath = $transArr[$_GET['uri']];
$fullPath = "$pagePath/$inPath";
if ((!$inPath) || (!file_exists($fullPath)))
{
header("HTTP/1.0 404 Not Found");
echo $header, '<br><h3>Unfortunately that page is no longer available.<br>',
'Please <a href="/index.html" class="arial">CLICK HERE</a> to return to our home page.<br><br>',
$footer;
exit;
}

// Get the actual data page...
require($fullPath);


// Echo the page...
echo $header, implode(chr(10), $content), $footer;

?>


Let's look at that step by step:

First step, obviously, define the vars that I'll be needing.

Second, the transation table.
The translation table is where I convert inbound URLs into actual

PHP

  files that I will be using. Essentially I will use an array of possible inbound URLs as my array indicies and the

php

  file that I want to use as the value. Note how both $transArr['/'] and $transArr['/index.html'] both get converted to 'index.

php

 ' - this helps be gather older URLs that might be cached at Google, or simply multiple ways that I might want a surfer to be able to get to a page. The $this = $that = $another form is simply a handy one-line way of assigning values.

Next I get the theme information. The header file looks like this:

<?

php

 

$year = date('Y', time());

$header = <<<HEADER
<title>Applauseemo by Perkiset</title>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<style type="text/css">
.arial { font-family: Arial, Helvetica, sans-serif; font-weight: normal; color: #ffffff; }
.bold { font-weight: bold; }
.s10 { font-size: 10px; }
.s11 { font-size: 11px; }
.s12 { font-size: 12px; }
.s14 { font-size: 14px; }
a.arial { text-decoration: none; }
a.arial:HOVER { background-color: #202020; }
</style>

</HEAD>
<BODY bgcolor="#363636" text="#FFFFFF" leftmargin="0" marginwidth="0">
<script src="/

javascript

 /clientBase.js"></script>

<center>
<table cellpadding="0" cellspacing="0" border="0">
<tr><td><img src="/graphics/dot_clear.gif" height="4" width="1"></td></tr>
<tr><td><a href="/index.html"><img src="/graphics/Signature.jpg" width="803" height="99" border="0"></a></td></tr>
<tr><td><img src="/graphics/dot_clear.gif" height="12" width="1"></td></tr>
<tr><td align="center">
HEADER;

$footer = <<<FOOTER
</td></tr>
<tr><td><img src="/graphics/dot_clear.gif" height="15" width="1"></td></tr>
<tr><td><img src="/graphics/stripe.jpg" height="20" width="803"></td></tr>
<tr><td><img src="/graphics/dot_clear.gif" height="10" width="1"></td></tr>
<tr><td align="center" nowrap>
<font size="3">
<a href="/" class="arial bold">&nbsp;home&nbsp;</a>
<img src="/graphics/dot_clear.gif" height="1" width="20">
<a href="/contact.html" class="arial bold">&nbsp;studio & contact&nbsp;</a>
<img src="/graphics/dot_clear.gif" height="1" width="20">
<a href="/bio.html" class="arial bold">&nbsp;artist bio&nbsp;</a>
<img src="/graphics/dot_clear.gif" height="1" width="20">
<a href="mailto:perk@perkiset.org" class="arial bold">&nbsp;email&nbsp;</a>
</font>
</td></tr>
<tr><td><img src="/graphics/dot_clear.gif" height="20" width="1"></td></tr>
<tr><td class="arial" align="center"><font size="2">
Code: Copyright &copy; Perkiset, 2002-$year<br>
Art: Copyright &copy; Someone else, but the copyright is equally important: 2002-$year<br>
Please don't scoff or scrape this content. Please.<br>
All Rights Reserved
</font></td></tr>
</body></html>
FOOTER;

?>


It is really only responsible for two variables: $header and $footer. Note that the clientBase.js is simply one of my stock "handy function"

javascript

  files that I often include with sites by default.

My next step is to attempt to convert the inbound URL into a working file name where I can go get the body-content of this page (reprint from above):

// Attempt translation - if not, then return a not found page...
$inPath = $transArr[$_GET['uri']];
$fullPath = "$pagePath/$inPath";
if ((!$inPath) || (!file_exists($fullPath)))
{
echo $header, '<br><h3>Unfortunately that page is no longer available.<br>',
'Please <a href="/index.html" class="arial">CLICK HERE</a> to return to our home page.<br><br>',
$footer;
exit;
}

// Get the actual data page...
require($fullPath);


As you can see, I simply see if the inbound URL is an entry in my translation table - if it is, require the file - if it isn't, I send back a nice looking 404 page that is within the framework of the site - a much prettier way of doing things than simply "404 Page not found." You can try it with this unknown URL:

http://demo1-after.perkiset.org/forcefail.html

Once the actual content has been included, I echo the whole mess out:

// Echo the page...
echo $header, implode(chr(10), $content), $footer;


And that's it! Next post, one of the content pages.

perkiset

Here is the content of the "Miscellany" page (You may want to expand the size of your browser window if the code is getting cut off, or better yet, go to the demo site and grab the source from there)


<?

php

 
$previousGallery = '/jungle.html';
$nextGallery = '/marbles.html';
$galleryName = 'misc';
$default = 'setFlash(1)';
include("$pagePath/galleryHeader.

php

 ");

$content[] = $gHeader;
$content[] = <<<HTML
<table cellpadding="0" cellspacing="0" border="0">
<tr valign="top">
<td valign="middle"><div id="detail"></div></td>
<td><img src="/graphics/dot_clear.gif" height="500" width="30"></td>
<td>
<table cellpadding="0" cellspacing="0" border="0">
<tr><td colspan="5"><img src="/graphics/Misc_Header.jpg" height="36" width="273"></td></tr>
<tr><td colspan="5"><img src="/graphics/dot_clear.gif" height="5"></td></tr>
<tr><td colspan="5" onMou

seO

 ver="highlightCell(this, '#202020')" onMou

seO

 ut="highlightCell(this, 'transparent')" onClick="setFlash(0)">
<div class="arial"><font size="3">&nbsp;Miscellaney is a word...</font></div>
</td></tr>
<tr><td colspan="5"><img src="/graphics/dot_clear.gif" height="15"></td></tr>
<tr>
<td><img src="/graphics/thumb/misc-01_thm.jpg" class="thumb" onClick="setFlash(1)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/misc-02_thm.jpg" class="thumb" onClick="setImage(0)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/misc-03_thm.jpg" class="thumb" onClick="setFlash(2)"></td>
</tr>
<tr><td colspan="5"><img src="/graphics/dot_clear.gif" class="vspace"></td></tr>
<tr>
<td><img src="/graphics/thumb/misc-04_thm.jpg" class="thumb" onClick="setImage(1)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/misc-05_thm.jpg" class="thumb" onClick="setImage(2)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/misc-06_thm.jpg" class="thumb" onClick="setImage(3)"></td>
</tr>
<tr><td colspan="5"><img src="/graphics/dot_clear.gif" class="vspace"></td></tr>
<tr>
<td><img src="/graphics/thumb/misc-07_thm.jpg" class="thumb" onClick="setImage(4)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/misc-08_thm.jpg" class="thumb" onClick="setFlash(3)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/misc-09_thm.jpg" class="thumb" onClick="setImage(5)"></td>
</tr>
<tr><td colspan="5"><img src="/graphics/dot_clear.gif" class="vspace"></td></tr>
<tr>
<td><img src="/graphics/thumb/misc-10_thm.jpg" class="thumb" onClick="setImage(6)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/misc-11_thm.jpg" class="thumb" onClick="setFlash(4)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/misc-12_thm.jpg" class="thumb" onClick="setFlash(5)"></td>
</tr>
<tr><td colspan="5"><img src="/graphics/dot_clear.gif" class="vspace"></td></tr>
<tr>
<td><img src="/graphics/thumb/misc-13_thm.jpg" class="thumb" onClick="setImage(7)"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/thumb/plow.jpg" class="thumb" onClick="setImage(Applause"></td>
<td><img src="/graphics/dot_clear.gif" class="hspace"></td>
<td><img src="/graphics/dot_clear.gif" height="74" width="74"></td>
</tr>
</table>
</td>
</tr>
</table>

<script>
images[0] = new Image(500, 148);
images[0].src = '/graphics/misc-02.jpg';
images[1] = new Image(500, 375);
images[1].src = '/graphics/misc-04.jpg';
images[2] = new Image(500, 375);
images[2].src = '/graphics/misc-05.jpg';
images[3] = new Image(500, 331);
images[3].src = '/graphics/misc-06.jpg';
images[4] = new Image(500, 497);
images[4].src = '/graphics/misc-07.jpg';
images[5] = new Image(500, 371);
images[5].src = '/graphics/misc-09.jpg';
images[6] = new Image(249, 500);
images[6].src = '/graphics/misc-10.jpg';
images[7] = new Image(371, 500);
images[7].src = '/graphics/misc-13.jpg';
images[8] = new Image(500, 397);
images[8].src = '/graphics/plow_full.jpg';

flash[0] = '<div style="text-align:left; width: 90%" class="arial"><font size="3">Text Removed<br><br>' +
'The content of this area has been removed' +
'<br><img src="/graphics/dot_clear.gif" height="150" width="1">';

flash[1] = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.

mac

 romedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="500" height="500" id="misc-01" align="middle">' +
'<param name="movie" value="/graphics/misc-01.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#363636" />' +
'<embed src="/graphics/misc-01.swf" quality="high" bgcolor="#363636" width="500" height="500" name="misc-01" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.

mac

 romedia.com/go/getflashplayer" />' +
'</object>';

flash[2] = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.

mac

 romedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="500" height="500" id="misc-03" align="middle">' +
'<param name="movie" value="/graphics/misc-03.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#363636" />' +
'<embed src="/graphics/misc-03.swf" quality="high" bgcolor="#363636" width="500" height="500" name="misc-03" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.

mac

 romedia.com/go/getflashplayer" />' +
'</object>';

flash[3] = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.

mac

 romedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="500" height="500" id="misc-08" align="middle">' +
'<param name="movie" value="/graphics/misc-08.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#363636" />' +
'<embed src="/graphics/misc-08.swf" quality="high" bgcolor="#363636" width="500" height="500" name="misc-08" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.

mac

 romedia.com/go/getflashplayer" />' +
'</object>';

flash[4] = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.

mac

 romedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="500" height="500" id="misc-011" align="middle">' +
'<param name="movie" value="/graphics/misc-011.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#363636" />' +
'<embed src="/graphics/misc-011.swf" quality="high" bgcolor="#363636" width="500" height="500" name="misc-011" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.

mac

 romedia.com/go/getflashplayer" />' +
'</object>';

flash[5] = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.

mac

 romedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="500" height="500" id="misc-12" align="middle">' +
'<param name="movie" value="/graphics/misc-12.swf" /><param name="quality" value="high" /><param name="bgcolor" value="#363636" />' +
'<embed src="/graphics/misc-12.swf" quality="high" bgcolor="#363636" width="500" height="500" name="misc-12" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.

mac

 romedia.com/go/getflashplayer" />' +
'</object>';

</script>
HTML;

$content[] = $gFooter;

?>


Examination of the

Javascript

  is beyond the scope of this article, but feel free to post questions about it if you wish.

The 2 essential elements of this file are the inclusion of the "galleryHeader.

php

 " file, which contains the nav and look for the galleries, and the gallery content itself. As I'm sure you are starting to see, working in a hierarichy of files will make my work really easy if the client wants to change the look of all galleries... or the look of the header. The galleryHeader.

php

  file looks like this:


<?

php

 

$gHeader = <<<HTML
<style>
#detail { width: 500px; text-align: center; }
.thumb { cursor: hand; height: 74px; width: 74px; }
.hspace { height: 1px; width: 22px; }
.vspace { height: 15px; width: 1px; }
.invis { display: none; }
</style>

<script>
function setImage(idx)
{
document.getElementById('detail').innerHTML = '<img id="detailImage" src="/graphics/dot_clear.gif" height="' + images[idx].height + '" width="' + images[idx].width + '">';
setTimeout("document.getElementById('detailImage').src = images[" + idx + "].src", 10);
storage.storeItem('$galleryName', 'setImage(' + idx + ')');
}
function setFlash(idx)
{
document.getElementById('detail').innerHTML = flash[idx];
storage.storeItem('$galleryName', 'setFlash(' + idx + ')');
}
images = new Array();
flash = new Array();
</script>
HTML;

$gFooter = <<<HTML
<br><br>
<table cellpadding="0" cellspacing="0" border="0"><tr><valign="middle">
<td><a href="$previousGallery"><img src="/graphics/l_arrow.gif" height="13" width="13" border="0"></a></td>
<td><a href="$previousGallery"><img src="/graphics/dot_clear.gif" height="1" width="3" border="0"></a></td>
<td><a href="$previousGallery" class="arial"><font size="3">&nbsp;Previous Gallery&nbsp;</font></a></td>
<td><img src="/graphics/dot_clear.gif" height="1" width="50"></td>
<td><a href="/index.html"><img src="/graphics/u_arrow.gif" height="13" width="13" border="0"></a></td>
<td><a href="/index.html"><img src="/graphics/dot_clear.gif" height="1" width="3" border="0"></a></td>
<td><a href="/index.html" class="arial s14"><font size="3">&nbsp;Home&nbsp;</font></a></td>
<td><img src="/graphics/dot_clear.gif" height="1" width="50"></td>
<td><a href="$nextGallery" class="arial s14"><font size="3">&nbsp;Next Gallery&nbsp;</font></a></td>
<td><a href="$nextGallery"><img src="/graphics/dot_clear.gif" height="1" width="3" border="0"></a></td>
<td><a href="$nextGallery"><img src="/graphics/r_arrow.gif" height="13" width="13" border="0"></a></td>
</tr></table>
<script>
storage = new localStorage();
storage.fileName = 'gallery';
jumpTo = storage.retrieveItem('$galleryName');
if (!jumpTo) { jumpTo = '$default'; }
setTimeout(jumpTo, 100);
</script>
HTML;

?>


As with the theme header, this file's only responsibility is to populate the gHeader and gFooter variables so that they can be included as content at the end of the misc.

php

  file.

perkiset

Of course this structure is simply a starting point.

When I do something like this the very next step for me is to use my own back-office management warez to create an editing system, translation manager and such for the site... I'll add caching to make the pages super fast and telemetry and tracking reports for the client. This is just way beyond what I wanted to get across today.

As I mentioned in the intro post, this entire exercise is simply to demonstrate one way to create a dynamic, completely

PHP

  site that is both fast and clean, as well as easily maintained. I look forward to any comments.

/p

m0nkeymafia

Great post perk, very good, ill discuss a little what I do maybe get some discussion going:

I used to run everything through a central file, but now I use separate files for each document and include the header and footer manually
Although this requires a bit more code It gives me greater control.

For example in my header I look for a $ptitle variable which stores the page title.  This way I can have unique titles for each page and not have to cache the entire page.  I do this a lot, for meta descriptions and other stuff. 

Perk wont running everything through a central

PHP

  file slow things down? Or is it minimal?

P.s. I always rewrite to .html rather than folders etc, I think it looks much more natural to users

perkiset

quote author=m0nkeymafia link=topic=502.msg3250#msg3250 date=1189717512

I used to run everything through a central file, but now I use separate files for each document and include the header and footer manually
Although this requires a bit more code It gives me greater control.

Cool, let's see:

quote author=m0nkeymafia link=topic=502.msg3250#msg3250 date=1189717512

For example in my header I look for a $ptitle variable which stores the page title.  This way I can have unique titles for each page and not have to cache the entire page.  I do this a lot, for meta descriptions and other stuff. 

Ah, excellent catch - here's how I do that. In my for-reals sites, I keep a variable in the $_GLOBAL array, something like:

$_GLOBAL['pageTitle'] = 'Default Title';
$_GLOBAL['pageKeywords'] = "this that and another';

One of the last steps in my for-reals sites is to do a simple str_replace of everything in the final output content that could have been modified by <the page that was called>. This again leaves me with central control, but every page has the capability (although not the requirement) to modify those high level items.

quote author=m0nkeymafia link=topic=502.msg3250#msg3250 date=1189717512

Perk wont running everything through a central

PHP

  file slow things down? Or is it minimal?

There is no speed penalty for running through this script or that one... if you're into script mode you're going to take the processing hit no matter what. The addition of a few lines of code to centralize it really won't impact that much. That being said, however, I am about efficiency and speed. So I use the APC cache for two things:

1) code precompile caching - it's automatic with APC and just makes a website BLAZE.

2) See my translation table? Consider for a moment that I can have all kinds of "parameters" on the translated URL - for example, I'll add "cached=true" to pages that I want cached. Then before I translate, I'll look to see if there is a completed version of the requested page in my personal cache - if there is, then I throw that back out and exit the script - it's monstrously fast. If it isn't there (either I didn't want it cached, or it's never been called, or it's expired, or I manually killed it because I changed it) then the entire process is run through, a new page is created, and if I see "cached=true" in the GET parameters then I'll throw it into the APC cache as my last act on the script. Voila, next call for the page it will be cached and wicked fast.

quote author=m0nkeymafia link=topic=502.msg3250#msg3250 date=1189717512

P.s. I always rewrite to .html rather than folders etc, I think it looks much more natural to users

Completely agree - and although I have ZERO evidence of this, I still think that certain engines like flat .html files better than dynamic ones...

jairez

Totally cool mod on the httpd.conf that will solve a problem I've only recently seen in people trying to hack mysites because I do indeed run everything through a single file (index.

php

 ?page=xxxxxx) and the code simply includes the .html file for viewing.  I actually

learn

 ed this from a ColdFusion buddy of mine who uses the FuseBox framework to accomplish basically the same thing.  The plus for me in doing this is my clients can use whatever editor they're comfortable with (Dreamweaver, MM Contribute ... etc.) and even though it throws out butchered HTML, I don't really care as it won't effect the site's functionality - by and large anyway.

This is elegant and I'm going to give it a whirl.

Thanks, Perk.  Once again, very nicely done.


btw - my post #2 - Hey, this stuff's easy Applause

perkiset

quote author=jairez link=topic=502.msg3252#msg3252 date=1189728352

Totally cool mod on the httpd.conf that will solve a problem

mod_rewrite is the shizz.

The good news is that it's as flexible and configurable as sendmail.  Applause
The bad news is that it's as flexible and configurable as sendmail.  :-

Once you start getting a hold of what all can be done you won't hardly be able to imagine sites without it. If you want, start a new thread with problems/challenges you're seeing that you think mod_rewrite might be able to work with and let's take a crack at it. I'm not kidding, it (in combination with

regular expression

 s) may be the most important tool you put in your webmaster superduper utility belt.


quote author=jairez link=topic=502.msg3252#msg3252 date=1189728352

btw - my post #2 - Hey, this stuff's easy Applause

See that now? I remember you saying that about my first Ogoshi. Say... didn't we break one of my toes or dislocate my shoulder on that one?  Applause

jairez

Deal.  Let me get my sh*t together and I'll start a new thread when I've begun the process.

q:  do you have any experience or thoughts about some of the GPL

PHP

  frameworks (e.g. Zend, Cake

PHP

 , FuseBox, ... etc.)?

perkiset

quote author=jairez link=topic=502.msg3254#msg3254 date=1189734262

q:  do you have any experience or thoughts about some of the GPL

PHP

  frameworks (e.g. Zend, Cake

PHP

 , FuseBox, ... etc.)?


Only as much experience as I needed to decide that I wanted to write my own  Applause

As you certainly remember, I'm rather a hand-rolled kind of guy, since typically what I want to accomplish is either unorthodox or unusual... but that is no slam against the frameworks... right tool for the job and all...

m0nkeymafia

Gah perk, im all set in my ways, and then u go show an alternative way to do things...and doing a good job of convincing me to try it out.

Now im gonna hav eto make a new site to see how it all comes together lol Applause
You'd better post more lol

perkiset

Most kind, MM, yet again

nutballs

cool post, thanks perk.

This is actually very similar to my standard site structure in

ASP

 . I do it slightly different.

Instead I token replacement instead, and though it might be a bit slower, it also allows for me to only manage 2 files for display purposes. I use a single template page that contains the entire HTML file with tokens for everything I want dynamic, such as #TITLE# #KEYWORDS# #NAV# #CONTENT# etc. The other file is a list of function wrappers for each of the replacements and content elements. I have a single parser function that collects all the tokens, then executes the appropriate replacement function. This also works for dynamic elements. Lets say I wanted to include a list of the last 5 searches on a site in the sidebar. the token would be #PASTSEARCHES-5# an the number can just be changed in the template. My replacement function executes appropriately.

This is a great way to deal with sites that might have different templates for different areas. So I code every page of different type of functionality seperately to make code management more visual. Login.

asp

  register.

asp

  browse.

asp

  etc etc. But I deal with very little display stuff in those pages.

I include the replacement functions, and a global parser functions file which deals with all the consistent tokens like title and keywords. But the CONTENT token, or similar things that might change functionality from page to page, I do in the specific page. I could just do it all in 1 big ass file, but I hate scrolling...


Downside to this method is

regex

 /replace.
upside is your entire HTML template is actually HTML, and complete. None of the header footer separation issues of editing the header and causing errors in the footer that you can't figure out. It also means that you can separate the display management from the code, which in a situation where you have retarded HTML designers monkeying with your shit.

perkiset

My older retail sites work the same way, and actually what I was describing really works out to that as well.

I like the content array method during construction because many of my pages have radically different needs or contributions... my back office applications particularly. I have page templates like simplePage, includePage, openContact, editRecord, rawEditor, tabbedForm, tabbed

Ajax

 Form etc etc... each of these is like a mini header that can also get <x> content. So in those sites, they all work with global vars and the content array, adding as they need to until the main.

php

  actually implodes the whole mess into a single string, then I run a str_replace on it.

(That all said, I agree with you about the fishing up the header/footer framework biz... whadda PIA if you're not on the ball)

With this method, I can supply different headers or footers pretty easily as things on the site change, should I need to. For example, one of my retailers has 4 different back-office themes that I use - not that they look much different, but there's one for the China manufacturers, local admin, local fulfillment and UK distribution (forgot, there's actually 5 - San Diego custom work). These different headers have different capabilities ++ for each, yet the content of the middle is the same... it's a little weird, but it allows me a great amount of flexibility.

Here's a handy thing that you probably don't know yet: str_replace takes arrays for parameters. This is a REALLY great way to do things, because it both steps out of

regex

  lag time and stays in the C code while it's working. Consider:

$searchArr[] = '<';
$searchArr[] = '>';
$searchArr[] = '"';
$replaceArr[] = '&lt;';
$replaceArr[] = '&gt;';
$replaceArr[] = '&quot;';
$buff = str_replace($searchArr, $replaceArr, implode(chr(10), $content));

... the 3rd param is the "imploded" version of the $content array, and the search and replace arrays are global variables that any script along the chain of execution could have modified. It's the best of both worlds IMO.

jairez

"OOooohhh, ... first chink in the armor there, Ted"

OK, so my sites are typically hosted and apparently the probability of monkeying around with my httpd.conf file is about the same as me putting super-unleaded in a rental car.

So did I read correctly (once upon a time in this thread) that there's also an .htaccess option here as well?  If I modify it your way, will I still lock everyone out like I normally do?  Applause

I tend to stay away from this file, but I'm open to your suggestions.  What's the worst thing that can happen?  No one can get to my sites? (they shouldn't be jacking around while at work anyway).

Roll, roll, roll your own ... and thanx again. 

Tag.

perkiset

Yes you can edit the .htaccess file... it is there for you to abuse. CAVEAT: Yes, if someone has written other things into it you want to be careful, but predominately unless you're a madman you can get away with a lot.

If there is one and it's got a bunch of directives, either post it here so we can look and see what nn2b done OR PM me with it first if you're worried so that I can make sure there's nothing proprietary and not-for-public-consumption.

You can have *lots and lots* of rewrite directives in there, so even if someone else is doing something, you can as well.

No worries, done all the time.

perkiset

Here is the entire source for the demo listed above. It is about 5.5M, graphics included.

http://demo1-after.perkiset.org/graphics/demo1.tar.gz

Rob83

Thanks perk!  Guess I'm not going to sleep tonight Applause

perkiset

Sorry bro...  Applause

MAN do I know how that goes Applause

JasonD

get ../../../../../etc/shadow ??

perkiset

ummmm... free association time?  Applause

WTF jd?

JasonD



// Needed vars...
$thisDir = '/www/sites/perkiset/demos/demo1-after';
$pages = 'pages';
$pagePath = "$thisDir/pages";
$content = array();

// Attempt translation - if not, then return a not found page...
$inPath = $transArr[$_GET['uri']];
$fullPath = "$pagePath/$inPath";
if ((!$inPath) || (!file_exists($fullPath)))

require($fullPath);


// Echo the page...
echo $header, implode(chr(10), $content), $footer;




http://www.site.dom/../../../../../../etc/passwd

Not tried but code looks (on quick scan) susceptable to include then echo any file on the server. This (on gut instinct and experience) may be able to be expanded to gain a shell as well, and all through a carefully contructed URL

JasonD

BTW where can I buy a print of some of his works ?

I can't wait to get http://demo1-after.perkiset.org/marbles.html hung up in my house. I love it Applause

perkiset

Oh I see where you're going...

Love to see if you have a way to violate this... here in the code:


// Translation table...
$transArr['/'] = 'index.

php

 ';
$transArr['/index.html'] = 'index.

php

 ';
$transArr['/cars.html'] = $transArr['/cars/cars-2.html'] = 'cars.

php

 ';
$transArr['/diners.html'] = $transArr['/diners/diners.html'] = 'diners.

php

 ';
$transArr['/figures.html'] = $transArr['/figures/figures.html'] = 'figures.

php

 ';
$transArr['/jungle.html'] = $transArr['/jungle/jungle.html'] = 'jungle.

php

 ';
$transArr['/misc.html'] = $transArr['/miscellany/miscellaney.html'] = 'misc.

php

 ';
$transArr['/marbles.html'] = $transArr['/marbles/marbles.html'] = 'marbles.

php

 ';
$transArr['/rocks.html'] = $transArr['/rocks/rocks.html'] = 'rocks.

php

 ';
$transArr['/contact.html'] = $transArr['/contact_page/contact.html'] = 'contact.

php

 ';
$transArr['/bio.html'] = $transArr['/artist_bio/bio.html'] = 'bio.

php

 ';


... I've set up the "valid" translations ie., my specific list of URLs that I will accept... then here:


$inPath = $transArr[$_GET['uri']];
$fullPath = "$pagePath/$inPath";

// Attempt translation - if not, then return a not found page...
if ((!$inPath) || (!file_exists($fullPath)))
{
header("HTTP/1.0 404 Not Found");
echo $header, '<br><h3>Unfortunately that page is no longer available.<br>Please <a href="/index.html" class="arial">CLICK HERE</a> to return to our home page.<br><br>', $footer;
exit;
}

// Get the actual data page...
require($fullPath);

I look to see if the passed uri is in that array... if it isn't, then the 404 will come up.

PHP

  will come back with a null if a $transArr[] element named (the url you are passing) is blank... in which case it's trapped in the If... unless I'm missing something...?

@purchase: I'll PM you the actual site... h is only sold through a series of galleries in the midwest, but I can probably hook you up Applause

/p

JasonD

Perk, please excuse me cos I was talking out of my arse.

Only allowing specific URLs to interact with the script is the perfect way to code it - My apologies Applause

Thanks for PM, will read it now

perkiset

quote author=JasonD link=topic=502.msg3303#msg3303 date=1190045588

Perk, please excuse me cos I was talking out of my arse.


"Excuse You?"
What, for watching my back? Are you kidding me?

The thanks are from ME mate.

/p

perkiset

I received a PM from a member here that wanted a bit more explanation about the concept above. This is a quick munge of my reply PM because it made a couple things more clear than the example above.

The way that this user had started trying to do something similar to this was like this:

www.aDomain.com/index.

php

 ?page=contact

with supporing code something like:


<?

php

 

echo file_get_contents($_GET['file'] . '.html');

?>


A valiant start towards dynamic websites, but that is just RIPE for dicking with. Here's conceptually why:

www. aDomain.com/index.

php

 ?page=../../usr/local/

apache

 2/conf/httpd.conf

... now in this particular case the author scuttled that form of direct attack by adding the '.html' on the end, which is why he hasn't been hacked yet: there is no
/usr/local/

apache

 2/conf/httpd.conf.html
file, so won't work immediately. But Boris the Russian Hacker will still try, having been intrigued... and he may well find a way to defeat this simple fence. Hell, I might do it on GP myself.

So, what to do?

Here's the essence: we want to send all page calls into a single

PHP

  script (pretty much as has been done) but hide the fact that we are doing this so that we don't attract Boris. Also, we want to add a certain level of protection to the routine so that Boris++ cannot work you over either.

The first step is to add an .htaccess file so that file non-graphics requests come into a single script:

<CAVEAT>
Before you do this, if you're FTPing it, have 2 versions ready: one that has this code, one that is empty. If this thing screws up you'll want to copy a blank up there right quick... and many FTP programs will not let you "see" the .htaccess file to delete it.
</CAVEAT>


RewriteEngine on
RewriteCond %{REQUEST_URI} .jpg$ [OR]
RewriteCond %{REQUEST_URI} .gif$ [OR]
RewriteCond %{REQUEST_URI} .png$
RewriteRule ^(.*)$ - <>

RewriteRule ^(.*)$   /main.

php

 ?file=$1


What we've said here is:
* turn the rewriting engine on
* Check to see if the URL ends in jpg or gif or png
* if so, DO NOT do any rewriting and stop the rewriting process (<>)
* Otherwise, rewrite everything to be "main.

php

 ?file=(what I found in the

regex

 Applause"

ie., /main.

php

 ?file=/index.html

So now, if you were to put this at the beginning of your main.

php

 :

print_r($_GET)

you'd see ['file'] = ['... whatver ... ']
and presto! You're there.

<IMPORTANT>
Of course, you'll need to adjust the

regex

 s for avoiding translating JPGs and GIFs and such... mine look a bit more like
%{REQUEST_URI}  /graphics
because I put all graphics in a single location... but YMMV.
</IMPORTANT>

Now, on to step two.

I'd make all urls look like .html, rather than not having an extension, because with no extension it piques my imagination about what you might be doing. Additionally, if you have an "open pull" ie., you allow the pull of *any* file based on the URL, then you're open to some form of attack. Better to have a translation array and only do pages that you have personally approved of.

For example:


<?

php

 

$okURLs['/index.html'] = '/contentDir/index.html';
$okURLs['/contact.html'] = '/contentDir/contact.html';
$okURLs['/products.html'] = '/contentDir/products.html';
$okURLs['/bio.html'] = '/contentDir/bio.html';
$okURLs['/download.html'] = '/contentDir/download.html';

$theReq = $_GET['file'];
if (! $buffFile = $okURLs[$theReq])
{
// Hack attempt or old URL being hit by surfer or spider...
header("HTTP/1.0 404 Not Found");
echo "Go away, you bonehead.";
}

$buff = file_get_contents($buffFile);
echo $buff;

?>


In this case, I've listed every valid URL, and then I don't even pull from that file directly - I "remap" it to another file that is where/what I want to actually include. Of course, the storage etc etc of the site is up to you and how you want to employ this - I've done this sort of verbosely so that you can see what I'm up to. Also, doing a full rename of the file instead of just a remap is something like an HTML version of port-mapping or NAT routing - what they are requesting has nothing to do with what is actually on your box. That additional level of abstraction is very good at keeping the innards of your box away from Boris.

First challenge: What if you keep adding pages and don't want to change your main.

php

  file?

Then there are a couple options: First, whenever you change the website, change the array. If it's a huge site, then you'll want to write a little routine to walk your content directories and get all valid pages. This is not terribly difficult, but might be more than you're willing to go after.

Second option is to take the second-best choice: don't use a translation array as listed above, just deny more stuff on the way in. This is not my personal preference because it's still too open, but it will be *pretty* good.


<?

php

 

$theReq = $_GET['file'];
$fails = 0;
if (preg_match('/../', $theReq))
$fails++;
if (!preg_match('/.html$/', $theReq))
$fails++
if ($preg_match('[&;#]', $theReq))
$fails++;

if ($fails)
{
// Hack attempt or old URL being hit by surfer or spider...
header("HTTP/1.0 404 Not Found");
echo "Go away, you bonehead.";
}

echo file_get_contents("/contentDir$theReq");

?>


As you can see here, we are trying hard to eliminate anything that looks bad on the URL, and in most cases this will probably work just fine. However, there may be old pages or inaapropriate pages that we don't want the user/engines to see, and this does nothing to protect us there.

Hope this clarifies the above example a bit.

/p

nutballs

quote author=perkiset link=topic=502.msg3375#msg3375 date=1190498274


A valiant start towards dynamic websites, but that is just RIPE for dicking with. Here's conceptually why:

www. aDomain.com/index.

php

 ?page=../../usr/local/

apache

 2/conf/httpd.conf

... now in this particular case the author scuttled that form of direct attack by adding the '.html' on the end, which is why he hasn't been hacked yet: there is no
/usr/local/

apache

 2/conf/httpd.conf.html
file, so won't work immediately. But Boris the Russian Hacker will still try, having been intrigued... and he may well find a way to defeat this simple fence. Hell, I might do it on GP myself.


wouldnt this get around the .html appendage? At least depending how its getting rewritten.

www. aDomain.com/index.

php

 ?page=../../usr/local/

apache

 2/conf/httpd.conf?eatme=

perkiset

Not exactly, but you're definitely on the right track.

With a little knowledge of the

Linux

  shell environment you are literally a Frank Zappa song.

"She was Buns Up Kneelin'.... Buns Up!"  Applause

jairez

q:  is clientBase.js a requirement to run this framework?

If it is, can you post it's contents or send it to me?  I believe all I have is a file that's really a sym link.

This is great stuff.  Thanks, Hoss.

JasonD

Depending on where you are in the chain of events


;
is your (friend | enemy)

echo
/bin/bash
/usr/sbin/sendmail

are all far from (friends | enemies) as well

JasonD

quote

wouldnt this get around the .html appendage? At least depending how its getting rewritten.

www. aDomain.com/index.

php

 ?page=../../usr/local/

apache

 2/conf/httpd.conf?eatme=


No it wont mate cos there isn't a file called.

httpd.conf?eatme=.html


What will work though, and is MUCH more dangerous (but hinted at in my previous post) is

http://www.myevilsite.com/rootme


which will translate to
http://www.myevilsite.com/rootme.html


Which can contain any code you want it to


Perkiset's Place Home   Politics @ Perkiset's