diff --git a/trunk/data/%%0A^0A3^0A3EFF25%%faq.tpl.php b/trunk/data/%%0A^0A3^0A3EFF25%%faq.tpl.php new file mode 100644 index 0000000..1e95474 --- /dev/null +++ b/trunk/data/%%0A^0A3^0A3EFF25%%faq.tpl.php @@ -0,0 +1,35 @@ + +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "header.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> + +
+
What does PlanningAlerts.com do?
+
+ It sends you emails of any planning applications we find based on the postcode you give us when you sign up. +
+
Where does the data come from?
+
+ We search as many local authority websites as we can find and forward the data on to you. +
+
How can I stop receiving alerts?
+
+ At the bottom of each email you receive there is a link to unsubscribe from that alert. Just click on it and we'll delete your alert. +
+
Do you cover the whole country?
+
+ No, but we are working on it. There is a list of the local authorities we currently cover here. Not every local authority publishes their planning data online so some places will always be missing. If your local authority is missing, you could always help out by writing a screen scraper to search their website. +
+
Can I help?
+
+ Yes please! To be able to cover the whole country we need help in writing screen scrapers to search the websites of every local authority in the country. If you'd like to write one for your local council get in touch. +
+
+_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "footer.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> \ No newline at end of file diff --git a/trunk/data/%%0E^0E4^0E407559%%footer.tpl.php b/trunk/data/%%0E^0E4^0E407559%%footer.tpl.php new file mode 100644 index 0000000..8cd8945 --- /dev/null +++ b/trunk/data/%%0E^0E4^0E407559%%footer.tpl.php @@ -0,0 +1,27 @@ + + +
+ +
+ + _tpl_vars['onloadscript'] != "" || $this->_tpl_vars['set_focus_control'] != ""): ?> + + + + + + \ No newline at end of file diff --git a/trunk/data/%%0F^0F1^0F1856BC%%unsubscribed.tpl.php b/trunk/data/%%0F^0F1^0F1856BC%%unsubscribed.tpl.php new file mode 100644 index 0000000..d7de265 --- /dev/null +++ b/trunk/data/%%0F^0F1^0F1856BC%%unsubscribed.tpl.php @@ -0,0 +1,26 @@ + + array(array('modifier', 'upper', 'unsubscribed.tpl', 5, false),)), $this); ?> +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "header.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> +
+

You have been unsubscribed

+

+ You will no longer receive email alerts for any planning applications we find near _tpl_vars['postcode'])) ? $this->_run_mod_handler('upper', true, $_tmp) : smarty_modifier_upper($_tmp)); ?> + (within approximately _tpl_vars['alert_area_size']; ?> +m). +

+

+ If you have unsubscribed because you were getting too many emails, you could always create a new alert for a smaller area? +

+
+ +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "footer.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> \ No newline at end of file diff --git a/trunk/data/%%3A^3A4^3A4E0A75%%checkmail.tpl.php b/trunk/data/%%3A^3A4^3A4E0A75%%checkmail.tpl.php new file mode 100644 index 0000000..611c992 --- /dev/null +++ b/trunk/data/%%3A^3A4^3A4E0A75%%checkmail.tpl.php @@ -0,0 +1,18 @@ + +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "header.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> +
+

Now check your email

+

+ We have sent you an email to confirm your alerts, click on the link in the email to start receiving planning alerts. If you don't get an email in the next few minutes, try checking your spam filter. +

+
+_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "footer.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> \ No newline at end of file diff --git a/trunk/data/%%45^45E^45E480CD%%index.tpl.php b/trunk/data/%%45^45E^45E480CD%%index.tpl.php new file mode 100644 index 0000000..65bd48f --- /dev/null +++ b/trunk/data/%%45^45E^45E480CD%%index.tpl.php @@ -0,0 +1,65 @@ + +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "header.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> +
+
+ +
+

This site is in Beta (test) mode, so local authorities near you may not be included in alerts.

+ +
+
+

Recent site updates

+ +
+ +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "footer.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> \ No newline at end of file diff --git a/trunk/data/%%6D^6DB^6DB28CFA%%about.tpl.php b/trunk/data/%%6D^6DB^6DB28CFA%%about.tpl.php new file mode 100644 index 0000000..ab30928 --- /dev/null +++ b/trunk/data/%%6D^6DB^6DB28CFA%%about.tpl.php @@ -0,0 +1,41 @@ + +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "header.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> + +

You'd probably know if your next door neighbour was going to knock their house down (you'd get a letter through the door telling you they had applied for planning permission and asking you what you thought about it). But you probably never find out if the old cinema or pub 5 streets away is going to be converted into luxury flats until the bulldozers turned up.

+

+ PlanningAlerts.com is a free service built by Richard Pope and Mikel Maron kindly hosted by mySociety.org. It searches as many local authority planning websites as it can find and emails you details of applications near you. The aim of this to enable shared scrutiny of what is being built (and knocked down) in peoples communities. +

+ +

Planning authorities currently covered by the service

+

New authorities are being added all the time, so if your local authority isn't listed below sign up any way and you'll start receiving alerts once it has been included.

+ + + +

If you are a programmer and would like to write a scraper for your local authority, or work for a local authority and would like to make your data available, find out how you can get involved.

+ +

Contact

+

+ You can get in touch at team@planningalerts.com +

+ +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "footer.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> \ No newline at end of file diff --git a/trunk/data/%%6E^6E1^6E190D42%%preview.tpl.php b/trunk/data/%%6E^6E1^6E190D42%%preview.tpl.php new file mode 100644 index 0000000..842537c --- /dev/null +++ b/trunk/data/%%6E^6E1^6E190D42%%preview.tpl.php @@ -0,0 +1,24 @@ + + + + + + PlanningAlerts.com | Alert area preview + + + + + +
+ + \ No newline at end of file diff --git a/trunk/data/%%80^80A^80A14535%%confirmed.tpl.php b/trunk/data/%%80^80A^80A14535%%confirmed.tpl.php new file mode 100644 index 0000000..a2148af --- /dev/null +++ b/trunk/data/%%80^80A^80A14535%%confirmed.tpl.php @@ -0,0 +1,32 @@ + + array(array('modifier', 'upper', 'confirmed.tpl', 9, false),)), $this); ?> +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "header.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> +
+
+ +
+
+

Thanks, your alert has been activated

+

+ You will now receive email alerts for any planning applications we find near _tpl_vars['postcode'])) ? $this->_run_mod_handler('upper', true, $_tmp) : smarty_modifier_upper($_tmp)); ?> + (within approximately _tpl_vars['alert_area_size']; ?> +m). If this alert doesn't cover everywhere you are interested in you can sign up for multiple alerts +

+ +

+ If you are interested in discussing local issues with your MP and other local people you can also join HearFromYourMP.com! +

+
+
+_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "footer.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> \ No newline at end of file diff --git a/trunk/data/%%A4^A42^A42CC6E6%%confirm_email_text.tpl.php b/trunk/data/%%A4^A42^A42CC6E6%%confirm_email_text.tpl.php new file mode 100644 index 0000000..117394a --- /dev/null +++ b/trunk/data/%%A4^A42^A42CC6E6%%confirm_email_text.tpl.php @@ -0,0 +1,15 @@ + + array(array('block', 'textformat', 'confirm_email_text.tpl', 1, false),)), $this); ?> +_tag_stack[] = array('textformat', array('style' => 'email')); $_block_repeat=true;smarty_block_textformat($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat);while ($_block_repeat) { ob_start(); ?> + +Please click on the link below to confirm you want to receive email alerts for planning applications near _tpl_vars['postcode']; ?> +: + +_tpl_vars['url']; ?> + + +If your email program does not let you click on this link, just copy and paste it into your web browser and hit return. + +_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat); } array_pop($this->_tag_stack); ?> \ No newline at end of file diff --git a/trunk/data/%%AD^AD7^AD71B708%%rss.tpl.php b/trunk/data/%%AD^AD7^AD71B708%%rss.tpl.php new file mode 100644 index 0000000..2a1e2a2 --- /dev/null +++ b/trunk/data/%%AD^AD7^AD71B708%%rss.tpl.php @@ -0,0 +1,35 @@ + + + version="1.0" encoding="UTF-8"'; ?> + + + + Search Results + + + _tpl_vars['applications']); if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }$this->_foreach['applications'] = array('total' => count($_from), 'iteration' => 0); +if ($this->_foreach['applications']['total'] > 0): + foreach ($_from as $this->_tpl_vars['application']): + $this->_foreach['applications']['iteration']++; +?> + + _tpl_vars['application']->council_reference; ?> + + _tpl_vars['application']->address; ?> + + _tpl_vars['application']->lat; ?> + _tpl_vars['application']->lon; ?> + + _tpl_vars['application']->description; ?> + + _tpl_vars['application']->info_url; ?> + + _tpl_vars['application']->comment_url; ?> + + _tpl_vars['application']->date_received; ?> + + + + + \ No newline at end of file diff --git a/trunk/data/%%D2^D26^D26D8BFE%%xml.tpl.php b/trunk/data/%%D2^D26^D26D8BFE%%xml.tpl.php new file mode 100644 index 0000000..748a2be --- /dev/null +++ b/trunk/data/%%D2^D26^D26D8BFE%%xml.tpl.php @@ -0,0 +1,42 @@ + + array(array('modifier', 'escape', 'xml.tpl', 6, false),)), $this); ?> + + version="1.0" encoding="UTF-8"'; ?> + + + + _tpl_vars['authority_name'])) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> + + + + _tpl_vars['authority_short_name'])) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> + + + + _tpl_vars['applications']); if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }$this->_foreach['applications'] = array('total' => count($_from), 'iteration' => 0); +if ($this->_foreach['applications']['total'] > 0): + foreach ($_from as $this->_tpl_vars['application']): + $this->_foreach['applications']['iteration']++; +?> + + _tpl_vars['application']->council_reference)) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> + +
_tpl_vars['application']->address)) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> +
+ _tpl_vars['application']->postcode)) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> + + _tpl_vars['application']->description)) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> + + _tpl_vars['application']->info_url)) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> + + _tpl_vars['application']->comment_url)) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> + + _tpl_vars['application']->date_received)) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> + +
+ +
+ +
\ No newline at end of file diff --git a/trunk/data/%%F7^F75^F752BDC0%%getinvolved.tpl.php b/trunk/data/%%F7^F75^F752BDC0%%getinvolved.tpl.php new file mode 100644 index 0000000..9db2e69 --- /dev/null +++ b/trunk/data/%%F7^F75^F752BDC0%%getinvolved.tpl.php @@ -0,0 +1,33 @@ + +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "header.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> + +

PlanningAlerts.com is run by volunteers if you would like to get involved we'd love to here from you:

+ +

I am a programmer and want to add my local authority

+

+ You can help by writing a screen scraper for your local authority that was can import into plannignalerts.com. There are only 2 criteria for the screen scraper: +

+
    +
  1. That it can output data in the following format: http://www.planningalerts.com/lambeth.xml
  2. +
  3. That it can accept a query sting in the format day=X&month=Y&year=Z
  4. +
+

+ Other than that it's up to you. It can be in any language. You can host them yourself or we can host it for you. +

+ +

I work for a local authority and would like to make our data available

+

+ The most important thing you can do is publish your data in a simple format that is freely available on the internet. Something like this is good but we can work with most formats. Please get in touch if you would like to discuss how you can help. +

+ + +_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "footer.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?> \ No newline at end of file diff --git a/trunk/data/%%F7^F7F^F7F34188%%header.tpl.php b/trunk/data/%%F7^F7F^F7F34188%%header.tpl.php new file mode 100644 index 0000000..4507fd9 --- /dev/null +++ b/trunk/data/%%F7^F7F^F7F34188%%header.tpl.php @@ -0,0 +1,41 @@ + + + + + + + PlanningAlerts.com | <?php echo $this->_tpl_vars['page_title']; ?> + + + + + + + + + +
+ Skip navigation +
+
+ +
+
+
+

PlanningAlerts.combeta

+

Email alerts of planning applications near you

+ logo +
+
+
_tpl_vars['warnings'] == ""): ?>class="hide"> + _tpl_vars['warnings']; ?> + +
+ \ No newline at end of file diff --git a/trunk/docs/.htaccess b/trunk/docs/.htaccess new file mode 100644 index 0000000..465e78f --- /dev/null +++ b/trunk/docs/.htaccess @@ -0,0 +1,5 @@ +php_value include_path ".:/data/vhost/planning/docs/include/:/data/vhost/planning/docs/include/PEAR" + + + + diff --git a/trunk/docs/.htaccess.haggis b/trunk/docs/.htaccess.haggis new file mode 100644 index 0000000..da2e24e --- /dev/null +++ b/trunk/docs/.htaccess.haggis @@ -0,0 +1,4 @@ +php_value include_path ".:/data/vhost/www.planningalerts.com/docs/include/:/data/vhost/www.planningalerts.com/docs/include/PEAR" +php_value register_globals 0 +php_value error_reporting E_ALL +php_value display_errors 1 diff --git a/trunk/docs/.htaccess.live b/trunk/docs/.htaccess.live new file mode 100644 index 0000000..2db06da --- /dev/null +++ b/trunk/docs/.htaccess.live @@ -0,0 +1,4 @@ +php_value include_path ".:/data/vhost/www.planningalerts.com/docs/include/:/data/vhost/www.planningalerts.com/docs/include/PEAR" +php_value register_globals 0 +php_value error_reporting E_ALL +php_value log_errors 1 diff --git a/trunk/docs/about.php b/trunk/docs/about.php new file mode 100644 index 0000000..25d0fee --- /dev/null +++ b/trunk/docs/about.php @@ -0,0 +1,57 @@ +setup(); + $this->bind(); + } + + //Setup + function setup (){ + + $db = DB::connect(DB_CONNECTION_STRING); + + //Grab a list of local authorities + $sql = "Select full_name, disabled from authority where disabled = 0 or disabled is null order by full_name"; + $results = $db->getAll($sql); + + for ($i=0; $i < sizeof($results); $i++){ + array_push($this->authorities, $results[$i][0]); + } + + } + + //Bind + function bind() { + + //page vars + $form_action = $_SERVER['PHP_SELF']; + //smarty + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + + $smarty->assign("page_title","About"); + $smarty->assign("menu_item", "about"); + + $smarty->assign("authorities",$this->authorities); + + //Render + $smarty->display('about.tpl'); + + } + +} + + + +?> \ No newline at end of file diff --git a/trunk/docs/api.php b/trunk/docs/api.php new file mode 100644 index 0000000..abd5016 --- /dev/null +++ b/trunk/docs/api.php @@ -0,0 +1,79 @@ +setup(); + $this->bind(); + } + + //setup + function setup (){ + + //Grab the postcode and area size from the get string + + if (!isset($_GET['area_size'])){ //check is_numeric and isn't too big + $this->warnings .= "No area size specified "; + } + if (!(isset($_GET['lat']) && isset($_GET['lng'])) + || !(isset($_GET['postcode'])) ) { + $this->warmings .= "No location specified "; + } + + if ($this->warnings == ""){ + + //Get OS ref from postcode + if (isset($_GET['postcode'])) { + $xy = postcode_to_location($_GET['postcode']); + } else { + $latlng = new LatLng($_GET['lat'], $_GET['lng']); + $xy = $latlng->toOSRef(); + } + + $this->easting = $xy->easting; + $this->northing = $xy->northing; + $this->area_size = $_GET['area_size']; + + $this->applications = Applications::query($this->easting, $this->northing, alert_size_to_meters($this->area_size)); + } + + } + + //Bind + function bind () { + + //page vars + $form_action = $_SERVER['PHP_SELF']; + + header("Content-Type: text/xml"); + + //smarty + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + + $smarty->assign("warnings", $this->warnings); + $smarty->assign("applications", $this->applications); + + //Render + $smarty->display('rss.tpl'); + + } + +} + + + +?> diff --git a/trunk/docs/checkmail.php b/trunk/docs/checkmail.php new file mode 100644 index 0000000..45abdd7 --- /dev/null +++ b/trunk/docs/checkmail.php @@ -0,0 +1,34 @@ +bind(); + } + + //Bind + function bind() { + + //page vars + $form_action = $_SERVER['PHP_SELF']; + //smarty + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + + $smarty->assign("page_title","Now check your email"); + $smarty->assign("menu_item", "signup"); + //Render + $smarty->display('checkmail.tpl'); + + } + +} + + + +?> \ No newline at end of file diff --git a/trunk/docs/confirmed.php b/trunk/docs/confirmed.php new file mode 100644 index 0000000..8ea381e --- /dev/null +++ b/trunk/docs/confirmed.php @@ -0,0 +1,76 @@ +setup(); + $this->bind(); + } + + //Setup + function setup (){ + + //Grab the user + if(isset($_GET['cid'])){ + $confirm_id = $_GET['cid']; + }else{ + header("HTTP/1.0 404 Not Found"); + exit; + } + + $user = new user(); + if($user->load_from_confirm_id($confirm_id)){ + + //Update the confirmed flag + $user->confirmed = true; + + //delete any other active alerts for this postcode + $user->remove_existing(); + + $user->save(false); + + //Grab the postcode and area + $this->postcode = $user->postcode; + $this->alert_area_size = alert_size_to_meters($user->alert_area_size); + + }else{ + header("HTTP/1.0 404 Not Found"); + exit; + } + + } + + //Bind + function bind() { + + $form_action = $_SERVER['PHP_SELF']; + + //smarty + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + + $smarty->assign("menu_item", "signup"); + $smarty->assign("page_title","Confirmed"); + $smarty->assign("form_action", $form_action); + $smarty->assign("postcode", $this->postcode); + $smarty->assign("alert_area_size", $this->alert_area_size); + + //Render + $smarty->display('confirmed.tpl'); + + } + +} + + + +?> \ No newline at end of file diff --git a/trunk/docs/cron.php b/trunk/docs/cron.php new file mode 100644 index 0000000..3e6bcb9 --- /dev/null +++ b/trunk/docs/cron.php @@ -0,0 +1,33 @@ +date = getdate(strtotime("-" . SCRAPE_DELAY . " days")); + $application_parser->run(); + + + } + + if ($action == "mail"){ + //Launch the mailer + $mailer = new mailer(); + $mailer->run(); + } + + +?> \ No newline at end of file diff --git a/trunk/docs/css/main.css b/trunk/docs/css/main.css new file mode 100644 index 0000000..60a2fd8 --- /dev/null +++ b/trunk/docs/css/main.css @@ -0,0 +1,81 @@ +body {font-family:georgia, serif; text-align:center;line-height:2em;margin:0; + color:#0f0f0f;} +#divPage {width:500px; margin:0 auto;background:white;text-align:left;} +h1 {font-weight:normal; font-size:2.7em;padding-left:115px;padding-bottom:0.2em;padding-top:0.1em;} +h1 a {color:#456FB1;text-decoration:none;} +h1 span {color:#96ca2f;} +h1 small {font-size:0.3em; text-transform:uppercase;position:absolute; + right:0.8em;top:2.1em;color:#456FB1} +h2 {font-weight:normal;font-size:1em;border-bottom:dotted 1px #808080;padding-left:115px; + padding-bottom:1.5em;color:#96ca2f;font-style:italic;font-size:1.15em;} +h2 em {text-decoration:underline} +h3, h4 {color:#96ca2f;font-style:italic;font-weight:normal} + +#divHeader {margin-top:1.7em;padding-bottom:1em;position:relative;} +#divHeader img {position:absolute;top:-15px;} + +/* Menu */ +#divMenu {background:#6E84BD;height:2.3em;font-size:0.7em;} +#divMenu ul {width:500px;margin:0 auto;} +#divMenu ul li {float:right;text-align:center;} +#divMenu ul li a {margin-left:1em;margin-right:1em;display:block; color:white; + text-decoration:none;position:relative; top:-0.2em;} +#divMenu ul li.selected {background:#96ca2f;} + +/* Beta note */ +#pBeta {font-size:0.8em; text-align:center;padding:0;margin:0;position:relative; top:-0.4em; + color:#666;} +#pBeta span {background:#fdd9ea;} + +/* Warnings & errors*/ +#divWarning {background:#fdd9ea;font-size:0.9em;text-align:center; margin-bottom:1em;} +input.error {background:#fdd9ea;} + +/* Signup form */ +span.number {font-size:2em;float:left;display:block; + text-align:center;padding-bottom:0.6em;margin-right:0.7em;margin-left:0.4em; + color:#456FB1;} +#liAlertArea span.number {margin-bottom:2em;} +#ulSignup label {width:250px;} +#ulSignup input.textbox {position:relative; top:0.4em;} +#ulSignup fieldset label {display:inline;float:none;} +#liEmail, #liPostcode, #liAlertArea {background:#ffffaa;} +#liEmail {height:2.3em;margin-bottom:1em;} +#liEmail input.textbox {width:230px;} +#liPostcode {height:2.3em;} +#liPostcode input.textbox {width:80px;} +#liPostcode small {position:relative; top:0.6em;padding-left:0.3em;} +#pAlertArea {margin-bottom:0;} +#ulAlertArea {font-size:0.9em;line-height:1.3em;} +#ulAlertArea li {} +#liSignup input.button {font-size:1.5em;margin-top:0.5em;margin-bottom:0.5em;} + +/* Attention grabbers */ +div.attention {text-align:center; padding-top:1em;} +div.attention h3 {font-size:2em;padding-bottom:0.3em;font-style:normal; color:#456FB1;} + +/* Faqs */ +dl.faq dt {color:#96ca2f;font-style:italic;} +dl.faq dd {padding-left:0;margin-left:0;} + +/* infobox */ +.infobox {background:#fdd9ea; font-size:0.9em;} + +/* highlight */ +span.highlight {background:#ffffaa;} + +/* Index footer */ +#divSiteUpdates {border-top:dotted 1px #808080;padding-top:0.5em;} +#divSiteUpdates ul {font-size:0.9em;margin:0;} +#divSiteUpdates em {padding-right:0.5em;color:#456FB1;} + +/* Links */ +a {color:#22579C;} + +/* Footer */ +#divFooter {text-align:center;font-size:0.9em; + padding-top:1.5em;} +#divFooter ul {border-top:dotted 1px #808080;} + +/* Buttons */ +input.button {font-family:georgia, serif;} \ No newline at end of file diff --git a/trunk/docs/css/memespring.css b/trunk/docs/css/memespring.css new file mode 100644 index 0000000..660ddf7 --- /dev/null +++ b/trunk/docs/css/memespring.css @@ -0,0 +1,59 @@ +/* +This file is for usefull reusable css +*/ + +/* lists */ +ul.form label {display:block; float:left; width:170px; padding-top:4px;} +ul.form label.radiolabel {display:inline; float: none;} +ul.form span.fauxlabel {display:block; float:left; width:160px;} +ul.form li {margin:5px 0;padding-bottom: 6px!important;} +ul.form input.textbox {width:165px;} +ul.form select {width:175px;} +ul.form input.textbox.small, ul.form select.small {width:80px;} +ul.form input.textbox.verysmall {width:30px;} +ul.form select.verysmall {width:45px;} +ul.form input.textbox.right {text-align:right;padding-right:0.2em;} + +ul.collapse, ol.collapse {margin:0;padding:0;} +ul.collapse li, ol.collapse li {margin:0;padding:0;float:left;} +ul.collapse li, ol.collapse {list-style-type:none;} + +ul.inline {margin:0;padding:0;} +ul.inline li {display:inline;margin:0;padding:0;} + +ul.nobullets {list-style-type:none;margin:0;padding:0;} +ul.nobullets li {margin:0;padding:0;} + +/* image replacement */ +.hide {display:none;} + +/* clear */ +br.clear {clear:both} + +/* controls */ +fieldset {border:none; padding:0; margin:0;} + +/* links */ +a img {border:none;} + +/* headings */ +h1, h2, h3, h4, h5, h6 {margin:0;padding:0;} + +/* form elements */ +input.textbox.autopostback {background: white url(../images/return.png) no-repeat right 50%;} +input select, textarea {background:white} +input.textbox {width:180px;} +input.textbox.small {width:80px;} +input.textbox.verysmall {width:80px;} +input.textbox.right {text-align:right;padding-right:0.2em;} + +/* alignment */ +.alignright {text-align:right;} + +/* tables */ +table {border-collapse: collapse;} + +/* buttons */ +div.buttons {text-align:right; padding:1em;} + + diff --git a/trunk/docs/faq.php b/trunk/docs/faq.php new file mode 100644 index 0000000..6c20fcd --- /dev/null +++ b/trunk/docs/faq.php @@ -0,0 +1,34 @@ +bind(); + } + + //Bind + function bind() { + + //page vars + $form_action = $_SERVER['PHP_SELF']; + //smarty + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + + $smarty->assign("page_title","Frequently asked questions"); + $smarty->assign("menu_item", "faq"); + //Render + $smarty->display('faq.tpl'); + + } + +} + + + +?> \ No newline at end of file diff --git a/trunk/docs/favicon.ico b/trunk/docs/favicon.ico new file mode 100755 index 0000000..afa7ff4 Binary files /dev/null and b/trunk/docs/favicon.ico differ diff --git a/trunk/docs/getinvolved.php b/trunk/docs/getinvolved.php new file mode 100644 index 0000000..70dac82 --- /dev/null +++ b/trunk/docs/getinvolved.php @@ -0,0 +1,35 @@ +bind(); + } + + //Bind + function bind() { + + //page vars + $form_action = $_SERVER['PHP_SELF']; + + //smarty + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + + $smarty->assign("page_title","Get involved"); + $smarty->assign("menu_item", "getinvolved"); + //Render + $smarty->display('getinvolved.tpl'); + + } + +} + + + +?> \ No newline at end of file diff --git a/trunk/docs/images/background.png b/trunk/docs/images/background.png new file mode 100644 index 0000000..8dd6150 Binary files /dev/null and b/trunk/docs/images/background.png differ diff --git a/trunk/docs/images/logo.png b/trunk/docs/images/logo.png new file mode 100644 index 0000000..5efb766 Binary files /dev/null and b/trunk/docs/images/logo.png differ diff --git a/trunk/docs/include/PEAR/DB.php b/trunk/docs/include/PEAR/DB.php new file mode 100644 index 0000000..a02097b --- /dev/null +++ b/trunk/docs/include/PEAR/DB.php @@ -0,0 +1,1388 @@ + + * @author Tomas V.V.Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: DB.php,v 1.1 2005/08/01 06:20:23 dancoulter Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + + +// {{{ constants +// {{{ error codes + +/**#@+ + * One of PEAR DB's portable error codes. + * @see DB_common::errorCode(), DB::errorMessage() + * + * {@internal If you add an error code here, make sure you also add a textual + * version of it in DB::errorMessage().}} + */ + +/** + * The code returned by many methods upon success + */ +define('DB_OK', 1); + +/** + * Unkown error + */ +define('DB_ERROR', -1); + +/** + * Syntax error + */ +define('DB_ERROR_SYNTAX', -2); + +/** + * Tried to insert a duplicate value into a primary or unique index + */ +define('DB_ERROR_CONSTRAINT', -3); + +/** + * An identifier in the query refers to a non-existant object + */ +define('DB_ERROR_NOT_FOUND', -4); + +/** + * Tried to create a duplicate object + */ +define('DB_ERROR_ALREADY_EXISTS', -5); + +/** + * The current driver does not support the action you attempted + */ +define('DB_ERROR_UNSUPPORTED', -6); + +/** + * The number of parameters does not match the number of placeholders + */ +define('DB_ERROR_MISMATCH', -7); + +/** + * A literal submitted did not match the data type expected + */ +define('DB_ERROR_INVALID', -8); + +/** + * The current DBMS does not support the action you attempted + */ +define('DB_ERROR_NOT_CAPABLE', -9); + +/** + * A literal submitted was too long so the end of it was removed + */ +define('DB_ERROR_TRUNCATED', -10); + +/** + * A literal number submitted did not match the data type expected + */ +define('DB_ERROR_INVALID_NUMBER', -11); + +/** + * A literal date submitted did not match the data type expected + */ +define('DB_ERROR_INVALID_DATE', -12); + +/** + * Attempt to divide something by zero + */ +define('DB_ERROR_DIVZERO', -13); + +/** + * A database needs to be selected + */ +define('DB_ERROR_NODBSELECTED', -14); + +/** + * Could not create the object requested + */ +define('DB_ERROR_CANNOT_CREATE', -15); + +/** + * Could not drop the database requested because it does not exist + */ +define('DB_ERROR_CANNOT_DROP', -17); + +/** + * An identifier in the query refers to a non-existant table + */ +define('DB_ERROR_NOSUCHTABLE', -18); + +/** + * An identifier in the query refers to a non-existant column + */ +define('DB_ERROR_NOSUCHFIELD', -19); + +/** + * The data submitted to the method was inappropriate + */ +define('DB_ERROR_NEED_MORE_DATA', -20); + +/** + * The attempt to lock the table failed + */ +define('DB_ERROR_NOT_LOCKED', -21); + +/** + * The number of columns doesn't match the number of values + */ +define('DB_ERROR_VALUE_COUNT_ON_ROW', -22); + +/** + * The DSN submitted has problems + */ +define('DB_ERROR_INVALID_DSN', -23); + +/** + * Could not connect to the database + */ +define('DB_ERROR_CONNECT_FAILED', -24); + +/** + * The PHP extension needed for this DBMS could not be found + */ +define('DB_ERROR_EXTENSION_NOT_FOUND',-25); + +/** + * The present user has inadequate permissions to perform the task requestd + */ +define('DB_ERROR_ACCESS_VIOLATION', -26); + +/** + * The database requested does not exist + */ +define('DB_ERROR_NOSUCHDB', -27); + +/** + * Tried to insert a null value into a column that doesn't allow nulls + */ +define('DB_ERROR_CONSTRAINT_NOT_NULL',-29); +/**#@-*/ + + +// }}} +// {{{ prepared statement-related + + +/**#@+ + * Identifiers for the placeholders used in prepared statements. + * @see DB_common::prepare() + */ + +/** + * Indicates a scalar (?) placeholder was used + * + * Quote and escape the value as necessary. + */ +define('DB_PARAM_SCALAR', 1); + +/** + * Indicates an opaque (&) placeholder was used + * + * The value presented is a file name. Extract the contents of that file + * and place them in this column. + */ +define('DB_PARAM_OPAQUE', 2); + +/** + * Indicates a misc (!) placeholder was used + * + * The value should not be quoted or escaped. + */ +define('DB_PARAM_MISC', 3); +/**#@-*/ + + +// }}} +// {{{ binary data-related + + +/**#@+ + * The different ways of returning binary data from queries. + */ + +/** + * Sends the fetched data straight through to output + */ +define('DB_BINMODE_PASSTHRU', 1); + +/** + * Lets you return data as usual + */ +define('DB_BINMODE_RETURN', 2); + +/** + * Converts the data to hex format before returning it + * + * For example the string "123" would become "313233". + */ +define('DB_BINMODE_CONVERT', 3); +/**#@-*/ + + +// }}} +// {{{ fetch modes + + +/**#@+ + * Fetch Modes. + * @see DB_common::setFetchMode() + */ + +/** + * Indicates the current default fetch mode should be used + * @see DB_common::$fetchmode + */ +define('DB_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ +define('DB_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ +define('DB_FETCHMODE_ASSOC', 2); + +/** + * Column data as object properties + */ +define('DB_FETCHMODE_OBJECT', 3); + +/** + * For multi-dimensional results, make the column name the first level + * of the array and put the row number in the second level of the array + * + * This is flipped from the normal behavior, which puts the row numbers + * in the first level of the array and the column names in the second level. + */ +define('DB_FETCHMODE_FLIPPED', 4); +/**#@-*/ + +/**#@+ + * Old fetch modes. Left here for compatibility. + */ +define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED); +define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC); +define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED); +/**#@-*/ + + +// }}} +// {{{ tableInfo() && autoPrepare()-related + + +/**#@+ + * The type of information to return from the tableInfo() method. + * + * Bitwised constants, so they can be combined using | + * and removed using ^. + * + * @see DB_common::tableInfo() + * + * {@internal Since the TABLEINFO constants are bitwised, if more of them are + * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}} + */ +define('DB_TABLEINFO_ORDER', 1); +define('DB_TABLEINFO_ORDERTABLE', 2); +define('DB_TABLEINFO_FULL', 3); +/**#@-*/ + + +/**#@+ + * The type of query to create with the automatic query building methods. + * @see DB_common::autoPrepare(), DB_common::autoExecute() + */ +define('DB_AUTOQUERY_INSERT', 1); +define('DB_AUTOQUERY_UPDATE', 2); +/**#@-*/ + + +// }}} +// {{{ portability modes + + +/**#@+ + * Portability Modes. + * + * Bitwised constants, so they can be combined using | + * and removed using ^. + * + * @see DB_common::setOption() + * + * {@internal Since the PORTABILITY constants are bitwised, if more of them are + * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}} + */ + +/** + * Turn off all portability features + */ +define('DB_PORTABILITY_NONE', 0); + +/** + * Convert names of tables and fields to lower case + * when using the get*(), fetch*() and tableInfo() methods + */ +define('DB_PORTABILITY_LOWERCASE', 1); + +/** + * Right trim the data output by get*() and fetch*() + */ +define('DB_PORTABILITY_RTRIM', 2); + +/** + * Force reporting the number of rows deleted + */ +define('DB_PORTABILITY_DELETE_COUNT', 4); + +/** + * Enable hack that makes numRows() work in Oracle + */ +define('DB_PORTABILITY_NUMROWS', 8); + +/** + * Makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to DB_ERROR_NOSUCHFIELD. + */ +define('DB_PORTABILITY_ERRORS', 16); + +/** + * Convert null values to empty strings in data output by + * get*() and fetch*() + */ +define('DB_PORTABILITY_NULL_TO_EMPTY', 32); + +/** + * Turn on all portability features + */ +define('DB_PORTABILITY_ALL', 63); +/**#@-*/ + +// }}} + + +// }}} +// {{{ class DB + +/** + * Database independent query interface + * + * The main "DB" class is simply a container class with some static + * methods for creating DB objects as well as some utility functions + * common to all parts of DB. + * + * The object model of DB is as follows (indentation means inheritance): + *
+ * DB           The main DB class.  This is simply a utility class
+ *              with some "static" methods for creating DB objects as
+ *              well as common utility functions for other DB classes.
+ *
+ * DB_common    The base for each DB implementation.  Provides default
+ * |            implementations (in OO lingo virtual methods) for
+ * |            the actual DB implementations as well as a bunch of
+ * |            query utility functions.
+ * |
+ * +-DB_mysql   The DB implementation for MySQL.  Inherits DB_common.
+ *              When calling DB::factory or DB::connect for MySQL
+ *              connections, the object returned is an instance of this
+ *              class.
+ * 
+ * + * @category Database + * @package DB + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB +{ + // {{{ &factory() + + /** + * Create a new DB object for the specified database type but don't + * connect to the database + * + * @param string $type the database type (eg "mysql") + * @param array $options an associative array of option names and values + * + * @return object a new DB object. A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function &factory($type, $options = false) + { + if (!is_array($options)) { + $options = array('persistent' => $options); + } + + if (isset($options['debug']) && $options['debug'] >= 2) { + // expose php errors with sufficient debug level + include_once "DB/{$type}.php"; + } else { + @include_once "DB/{$type}.php"; + } + + $classname = "DB_${type}"; + + if (!class_exists($classname)) { + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php" + . " file for '$dsn'", + 'DB_Error', true); + return $tmp; + } + + @$obj =& new $classname; + + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + + return $obj; + } + + // }}} + // {{{ &connect() + + /** + * Create a new DB object including a connection to the specified database + * + * Example 1. + * + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:password@host/database'; + * $options = array( + * 'debug' => 2, + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db =& DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param mixed $dsn the string "data source name" or array in the + * format returned by DB::parseDSN() + * @param array $options an associative array of option names and values + * + * @return object a new DB object. A DB_Error object on failure. + * + * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(), + * DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(), + * DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(), + * DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(), + * DB_sybase::connect() + * + * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError() + */ + function &connect($dsn, $options = array()) + { + $dsninfo = DB::parseDSN($dsn); + $type = $dsninfo['phptype']; + + if (!is_array($options)) { + /* + * For backwards compatibility. $options used to be boolean, + * indicating whether the connection should be persistent. + */ + $options = array('persistent' => $options); + } + + if (isset($options['debug']) && $options['debug'] >= 2) { + // expose php errors with sufficient debug level + include_once "DB/${type}.php"; + } else { + @include_once "DB/${type}.php"; + } + + $classname = "DB_${type}"; + if (!class_exists($classname)) { + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php" + . " file for '$dsn'", + 'DB_Error', true); + return $tmp; + } + + @$obj =& new $classname; + + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + + $err = $obj->connect($dsninfo, $obj->getOption('persistent')); + if (DB::isError($err)) { + $err->addUserInfo($dsn); + return $err; + } + + return $obj; + } + + // }}} + // {{{ apiVersion() + + /** + * Return the DB API version + * + * @return string the DB API version number + */ + function apiVersion() + { + return '@package_version@'; + } + + // }}} + // {{{ isError() + + /** + * Determines if a variable is a DB_Error object + * + * @param mixed $value the variable to check + * + * @return bool whether $value is DB_Error object + */ + function isError($value) + { + return is_a($value, 'DB_Error'); + } + + // }}} + // {{{ isConnection() + + /** + * Determines if a value is a DB_ object + * + * @param mixed $value the value to test + * + * @return bool whether $value is a DB_ object + */ + function isConnection($value) + { + return (is_object($value) && + is_subclass_of($value, 'db_common') && + method_exists($value, 'simpleQuery')); + } + + // }}} + // {{{ isManip() + + /** + * Tell whether a query is a data manipulation or data definition query + * + * Examples of data manipulation queries are INSERT, UPDATE and DELETE. + * Examples of data definition queries are CREATE, DROP, ALTER, GRANT, + * REVOKE. + * + * @param string $query the query + * + * @return boolean whether $query is a data manipulation query + */ + function isManip($query) + { + $manips = 'INSERT|UPDATE|DELETE|REPLACE|' + . 'CREATE|DROP|' + . 'LOAD DATA|SELECT .* INTO|COPY|' + . 'ALTER|GRANT|REVOKE|' + . 'LOCK|UNLOCK'; + if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) { + return true; + } + return false; + } + + // }}} + // {{{ errorMessage() + + /** + * Return a textual error message for a DB error code + * + * @param integer $value the DB error code + * + * @return string the error message or false if the error code was + * not recognized + */ + function errorMessage($value) + { + static $errorMessages; + if (!isset($errorMessages)) { + $errorMessages = array( + DB_ERROR => 'unknown error', + DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions', + DB_ERROR_ALREADY_EXISTS => 'already exists', + DB_ERROR_CANNOT_CREATE => 'can not create', + DB_ERROR_CANNOT_DROP => 'can not drop', + DB_ERROR_CONNECT_FAILED => 'connect failed', + DB_ERROR_CONSTRAINT => 'constraint violation', + DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + DB_ERROR_DIVZERO => 'division by zero', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + DB_ERROR_INVALID => 'invalid', + DB_ERROR_INVALID_DATE => 'invalid date or time', + DB_ERROR_INVALID_DSN => 'invalid DSN', + DB_ERROR_INVALID_NUMBER => 'invalid number', + DB_ERROR_MISMATCH => 'mismatch', + DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + DB_ERROR_NODBSELECTED => 'no database selected', + DB_ERROR_NOSUCHDB => 'no such database', + DB_ERROR_NOSUCHFIELD => 'no such field', + DB_ERROR_NOSUCHTABLE => 'no such table', + DB_ERROR_NOT_CAPABLE => 'DB backend not capable', + DB_ERROR_NOT_FOUND => 'not found', + DB_ERROR_NOT_LOCKED => 'not locked', + DB_ERROR_SYNTAX => 'syntax error', + DB_ERROR_UNSUPPORTED => 'not supported', + DB_ERROR_TRUNCATED => 'truncated', + DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + DB_OK => 'no error', + ); + } + + if (DB::isError($value)) { + $value = $value->getCode(); + } + + return isset($errorMessages[$value]) ? $errorMessages[$value] + : $errorMessages[DB_ERROR]; + } + + // }}} + // {{{ parseDSN() + + /** + * Parse a data source name + * + * Additional keys can be added by appending a URI query string to the + * end of the DSN. + * + * The format of the supplied DSN is in its fullest form: + * + * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true + * + * + * Most variations are allowed: + * + * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 + * phptype://username:password@hostspec/database_name + * phptype://username:password@hostspec + * phptype://username@hostspec + * phptype://hostspec/database + * phptype://hostspec + * phptype(dbsyntax) + * phptype + * + * + * @param string $dsn Data Source Name to be parsed + * + * @return array an associative array with the following keys: + * + phptype: Database backend used in PHP (mysql, odbc etc.) + * + dbsyntax: Database used with regards to SQL syntax etc. + * + protocol: Communication protocol to use (tcp, unix etc.) + * + hostspec: Host specification (hostname[:port]) + * + database: Database to use on the DBMS server + * + username: User name for login + * + password: Password for login + */ + function parseDSN($dsn) + { + $parsed = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false, + ); + + if (is_array($dsn)) { + $dsn = array_merge($parsed, $dsn); + if (!$dsn['dbsyntax']) { + $dsn['dbsyntax'] = $dsn['phptype']; + } + return $dsn; + } + + // Find phptype and dbsyntax + if (($pos = strpos($dsn, '://')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 3); + } else { + $str = $dsn; + $dsn = null; + } + + // Get phptype and dbsyntax + // $str => phptype(dbsyntax) + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { + $parsed['phptype'] = $arr[1]; + $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + if (!count($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strrpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = rawurldecode(substr($str, 0, $pos)); + $parsed['password'] = rawurldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = rawurldecode($str); + } + } + + // Find protocol and hostspec + + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + // $dsn => proto(proto_opts)/database + $proto = $match[1]; + $proto_opts = $match[2] ? $match[2] : false; + $dsn = $match[3]; + + } else { + // $dsn => protocol+hostspec/database (old format) + if (strpos($dsn, '+') !== false) { + list($proto, $dsn) = explode('+', $dsn, 2); + } + if (strpos($dsn, '/') !== false) { + list($proto_opts, $dsn) = explode('/', $dsn, 2); + } else { + $proto_opts = $dsn; + $dsn = null; + } + } + + // process the different protocol options + $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; + $proto_opts = rawurldecode($proto_opts); + if ($parsed['protocol'] == 'tcp') { + if (strpos($proto_opts, ':') !== false) { + list($parsed['hostspec'], + $parsed['port']) = explode(':', $proto_opts); + } else { + $parsed['hostspec'] = $proto_opts; + } + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if ($dsn) { + if (($pos = strpos($dsn, '?')) === false) { + // /database + $parsed['database'] = rawurldecode($dsn); + } else { + // /database?param1=value1¶m2=value2 + $parsed['database'] = rawurldecode(substr($dsn, 0, $pos)); + $dsn = substr($dsn, $pos + 1); + if (strpos($dsn, '&') !== false) { + $opts = explode('&', $dsn); + } else { // database?param1=value1 + $opts = array($dsn); + } + foreach ($opts as $opt) { + list($key, $value) = explode('=', $opt); + if (!isset($parsed[$key])) { + // don't allow params overwrite + $parsed[$key] = rawurldecode($value); + } + } + } + } + + return $parsed; + } + + // }}} +} + +// }}} +// {{{ class DB_Error + +/** + * DB_Error implements a class for reporting portable database error + * messages + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_Error extends PEAR_Error +{ + // {{{ constructor + + /** + * DB_Error constructor + * + * @param mixed $code DB error code, or string with error message + * @param int $mode what "error mode" to operate in + * @param int $level what error level to use for $mode & + * PEAR_ERROR_TRIGGER + * @param mixed $debuginfo additional debug info, such as the last query + * + * @see PEAR_Error + */ + function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null) + { + if (is_int($code)) { + $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code, + $mode, $level, $debuginfo); + } else { + $this->PEAR_Error("DB Error: $code", DB_ERROR, + $mode, $level, $debuginfo); + } + } + + // }}} +} + +// }}} +// {{{ class DB_result + +/** + * This class implements a wrapper for a DB result set + * + * A new instance of this class will be returned by the DB implementation + * after processing a query that returns data. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_result +{ + // {{{ properties + + /** + * Should results be freed automatically when there are no more rows? + * @var boolean + * @see DB_common::$options + */ + var $autofree; + + /** + * A reference to the DB_ object + * @var object + */ + var $dbh; + + /** + * The current default fetch mode + * @var integer + * @see DB_common::$fetchmode + */ + var $fetchmode; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + * @see DB_common::$fetchmode_object_class + */ + var $fetchmode_object_class; + + /** + * The number of rows to fetch from a limit query + * @var integer + */ + var $limit_count = null; + + /** + * The row to start fetching from in limit queries + * @var integer + */ + var $limit_from = null; + + /** + * The execute parameters that created this result + * @var array + * @since Property available since Release 1.7.0 + */ + var $parameters; + + /** + * The query string that created this result + * + * Copied here incase it changes in $dbh, which is referenced + * + * @var string + * @since Property available since Release 1.7.0 + */ + var $query; + + /** + * The query result resource id created by PHP + * @var resource + */ + var $result; + + /** + * The present row being dealt with + * @var integer + */ + var $row_counter = null; + + /** + * The prepared statement resource id created by PHP in $dbh + * + * This resource is only available when the result set was created using + * a driver's native execute() method, not PEAR DB's emulated one. + * + * Copied here incase it changes in $dbh, which is referenced + * + * {@internal Mainly here because the InterBase/Firebird API is only + * able to retrieve data from result sets if the statemnt handle is + * still in scope.}} + * + * @var resource + * @since Property available since Release 1.7.0 + */ + var $statement; + + + // }}} + // {{{ constructor + + /** + * This constructor sets the object's properties + * + * @param object &$dbh the DB object reference + * @param resource $result the result resource id + * @param array $options an associative array with result options + * + * @return void + */ + function DB_result(&$dbh, $result, $options = array()) + { + $this->autofree = $dbh->options['autofree']; + $this->dbh = &$dbh; + $this->fetchmode = $dbh->fetchmode; + $this->fetchmode_object_class = $dbh->fetchmode_object_class; + $this->parameters = $dbh->last_parameters; + $this->query = $dbh->last_query; + $this->result = $result; + $this->statement = empty($dbh->last_stmt) ? null : $dbh->last_stmt; + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + } + + /** + * Set options for the DB_result object + * + * @param string $key the option to set + * @param mixed $value the value to set the option to + * + * @return void + */ + function setOption($key, $value = null) + { + switch ($key) { + case 'limit_from': + $this->limit_from = $value; + break; + case 'limit_count': + $this->limit_count = $value; + } + } + + // }}} + // {{{ fetchRow() + + /** + * Fetch a row of data and return it by reference into an array + * + * The type of array returned can be controlled either by setting this + * method's $fetchmode parameter or by changing the default + * fetch mode setFetchMode() before calling this method. + * + * There are two options for standardizing the information returned + * from databases, ensuring their values are consistent when changing + * DBMS's. These portability options can be turned on when creating a + * new DB object or by using setOption(). + * + * + DB_PORTABILITY_LOWERCASE + * convert names of fields to lower case + * + * + DB_PORTABILITY_RTRIM + * right trim the data + * + * @param int $fetchmode the constant indicating how to format the data + * @param int $rownum the row number to fetch (index starts at 0) + * + * @return mixed an array or object containing the row's data, + * NULL when the end of the result set is reached + * or a DB_Error object on failure. + * + * @see DB_common::setOption(), DB_common::setFetchMode() + */ + function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if ($this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->dbh->features['limit'] === false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ($this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + $tmp = null; + return $tmp; + } + if ($this->dbh->features['limit'] === 'emulate') { + $rownum = $this->row_counter; + } + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // The default mode is specified in the + // DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = &new $object_class($arr); + } + } + return $arr; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ fetchInto() + + /** + * Fetch a row of data into an array which is passed by reference + * + * The type of array returned can be controlled either by setting this + * method's $fetchmode parameter or by changing the default + * fetch mode setFetchMode() before calling this method. + * + * There are two options for standardizing the information returned + * from databases, ensuring their values are consistent when changing + * DBMS's. These portability options can be turned on when creating a + * new DB object or by using setOption(). + * + * + DB_PORTABILITY_LOWERCASE + * convert names of fields to lower case + * + * + DB_PORTABILITY_RTRIM + * right trim the data + * + * @param array &$arr the variable where the data should be placed + * @param int $fetchmode the constant indicating how to format the data + * @param int $rownum the row number to fetch (index starts at 0) + * + * @return mixed DB_OK if a row is processed, NULL when the end of the + * result set is reached or a DB_Error object on failure + * + * @see DB_common::setOption(), DB_common::setFetchMode() + */ + function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if ($this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->dbh->features['limit'] === false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ( + $this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + return null; + } + if ($this->dbh->features['limit'] === 'emulate') { + $rownum = $this->row_counter; + } + + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // default mode specified in the + // DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = new $object_class($arr); + } + } + return DB_OK; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ numCols() + + /** + * Get the the number of columns in a result set + * + * @return int the number of columns. A DB_Error object on failure. + */ + function numCols() + { + return $this->dbh->numCols($this->result); + } + + // }}} + // {{{ numRows() + + /** + * Get the number of rows in a result set + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows() + { + if ($this->dbh->features['numrows'] === 'emulate' + && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS) + { + if ($this->dbh->features['prepare']) { + $res = $this->dbh->query($this->query, $this->parameters); + } else { + $res = $this->dbh->query($this->query); + } + if (DB::isError($res)) { + return $res; + } + $i = 0; + while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) { + $i++; + } + return $i; + } else { + return $this->dbh->numRows($this->result); + } + } + + // }}} + // {{{ nextResult() + + /** + * Get the next result if a batch of queries was executed + * + * @return bool true if a new result is available or false if not + */ + function nextResult() + { + return $this->dbh->nextResult($this->result); + } + + // }}} + // {{{ free() + + /** + * Frees the resources allocated for this result set + * + * @return bool true on success. A DB_Error object on failure. + */ + function free() + { + $err = $this->dbh->freeResult($this->result); + if (DB::isError($err)) { + return $err; + } + $this->result = false; + $this->statement = false; + return true; + } + + // }}} + // {{{ tableInfo() + + /** + * @see DB_common::tableInfo() + * @deprecated Method deprecated some time before Release 1.2 + */ + function tableInfo($mode = null) + { + if (is_string($mode)) { + return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA); + } + return $this->dbh->tableInfo($this, $mode); + } + + // }}} + // {{{ getQuery() + + /** + * Determine the query string that created this result + * + * @return string the query string + * + * @since Method available since Release 1.7.0 + */ + function getQuery() + { + return $this->query; + } + + // }}} + // {{{ getRowCounter() + + /** + * Tells which row number is currently being processed + * + * @return integer the current row being looked at. Starts at 1. + */ + function getRowCounter() + { + return $this->row_counter; + } + + // }}} +} + +// }}} +// {{{ class DB_row + +/** + * PEAR DB Row Object + * + * The object contains a row of data from a result set. Each column's data + * is placed in a property named for the column. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + * @see DB_common::setFetchMode() + */ +class DB_row +{ + // {{{ constructor + + /** + * The constructor places a row's data into properties of this object + * + * @param array the array containing the row's data + * + * @return void + */ + function DB_row(&$arr) + { + foreach ($arr as $key => $value) { + $this->$key = &$arr[$key]; + } + } + + // }}} +} + +// }}} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/trunk/docs/include/PEAR/DB/common.php b/trunk/docs/include/PEAR/DB/common.php new file mode 100644 index 0000000..c5d86de --- /dev/null +++ b/trunk/docs/include/PEAR/DB/common.php @@ -0,0 +1,2157 @@ + + * @author Tomas V.V. Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: common.php,v 1.1 2005/08/01 06:21:02 dancoulter Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + +/** + * DB_common is the base class from which each database driver class extends + * + * All common methods are declared here. If a given DBMS driver contains + * a particular method, that method will overload the one here. + * + * @category Database + * @package DB + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_common extends PEAR +{ + // {{{ properties + + /** + * The current default fetch mode + * @var integer + */ + var $fetchmode = DB_FETCHMODE_ORDERED; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + */ + var $fetchmode_object_class = 'stdClass'; + + /** + * Was a connection present when the object was serialized()? + * @var bool + * @see DB_common::__sleep(), DB_common::__wake() + */ + var $was_connected = null; + + /** + * The most recently executed query + * @var string + */ + var $last_query = ''; + + /** + * Run-time configuration options + * + * The 'optimize' option has been deprecated. Use the 'portability' + * option instead. + * + * @var array + * @see DB_common::setOption() + */ + var $options = array( + 'result_buffering' => 500, + 'persistent' => false, + 'ssl' => false, + 'debug' => 0, + 'seqname_format' => '%s_seq', + 'autofree' => false, + 'portability' => DB_PORTABILITY_NONE, + 'optimize' => 'performance', // Deprecated. Use 'portability'. + ); + + /** + * The parameters from the most recently executed query + * @var array + * @since Property available since Release 1.7.0 + */ + var $last_parameters = array(); + + /** + * The elements from each prepared statement + * @var array + */ + var $prepare_tokens = array(); + + /** + * The data types of the various elements in each prepared statement + * @var array + */ + var $prepare_types = array(); + + /** + * The prepared queries + * @var array + */ + var $prepared_queries = array(); + + + // }}} + // {{{ DB_common + + /** + * This constructor calls $this->PEAR('DB_Error') + * + * @return void + */ + function DB_common() + { + $this->PEAR('DB_Error'); + } + + // }}} + // {{{ __sleep() + + /** + * Automatically indicates which properties should be saved + * when PHP's serialize() function is called + * + * @return array the array of properties names that should be saved + */ + function __sleep() + { + if ($this->connection) { + // Don't disconnect(), people use serialize() for many reasons + $this->was_connected = true; + } else { + $this->was_connected = false; + } + if (isset($this->autocommit)) { + return array('autocommit', + 'dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } else { + return array('dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } + } + + // }}} + // {{{ __wakeup() + + /** + * Automatically reconnects to the database when PHP's unserialize() + * function is called + * + * The reconnection attempt is only performed if the object was connected + * at the time PHP's serialize() function was run. + * + * @return void + */ + function __wakeup() + { + if ($this->was_connected) { + $this->connect($this->dsn, $this->options); + } + } + + // }}} + // {{{ __toString() + + /** + * Automatic string conversion for PHP 5 + * + * @return string a string describing the current PEAR DB object + * + * @since Method available since Release 1.7.0 + */ + function __toString() + { + $info = strtolower(get_class($this)); + $info .= ': (phptype=' . $this->phptype . + ', dbsyntax=' . $this->dbsyntax . + ')'; + if ($this->connection) { + $info .= ' [connected]'; + } + return $info; + } + + // }}} + // {{{ toString() + + /** + * DEPRECATED: String conversion method + * + * @return string a string describing the current PEAR DB object + * + * @deprecated Method deprecated in Release 1.7.0 + */ + function toString() + { + return $this->__toString(); + } + + // }}} + // {{{ quoteString() + + /** + * DEPRECATED: Quotes a string so it can be safely used within string + * delimiters in a query + * + * @param string $string the string to be quoted + * + * @return string the quoted string + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Method deprecated some time before Release 1.2 + */ + function quoteString($string) + { + $string = $this->quote($string); + if ($string{0} == "'") { + return substr($string, 1, -1); + } + return $string; + } + + // }}} + // {{{ quote() + + /** + * DEPRECATED: Quotes a string so it can be safely used in a query + * + * @param string $string the string to quote + * + * @return string the quoted string or the string NULL + * if the value submitted is null. + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Deprecated in release 1.6.0 + */ + function quote($string = null) + { + return ($string === null) ? 'NULL' + : "'" . str_replace("'", "''", $string) . "'"; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + odbc(access) + * + odbc(db2) + * + pgsql + * + sqlite + * + sybase (must execute set quoted_identifier on sometime + * prior to use) + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string $str the identifier name to be quoted + * + * @return string the quoted identifier + * + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '"' . str_replace('"', '""', $str) . '"'; + } + + // }}} + // {{{ quoteSmart() + + /** + * Formats input so it can be safely used in a query + * + * The output depends on the PHP data type of input and the database + * type being used. + * + * @param mixed $in the data to be formatted + * + * @return mixed the formatted data. The format depends on the input's + * PHP type: + *
    + *
  • + * input -> returns + *
  • + *
  • + * null -> the string NULL + *
  • + *
  • + * integer or double -> the unquoted number + *
  • + *
  • + * bool -> output depends on the driver in use + * Most drivers return integers: 1 if + * true or 0 if + * false. + * Some return strings: TRUE if + * true or FALSE if + * false. + * Finally one returns strings: T if + * true or F if + * false. Here is a list of each DBMS, + * the values returned and the suggested column type: + *
      + *
    • + * dbase -> T/F + * (Logical) + *
    • + *
    • + * fbase -> TRUE/FALSE + * (BOOLEAN) + *
    • + *
    • + * ibase -> 1/0 + * (SMALLINT) [1] + *
    • + *
    • + * ifx -> 1/0 + * (SMALLINT) [1] + *
    • + *
    • + * msql -> 1/0 + * (INTEGER) + *
    • + *
    • + * mssql -> 1/0 + * (BIT) + *
    • + *
    • + * mysql -> 1/0 + * (TINYINT(1)) + *
    • + *
    • + * mysqli -> 1/0 + * (TINYINT(1)) + *
    • + *
    • + * oci8 -> 1/0 + * (NUMBER(1)) + *
    • + *
    • + * odbc -> 1/0 + * (SMALLINT) [1] + *
    • + *
    • + * pgsql -> TRUE/FALSE + * (BOOLEAN) + *
    • + *
    • + * sqlite -> 1/0 + * (INTEGER) + *
    • + *
    • + * sybase -> 1/0 + * (TINYINT(1)) + *
    • + *
    + * [1] Accommodate the lowest common denominator because not all + * versions of have BOOLEAN. + *
  • + *
  • + * other (including strings and numeric strings) -> + * the data with single quotes escaped by preceeding + * single quotes, backslashes are escaped by preceeding + * backslashes, then the whole string is encapsulated + * between single quotes + *
  • + *
+ * + * @see DB_common::escapeSimple() + * @since Method available since Release 1.6.0 + */ + function quoteSmart($in) + { + if (is_int($in) || is_double($in)) { + return $in; + } elseif (is_bool($in)) { + return $in ? 1 : 0; + } elseif (is_null($in)) { + return 'NULL'; + } else { + return "'" . $this->escapeSimple($in) . "'"; + } + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * In SQLite, this makes things safe for inserts/updates, but may + * cause problems when performing text comparisons against columns + * containing binary data. See the + * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return str_replace("'", "''", $str); + } + + // }}} + // {{{ provides() + + /** + * Tells whether the present driver supports a given feature + * + * @param string $feature the feature you're curious about + * + * @return bool whether this driver supports $feature + */ + function provides($feature) + { + return $this->features[$feature]; + } + + // }}} + // {{{ setFetchMode() + + /** + * Sets the fetch mode that should be used by default for query results + * + * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC + * or DB_FETCHMODE_OBJECT + * @param string $object_class the class name of the object to be returned + * by the fetch methods when the + * DB_FETCHMODE_OBJECT mode is selected. + * If no class is specified by default a cast + * to object from the assoc array row will be + * done. There is also the posibility to use + * and extend the 'DB_row' class. + * + * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT + */ + function setFetchMode($fetchmode, $object_class = 'stdClass') + { + switch ($fetchmode) { + case DB_FETCHMODE_OBJECT: + $this->fetchmode_object_class = $object_class; + case DB_FETCHMODE_ORDERED: + case DB_FETCHMODE_ASSOC: + $this->fetchmode = $fetchmode; + break; + default: + return $this->raiseError('invalid fetchmode mode'); + } + } + + // }}} + // {{{ setOption() + + /** + * Sets run-time configuration options for PEAR DB + * + * Options, their data types, default values and description: + *
    + *
  • + * autofree boolean = false + *
    should results be freed automatically when there are no + * more rows? + *
  • + * result_buffering integer = 500 + *
    how many rows of the result set should be buffered? + *
    In mysql: mysql_unbuffered_query() is used instead of + * mysql_query() if this value is 0. (Release 1.7.0) + *
    In oci8: this value is passed to ocisetprefetch(). + * (Release 1.7.0) + *
  • + * debug integer = 0 + *
    debug level + *
  • + * persistent boolean = false + *
    should the connection be persistent? + *
  • + * portability integer = DB_PORTABILITY_NONE + *
    portability mode constant (see below) + *
  • + * seqname_format string = %s_seq + *
    the sprintf() format string used on sequence names. This + * format is applied to sequence names passed to + * createSequence(), nextID() and dropSequence(). + *
  • + * ssl boolean = false + *
    use ssl to connect? + *
  • + *
+ * + * ----------------------------------------- + * + * PORTABILITY MODES + * + * These modes are bitwised, so they can be combined using | + * and removed using ^. See the examples section below on how + * to do this. + * + * DB_PORTABILITY_NONE + * turn off all portability features + * + * This mode gets automatically turned on if the deprecated + * optimize option gets set to performance. + * + * + * DB_PORTABILITY_LOWERCASE + * convert names of tables and fields to lower case when using + * get*(), fetch*() and tableInfo() + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + oci8 + * + * + * DB_PORTABILITY_RTRIM + * right trim the data output by get*() fetch*() + * + * + * DB_PORTABILITY_DELETE_COUNT + * force reporting the number of rows deleted + * + * Some DBMS's don't count the number of rows deleted when performing + * simple DELETE FROM tablename queries. This portability + * mode tricks such DBMS's into telling the count by adding + * WHERE 1=1 to the end of DELETE queries. + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + fbsql + * + mysql + * + mysqli + * + sqlite + * + * + * DB_PORTABILITY_NUMROWS + * enable hack that makes numRows() work in Oracle + * + * This mode gets automatically turned on in the following databases + * if the deprecated option optimize gets set to + * portability: + * + oci8 + * + * + * DB_PORTABILITY_ERRORS + * makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to DB_ERROR_NOSUCHFIELD. + * DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD + * + * DB_PORTABILITY_NULL_TO_EMPTY + * convert null values to empty strings in data output by get*() and + * fetch*(). Needed because Oracle considers empty strings to be null, + * while most other DBMS's know the difference between empty and null. + * + * + * DB_PORTABILITY_ALL + * turn on all portability features + * + * ----------------------------------------- + * + * Example 1. Simple setOption() example + * + * $db->setOption('autofree', true); + * + * + * Example 2. Portability for lowercasing and trimming + * + * $db->setOption('portability', + * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM); + * + * + * Example 3. All portability options except trimming + * + * $db->setOption('portability', + * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM); + * + * + * @param string $option option name + * @param mixed $value value for the option + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::$options + */ + function setOption($option, $value) + { + if (isset($this->options[$option])) { + $this->options[$option] = $value; + + /* + * Backwards compatibility check for the deprecated 'optimize' + * option. Done here in case settings change after connecting. + */ + if ($option == 'optimize') { + if ($value == 'portability') { + switch ($this->phptype) { + case 'oci8': + $this->options['portability'] = + DB_PORTABILITY_LOWERCASE | + DB_PORTABILITY_NUMROWS; + break; + case 'fbsql': + case 'mysql': + case 'mysqli': + case 'sqlite': + $this->options['portability'] = + DB_PORTABILITY_DELETE_COUNT; + break; + } + } else { + $this->options['portability'] = DB_PORTABILITY_NONE; + } + } + + return DB_OK; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ getOption() + + /** + * Returns the value of an option + * + * @param string $option the option name you're curious about + * + * @return mixed the option's value + */ + function getOption($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute() + * + * Creates a query that can be run multiple times. Each time it is run, + * the placeholders, if any, will be replaced by the contents of + * execute()'s $data argument. + * + * Three types of placeholders can be used: + * + ? scalar value (i.e. strings, integers). The system + * will automatically quote and escape the data. + * + ! value is inserted 'as is' + * + & requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Example 1. + * + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res = $db->execute($sth, $data); + * + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders: + *
+     *    "UPDATE foo SET col=? WHERE col='over \& under'"
+     * 
+ * + * With some database backends, this is emulated. + * + * {@internal ibase and oci8 have their own prepare() methods.}} + * + * @param string $query the query to be prepared + * + * @return mixed DB statement resource on success. A DB_Error object + * on failure. + * + * @see DB_common::execute() + */ + function prepare($query) + { + $tokens = preg_split('/((?prepare_tokens[] = &$newtokens; + end($this->prepare_tokens); + + $k = key($this->prepare_tokens); + $this->prepare_types[$k] = $types; + $this->prepared_queries[$k] = implode(' ', $newtokens); + + return $k; + } + + // }}} + // {{{ autoPrepare() + + /** + * Automaticaly generates an insert or update query and pass it to prepare() + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return resource the query handle + * + * @uses DB_common::prepare(), DB_common::buildManipSQL() + */ + function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (DB::isError($query)) { + return $query; + } + return $this->prepare($query); + } + + // }}} + // {{{ autoExecute() + + /** + * Automaticaly generates an insert or update query and call prepare() + * and execute() with it + * + * @param string $table the table name + * @param array $fields_values the associative array where $key is a + * field name and $value its value + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @uses DB_common::autoPrepare(), DB_common::execute() + */ + function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $sth = $this->autoPrepare($table, array_keys($fields_values), $mode, + $where); + if (DB::isError($sth)) { + return $sth; + } + $ret =& $this->execute($sth, array_values($fields_values)); + $this->freePrepared($sth); + return $ret; + + } + + // }}} + // {{{ buildManipSQL() + + /** + * Produces an SQL query string for autoPrepare() + * + * Example: + *
+     * buildManipSQL('table_sql', array('field1', 'field2', 'field3'),
+     *               DB_AUTOQUERY_INSERT);
+     * 
+ * + * That returns + * + * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) + * + * + * NOTES: + * - This belongs more to a SQL Builder class, but this is a simple + * facility. + * - Be carefull! If you don't give a $where param with an UPDATE + * query, all the records of the table will be updated! + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return string the sql query for autoPrepare() + */ + function buildManipSQL($table, $table_fields, $mode, $where = false) + { + if (count($table_fields) == 0) { + return $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + $first = true; + switch ($mode) { + case DB_AUTOQUERY_INSERT: + $values = ''; + $names = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $names .= ','; + $values .= ','; + } + $names .= $value; + $values .= '?'; + } + return "INSERT INTO $table ($names) VALUES ($values)"; + case DB_AUTOQUERY_UPDATE: + $set = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $set .= ','; + } + $set .= "$value = ?"; + } + $sql = "UPDATE $table SET $set"; + if ($where) { + $sql .= " WHERE $where"; + } + return $sql; + default: + return $this->raiseError(DB_ERROR_SYNTAX); + } + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare() + * + * Example 1. + * + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res =& $db->execute($sth, $data); + * + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * {@internal ibase and oci8 have their own execute() methods.}} + * + * @see DB_common::prepare() + */ + function &execute($stmt, $data = array()) + { + $realquery = $this->executeEmulateQuery($stmt, $data); + if (DB::isError($realquery)) { + return $realquery; + } + $result = $this->simpleQuery($realquery); + + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp =& new DB_result($this, $result); + return $tmp; + } + } + + // }}} + // {{{ executeEmulateQuery() + + /** + * Emulates executing prepared statements if the DBMS not support them + * + * @param resource $stmt a DB statement resource returned from execute() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a string containing the real query run when emulating + * prepare/execute. A DB_Error object on failure. + * + * @access protected + * @see DB_common::execute() + */ + function executeEmulateQuery($stmt, $data = array()) + { + $stmt = (int)$stmt; + $data = (array)$data; + $this->last_parameters = $data; + + if (count($this->prepare_types[$stmt]) != count($data)) { + $this->last_query = $this->prepared_queries[$stmt]; + return $this->raiseError(DB_ERROR_MISMATCH); + } + + $realquery = $this->prepare_tokens[$stmt][0]; + + $i = 0; + foreach ($data as $value) { + if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) { + $realquery .= $this->quoteSmart($value); + } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($value, 'rb'); + if (!$fp) { + return $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + } + $realquery .= $this->quoteSmart(fread($fp, filesize($value))); + fclose($fp); + } else { + $realquery .= $value; + } + + $realquery .= $this->prepare_tokens[$stmt][++$i]; + } + + return $realquery; + } + + // }}} + // {{{ executeMultiple() + + /** + * Performs several execute() calls on the same statement handle + * + * $data must be an array indexed numerically + * from 0, one execute call is done for every "row" in the array. + * + * If an error occurs during execute(), executeMultiple() does not + * execute the unfinished rows, but rather returns that error. + * + * @param resource $stmt query handle from prepare() + * @param array $data numeric array containing the + * data to insert into the query + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::prepare(), DB_common::execute() + */ + function executeMultiple($stmt, $data) + { + foreach ($data as $value) { + $res =& $this->execute($stmt, $value); + if (DB::isError($res)) { + return $res; + } + } + return DB_OK; + } + + // }}} + // {{{ freePrepared() + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_common::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + $stmt = (int)$stmt; + if (isset($this->prepare_tokens[$stmt])) { + unset($this->prepare_tokens[$stmt]); + unset($this->prepare_types[$stmt]); + unset($this->prepared_queries[$stmt]); + return true; + } + return false; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * It is defined here to ensure all drivers have this method available. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(), + * DB_sqlite::modifyQuery() + */ + function modifyQuery($query) + { + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * It is defined here to assure that all implementations + * have this method defined. + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return $query; + } + + // }}} + // {{{ query() + + /** + * Sends a query to the database server + * + * The query string can be either a normal statement to be sent directly + * to the server OR if $params are passed the query can have + * placeholders and it will be passed through prepare() and execute(). + * + * @param string $query the SQL query or the statement to prepare + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @see DB_result, DB_common::prepare(), DB_common::execute() + */ + function &query($query, $params = array()) + { + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $ret =& $this->execute($sth, $params); + $this->freePrepared($sth, false); + return $ret; + } else { + $this->last_parameters = array(); + $result = $this->simpleQuery($query); + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp =& new DB_result($this, $result); + return $tmp; + } + } + } + + // }}} + // {{{ limitQuery() + + /** + * Generates and executes a LIMIT query + * + * @param string $query the query + * @param intr $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + */ + function &limitQuery($query, $from, $count, $params = array()) + { + $query = $this->modifyLimitQuery($query, $from, $count, $params); + if (DB::isError($query)){ + return $query; + } + $result =& $this->query($query, $params); + if (is_a($result, 'DB_result')) { + $result->setOption('limit_from', $from); + $result->setOption('limit_count', $count); + } + return $result; + } + + // }}} + // {{{ getOne() + + /** + * Fetches the first column of the first row from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed the returned value of the query. + * A DB_Error object on failure. + */ + function &getOne($query, $params = array()) + { + $params = (array)$params; + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row[0]; + } + + // }}} + // {{{ getRow() + + /** + * Fetches the first row of data returned from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * + * @return array the first row of results as an array. + * A DB_Error object on failure. + */ + function &getRow($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, $fetchmode); + + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row; + } + + // }}} + // {{{ getCol() + + /** + * Fetches a single column from a query result and returns it as an + * indexed array + * + * @param string $query the SQL query + * @param mixed $col which column to return (integer [column number, + * starting at 0] or string [column name]) + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return array the results as an array. A DB_Error object on failure. + * + * @see DB_common::query() + */ + function &getCol($query, $col = 0, $params = array()) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; + + if (!is_array($row = $res->fetchRow($fetchmode))) { + $ret = array(); + } else { + if (!array_key_exists($col, $row)) { + $ret =& $this->raiseError(DB_ERROR_NOSUCHFIELD); + } else { + $ret = array($row[$col]); + while (is_array($row = $res->fetchRow($fetchmode))) { + $ret[] = $row[$col]; + } + } + } + + $res->free(); + + if (DB::isError($row)) { + $ret = $row; + } + + return $ret; + } + + // }}} + // {{{ getAssoc() + + /** + * Fetches an entire query result and returns it as an + * associative array using the first column as the key + * + * If the result set contains more than two columns, the value + * will be an array of the values from column 2-n. If the result + * set contains only two columns, the returned value will be a + * scalar with the value of the second column (unless forced to an + * array with the $force_array parameter). A DB error code is + * returned on errors. If the result set contains fewer than two + * columns, a DB_ERROR_TRUNCATED error is returned. + * + * For example, if the table "mytable" contains: + * + *
+     *  ID      TEXT       DATE
+     * --------------------------------
+     *  1       'one'      944679408
+     *  2       'two'      944679408
+     *  3       'three'    944679408
+     * 
+ * + * Then the call getAssoc('SELECT id,text FROM mytable') returns: + *
+     *   array(
+     *     '1' => 'one',
+     *     '2' => 'two',
+     *     '3' => 'three',
+     *   )
+     * 
+ * + * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + *
+     *   array(
+     *     '1' => array('one', '944679408'),
+     *     '2' => array('two', '944679408'),
+     *     '3' => array('three', '944679408')
+     *   )
+     * 
+ * + * If the more than one row occurs with the same value in the + * first column, the last row overwrites all previous ones by + * default. Use the $group parameter if you don't want to + * overwrite like this. Example: + * + *
+     * getAssoc('SELECT category,id,name FROM mytable', false, null,
+     *          DB_FETCHMODE_ASSOC, true) returns:
+     *
+     *   array(
+     *     '1' => array(array('id' => '4', 'name' => 'number four'),
+     *                  array('id' => '6', 'name' => 'number six')
+     *            ),
+     *     '9' => array(array('id' => '4', 'name' => 'number four'),
+     *                  array('id' => '6', 'name' => 'number six')
+     *            )
+     *   )
+     * 
+ * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string $query the SQL query + * @param bool $force_array used only when the query returns + * exactly two columns. If true, the values + * of the returned array will be one-element + * arrays instead of scalars. + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * @param bool $group if true, the values of the returned array + * is wrapped in another array. If the same + * key value (in the first column) repeats + * itself, the values will be appended to + * this array instead of overwriting the + * existing values. + * + * @return array the associative array containing the query results. + * A DB_Error object on failure. + */ + function &getAssoc($query, $force_array = false, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT, $group = false) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + if ($fetchmode == DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + $cols = $res->numCols(); + + if ($cols < 2) { + $tmp =& $this->raiseError(DB_ERROR_TRUNCATED); + return $tmp; + } + + $results = array(); + + if ($cols > 2 || $force_array) { + // return array values + // XXX this part can be optimized + if ($fetchmode == DB_FETCHMODE_ASSOC) { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) { + reset($row); + $key = current($row); + unset($row[key($row)]); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } elseif ($fetchmode == DB_FETCHMODE_OBJECT) { + while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) { + $arr = get_object_vars($row); + $key = current($arr); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } else { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + // we shift away the first element to get + // indices running from 0 again + $key = array_shift($row); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } + if (DB::isError($row)) { + $results = $row; + } + } else { + // return scalar values + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + if ($group) { + $results[$row[0]][] = $row[1]; + } else { + $results[$row[0]] = $row[1]; + } + } + if (DB::isError($row)) { + $results = $row; + } + } + + $res->free(); + + return $results; + } + + // }}} + // {{{ getAll() + + /** + * Fetches all of the rows from a query result + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use: + * + DB_FETCHMODE_ORDERED + * + DB_FETCHMODE_ASSOC + * + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED + * + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED + * + * @return array the nested array. A DB_Error object on failure. + */ + function &getAll($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res =& $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res =& $this->query($query); + } + + if ($res === DB_OK || DB::isError($res)) { + return $res; + } + + $results = array(); + while (DB_OK === $res->fetchInto($row, $fetchmode)) { + if ($fetchmode & DB_FETCHMODE_FLIPPED) { + foreach ($row as $key => $val) { + $results[$key][] = $val; + } + } else { + $results[] = $row; + } + } + + $res->free(); + + if (DB::isError($row)) { + $tmp =& $this->raiseError($row); + return $tmp; + } + return $results; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numRows() + + /** + * Determines the number of rows in a query result + * + * @param resource $result the query result idenifier produced by PHP + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows($result) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getSequenceName() + + /** + * Generates the name used inside the database for a sequence + * + * The createSequence() docblock contains notes about storing sequence + * names. + * + * @param string $sqn the sequence's public name + * + * @return string the sequence's name in the backend + * + * @access protected + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::nextID(), DB_common::setOption() + */ + function getSequenceName($sqn) + { + return sprintf($this->getOption('seqname_format'), + preg_replace('/[^a-z0-9_.]/i', '_', $sqn)); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::getSequenceName() + */ + function nextId($seq_name, $ondemand = true) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * The name of a given sequence is determined by passing the string + * provided in the $seq_name argument through PHP's sprintf() + * function using the value from the seqname_format option as + * the sprintf()'s format argument. + * + * seqname_format is set via setOption(). + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function createSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function dropSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ raiseError() + + /** + * Communicates an error and invoke error callbacks, etc + * + * Basically a wrapper for PEAR::raiseError without the message string. + * + * @param mixed integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * @param int error mode, see PEAR_Error docs + * @param mixed if error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string extra debug information. Defaults to the last + * query and native error code. + * @param mixed native error code, integer or string depending the + * backend + * + * @return object the PEAR_Error object + * + * @see PEAR_Error + */ + function &raiseError($code = DB_ERROR, $mode = null, $options = null, + $userinfo = null, $nativecode = null) + { + // The error is yet a DB error object + if (is_object($code)) { + // because we the static PEAR::raiseError, our global + // handler should be used if it is set + if ($mode === null && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + $tmp = PEAR::raiseError($code, null, $mode, $options, + null, null, true); + return $tmp; + } + + if ($userinfo === null) { + $userinfo = $this->last_query; + } + + if ($nativecode) { + $userinfo .= ' [nativecode=' . trim($nativecode) . ']'; + } else { + $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']'; + } + + $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo, + 'DB_Error', true); + return $tmp; + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return mixed the DBMS' error code. A DB_Error object on failure. + */ + function errorNative() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones + * + * Uses the $errorcode_map property defined in each driver. + * + * @param string|int $nativecode the error code returned by the DBMS + * + * @return int the portable DB error code. Return DB_ERROR if the + * current driver doesn't have a mapping for the + * $nativecode submitted. + */ + function errorCode($nativecode) + { + if (isset($this->errorcode_map[$nativecode])) { + return $this->errorcode_map[$nativecode]; + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ errorMessage() + + /** + * Maps a DB error code to a textual message + * + * @param integer $dbcode the DB error code + * + * @return string the error message corresponding to the error code + * submitted. FALSE if the error code is unknown. + * + * @see DB::errorMessage() + */ + function errorMessage($dbcode) + { + return DB::errorMessage($this->errorcode_map[$dbcode]); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which $mode + * you select. The sample output below is based on this query: + *
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * 
+ * + *
    + *
  • + * + * null (default) + *
    +     *   [0] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   [1] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldPhone
    +     *       [type] => string
    +     *       [len] => 20
    +     *       [flags] =>
    +     *   )
    +     *   [2] => Array (
    +     *       [table] => tblBar
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   
    + * + *
  • + * + * DB_TABLEINFO_ORDER + * + *

    In addition to the information found in the default output, + * a notation of the number of columns is provided by the + * num_fields element while the order + * element provides an array with the column names as the keys and + * their location index number (corresponding to the keys in the + * the default output) as the values.

    + * + *

    If a result set has identical field names, the last one is + * used.

    + * + *
    +     *   [num_fields] => 3
    +     *   [order] => Array (
    +     *       [fldId] => 2
    +     *       [fldTrans] => 1
    +     *   )
    +     *   
    + * + *
  • + * + * DB_TABLEINFO_ORDERTABLE + * + *

    Similar to DB_TABLEINFO_ORDER but adds more + * dimensions to the array in which the table names are keys and + * the field names are sub-keys. This is helpful for queries that + * join tables which have identical field names.

    + * + *
    +     *   [num_fields] => 3
    +     *   [ordertable] => Array (
    +     *       [tblFoo] => Array (
    +     *           [fldId] => 0
    +     *           [fldPhone] => 1
    +     *       )
    +     *       [tblBar] => Array (
    +     *           [fldId] => 2
    +     *       )
    +     *   )
    +     *   
    + * + *
  • + *
+ * + * The flags element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + primary_key + * + unique_key + * + multiple_key + * + not_null + * + * Most DBMS's only provide the table and flags + * elements if $result is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has DB_PORTABILITY_LOWERCASE + * turned on, the names of tables and fields will be lowercased. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * DB_TABLEINFO_ORDERTABLE, + * DB_TABLEINFO_ORDER or + * DB_TABLEINFO_FULL (which does both). + * These are bitwise, so the first two can be + * combined using |. + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + /* + * If the DB_ class has a tableInfo() method, that one + * overrides this one. But, if the driver doesn't have one, + * this method runs and tells users about that fact. + */ + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getTables() + + /** + * Lists the tables in the current database + * + * @return array the list of tables. A DB_Error object on failure. + * + * @deprecated Method deprecated some time before Release 1.2 + */ + function getTables() + { + return $this->getListOf('tables'); + } + + // }}} + // {{{ getListOf() + + /** + * Lists internal database information + * + * @param string $type type of information being sought. + * Common items being sought are: + * tables, databases, users, views, functions + * Each DBMS's has its own capabilities. + * + * @return array an array listing the items sought. + * A DB DB_Error object on failure. + */ + function getListOf($type) + { + $sql = $this->getSpecialQuery($type); + if ($sql === null) { + $this->last_query = ''; + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } elseif (is_int($sql) || DB::isError($sql)) { + // Previous error + return $this->raiseError($sql); + } elseif (is_array($sql)) { + // Already the result + return $sql; + } + // Launch this query + return $this->getCol($sql); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } + + // }}} + // {{{ _rtrimArrayValues() + + /** + * Right-trims all strings in an array + * + * @param array $array the array to be trimmed (passed by reference) + * + * @return void + * + * @access protected + */ + function _rtrimArrayValues(&$array) + { + foreach ($array as $key => $value) { + if (is_string($value)) { + $array[$key] = rtrim($value); + } + } + } + + // }}} + // {{{ _convertNullArrayValuesToEmpty() + + /** + * Converts all null values in an array to empty strings + * + * @param array $array the array to be de-nullified (passed by reference) + * + * @return void + * + * @access protected + */ + function _convertNullArrayValuesToEmpty(&$array) + { + foreach ($array as $key => $value) { + if (is_null($value)) { + $array[$key] = ''; + } + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/trunk/docs/include/PEAR/DB/mysql.php b/trunk/docs/include/PEAR/DB/mysql.php new file mode 100644 index 0000000..dfce459 --- /dev/null +++ b/trunk/docs/include/PEAR/DB/mysql.php @@ -0,0 +1,1034 @@ + + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mysql.php,v 1.1 2005/08/01 06:21:02 dancoulter Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mysql extension + * for interacting with MySQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_mysql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mysql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mysql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.2.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1022 => DB_ERROR_ALREADY_EXISTS, + 1044 => DB_ERROR_ACCESS_VIOLATION, + 1046 => DB_ERROR_NODBSELECTED, + 1048 => DB_ERROR_CONSTRAINT, + 1049 => DB_ERROR_NOSUCHDB, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1061 => DB_ERROR_ALREADY_EXISTS, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1091 => DB_ERROR_NOT_FOUND, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1142 => DB_ERROR_ACCESS_VIOLATION, + 1146 => DB_ERROR_NOSUCHTABLE, + 1216 => DB_ERROR_CONSTRAINT, + 1217 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_mysql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's mysql driver supports the following extra DSN options: + * + new_link If set to true, causes subsequent calls to connect() + * to return a new connection link instead of the + * existing one. WARNING: this is not portable to + * other DBMS's. Available since PEAR DB 1.7.0. + * + client_flags Any combination of MYSQL_CLIENT_* constants. + * Only used if PHP is at version 4.3.0 or greater. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mysql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array(); + if ($dsn['protocol'] && $dsn['protocol'] == 'unix') { + $params[0] = ':' . $dsn['socket']; + } else { + $params[0] = $dsn['hostspec'] ? $dsn['hostspec'] + : 'localhost'; + if ($dsn['port']) { + $params[0] .= ':' . $dsn['port']; + } + } + $params[] = $dsn['username'] ? $dsn['username'] : null; + $params[] = $dsn['password'] ? $dsn['password'] : null; + + if (!$persistent) { + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($dsn['client_flags']) + ? $dsn['client_flags'] : null; + } + + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + ini_set('track_errors', $ini); + } + + if (!$this->connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if ($dsn['database']) { + if (!@mysql_select_db($dsn['database'], $this->connection)) { + return $this->mysqlRaiseError(); + } + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mysql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * Generally uses mysql_query(). If you want to use + * mysql_unbuffered_query() set the "result_buffering" option to 0 using + * setOptions(). This option was added in Release 1.7.0. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = DB::isManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection); + $result = @mysql_query('BEGIN', $this->connection); + if (!$result) { + return $this->mysqlRaiseError(); + } + } + $this->transaction_opcount++; + } + if (!$this->options['result_buffering']) { + $result = @mysql_unbuffered_query($query, $this->connection); + } else { + $result = @mysql_query($query, $this->connection); + } + if (!$result) { + return $this->mysqlRaiseError(); + } + if (is_resource($result)) { + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mysql result pointer to the next available result + * + * This method has not been implemented yet. + * + * @param a valid sql result resource + * + * @return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mysql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mysql_fetch_array($result, MYSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mysql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return @mysql_free_result($result); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mysql_num_fields($result); + if (!$cols) { + return $this->mysqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mysql_num_rows($result); + if ($rows === null) { + return $this->mysqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('COMMIT', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('ROLLBACK', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (DB::isManip($this->last_query)) { + return @mysql_affected_rows($this->connection); + } else { + return 0; + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mysql::createSequence(), DB_mysql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("UPDATE ${seqname} ". + 'SET id=LAST_INSERT_ID(id+1)'); + $this->popErrorHandling(); + if ($result === DB_OK) { + // COMMON CASE + $id = @mysql_insert_id($this->connection); + if ($id != 0) { + return $id; + } + // EMPTY SEQ TABLE + // Sequence table must be empty for some reason, so fill + // it and return 1 and obtain a user-level lock + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + if ($result == 0) { + // Failed to get the lock + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + // add the default value + $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + + // Release the lock + $result = $this->getOne('SELECT RELEASE_LOCK(' + . "'${seqname}_lock')"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + // We know what the result will be, so no need to try again + return 1; + + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + // ONDEMAND TABLE CREATION + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + $repeat = 1; + } + + } elseif (DB::isError($result) && + $result->getCode() == DB_ERROR_ALREADY_EXISTS) + { + // BACKWARDS COMPAT + // see _BCsequence() comment + $result = $this->_BCsequence($seqname); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $repeat = 1; + } + } while ($repeat); + + return $this->raiseError($result); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); + if (DB::isError($res)) { + return $res; + } + // insert yields value 1, nextId call will generate ID 2 + $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($res)) { + return $res; + } + // so reset to zero + return $this->query("UPDATE ${seqname} SET id = 0"); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ _BCsequence() + + /** + * Backwards compatibility with old sequence emulation implementation + * (clean up the dupes) + * + * @param string $seqname the sequence name to clean up + * + * @return bool true on success. A DB_Error object on failure. + * + * @access private + */ + function _BCsequence($seqname) + { + // Obtain a user-level lock... this will release any previous + // application locks, but unlike LOCK TABLES, it does not abort + // the current transaction and is much less frequently used. + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $result; + } + if ($result == 0) { + // Failed to get the lock, can't do the conversion, bail + // with a DB_ERROR_NOT_LOCKED error + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); + if (DB::isError($highest_id)) { + return $highest_id; + } + // This should kill all rows except the highest + // We should probably do something if $highest_id isn't + // numeric, but I'm at a loss as how to handle that... + $result = $this->query('DELETE FROM ' . $seqname + . " WHERE id <> $highest_id"); + if (DB::isError($result)) { + return $result; + } + + // If another thread has been waiting for this lock, + // it will go thru the above procedure, but will have no + // real effect + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); + if (DB::isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * MySQL can't handle the backtick character (`) in + * table or column names. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '`' . $str . '`'; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + if (function_exists('mysql_real_escape_string')) { + return @mysql_real_escape_string($str, $this->connection); + } else { + return @mysql_escape_string($str); + } + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + function modifyQuery($query) + { + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query)) { + return $query . " LIMIT $count"; + } else { + return $query . " LIMIT $from, $count"; + } + } + + // }}} + // {{{ mysqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysql::errorNative(), DB_common::errorCode() + */ + function mysqlRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysql_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @mysql_errno($this->connection) . ' ** ' . + @mysql_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @mysql_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @mysql_list_fields($this->dsn['database'], + $result, $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($id, $i)), + 'name' => $case_func(@mysql_field_name($id, $i)), + 'type' => @mysql_field_type($id, $i), + 'len' => @mysql_field_len($id, $i), + 'flags' => @mysql_field_flags($id, $i), + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mysql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SHOW TABLES'; + case 'users': + return 'SELECT DISTINCT User FROM mysql.user'; + case 'databases': + return 'SHOW DATABASES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/trunk/docs/include/PEAR/DB/pgsql.php b/trunk/docs/include/PEAR/DB/pgsql.php new file mode 100644 index 0000000..0fcdcf2 --- /dev/null +++ b/trunk/docs/include/PEAR/DB/pgsql.php @@ -0,0 +1,1097 @@ + + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: pgsql.php,v 1.1 2005/08/01 06:21:02 dancoulter Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's pgsql extension + * for interacting with PostgreSQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Rui Hirokawa + * @author Stig Bakken + * @author Daniel Convissor + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_pgsql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'pgsql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'pgsql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.3.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The number of rows affected by a data manipulation query + * @var integer + */ + var $affected = 0; + + /** + * The current row being looked at in fetchInto() + * @var array + * @access private + */ + var $row = array(); + + /** + * The number of rows in a given result set + * @var array + * @access private + */ + var $_num_rows = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls $this->DB_common() + * + * @return void + */ + function DB_pgsql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's pgsql driver supports the following extra DSN options: + * + connect_timeout How many seconds to wait for a connection to + * be established. Available since PEAR DB 1.7.0. + * + new_link If set to true, causes subsequent calls to + * connect() to return a new connection link + * instead of the existing one. WARNING: this is + * not portable to other DBMS's. Available only + * if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0. + * + options Command line options to be sent to the server. + * Available since PEAR DB 1.6.4. + * + service Specifies a service name in pg_service.conf that + * holds additional connection parameters. + * Available since PEAR DB 1.7.0. + * + sslmode How should SSL be used when connecting? Values: + * disable, allow, prefer or require. + * Available since PEAR DB 1.7.0. + * + tty This was used to specify where to send server + * debug output. Available since PEAR DB 1.6.4. + * + * Example of connecting to a new link via a socket: + * + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true'; + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db =& DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('pgsql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp'; + + $params = array(''); + if ($protocol == 'tcp') { + if ($dsn['hostspec']) { + $params[0] .= 'host=' . $dsn['hostspec']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } elseif ($protocol == 'unix') { + // Allow for pg socket in non-standard locations. + if ($dsn['socket']) { + $params[0] .= 'host=' . $dsn['socket']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } + if ($dsn['database']) { + $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\''; + } + if ($dsn['username']) { + $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\''; + } + if ($dsn['password']) { + $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\''; + } + if (!empty($dsn['options'])) { + $params[0] .= ' options=' . $dsn['options']; + } + if (!empty($dsn['tty'])) { + $params[0] .= ' tty=' . $dsn['tty']; + } + if (!empty($dsn['connect_timeout'])) { + $params[0] .= ' connect_timeout=' . $dsn['connect_timeout']; + } + if (!empty($dsn['sslmode'])) { + $params[0] .= ' sslmode=' . $dsn['sslmode']; + } + if (!empty($dsn['service'])) { + $params[0] .= ' service=' . $dsn['service']; + } + + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = PGSQL_CONNECT_FORCE_NEW; + } + } + + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + ini_set('track_errors', $ini); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @pg_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = DB::isManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @pg_exec($this->connection, 'begin;'); + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @pg_exec($this->connection, $query); + if (!$result) { + return $this->pgsqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($ismanip) { + $this->affected = @pg_affected_rows($result); + return DB_OK; + } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|SHOW)\s/si', $query)) { + /* PostgreSQL commands: + ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY, + CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH, + GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET, + REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW, + UNLISTEN, UPDATE, VACUUM + */ + $this->row[(int)$result] = 0; // reset the row counter. + $numrows = $this->numRows($result); + if (is_object($numrows)) { + return $numrows; + } + $this->_num_rows[(int)$result] = $numrows; + $this->affected = 0; + return $result; + } else { + $this->affected = 0; + return DB_OK; + } + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal pgsql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + $result_int = (int)$result; + $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int]; + if ($rownum >= $this->_num_rows[$result_int]) { + return null; + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @pg_fetch_row($result, $rownum); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + $this->row[$result_int] = ++$rownum; + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + if (is_resource($result)) { + unset($this->row[(int)$result]); + unset($this->_num_rows[(int)$result]); + $this->affected = 0; + return @pg_freeresult($result); + } + return false; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + * @internal + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ quoteSmart() + + /** + * Formats input so it can be safely used in a query + * + * @param mixed $in the data to be formatted + * + * @return mixed the formatted data. The format depends on the input's + * PHP type: + * + null = the string NULL + * + boolean = string TRUE or FALSE + * + integer or double = the unquoted number + * + other (including strings and numeric strings) = + * the data escaped according to MySQL's settings + * then encapsulated between single quotes + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function quoteSmart($in) + { + if (is_int($in) || is_double($in)) { + return $in; + } elseif (is_bool($in)) { + return $in ? 'TRUE' : 'FALSE'; + } elseif (is_null($in)) { + return 'NULL'; + } else { + return "'" . $this->escapeSimple($in) . "'"; + } + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * {@internal PostgreSQL treats a backslash as an escape character, + * so they are escaped as well. + * + * Not using pg_escape_string() yet because it requires PostgreSQL + * to be at version 7.2 or greater.}} + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return str_replace("'", "''", str_replace('\\', '\\\\', $str)); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @pg_numfields($result); + if (!$cols) { + return $this->pgsqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @pg_numrows($result); + if ($rows === null) { + return $this->pgsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + // (disabled) hack to shut up error messages from libpq.a + //@fclose(@fopen("php://stderr", "w")); + $result = @pg_exec($this->connection, 'end;'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + $result = @pg_exec($this->connection, 'abort;'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->affected; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_pgsql::createSequence(), DB_pgsql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = false; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result =& $this->query("SELECT NEXTVAL('${seqname}')"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = true; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $result = $this->query("CREATE SEQUENCE ${seqname}"); + return $result; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return "$query LIMIT $count OFFSET $from"; + } + + // }}} + // {{{ pgsqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_pgsql::errorNative(), DB_pgsql::errorCode() + */ + function pgsqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal Error messages are used instead of error codes + * in order to support older versions of PostgreSQL.}} + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @pg_errormessage($this->connection); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + function errorCode($errormsg) + { + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/(relation|sequence|table).*does not exist|class .* not found/i' + => DB_ERROR_NOSUCHTABLE, + '/index .* does not exist/' + => DB_ERROR_NOT_FOUND, + '/column .* does not exist/i' + => DB_ERROR_NOSUCHFIELD, + '/relation .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/(divide|division) by zero$/i' + => DB_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /i' + => DB_ERROR_INVALID_NUMBER, + '/invalid input syntax for( type)? (integer|numeric)/i' + => DB_ERROR_INVALID_NUMBER, + '/value .* is out of range for type \w*int/i' + => DB_ERROR_INVALID_NUMBER, + '/integer out of range/i' + => DB_ERROR_INVALID_NUMBER, + '/value too long for type character/i' + => DB_ERROR_INVALID, + '/attribute .* not found|relation .* does not have attribute/i' + => DB_ERROR_NOSUCHFIELD, + '/column .* specified in USING clause does not exist in (left|right) table/i' + => DB_ERROR_NOSUCHFIELD, + '/parser: parse error at or near/i' + => DB_ERROR_SYNTAX, + '/syntax error at/' + => DB_ERROR_SYNTAX, + '/column reference .* is ambiguous/i' + => DB_ERROR_SYNTAX, + '/permission denied/' + => DB_ERROR_ACCESS_VIOLATION, + '/violates not-null constraint/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/violates [\w ]+ constraint/' + => DB_ERROR_CONSTRAINT, + '/referential integrity violation/' + => DB_ERROR_CONSTRAINT, + '/more expressions than target columns/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @pg_numfields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@pg_fieldname($id, $i)), + 'type' => @pg_fieldtype($id, $i), + 'len' => @pg_fieldsize($id, $i), + 'flags' => $got_string + ? $this->_pgFieldFlags($id, $i, $result) + : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @pg_freeresult($id); + } + return $res; + } + + // }}} + // {{{ _pgFieldFlags() + + /** + * Get a column's flags + * + * Supports "not_null", "default_value", "primary_key", "unique_key" + * and "multiple_key". The default value is passed through + * rawurlencode() in case there are spaces in it. + * + * @param int $resource the PostgreSQL result identifier + * @param int $num_field the field number + * + * @return string the flags + * + * @access private + */ + function _pgFieldFlags($resource, $num_field, $table_name) + { + $field_name = @pg_fieldname($resource, $num_field); + + $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef + FROM pg_attribute f, pg_class tab, pg_type typ + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attname = '$field_name' + AND tab.relname = '$table_name'"); + if (@pg_numrows($result) > 0) { + $row = @pg_fetch_row($result, 0); + $flags = ($row[0] == 't') ? 'not_null ' : ''; + + if ($row[1] == 't') { + $result = @pg_exec($this->connection, "SELECT a.adsrc + FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a + WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid + AND f.attrelid = a.adrelid AND f.attname = '$field_name' + AND tab.relname = '$table_name' AND f.attnum = a.adnum"); + $row = @pg_fetch_row($result, 0); + $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]); + $flags .= 'default_' . rawurlencode($num) . ' '; + } + } else { + $flags = ''; + } + $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey + FROM pg_attribute f, pg_class tab, pg_type typ, pg_index i + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attrelid = i.indrelid + AND f.attname = '$field_name' + AND tab.relname = '$table_name'"); + $count = @pg_numrows($result); + + for ($i = 0; $i < $count ; $i++) { + $row = @pg_fetch_row($result, $i); + $keys = explode(' ', $row[2]); + + if (in_array($num_field + 1, $keys)) { + $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : ''; + $flags .= ($row[1] == 't') ? 'primary_key ' : ''; + if (count($keys) > 1) + $flags .= 'multiple_key '; + } + } + + return trim($flags); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT c.relname AS "Name"' + . ' FROM pg_class c, pg_user u' + . ' WHERE c.relowner = u.usesysid' + . " AND c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . " AND c.relname !~ '^(pg_|sql_)'" + . ' UNION' + . ' SELECT c.relname AS "Name"' + . ' FROM pg_class c' + . " WHERE c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_user' + . ' WHERE usesysid = c.relowner)' + . " AND c.relname !~ '^pg_'"; + case 'schema.tables': + return "SELECT schemaname || '.' || tablename" + . ' AS "Name"' + . ' FROM pg_catalog.pg_tables' + . ' WHERE schemaname NOT IN' + . " ('pg_catalog', 'information_schema', 'pg_toast')"; + case 'views': + // Table cols: viewname | viewowner | definition + return 'SELECT viewname from pg_views WHERE schemaname' + . " NOT IN ('information_schema', 'pg_catalog')"; + case 'users': + // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil + return 'SELECT usename FROM pg_user'; + case 'databases': + return 'SELECT datname FROM pg_database'; + case 'functions': + case 'procedures': + return 'SELECT proname FROM pg_proc WHERE proowner <> 1'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/trunk/docs/include/PEAR/DB/storage.php b/trunk/docs/include/PEAR/DB/storage.php new file mode 100644 index 0000000..9e3a88f --- /dev/null +++ b/trunk/docs/include/PEAR/DB/storage.php @@ -0,0 +1,504 @@ + + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: storage.php,v 1.1 2005/08/01 06:21:02 dancoulter Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB class so it can be extended from + */ +require_once 'DB.php'; + +/** + * Provides an object interface to a table row + * + * It lets you add, delete and change rows using objects rather than SQL + * statements. + * + * @category Database + * @package DB + * @author Stig Bakken + * @copyright 1997-2005 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: @package_version@ + * @link http://pear.php.net/package/DB + */ +class DB_storage extends PEAR +{ + // {{{ properties + + /** the name of the table (or view, if the backend database supports + updates in views) we hold data from */ + var $_table = null; + + /** which column(s) in the table contains primary keys, can be a + string for single-column primary keys, or an array of strings + for multiple-column primary keys */ + var $_keycolumn = null; + + /** DB connection handle used for all transactions */ + var $_dbh = null; + + /** an assoc with the names of database fields stored as properties + in this object */ + var $_properties = array(); + + /** an assoc with the names of the properties in this object that + have been changed since they were fetched from the database */ + var $_changes = array(); + + /** flag that decides if data in this object can be changed. + objects that don't have their table's key column in their + property lists will be flagged as read-only. */ + var $_readonly = false; + + /** function or method that implements a validator for fields that + are set, this validator function returns true if the field is + valid, false if not */ + var $_validator = null; + + // }}} + // {{{ constructor + + /** + * Constructor + * + * @param $table string the name of the database table + * + * @param $keycolumn mixed string with name of key column, or array of + * strings if the table has a primary key of more than one column + * + * @param $dbh object database connection object + * + * @param $validator mixed function or method used to validate + * each new value, called with three parameters: the name of the + * field/column that is changing, a reference to the new value and + * a reference to this object + * + */ + function DB_storage($table, $keycolumn, &$dbh, $validator = null) + { + $this->PEAR('DB_Error'); + $this->_table = $table; + $this->_keycolumn = $keycolumn; + $this->_dbh = $dbh; + $this->_readonly = false; + $this->_validator = $validator; + } + + // }}} + // {{{ _makeWhere() + + /** + * Utility method to build a "WHERE" clause to locate ourselves in + * the table. + * + * XXX future improvement: use rowids? + * + * @access private + */ + function _makeWhere($keyval = null) + { + if (is_array($this->_keycolumn)) { + if ($keyval === null) { + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + $keyval[] = $this->{$this->_keycolumn[$i]}; + } + } + $whereclause = ''; + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + if ($i > 0) { + $whereclause .= ' AND '; + } + $whereclause .= $this->_keycolumn[$i]; + if (is_null($keyval[$i])) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); + } + } + } else { + if ($keyval === null) { + $keyval = @$this->{$this->_keycolumn}; + } + $whereclause = $this->_keycolumn; + if (is_null($keyval)) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval); + } + } + return $whereclause; + } + + // }}} + // {{{ setup() + + /** + * Method used to initialize a DB_storage object from the + * configured table. + * + * @param $keyval mixed the key[s] of the row to fetch (string or array) + * + * @return int DB_OK on success, a DB error if not + */ + function setup($keyval) + { + $whereclause = $this->_makeWhere($keyval); + $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause; + $sth = $this->_dbh->query($query); + if (DB::isError($sth)) { + return $sth; + } + $row = $sth->fetchRow(DB_FETCHMODE_ASSOC); + if (DB::isError($row)) { + return $row; + } + if (!$row) { + return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null, + $query, null, true); + } + foreach ($row as $key => $value) { + $this->_properties[$key] = true; + $this->$key = $value; + } + return DB_OK; + } + + // }}} + // {{{ insert() + + /** + * Create a new (empty) row in the configured table for this + * object. + */ + function insert($newpk) + { + if (is_array($this->_keycolumn)) { + $primarykey = $this->_keycolumn; + } else { + $primarykey = array($this->_keycolumn); + } + settype($newpk, "array"); + for ($i = 0; $i < sizeof($primarykey); $i++) { + $pkvals[] = $this->_dbh->quote($newpk[$i]); + } + + $sth = $this->_dbh->query("INSERT INTO $this->_table (" . + implode(",", $primarykey) . ") VALUES(" . + implode(",", $pkvals) . ")"); + if (DB::isError($sth)) { + return $sth; + } + if (sizeof($newpk) == 1) { + $newpk = $newpk[0]; + } + $this->setup($newpk); + } + + // }}} + // {{{ toString() + + /** + * Output a simple description of this DB_storage object. + * @return string object description + */ + function toString() + { + $info = strtolower(get_class($this)); + $info .= " (table="; + $info .= $this->_table; + $info .= ", keycolumn="; + if (is_array($this->_keycolumn)) { + $info .= "(" . implode(",", $this->_keycolumn) . ")"; + } else { + $info .= $this->_keycolumn; + } + $info .= ", dbh="; + if (is_object($this->_dbh)) { + $info .= $this->_dbh->toString(); + } else { + $info .= "null"; + } + $info .= ")"; + if (sizeof($this->_properties)) { + $info .= " [loaded, key="; + $keyname = $this->_keycolumn; + if (is_array($keyname)) { + $info .= "("; + for ($i = 0; $i < sizeof($keyname); $i++) { + if ($i > 0) { + $info .= ","; + } + $info .= $this->$keyname[$i]; + } + $info .= ")"; + } else { + $info .= $this->$keyname; + } + $info .= "]"; + } + if (sizeof($this->_changes)) { + $info .= " [modified]"; + } + return $info; + } + + // }}} + // {{{ dump() + + /** + * Dump the contents of this object to "standard output". + */ + function dump() + { + foreach ($this->_properties as $prop => $foo) { + print "$prop = "; + print htmlentities($this->$prop); + print "
\n"; + } + } + + // }}} + // {{{ &create() + + /** + * Static method used to create new DB storage objects. + * @param $data assoc. array where the keys are the names + * of properties/columns + * @return object a new instance of DB_storage or a subclass of it + */ + function &create($table, &$data) + { + $classname = strtolower(get_class($this)); + $obj =& new $classname($table); + foreach ($data as $name => $value) { + $obj->_properties[$name] = true; + $obj->$name = &$value; + } + return $obj; + } + + // }}} + // {{{ loadFromQuery() + + /** + * Loads data into this object from the given query. If this + * object already contains table data, changes will be saved and + * the object re-initialized first. + * + * @param $query SQL query + * + * @param $params parameter list in case you want to use + * prepare/execute mode + * + * @return int DB_OK on success, DB_WARNING_READ_ONLY if the + * returned object is read-only (because the object's specified + * key column was not found among the columns returned by $query), + * or another DB error code in case of errors. + */ +// XXX commented out for now +/* + function loadFromQuery($query, $params = null) + { + if (sizeof($this->_properties)) { + if (sizeof($this->_changes)) { + $this->store(); + $this->_changes = array(); + } + $this->_properties = array(); + } + $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params); + if (DB::isError($rowdata)) { + return $rowdata; + } + reset($rowdata); + $found_keycolumn = false; + while (list($key, $value) = each($rowdata)) { + if ($key == $this->_keycolumn) { + $found_keycolumn = true; + } + $this->_properties[$key] = true; + $this->$key = &$value; + unset($value); // have to unset, or all properties will + // refer to the same value + } + if (!$found_keycolumn) { + $this->_readonly = true; + return DB_WARNING_READ_ONLY; + } + return DB_OK; + } + */ + + // }}} + // {{{ set() + + /** + * Modify an attriute value. + */ + function set($property, $newvalue) + { + // only change if $property is known and object is not + // read-only + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + if (@isset($this->_properties[$property])) { + if (empty($this->_validator)) { + $valid = true; + } else { + $valid = @call_user_func($this->_validator, + $this->_table, + $property, + $newvalue, + $this->$property, + $this); + } + if ($valid) { + $this->$property = $newvalue; + if (empty($this->_changes[$property])) { + $this->_changes[$property] = 0; + } else { + $this->_changes[$property]++; + } + } else { + return $this->raiseError(null, DB_ERROR_INVALID, null, + null, "invalid field: $property", + null, true); + } + return true; + } + return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null, + null, "unknown field: $property", + null, true); + } + + // }}} + // {{{ &get() + + /** + * Fetch an attribute value. + * + * @param string attribute name + * + * @return attribute contents, or null if the attribute name is + * unknown + */ + function &get($property) + { + // only return if $property is known + if (isset($this->_properties[$property])) { + return $this->$property; + } + $tmp = null; + return $tmp; + } + + // }}} + // {{{ _DB_storage() + + /** + * Destructor, calls DB_storage::store() if there are changes + * that are to be kept. + */ + function _DB_storage() + { + if (sizeof($this->_changes)) { + $this->store(); + } + $this->_properties = array(); + $this->_changes = array(); + $this->_table = null; + } + + // }}} + // {{{ store() + + /** + * Stores changes to this object in the database. + * + * @return DB_OK or a DB error + */ + function store() + { + foreach ($this->_changes as $name => $foo) { + $params[] = &$this->$name; + $vars[] = $name . ' = ?'; + } + if ($vars) { + $query = 'UPDATE ' . $this->_table . ' SET ' . + implode(', ', $vars) . ' WHERE ' . + $this->_makeWhere(); + $stmt = $this->_dbh->prepare($query); + $res = $this->_dbh->execute($stmt, $params); + if (DB::isError($res)) { + return $res; + } + $this->_changes = array(); + } + return DB_OK; + } + + // }}} + // {{{ remove() + + /** + * Remove the row represented by this object from the database. + * + * @return mixed DB_OK or a DB error + */ + function remove() + { + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + $query = 'DELETE FROM ' . $this->_table .' WHERE '. + $this->_makeWhere(); + $res = $this->_dbh->query($query); + if (DB::isError($res)) { + return $res; + } + foreach ($this->_properties as $prop => $foo) { + unset($this->$prop); + } + $this->_properties = array(); + $this->_changes = array(); + return DB_OK; + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/trunk/docs/include/PEAR/HTTP/Request.php b/trunk/docs/include/PEAR/HTTP/Request.php new file mode 100644 index 0000000..99ab52b --- /dev/null +++ b/trunk/docs/include/PEAR/HTTP/Request.php @@ -0,0 +1,1395 @@ + | +// +-----------------------------------------------------------------------+ +// +// $Id: Request.php,v 1.51 2006/10/25 16:23:31 avb Exp $ +// +// HTTP_Request Class +// +// Simple example, (Fetches yahoo.com and displays it): +// +// $a = &new HTTP_Request('http://www.yahoo.com/'); +// $a->sendRequest(); +// echo $a->getResponseBody(); +// + +require_once 'PEAR.php'; +require_once 'Net/Socket.php'; +require_once 'Net/URL.php'; + +define('HTTP_REQUEST_METHOD_GET', 'GET', true); +define('HTTP_REQUEST_METHOD_HEAD', 'HEAD', true); +define('HTTP_REQUEST_METHOD_POST', 'POST', true); +define('HTTP_REQUEST_METHOD_PUT', 'PUT', true); +define('HTTP_REQUEST_METHOD_DELETE', 'DELETE', true); +define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true); +define('HTTP_REQUEST_METHOD_TRACE', 'TRACE', true); + +define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true); +define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true); + +class HTTP_Request { + + /** + * Instance of Net_URL + * @var object Net_URL + */ + var $_url; + + /** + * Type of request + * @var string + */ + var $_method; + + /** + * HTTP Version + * @var string + */ + var $_http; + + /** + * Request headers + * @var array + */ + var $_requestHeaders; + + /** + * Basic Auth Username + * @var string + */ + var $_user; + + /** + * Basic Auth Password + * @var string + */ + var $_pass; + + /** + * Socket object + * @var object Net_Socket + */ + var $_sock; + + /** + * Proxy server + * @var string + */ + var $_proxy_host; + + /** + * Proxy port + * @var integer + */ + var $_proxy_port; + + /** + * Proxy username + * @var string + */ + var $_proxy_user; + + /** + * Proxy password + * @var string + */ + var $_proxy_pass; + + /** + * Post data + * @var array + */ + var $_postData; + + /** + * Request body + * @var string + */ + var $_body; + + /** + * A list of methods that MUST NOT have a request body, per RFC 2616 + * @var array + */ + var $_bodyDisallowed = array('TRACE'); + + /** + * Files to post + * @var array + */ + var $_postFiles = array(); + + /** + * Connection timeout. + * @var float + */ + var $_timeout; + + /** + * HTTP_Response object + * @var object HTTP_Response + */ + var $_response; + + /** + * Whether to allow redirects + * @var boolean + */ + var $_allowRedirects; + + /** + * Maximum redirects allowed + * @var integer + */ + var $_maxRedirects; + + /** + * Current number of redirects + * @var integer + */ + var $_redirects; + + /** + * Whether to append brackets [] to array variables + * @var bool + */ + var $_useBrackets = true; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Whether to save response body in response object property + * @var bool + */ + var $_saveBody = true; + + /** + * Timeout for reading from socket (array(seconds, microseconds)) + * @var array + */ + var $_readTimeout = null; + + /** + * Options to pass to Net_Socket::connect. See stream_context_create + * @var array + */ + var $_socketOptions = null; + + /** + * Constructor + * + * Sets up the object + * @param string The url to fetch/access + * @param array Associative array of parameters which can have the following keys: + *
    + *
  • method - Method to use, GET, POST etc (string)
  • + *
  • http - HTTP Version to use, 1.0 or 1.1 (string)
  • + *
  • user - Basic Auth username (string)
  • + *
  • pass - Basic Auth password (string)
  • + *
  • proxy_host - Proxy server host (string)
  • + *
  • proxy_port - Proxy server port (integer)
  • + *
  • proxy_user - Proxy auth username (string)
  • + *
  • proxy_pass - Proxy auth password (string)
  • + *
  • timeout - Connection timeout in seconds (float)
  • + *
  • allowRedirects - Whether to follow redirects or not (bool)
  • + *
  • maxRedirects - Max number of redirects to follow (integer)
  • + *
  • useBrackets - Whether to append [] to array variable names (bool)
  • + *
  • saveBody - Whether to save response body in response object property (bool)
  • + *
  • readTimeout - Timeout for reading / writing data over the socket (array (seconds, microseconds))
  • + *
  • socketOptions - Options to pass to Net_Socket object (array)
  • + *
+ * @access public + */ + function HTTP_Request($url = '', $params = array()) + { + $this->_method = HTTP_REQUEST_METHOD_GET; + $this->_http = HTTP_REQUEST_HTTP_VER_1_1; + $this->_requestHeaders = array(); + $this->_postData = array(); + $this->_body = null; + + $this->_user = null; + $this->_pass = null; + + $this->_proxy_host = null; + $this->_proxy_port = null; + $this->_proxy_user = null; + $this->_proxy_pass = null; + + $this->_allowRedirects = false; + $this->_maxRedirects = 3; + $this->_redirects = 0; + + $this->_timeout = null; + $this->_response = null; + + foreach ($params as $key => $value) { + $this->{'_' . $key} = $value; + } + + if (!empty($url)) { + $this->setURL($url); + } + + // Default useragent + $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )'); + + // We don't do keep-alives by default + $this->addHeader('Connection', 'close'); + + // Basic authentication + if (!empty($this->_user)) { + $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass)); + } + + // Proxy authentication (see bug #5913) + if (!empty($this->_proxy_user)) { + $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass)); + } + + // Use gzip encoding if possible + // Avoid gzip encoding if using multibyte functions (see #1781) + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib') && + 0 == (2 & ini_get('mbstring.func_overload'))) { + + $this->addHeader('Accept-Encoding', 'gzip'); + } + } + + /** + * Generates a Host header for HTTP/1.1 requests + * + * @access private + * @return string + */ + function _generateHostHeader() + { + if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) { + $host = $this->_url->host . ':' . $this->_url->port; + + } else { + $host = $this->_url->host; + } + + return $host; + } + + /** + * Resets the object to its initial state (DEPRECATED). + * Takes the same parameters as the constructor. + * + * @param string $url The url to be requested + * @param array $params Associative array of parameters + * (see constructor for details) + * @access public + * @deprecated deprecated since 1.2, call the constructor if this is necessary + */ + function reset($url, $params = array()) + { + $this->HTTP_Request($url, $params); + } + + /** + * Sets the URL to be requested + * + * @param string The url to be requested + * @access public + */ + function setURL($url) + { + $this->_url = &new Net_URL($url, $this->_useBrackets); + + if (!empty($this->_url->user) || !empty($this->_url->pass)) { + $this->setBasicAuth($this->_url->user, $this->_url->pass); + } + + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) { + $this->addHeader('Host', $this->_generateHostHeader()); + } + + // set '/' instead of empty path rather than check later (see bug #8662) + if (empty($this->_url->path)) { + $this->_url->path = '/'; + } + } + + /** + * Returns the current request URL + * + * @return string Current request URL + * @access public + */ + function getUrl($url) + { + return empty($this->_url)? '': $this->_url->getUrl(); + } + + /** + * Sets a proxy to be used + * + * @param string Proxy host + * @param int Proxy port + * @param string Proxy username + * @param string Proxy password + * @access public + */ + function setProxy($host, $port = 8080, $user = null, $pass = null) + { + $this->_proxy_host = $host; + $this->_proxy_port = $port; + $this->_proxy_user = $user; + $this->_proxy_pass = $pass; + + if (!empty($user)) { + $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + } + + /** + * Sets basic authentication parameters + * + * @param string Username + * @param string Password + */ + function setBasicAuth($user, $pass) + { + $this->_user = $user; + $this->_pass = $pass; + + $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + + /** + * Sets the method to be used, GET, POST etc. + * + * @param string Method to use. Use the defined constants for this + * @access public + */ + function setMethod($method) + { + $this->_method = $method; + } + + /** + * Sets the HTTP version to use, 1.0 or 1.1 + * + * @param string Version to use. Use the defined constants for this + * @access public + */ + function setHttpVer($http) + { + $this->_http = $http; + } + + /** + * Adds a request header + * + * @param string Header name + * @param string Header value + * @access public + */ + function addHeader($name, $value) + { + $this->_requestHeaders[strtolower($name)] = $value; + } + + /** + * Removes a request header + * + * @param string Header name to remove + * @access public + */ + function removeHeader($name) + { + if (isset($this->_requestHeaders[strtolower($name)])) { + unset($this->_requestHeaders[strtolower($name)]); + } + } + + /** + * Adds a querystring parameter + * + * @param string Querystring parameter name + * @param string Querystring parameter value + * @param bool Whether the value is already urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + $this->_url->addQueryString($name, $value, $preencoded); + } + + /** + * Sets the querystring to literally what you supply + * + * @param string The querystring data. Should be of the format foo=bar&x=y etc + * @param bool Whether data is already urlencoded or not, default = already encoded + * @access public + */ + function addRawQueryString($querystring, $preencoded = true) + { + $this->_url->addRawQueryString($querystring, $preencoded); + } + + /** + * Adds postdata items + * + * @param string Post data name + * @param string Post data value + * @param bool Whether data is already urlencoded or not, default = not + * @access public + */ + function addPostData($name, $value, $preencoded = false) + { + if ($preencoded) { + $this->_postData[$name] = $value; + } else { + $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value); + } + } + + /** + * Recursively applies the callback function to the value + * + * @param mixed Callback function + * @param mixed Value to process + * @access private + * @return mixed Processed value + */ + function _arrayMapRecursive($callback, $value) + { + if (!is_array($value)) { + return call_user_func($callback, $value); + } else { + $map = array(); + foreach ($value as $k => $v) { + $map[$k] = $this->_arrayMapRecursive($callback, $v); + } + return $map; + } + } + + /** + * Adds a file to upload + * + * This also changes content-type to 'multipart/form-data' for proper upload + * + * @access public + * @param string name of file-upload field + * @param mixed file name(s) + * @param mixed content-type(s) of file(s) being uploaded + * @return bool true on success + * @throws PEAR_Error + */ + function addFile($inputName, $fileName, $contentType = 'application/octet-stream') + { + if (!is_array($fileName) && !is_readable($fileName)) { + return PEAR::raiseError("File '{$fileName}' is not readable"); + } elseif (is_array($fileName)) { + foreach ($fileName as $name) { + if (!is_readable($name)) { + return PEAR::raiseError("File '{$name}' is not readable"); + } + } + } + $this->addHeader('Content-Type', 'multipart/form-data'); + $this->_postFiles[$inputName] = array( + 'name' => $fileName, + 'type' => $contentType + ); + return true; + } + + /** + * Adds raw postdata (DEPRECATED) + * + * @param string The data + * @param bool Whether data is preencoded or not, default = already encoded + * @access public + * @deprecated deprecated since 1.3.0, method setBody() should be used instead + */ + function addRawPostData($postdata, $preencoded = true) + { + $this->_body = $preencoded ? $postdata : urlencode($postdata); + } + + /** + * Sets the request body (for POST, PUT and similar requests) + * + * @param string Request body + * @access public + */ + function setBody($body) + { + $this->_body = $body; + } + + /** + * Clears any postdata that has been added (DEPRECATED). + * + * Useful for multiple request scenarios. + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearPostData() + { + $this->_postData = null; + } + + /** + * Appends a cookie to "Cookie:" header + * + * @param string $name cookie name + * @param string $value cookie value + * @access public + */ + function addCookie($name, $value) + { + $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : ''; + $this->addHeader('Cookie', $cookies . $name . '=' . $value); + } + + /** + * Clears any cookies that have been added (DEPRECATED). + * + * Useful for multiple request scenarios + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearCookies() + { + $this->removeHeader('Cookie'); + } + + /** + * Sends the request + * + * @access public + * @param bool Whether to store response body in Response object property, + * set this to false if downloading a LARGE file and using a Listener + * @return mixed PEAR error on error, true otherwise + */ + function sendRequest($saveBody = true) + { + if (!is_a($this->_url, 'Net_URL')) { + return PEAR::raiseError('No URL given.'); + } + + $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host; + $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port; + + // 4.3.0 supports SSL connections using OpenSSL. The function test determines + // we running on at least 4.3.0 + if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) { + if (isset($this->_proxy_host)) { + return PEAR::raiseError('HTTPS proxies are not supported.'); + } + $host = 'ssl://' . $host; + } + + // magic quotes may fuck up file uploads and chunked response processing + $magicQuotes = ini_get('magic_quotes_runtime'); + ini_set('magic_quotes_runtime', false); + + // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive + // connection token to a proxy server... + if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) && + 'Keep-Alive' == $this->_requestHeaders['connection']) + { + $this->removeHeader('connection'); + } + + $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) || + (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']); + $sockets = &PEAR::getStaticProperty('HTTP_Request', 'sockets'); + $sockKey = $host . ':' . $port; + unset($this->_sock); + + // There is a connected socket in the "static" property? + if ($keepAlive && !empty($sockets[$sockKey]) && + !empty($sockets[$sockKey]->fp)) + { + $this->_sock =& $sockets[$sockKey]; + $err = null; + } else { + $this->_notify('connect'); + $this->_sock =& new Net_Socket(); + $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions); + } + PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest()); + + if (!PEAR::isError($err)) { + if (!empty($this->_readTimeout)) { + $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]); + } + + $this->_notify('sentRequest'); + + // Read the response + $this->_response = &new HTTP_Response($this->_sock, $this->_listeners); + $err = $this->_response->process( + $this->_saveBody && $saveBody, + HTTP_REQUEST_METHOD_HEAD != $this->_method + ); + + if ($keepAlive) { + $keepAlive = (isset($this->_response->_headers['content-length']) + || (isset($this->_response->_headers['transfer-encoding']) + && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked')); + if ($keepAlive) { + if (isset($this->_response->_headers['connection'])) { + $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive'; + } else { + $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol; + } + } + } + } + + ini_set('magic_quotes_runtime', $magicQuotes); + + if (PEAR::isError($err)) { + return $err; + } + + if (!$keepAlive) { + $this->disconnect(); + // Store the connected socket in "static" property + } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) { + $sockets[$sockKey] =& $this->_sock; + } + + // Check for redirection + if ( $this->_allowRedirects + AND $this->_redirects <= $this->_maxRedirects + AND $this->getResponseCode() > 300 + AND $this->getResponseCode() < 399 + AND !empty($this->_response->_headers['location'])) { + + + $redirect = $this->_response->_headers['location']; + + // Absolute URL + if (preg_match('/^https?:\/\//i', $redirect)) { + $this->_url = &new Net_URL($redirect); + $this->addHeader('Host', $this->_generateHostHeader()); + // Absolute path + } elseif ($redirect{0} == '/') { + $this->_url->path = $redirect; + + // Relative path + } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $redirect = Net_URL::resolvePath($redirect); + $this->_url->path = $redirect; + + // Filename, no path + } else { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $this->_url->path = $redirect; + } + + $this->_redirects++; + return $this->sendRequest($saveBody); + + // Too many redirects + } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) { + return PEAR::raiseError('Too many redirects'); + } + + return true; + } + + /** + * Disconnect the socket, if connected. Only useful if using Keep-Alive. + * + * @access public + */ + function disconnect() + { + if (!empty($this->_sock) && !empty($this->_sock->fp)) { + $this->_notify('disconnect'); + $this->_sock->disconnect(); + } + } + + /** + * Returns the response code + * + * @access public + * @return mixed Response code, false if not set + */ + function getResponseCode() + { + return isset($this->_response->_code) ? $this->_response->_code : false; + } + + /** + * Returns either the named header or all if no name given + * + * @access public + * @param string The header name to return, do not set to get all headers + * @return mixed either the value of $headername (false if header is not present) + * or an array of all headers + */ + function getResponseHeader($headername = null) + { + if (!isset($headername)) { + return isset($this->_response->_headers)? $this->_response->_headers: array(); + } else { + $headername = strtolower($headername); + return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false; + } + } + + /** + * Returns the body of the response + * + * @access public + * @return mixed response body, false if not set + */ + function getResponseBody() + { + return isset($this->_response->_body) ? $this->_response->_body : false; + } + + /** + * Returns cookies set in response + * + * @access public + * @return mixed array of response cookies, false if none are present + */ + function getResponseCookies() + { + return isset($this->_response->_cookies) ? $this->_response->_cookies : false; + } + + /** + * Builds the request string + * + * @access private + * @return string The request string + */ + function _buildRequest() + { + $separator = ini_get('arg_separator.output'); + ini_set('arg_separator.output', '&'); + $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : ''; + ini_set('arg_separator.output', $separator); + + $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : ''; + $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : ''; + $path = $this->_url->path . $querystring; + $url = $host . $port . $path; + + $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n"; + + if (in_array($this->_method, $this->_bodyDisallowed) || + (empty($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method || + (empty($this->_postData) && empty($this->_postFiles))))) + { + $this->removeHeader('Content-Type'); + } else { + if (empty($this->_requestHeaders['content-type'])) { + // Add default content-type + $this->addHeader('Content-Type', 'application/x-www-form-urlencoded'); + } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) { + $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime()); + $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary); + } + } + + // Request Headers + if (!empty($this->_requestHeaders)) { + foreach ($this->_requestHeaders as $name => $value) { + $canonicalName = implode('-', array_map('ucfirst', explode('-', $name))); + $request .= $canonicalName . ': ' . $value . "\r\n"; + } + } + + // No post data or wrong method, so simply add a final CRLF + if (in_array($this->_method, $this->_bodyDisallowed) || + (HTTP_REQUEST_METHOD_POST != $this->_method && empty($this->_body))) { + + $request .= "\r\n"; + + // Post data if it's an array + } elseif (HTTP_REQUEST_METHOD_POST == $this->_method && + (!empty($this->_postData) || !empty($this->_postFiles))) { + + // "normal" POST request + if (!isset($boundary)) { + $postdata = implode('&', array_map( + create_function('$a', 'return $a[0] . \'=\' . $a[1];'), + $this->_flattenArray('', $this->_postData) + )); + + // multipart request, probably with file uploads + } else { + $postdata = ''; + if (!empty($this->_postData)) { + $flatData = $this->_flattenArray('', $this->_postData); + foreach ($flatData as $item) { + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"'; + $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n"; + } + } + foreach ($this->_postFiles as $name => $value) { + if (is_array($value['name'])) { + $varname = $name . ($this->_useBrackets? '[]': ''); + } else { + $varname = $name; + $value['name'] = array($value['name']); + } + foreach ($value['name'] as $key => $filename) { + $fp = fopen($filename, 'r'); + $data = fread($fp, filesize($filename)); + fclose($fp); + $basename = basename($filename); + $type = is_array($value['type'])? @$value['type'][$key]: $value['type']; + + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"'; + $postdata .= "\r\nContent-Type: " . $type; + $postdata .= "\r\n\r\n" . $data . "\r\n"; + } + } + $postdata .= '--' . $boundary . "--\r\n"; + } + $request .= 'Content-Length: ' . strlen($postdata) . "\r\n\r\n"; + $request .= $postdata; + + // Explicitly set request body + } elseif (!empty($this->_body)) { + + $request .= 'Content-Length: ' . strlen($this->_body) . "\r\n\r\n"; + $request .= $this->_body; + } + + return $request; + } + + /** + * Helper function to change the (probably multidimensional) associative array + * into the simple one. + * + * @param string name for item + * @param mixed item's values + * @return array array with the following items: array('item name', 'item value'); + */ + function _flattenArray($name, $values) + { + if (!is_array($values)) { + return array(array($name, $values)); + } else { + $ret = array(); + foreach ($values as $k => $v) { + if (empty($name)) { + $newName = $k; + } elseif ($this->_useBrackets) { + $newName = $name . '[' . $k . ']'; + } else { + $newName = $name; + } + $ret = array_merge($ret, $this->_flattenArray($newName, $v)); + } + return $ret; + } + } + + + /** + * Adds a Listener to the list of listeners that are notified of + * the object's events + * + * @param object HTTP_Request_Listener instance to attach + * @return boolean whether the listener was successfully attached + * @access public + */ + function attach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener')) { + return false; + } + $this->_listeners[$listener->getId()] =& $listener; + return true; + } + + + /** + * Removes a Listener from the list of listeners + * + * @param object HTTP_Request_Listener instance to detach + * @return boolean whether the listener was successfully detached + * @access public + */ + function detach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener') || + !isset($this->_listeners[$listener->getId()])) { + return false; + } + unset($this->_listeners[$listener->getId()]); + return true; + } + + + /** + * Notifies all registered listeners of an event. + * + * Events sent by HTTP_Request object + * - 'connect': on connection to server + * - 'sentRequest': after the request was sent + * - 'disconnect': on disconnection from server + * + * Events sent by HTTP_Response object + * - 'gotHeaders': after receiving response headers (headers are passed in $data) + * - 'tick': on receiving a part of response body (the part is passed in $data) + * - 'gzTick': on receiving a gzip-encoded part of response body (ditto) + * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped) + * + * @param string Event name + * @param mixed Additional data + * @access private + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } +} + + +/** +* Response class to complement the Request class +*/ +class HTTP_Response +{ + /** + * Socket object + * @var object + */ + var $_sock; + + /** + * Protocol + * @var string + */ + var $_protocol; + + /** + * Return code + * @var string + */ + var $_code; + + /** + * Response headers + * @var array + */ + var $_headers; + + /** + * Cookies set in response + * @var array + */ + var $_cookies; + + /** + * Response body + * @var string + */ + var $_body = ''; + + /** + * Used by _readChunked(): remaining length of the current chunk + * @var string + */ + var $_chunkLength = 0; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Bytes left to read from message-body + * @var null|int + */ + var $_toRead; + + /** + * Constructor + * + * @param object Net_Socket socket to read the response from + * @param array listeners attached to request + * @return mixed PEAR Error on error, true otherwise + */ + function HTTP_Response(&$sock, &$listeners) + { + $this->_sock =& $sock; + $this->_listeners =& $listeners; + } + + + /** + * Processes a HTTP response + * + * This extracts response code, headers, cookies and decodes body if it + * was encoded in some way + * + * @access public + * @param bool Whether to store response body in object property, set + * this to false if downloading a LARGE file and using a Listener. + * This is assumed to be true if body is gzip-encoded. + * @param bool Whether the response can actually have a message-body. + * Will be set to false for HEAD requests. + * @throws PEAR_Error + * @return mixed true on success, PEAR_Error in case of malformed response + */ + function process($saveBody = true, $canHaveBody = true) + { + do { + $line = $this->_sock->readLine(); + if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) { + return PEAR::raiseError('Malformed response.'); + } else { + $this->_protocol = 'HTTP/' . $http_version; + $this->_code = intval($returncode); + } + while ('' !== ($header = $this->_sock->readLine())) { + $this->_processHeader($header); + } + } while (100 == $this->_code); + + $this->_notify('gotHeaders', $this->_headers); + + // RFC 2616, section 4.4: + // 1. Any response message which "MUST NOT" include a message-body ... + // is always terminated by the first empty line after the header fields + // 3. ... If a message is received with both a + // Transfer-Encoding header field and a Content-Length header field, + // the latter MUST be ignored. + $canHaveBody = $canHaveBody && $this->_code >= 200 && + $this->_code != 204 && $this->_code != 304; + + // If response body is present, read it and decode + $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']); + $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']); + $hasBody = false; + if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) || + 0 != $this->_headers['content-length'])) + { + if ($chunked || !isset($this->_headers['content-length'])) { + $this->_toRead = null; + } else { + $this->_toRead = $this->_headers['content-length']; + } + while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) { + if ($chunked) { + $data = $this->_readChunked(); + } elseif (is_null($this->_toRead)) { + $data = $this->_sock->read(4096); + } else { + $data = $this->_sock->read(min(4096, $this->_toRead)); + $this->_toRead -= strlen($data); + } + if ('' == $data) { + break; + } else { + $hasBody = true; + if ($saveBody || $gzipped) { + $this->_body .= $data; + } + $this->_notify($gzipped? 'gzTick': 'tick', $data); + } + } + } + + if ($hasBody) { + // Uncompress the body if needed + if ($gzipped) { + $body = $this->_decodeGzip($this->_body); + if (PEAR::isError($body)) { + return $body; + } + $this->_body = $body; + $this->_notify('gotBody', $this->_body); + } else { + $this->_notify('gotBody'); + } + } + return true; + } + + + /** + * Processes the response header + * + * @access private + * @param string HTTP header + */ + function _processHeader($header) + { + if (false === strpos($header, ':')) { + return; + } + list($headername, $headervalue) = explode(':', $header, 2); + $headername = strtolower($headername); + $headervalue = ltrim($headervalue); + + if ('set-cookie' != $headername) { + if (isset($this->_headers[$headername])) { + $this->_headers[$headername] .= ',' . $headervalue; + } else { + $this->_headers[$headername] = $headervalue; + } + } else { + $this->_parseCookie($headervalue); + } + } + + /** + * Parse a Set-Cookie header to fill $_cookies array + * + * @access private + * @param string value of Set-Cookie header + */ + function _parseCookie($headervalue) + { + $cookie = array( + 'expires' => null, + 'domain' => null, + 'path' => null, + 'secure' => false + ); + + // Only a name=value pair + if (!strpos($headervalue, ';')) { + $pos = strpos($headervalue, '='); + $cookie['name'] = trim(substr($headervalue, 0, $pos)); + $cookie['value'] = trim(substr($headervalue, $pos + 1)); + + // Some optional parameters are supplied + } else { + $elements = explode(';', $headervalue); + $pos = strpos($elements[0], '='); + $cookie['name'] = trim(substr($elements[0], 0, $pos)); + $cookie['value'] = trim(substr($elements[0], $pos + 1)); + + for ($i = 1; $i < count($elements); $i++) { + if (false === strpos($elements[$i], '=')) { + $elName = trim($elements[$i]); + $elValue = null; + } else { + list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); + } + $elName = strtolower($elName); + if ('secure' == $elName) { + $cookie['secure'] = true; + } elseif ('expires' == $elName) { + $cookie['expires'] = str_replace('"', '', $elValue); + } elseif ('path' == $elName || 'domain' == $elName) { + $cookie[$elName] = urldecode($elValue); + } else { + $cookie[$elName] = $elValue; + } + } + } + $this->_cookies[] = $cookie; + } + + + /** + * Read a part of response body encoded with chunked Transfer-Encoding + * + * @access private + * @return string + */ + function _readChunked() + { + // at start of the next chunk? + if (0 == $this->_chunkLength) { + $line = $this->_sock->readLine(); + if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) { + $this->_chunkLength = hexdec($matches[1]); + // Chunk with zero length indicates the end + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // make this an eof() + return ''; + } + } else { + return ''; + } + } + $data = $this->_sock->read($this->_chunkLength); + $this->_chunkLength -= strlen($data); + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // Trailing CRLF + } + return $data; + } + + + /** + * Notifies all registered listeners of an event. + * + * @param string Event name + * @param mixed Additional data + * @access private + * @see HTTP_Request::_notify() + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } + + + /** + * Decodes the message-body encoded by gzip + * + * The real decoding work is done by gzinflate() built-in function, this + * method only parses the header and checks data for compliance with + * RFC 1952 + * + * @access private + * @param string gzip-encoded data + * @return string decoded data + */ + function _decodeGzip($data) + { + $length = strlen($data); + // If it doesn't look like gzip-encoded data, don't bother + if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) { + return $data; + } + $method = ord(substr($data, 2, 1)); + if (8 != $method) { + return PEAR::raiseError('_decodeGzip(): unknown compression method'); + } + $flags = ord(substr($data, 3, 1)); + if ($flags & 224) { + return PEAR::raiseError('_decodeGzip(): reserved bits are set'); + } + + // header is 10 bytes minimum. may be longer, though. + $headerLength = 10; + // extra fields, need to skip 'em + if ($flags & 4) { + if ($length - $headerLength - 2 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short'); + } + $extraLength = unpack('v', substr($data, 10, 2)); + if ($length - $headerLength - 2 - $extraLength[1] < 8) { + return PEAR::raiseError('_decodeGzip(): data too short'); + } + $headerLength += $extraLength[1] + 2; + } + // file name, need to skip that + if ($flags & 8) { + if ($length - $headerLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short'); + } + $filenameLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short'); + } + $headerLength += $filenameLength + 1; + } + // comment, need to skip that also + if ($flags & 16) { + if ($length - $headerLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short'); + } + $commentLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short'); + } + $headerLength += $commentLength + 1; + } + // have a CRC for header. let's check + if ($flags & 1) { + if ($length - $headerLength - 2 < 8) { + return PEAR::raiseError('_decodeGzip(): data too short'); + } + $crcReal = 0xffff & crc32(substr($data, 0, $headerLength)); + $crcStored = unpack('v', substr($data, $headerLength, 2)); + if ($crcReal != $crcStored[1]) { + return PEAR::raiseError('_decodeGzip(): header CRC check failed'); + } + $headerLength += 2; + } + // unpacked data CRC and size at the end of encoded data + $tmp = unpack('V2', substr($data, -8)); + $dataCrc = $tmp[1]; + $dataSize = $tmp[2]; + + // finally, call the gzinflate() function + $unpacked = @gzinflate(substr($data, $headerLength, -8), $dataSize); + if (false === $unpacked) { + return PEAR::raiseError('_decodeGzip(): gzinflate() call failed'); + } elseif ($dataSize != strlen($unpacked)) { + return PEAR::raiseError('_decodeGzip(): data size check failed'); + } elseif ($dataCrc != crc32($unpacked)) { + return PEAR::raiseError('_decodeGzip(): data CRC check failed'); + } + return $unpacked; + } +} // End class HTTP_Response +?> diff --git a/trunk/docs/include/PEAR/Log.php b/trunk/docs/include/PEAR/Log.php new file mode 100644 index 0000000..0b940c4 --- /dev/null +++ b/trunk/docs/include/PEAR/Log.php @@ -0,0 +1,668 @@ + + * @author Jon Parise + * @since Horde 1.3 + * @package Log + */ +class Log +{ + /** + * Indicates whether or not the log can been opened / connected. + * + * @var boolean + * @access private + */ + var $_opened = false; + + /** + * Instance-specific unique identification number. + * + * @var integer + * @access private + */ + var $_id = 0; + + /** + * The label that uniquely identifies this set of log messages. + * + * @var string + * @access private + */ + var $_ident = ''; + + /** + * The default priority to use when logging an event. + * + * @var integer + * @access private + */ + var $_priority = PEAR_LOG_INFO; + + /** + * The bitmask of allowed log levels. + * @var integer + * @access private + */ + var $_mask = PEAR_LOG_ALL; + + /** + * Holds all Log_observer objects that wish to be notified of new messages. + * + * @var array + * @access private + */ + var $_listeners = array(); + + + /** + * Attempts to return a concrete Log instance of type $handler. + * + * @param string $handler The type of concrete Log subclass to return. + * Attempt to dynamically include the code for + * this subclass. Currently, valid values are + * 'console', 'syslog', 'sql', 'file', and 'mcal'. + * + * @param string $name The name of the actually log file, table, or + * other specific store to use. Defaults to an + * empty string, with which the subclass will + * attempt to do something intelligent. + * + * @param string $ident The identity reported to the log system. + * + * @param array $conf A hash containing any additional configuration + * information that a subclass might need. + * + * @param int $level Log messages up to and including this level. + * + * @return object Log The newly created concrete Log instance, or + * null on an error. + * @access public + * @since Log 1.0 + */ + function &factory($handler, $name = '', $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $handler = strtolower($handler); + $class = 'Log_' . $handler; + $classfile = 'Log/' . $handler . '.php'; + + /* + * Attempt to include our version of the named class, but don't treat + * a failure as fatal. The caller may have already included their own + * version of the named class. + */ + if (!class_exists($class)) { + @include_once $classfile; + } + + /* If the class exists, return a new instance of it. */ + if (class_exists($class)) { + $obj = new $class($name, $ident, $conf, $level); + return $obj; + } + + return null; + } + + /** + * Attempts to return a reference to a concrete Log instance of type + * $handler, only creating a new instance if no log instance with the same + * parameters currently exists. + * + * You should use this if there are multiple places you might create a + * logger, you don't want to create multiple loggers, and you don't want to + * check for the existance of one each time. The singleton pattern does all + * the checking work for you. + * + * You MUST call this method with the $var = &Log::singleton() syntax. + * Without the ampersand (&) in front of the method name, you will not get + * a reference, you will get a copy. + * + * @param string $handler The type of concrete Log subclass to return. + * Attempt to dynamically include the code for + * this subclass. Currently, valid values are + * 'console', 'syslog', 'sql', 'file', and 'mcal'. + * + * @param string $name The name of the actually log file, table, or + * other specific store to use. Defaults to an + * empty string, with which the subclass will + * attempt to do something intelligent. + * + * @param string $ident The identity reported to the log system. + * + * @param array $conf A hash containing any additional configuration + * information that a subclass might need. + * + * @param int $level Log messages up to and including this level. + * + * @return object Log The newly created concrete Log instance, or + * null on an error. + * @access public + * @since Log 1.0 + */ + function &singleton($handler, $name = '', $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + static $instances; + if (!isset($instances)) $instances = array(); + + $signature = serialize(array($handler, $name, $ident, $conf, $level)); + if (!isset($instances[$signature])) { + $instances[$signature] = &Log::factory($handler, $name, $ident, + $conf, $level); + } + + return $instances[$signature]; + } + + /** + * Abstract implementation of the open() method. + * @since Log 1.0 + */ + function open() + { + return false; + } + + /** + * Abstract implementation of the close() method. + * @since Log 1.0 + */ + function close() + { + return false; + } + + /** + * Abstract implementation of the flush() method. + * @since Log 1.8.2 + */ + function flush() + { + return false; + } + + /** + * Abstract implementation of the log() method. + * @since Log 1.0 + */ + function log($message, $priority = null) + { + return false; + } + + /** + * A convenience function for logging a emergency event. It will log a + * message at the PEAR_LOG_EMERG log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function emerg($message) + { + return $this->log($message, PEAR_LOG_EMERG); + } + + /** + * A convenience function for logging an alert event. It will log a + * message at the PEAR_LOG_ALERT log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function alert($message) + { + return $this->log($message, PEAR_LOG_ALERT); + } + + /** + * A convenience function for logging a critical event. It will log a + * message at the PEAR_LOG_CRIT log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function crit($message) + { + return $this->log($message, PEAR_LOG_CRIT); + } + + /** + * A convenience function for logging a error event. It will log a + * message at the PEAR_LOG_ERR log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function err($message) + { + return $this->log($message, PEAR_LOG_ERR); + } + + /** + * A convenience function for logging a warning event. It will log a + * message at the PEAR_LOG_WARNING log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function warning($message) + { + return $this->log($message, PEAR_LOG_WARNING); + } + + /** + * A convenience function for logging a notice event. It will log a + * message at the PEAR_LOG_NOTICE log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function notice($message) + { + return $this->log($message, PEAR_LOG_NOTICE); + } + + /** + * A convenience function for logging a information event. It will log a + * message at the PEAR_LOG_INFO log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function info($message) + { + return $this->log($message, PEAR_LOG_INFO); + } + + /** + * A convenience function for logging a debug event. It will log a + * message at the PEAR_LOG_DEBUG log level. + * + * @param mixed $message String or object containing the message + * to log. + * + * @return boolean True if the message was successfully logged. + * + * @access public + * @since Log 1.7.0 + */ + function debug($message) + { + return $this->log($message, PEAR_LOG_DEBUG); + } + + /** + * Returns the string representation of the message data. + * + * If $message is an object, _extractMessage() will attempt to extract + * the message text using a known method (such as a PEAR_Error object's + * getMessage() method). If a known method, cannot be found, the + * serialized representation of the object will be returned. + * + * If the message data is already a string, it will be returned unchanged. + * + * @param mixed $message The original message data. This may be a + * string or any object. + * + * @return string The string representation of the message. + * + * @access private + */ + function _extractMessage($message) + { + /* + * If we've been given an object, attempt to extract the message using + * a known method. If we can't find such a method, default to the + * "human-readable" version of the object. + * + * We also use the human-readable format for arrays. + */ + if (is_object($message)) { + if (method_exists($message, 'getmessage')) { + $message = $message->getMessage(); + } else if (method_exists($message, 'tostring')) { + $message = $message->toString(); + } else if (method_exists($message, '__tostring')) { + if (version_compare(PHP_VERSION, '5.0.0', 'ge')) { + $message = (string)$message; + } else { + $message = $message->__toString(); + } + } else { + $message = print_r($message, true); + } + } else if (is_array($message)) { + if (isset($message['message'])) { + $message = $message['message']; + } else { + $message = print_r($message, true); + } + } + + /* Otherwise, we assume the message is a string. */ + return $message; + } + + /** + * Returns the string representation of a PEAR_LOG_* integer constant. + * + * @param int $priority A PEAR_LOG_* integer constant. + * + * @return string The string representation of $level. + * + * @since Log 1.0 + */ + function priorityToString($priority) + { + $levels = array( + PEAR_LOG_EMERG => 'emergency', + PEAR_LOG_ALERT => 'alert', + PEAR_LOG_CRIT => 'critical', + PEAR_LOG_ERR => 'error', + PEAR_LOG_WARNING => 'warning', + PEAR_LOG_NOTICE => 'notice', + PEAR_LOG_INFO => 'info', + PEAR_LOG_DEBUG => 'debug' + ); + + return $levels[$priority]; + } + + /** + * Returns the the PEAR_LOG_* integer constant for the given string + * representation of a priority name. This function performs a + * case-insensitive search. + * + * @param string $name String containing a priority name. + * + * @return string The PEAR_LOG_* integer contstant corresponding + * the the specified priority name. + * + * @since Log 1.9.0 + */ + function stringToPriority($name) + { + $levels = array( + 'emergency' => PEAR_LOG_EMERG, + 'alert' => PEAR_LOG_ALERT, + 'critical' => PEAR_LOG_CRIT, + 'error' => PEAR_LOG_ERR, + 'warning' => PEAR_LOG_WARNING, + 'notice' => PEAR_LOG_NOTICE, + 'info' => PEAR_LOG_INFO, + 'debug' => PEAR_LOG_DEBUG + ); + + return $levels[strtolower($name)]; + } + + /** + * Calculate the log mask for the given priority. + * + * @param integer $priority The priority whose mask will be calculated. + * + * @return integer The calculated log mask. + * + * @access public + * @since Log 1.7.0 + */ + function MASK($priority) + { + return (1 << $priority); + } + + /** + * Calculate the log mask for all priorities up to the given priority. + * + * @param integer $priority The maximum priority covered by this mask. + * + * @return integer The calculated log mask. + * + * @access public + * @since Log 1.7.0 + */ + function UPTO($priority) + { + return ((1 << ($priority + 1)) - 1); + } + + /** + * Set and return the level mask for the current Log instance. + * + * @param integer $mask A bitwise mask of log levels. + * + * @return integer The current level mask. + * + * @access public + * @since Log 1.7.0 + */ + function setMask($mask) + { + $this->_mask = $mask; + + return $this->_mask; + } + + /** + * Returns the current level mask. + * + * @return interger The current level mask. + * + * @access public + * @since Log 1.7.0 + */ + function getMask() + { + return $this->_mask; + } + + /** + * Check if the given priority is included in the current level mask. + * + * @param integer $priority The priority to check. + * + * @return boolean True if the given priority is included in the current + * log mask. + * + * @access private + * @since Log 1.7.0 + */ + function _isMasked($priority) + { + return (Log::MASK($priority) & $this->_mask); + } + + /** + * Returns the current default priority. + * + * @return integer The current default priority. + * + * @access public + * @since Log 1.8.4 + */ + function getPriority() + { + return $this->_priority; + } + + /** + * Sets the default priority to the specified value. + * + * @param integer $priority The new default priority. + * + * @access public + * @since Log 1.8.4 + */ + function setPriority($priority) + { + $this->_priority = $priority; + } + + /** + * Adds a Log_observer instance to the list of observers that are listening + * for messages emitted by this Log instance. + * + * @param object $observer The Log_observer instance to attach as a + * listener. + * + * @param boolean True if the observer is successfully attached. + * + * @access public + * @since Log 1.0 + */ + function attach(&$observer) + { + if (!is_a($observer, 'Log_observer')) { + return false; + } + + $this->_listeners[$observer->_id] = &$observer; + + return true; + } + + /** + * Removes a Log_observer instance from the list of observers. + * + * @param object $observer The Log_observer instance to detach from + * the list of listeners. + * + * @param boolean True if the observer is successfully detached. + * + * @access public + * @since Log 1.0 + */ + function detach($observer) + { + if (!is_a($observer, 'Log_observer') || + !isset($this->_listeners[$observer->_id])) { + return false; + } + + unset($this->_listeners[$observer->_id]); + + return true; + } + + /** + * Informs each registered observer instance that a new message has been + * logged. + * + * @param array $event A hash describing the log event. + * + * @access private + */ + function _announce($event) + { + foreach ($this->_listeners as $id => $listener) { + if ($event['priority'] <= $this->_listeners[$id]->_priority) { + $this->_listeners[$id]->notify($event); + } + } + } + + /** + * Indicates whether this is a composite class. + * + * @return boolean True if this is a composite class. + * + * @access public + * @since Log 1.0 + */ + function isComposite() + { + return false; + } + + /** + * Sets this Log instance's identification string. + * + * @param string $ident The new identification string. + * + * @access public + * @since Log 1.6.3 + */ + function setIdent($ident) + { + $this->_ident = $ident; + } + + /** + * Returns the current identification string. + * + * @return string The current Log instance's identification string. + * + * @access public + * @since Log 1.6.3 + */ + function getIdent() + { + return $this->_ident; + } +} diff --git a/trunk/docs/include/PEAR/Log/composite.php b/trunk/docs/include/PEAR/Log/composite.php new file mode 100644 index 0000000..fd3feaa --- /dev/null +++ b/trunk/docs/include/PEAR/Log/composite.php @@ -0,0 +1,200 @@ + + * @author Jon Parise + * + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + * + * @example composite.php Using the composite handler. + */ +class Log_composite extends Log +{ + /** + * Array holding all of the Log instances to which log events should be + * sent. + * + * @var array + * @access private + */ + var $_children = array(); + + + /** + * Constructs a new composite Log object. + * + * @param boolean $name This parameter is ignored. + * @param boolean $ident This parameter is ignored. + * @param boolean $conf This parameter is ignored. + * @param boolean $level This parameter is ignored. + * + * @access public + */ + function Log_composite($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_ident = $ident; + } + + /** + * Opens the child connections. + * + * @access public + */ + function open() + { + if (!$this->_opened) { + foreach ($this->_children as $id => $child) { + $this->_children[$id]->open(); + } + $this->_opened = true; + } + } + + /** + * Closes any child instances. + * + * @access public + */ + function close() + { + if ($this->_opened) { + foreach ($this->_children as $id => $child) { + $this->_children[$id]->close(); + } + $this->_opened = false; + } + } + + /** + * Flushes all open child instances. + * + * @access public + * @since Log 1.8.2 + */ + function flush() + { + if ($this->_opened) { + foreach ($this->_children as $id => $child) { + $this->_children[$id]->flush(); + } + } + } + + /** + * Sends $message and $priority to each child of this composite. + * + * @param mixed $message String or object containing the message + * to log. + * @param string $priority (optional) The priority of the message. + * Valid values are: PEAR_LOG_EMERG, + * PEAR_LOG_ALERT, PEAR_LOG_CRIT, + * PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and + * PEAR_LOG_DEBUG. + * + * @return boolean True if the entry is successfully logged. + * + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + foreach ($this->_children as $id => $child) { + $this->_children[$id]->log($message, $priority); + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Returns true if this is a composite. + * + * @return boolean True if this is a composite class. + * + * @access public + */ + function isComposite() + { + return true; + } + + /** + * Sets this identification string for all of this composite's children. + * + * @param string $ident The new identification string. + * + * @access public + * @since Log 1.6.7 + */ + function setIdent($ident) + { + /* Call our base class's setIdent() method. */ + parent::setIdent($ident); + + /* ... and then call setIdent() on all of our children. */ + foreach ($this->_children as $id => $child) { + $this->_children[$id]->setIdent($ident); + } + } + + /** + * Adds a Log instance to the list of children. + * + * @param object $child The Log instance to add. + * + * @return boolean True if the Log instance was successfully added. + * + * @access public + */ + function addChild(&$child) + { + /* Make sure this is a Log instance. */ + if (!is_a($child, 'Log')) { + return false; + } + + $this->_children[$child->_id] = &$child; + + return true; + } + + /** + * Removes a Log instance from the list of children. + * + * @param object $child The Log instance to remove. + * + * @return boolean True if the Log instance was successfully removed. + * + * @access public + */ + function removeChild($child) + { + if (!is_a($child, 'Log') || !isset($this->_children[$child->_id])) { + return false; + } + + unset($this->_children[$child->_id]); + + return true; + } + +} diff --git a/trunk/docs/include/PEAR/Log/console.php b/trunk/docs/include/PEAR/Log/console.php new file mode 100644 index 0000000..a21e417 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/console.php @@ -0,0 +1,202 @@ + + * @since Log 1.1 + * @package Log + * + * @example console.php Using the console handler. + */ +class Log_console extends Log +{ + /** + * Handle to the current output stream. + * @var resource + * @access private + */ + var $_stream = STDOUT; + + /** + * Should the output be buffered or displayed immediately? + * @var string + * @access private + */ + var $_buffering = false; + + /** + * String holding the buffered output. + * @var string + * @access private + */ + var $_buffer = ''; + + /** + * String containing the format of a log line. + * @var string + * @access private + */ + var $_lineFormat = '%1$s %2$s [%3$s] %4$s'; + + /** + * String containing the timestamp format. It will be passed directly to + * strftime(). Note that the timestamp string will generated using the + * current locale. + * @var string + * @access private + */ + var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * Hash that maps canonical format keys to position arguments for the + * "line format" string. + * @var array + * @access private + */ + var $_formatMap = array('%{timestamp}' => '%1$s', + '%{ident}' => '%2$s', + '%{priority}' => '%3$s', + '%{message}' => '%4$s', + '%\{' => '%%{'); + + /** + * Constructs a new Log_console object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_console($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (!empty($conf['stream'])) { + $this->_stream = $conf['stream']; + } + + if (isset($conf['buffering'])) { + $this->_buffering = $conf['buffering']; + } + + if (!empty($conf['lineFormat'])) { + $this->_lineFormat = str_replace(array_keys($this->_formatMap), + array_values($this->_formatMap), + $conf['lineFormat']); + } + + if (!empty($conf['timeFormat'])) { + $this->_timeFormat = $conf['timeFormat']; + } + + /* + * If output buffering has been requested, we need to register a + * shutdown function that will dump the buffer upon termination. + */ + if ($this->_buffering) { + register_shutdown_function(array(&$this, '_Log_console')); + } + } + + /** + * Destructor + */ + function _Log_console() + { + $this->close(); + } + + /** + * Closes the output stream. + * + * This results in a call to flush(). + * + * @access public + * @since Log 1.9.0 + */ + function close() + { + $this->flush(); + } + + /** + * Flushes all pending ("buffered") data to the output stream. + * + * @access public + * @since Log 1.8.2 + */ + function flush() + { + /* + * If output buffering is enabled, dump the contents of the buffer to + * the output stream. + */ + if ($this->_buffering && (strlen($this->_buffer) > 0)) { + fwrite($this->_stream, $this->_buffer); + $this->_buffer = ''; + } + + return fflush($this->_stream); + } + + /** + * Writes $message to the text console. Also, passes the message + * along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build the string containing the complete log line. */ + $line = sprintf($this->_lineFormat, strftime($this->_timeFormat), + $this->_ident, $this->priorityToString($priority), + $message) . "\n"; + + /* + * If buffering is enabled, append this line to the output buffer. + * Otherwise, print the line to the output stream immediately. + */ + if ($this->_buffering) { + $this->_buffer .= $line; + } else { + fwrite($this->_stream, $line); + } + + /* Notify observers about this log message. */ + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/trunk/docs/include/PEAR/Log/daemon.php b/trunk/docs/include/PEAR/Log/daemon.php new file mode 100644 index 0000000..61079e1 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/daemon.php @@ -0,0 +1,230 @@ + + * @version $Revision: 1.1 $ + * @package Log + */ +class Log_daemon extends Log +{ + /** + * Integer holding the log facility to use. + * @var string + */ + var $_name = LOG_DAEMON; + + /** + * Var holding the resource pointer to the socket + * @var resource + */ + var $_socket; + + /** + * The ip address or servername + * @see http://www.php.net/manual/en/transports.php + * @var string + */ + var $_ip = '127.0.0.1'; + + /** + * Protocol to use (tcp, udp, etc.) + * @see http://www.php.net/manual/en/transports.php + * @var string + */ + var $_proto = 'udp'; + + /** + * Port to connect to + * @var int + */ + var $_port = 514; + + /** + * Maximum message length in bytes + * @var int + */ + var $_maxsize = 4096; + + /** + * Socket timeout in seconds + * @var int + */ + var $_timeout = 1; + + + /** + * Constructs a new syslog object. + * + * @param string $name The syslog facility. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $maxLevel Maximum level at which to log. + * @access public + */ + function Log_daemon($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + /* Ensure we have a valid integer value for $name. */ + if (empty($name) || !is_int($name)) { + $name = LOG_SYSLOG; + } + + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (isset($conf['ip'])) { + $this->_ip = $conf['ip']; + } + if (isset($conf['proto'])) { + $this->_proto = $conf['proto']; + } + if (isset($conf['port'])) { + $this->_port = $conf['port']; + } + if (isset($conf['maxsize'])) { + $this->_maxsize = $conf['maxsize']; + } + if (isset($conf['timeout'])) { + $this->_timeout = $conf['timeout']; + } + $this->_proto = $this->_proto . '://'; + + register_shutdown_function(array(&$this, '_Log_daemon')); + } + + /** + * Destructor. + * + * @access private + */ + function _Log_daemon() + { + $this->close(); + } + + /** + * Opens a connection to the system logger, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * @access public + */ + function open() + { + if (!$this->_opened) { + $this->_opened = (bool)($this->_socket = @fsockopen( + $this->_proto . $this->_ip, + $this->_port, + $errno, + $errstr, + $this->_timeout)); + } + return $this->_opened; + } + + /** + * Closes the connection to the system logger, if it is open. + * @access public + */ + function close() + { + if ($this->_opened) { + $this->_opened = false; + return fclose($this->_socket); + } + return true; + } + + /** + * Sends $message to the currently open syslog connection. Calls + * open() if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param string $message The textual message to be logged. + * @param int $priority (optional) The priority of the message. Valid + * values are: LOG_EMERG, LOG_ALERT, LOG_CRIT, + * LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, + * and LOG_DEBUG. The default is LOG_INFO. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Set the facility level. */ + $facility_level = intval($this->_name) + + intval($this->_toSyslog($priority)); + + /* Prepend ident info. */ + if (!empty($this->_ident)) { + $message = $this->_ident . ' ' . $message; + } + + /* Check for message length. */ + if (strlen($message) > $this->_maxsize) { + $message = substr($message, 0, ($this->_maxsize) - 10) . ' [...]'; + } + + /* Write to socket. */ + fwrite($this->_socket, '<' . $facility_level . '>' . $message . "\n"); + + $this->_announce(array('priority' => $priority, 'message' => $message)); + } + + /** + * Converts a PEAR_LOG_* constant into a syslog LOG_* constant. + * + * This function exists because, under Windows, not all of the LOG_* + * constants have unique values. Instead, the PEAR_LOG_* were introduced + * for global use, with the conversion to the LOG_* constants kept local to + * to the syslog driver. + * + * @param int $priority PEAR_LOG_* value to convert to LOG_* value. + * + * @return The LOG_* representation of $priority. + * + * @access private + */ + function _toSyslog($priority) + { + static $priorities = array( + PEAR_LOG_EMERG => LOG_EMERG, + PEAR_LOG_ALERT => LOG_ALERT, + PEAR_LOG_CRIT => LOG_CRIT, + PEAR_LOG_ERR => LOG_ERR, + PEAR_LOG_WARNING => LOG_WARNING, + PEAR_LOG_NOTICE => LOG_NOTICE, + PEAR_LOG_INFO => LOG_INFO, + PEAR_LOG_DEBUG => LOG_DEBUG + ); + + /* If we're passed an unknown priority, default to LOG_INFO. */ + if (!is_int($priority) || !in_array($priority, $priorities)) { + return LOG_INFO; + } + + return $priorities[$priority]; + } + +} diff --git a/trunk/docs/include/PEAR/Log/display.php b/trunk/docs/include/PEAR/Log/display.php new file mode 100644 index 0000000..dff2152 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/display.php @@ -0,0 +1,117 @@ + + * @since Log 1.8.0 + * @package Log + * + * @example display.php Using the display handler. + */ +class Log_display extends Log +{ + /** + * String to output before an error message + * @var string + * @access private + */ + var $_error_prepend = ''; + + /** + * String to output after an error message + * @var string + * @access private + */ + var $_error_append = ''; + + /** + * String used to represent a line break. + * @var string + * @access private + */ + var $_linebreak = "
\n"; + + /** + * Constructs a new Log_display object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_display($name = '', $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (isset($conf['error_prepend'])) { + $this->_error_prepend = $conf['error_prepend']; + } else { + $this->_error_prepend = ini_get('error_prepend_string'); + } + + if (isset($conf['error_append'])) { + $this->_error_append = $conf['error_append']; + } else { + $this->_error_append = ini_get('error_append_string'); + } + + if (isset($conf['linebreak'])) { + $this->_linebreak = $conf['linebreak']; + } + } + + /** + * Writes $message to the text browser. Also, passes the message + * along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build and output the complete log line. */ + echo $this->_error_prepend . + '' . ucfirst($this->priorityToString($priority)) . ': '. + nl2br(htmlspecialchars($message)) . + $this->_error_append . $this->_linebreak; + + /* Notify observers about this log message. */ + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/trunk/docs/include/PEAR/Log/error_log.php b/trunk/docs/include/PEAR/Log/error_log.php new file mode 100644 index 0000000..48d5c0e --- /dev/null +++ b/trunk/docs/include/PEAR/Log/error_log.php @@ -0,0 +1,103 @@ + + * @since Log 1.7.0 + * @package Log + * + * @example error_log.php Using the error_log handler. + */ +class Log_error_log extends Log +{ + /** + * The error_log() log type. + * @var integer + * @access private + */ + var $_type = PEAR_LOG_TYPE_SYSTEM; + + /** + * The type-specific destination value. + * @var string + * @access private + */ + var $_destination = ''; + + /** + * Additional headers to pass to the mail() function when the + * PEAR_LOG_TYPE_MAIL type is used. + * @var string + * @access private + */ + var $_extra_headers = ''; + + /** + * Constructs a new Log_error_log object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_error_log($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_type = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (!empty($conf['destination'])) { + $this->_destination = $conf['destination']; + } + if (!empty($conf['extra_headers'])) { + $this->_extra_headers = $conf['extra_headers']; + } + } + + /** + * Logs $message using PHP's error_log() function. The message is also + * passed along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + $success = error_log($this->_ident . ': ' . $message, $this->_type, + $this->_destination, $this->_extra_headers); + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return $success; + } + +} diff --git a/trunk/docs/include/PEAR/Log/file.php b/trunk/docs/include/PEAR/Log/file.php new file mode 100644 index 0000000..f12228c --- /dev/null +++ b/trunk/docs/include/PEAR/Log/file.php @@ -0,0 +1,324 @@ + + * @author Roman Neuhauser + * @since Log 1.0 + * @package Log + * + * @example file.php Using the file handler. + */ +class Log_file extends Log +{ + /** + * String containing the name of the log file. + * @var string + * @access private + */ + var $_filename = 'php.log'; + + /** + * Handle to the log file. + * @var resource + * @access private + */ + var $_fp = false; + + /** + * Should new log entries be append to an existing log file, or should the + * a new log file overwrite an existing one? + * @var boolean + * @access private + */ + var $_append = true; + + /** + * Should advisory file locking (i.e., flock()) be used? + * @var boolean + * @access private + */ + var $_locking = false; + + /** + * Integer (in octal) containing the log file's permissions mode. + * @var integer + * @access private + */ + var $_mode = 0644; + + /** + * Integer (in octal) specifying the file permission mode that will be + * used when creating directories that do not already exist. + * @var integer + * @access private + */ + var $_dirmode = 0755; + + /** + * String containing the format of a log line. + * @var string + * @access private + */ + var $_lineFormat = '%1$s %2$s [%3$s] %4$s'; + + /** + * String containing the timestamp format. It will be passed directly to + * strftime(). Note that the timestamp string will generated using the + * current locale. + * @var string + * @access private + */ + var $_timeFormat = '%b %d %H:%M:%S'; + + /** + * Hash that maps canonical format keys to position arguments for the + * "line format" string. + * @var array + * @access private + */ + var $_formatMap = array('%{timestamp}' => '%1$s', + '%{ident}' => '%2$s', + '%{priority}' => '%3$s', + '%{message}' => '%4$s', + '%\{' => '%%{'); + + /** + * String containing the end-on-line character sequence. + * @var string + * @access private + */ + var $_eol = "\n"; + + /** + * Constructs a new Log_file object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_file($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_filename = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (isset($conf['append'])) { + $this->_append = $conf['append']; + } + + if (isset($conf['locking'])) { + $this->_locking = $conf['locking']; + } + + if (!empty($conf['mode'])) { + if (is_string($conf['mode'])) { + $this->_mode = octdec($conf['mode']); + } else { + $this->_mode = $conf['mode']; + } + } + + if (!empty($conf['dirmode'])) { + if (is_string($conf['dirmode'])) { + $this->_dirmode = octdec($conf['dirmode']); + } else { + $this->_dirmode = $conf['dirmode']; + } + } + + if (!empty($conf['lineFormat'])) { + $this->_lineFormat = str_replace(array_keys($this->_formatMap), + array_values($this->_formatMap), + $conf['lineFormat']); + } + + if (!empty($conf['timeFormat'])) { + $this->_timeFormat = $conf['timeFormat']; + } + + if (!empty($conf['eol'])) { + $this->_eol = $conf['eol']; + } else { + $this->_eol = (strstr(PHP_OS, 'WIN')) ? "\r\n" : "\n"; + } + + register_shutdown_function(array(&$this, '_Log_file')); + } + + /** + * Destructor + */ + function _Log_file() + { + if ($this->_opened) { + $this->close(); + } + } + + /** + * Creates the given directory path. If the parent directories don't + * already exist, they will be created, too. + * + * This implementation is inspired by Python's os.makedirs function. + * + * @param string $path The full directory path to create. + * @param integer $mode The permissions mode with which the + * directories will be created. + * + * @return True if the full path is successfully created or already + * exists. + * + * @access private + */ + function _mkpath($path, $mode = 0700) + { + /* Separate the last pathname component from the rest of the path. */ + $head = dirname($path); + $tail = basename($path); + + /* Make sure we've split the path into two complete components. */ + if (empty($tail)) { + $head = dirname($path); + $tail = basename($path); + } + + /* Recurse up the path if our current segment does not exist. */ + if (!empty($head) && !empty($tail) && !is_dir($head)) { + $this->_mkpath($head, $mode); + } + + /* Create this segment of the path. */ + return @mkdir($head, $mode); + } + + /** + * Opens the log file for output. If the specified log file does not + * already exist, it will be created. By default, new log entries are + * appended to the end of the log file. + * + * This is implicitly called by log(), if necessary. + * + * @access public + */ + function open() + { + if (!$this->_opened) { + /* If the log file's directory doesn't exist, create it. */ + if (!is_dir(dirname($this->_filename))) { + $this->_mkpath($this->_filename, $this->_dirmode); + } + + /* Determine whether the log file needs to be created. */ + $creating = !file_exists($this->_filename); + + /* Obtain a handle to the log file. */ + $this->_fp = fopen($this->_filename, ($this->_append) ? 'a' : 'w'); + + /* We consider the file "opened" if we have a valid file pointer. */ + $this->_opened = ($this->_fp !== false); + + /* Attempt to set the file's permissions if we just created it. */ + if ($creating && $this->_opened) { + chmod($this->_filename, $this->_mode); + } + } + + return $this->_opened; + } + + /** + * Closes the log file if it is open. + * + * @access public + */ + function close() + { + /* If the log file is open, close it. */ + if ($this->_opened && fclose($this->_fp)) { + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Flushes all pending data to the file handle. + * + * @access public + * @since Log 1.8.2 + */ + function flush() + { + return fflush($this->_fp); + } + + /** + * Logs $message to the output window. The message is also passed along + * to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the log file isn't already open, open it now. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build the string containing the complete log line. */ + $line = sprintf($this->_lineFormat, strftime($this->_timeFormat), + $this->_ident, $this->priorityToString($priority), + $message) . $this->_eol; + + /* If locking is enabled, acquire an exclusive lock on the file. */ + if ($this->_locking) { + flock($this->_fp, LOCK_EX); + } + + /* Write the log line to the log file. */ + $success = (fwrite($this->_fp, $line) !== false); + + /* Unlock the file now that we're finished writing to it. */ + if ($this->_locking) { + flock($this->_fp, LOCK_UN); + } + + /* Notify observers about this log message. */ + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return $success; + } + +} diff --git a/trunk/docs/include/PEAR/Log/mail.php b/trunk/docs/include/PEAR/Log/mail.php new file mode 100644 index 0000000..286aaca --- /dev/null +++ b/trunk/docs/include/PEAR/Log/mail.php @@ -0,0 +1,220 @@ + + * @author Jon Parise + * @since Log 1.3 + * @package Log + * + * @example mail.php Using the mail handler. + */ +class Log_mail extends Log +{ + /** + * String holding the recipient's email address. + * @var string + * @access private + */ + var $_recipient = ''; + + /** + * String holding the sender's email address. + * @var string + * @access private + */ + var $_from = ''; + + /** + * String holding the email's subject. + * @var string + * @access private + */ + var $_subject = '[Log_mail] Log message'; + + /** + * String holding an optional preamble for the log messages. + * @var string + * @access private + */ + var $_preamble = ''; + + /** + * String holding the mail message body. + * @var string + * @access private + */ + var $_message = ''; + + + /** + * Constructs a new Log_mail object. + * + * Here is how you can customize the mail driver with the conf[] hash : + * $conf['from'] : the mail's "From" header line, + * $conf['subject'] : the mail's "Subject" line. + * + * @param string $name The filename of the logfile. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_mail($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_recipient = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (!empty($conf['from'])) { + $this->_from = $conf['from']; + } else { + $this->_from = ini_get('sendmail_from'); + } + + if (!empty($conf['subject'])) { + $this->_subject = $conf['subject']; + } + + if (!empty($conf['preamble'])) { + $this->_preamble = $conf['preamble']; + } + + /* register the destructor */ + register_shutdown_function(array(&$this, '_Log_mail')); + } + + /** + * Destructor. Calls close(). + * + * @access private + */ + function _Log_mail() + { + $this->close(); + } + + /** + * Starts a new mail message. + * This is implicitly called by log(), if necessary. + * + * @access public + */ + function open() + { + if (!$this->_opened) { + if (!empty($this->_preamble)) { + $this->_message = $this->_preamble . "\r\n\r\n"; + } + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the message, if it is open, and sends the mail. + * This is implicitly called by the destructor, if necessary. + * + * @access public + */ + function close() + { + if ($this->_opened) { + if (!empty($this->_message)) { + $headers = "From: $this->_from\r\n"; + $headers .= "User-Agent: Log_mail"; + + if (mail($this->_recipient, $this->_subject, $this->_message, + $headers) == false) { + error_log("Log_mail: Failure executing mail()", 0); + return false; + } + + /* Clear the message string now that the email has been sent. */ + $this->_message = ''; + } + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Flushes the log output by forcing the email message to be sent now. + * Events that are logged after flush() is called will be appended to a + * new email message. + * + * @access public + * @since Log 1.8.2 + */ + function flush() + { + /* + * It's sufficient to simply call close() to flush the output. + * The next call to log() will cause the handler to be reopened. + */ + return $this->close(); + } + + /** + * Writes $message to the currently open mail message. + * Calls open(), if necessary. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the message isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + $entry = sprintf("%s %s [%s] %s\r\n", strftime('%b %d %H:%M:%S'), + $this->_ident, Log::priorityToString($priority), + $message); + + $this->_message .= $entry; + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } +} diff --git a/trunk/docs/include/PEAR/Log/mcal.php b/trunk/docs/include/PEAR/Log/mcal.php new file mode 100644 index 0000000..446005b --- /dev/null +++ b/trunk/docs/include/PEAR/Log/mcal.php @@ -0,0 +1,170 @@ + + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + */ +class Log_mcal extends Log +{ + /** + * holding the calendar specification to connect to. + * @var string + * @access private + */ + var $_calendar = '{localhost/mstore}'; + + /** + * holding the username to use. + * @var string + * @access private + */ + var $_username = ''; + + /** + * holding the password to use. + * @var string + * @access private + */ + var $_password = ''; + + /** + * holding the options to pass to the calendar stream. + * @var integer + * @access private + */ + var $_options = 0; + + /** + * ResourceID of the MCAL stream. + * @var string + * @access private + */ + var $_stream = ''; + + /** + * Integer holding the log facility to use. + * @var string + * @access private + */ + var $_name = LOG_SYSLOG; + + + /** + * Constructs a new Log_mcal object. + * + * @param string $name The category to use for our events. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_mcal($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + $this->_calendar = $conf['calendar']; + $this->_username = $conf['username']; + $this->_password = $conf['password']; + $this->_options = $conf['options']; + } + + /** + * Opens a calendar stream, if it has not already been + * opened. This is implicitly called by log(), if necessary. + * @access public + */ + function open() + { + if (!$this->_opened) { + $this->_stream = mcal_open($this->_calendar, $this->_username, + $this->_password, $this->_options); + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the calendar stream, if it is open. + * @access public + */ + function close() + { + if ($this->_opened) { + mcal_close($this->_stream); + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Logs $message and associated information to the currently open + * calendar stream. Calls open() if necessary. Also passes the + * message along to any Log_observer instances that are observing + * this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + $date_str = date('Y:n:j:G:i:s'); + $dates = explode(':', $date_str); + + mcal_event_init($this->_stream); + mcal_event_set_title($this->_stream, $this->_ident); + mcal_event_set_category($this->_stream, $this->_name); + mcal_event_set_description($this->_stream, $message); + mcal_event_add_attribute($this->_stream, 'priority', $priority); + mcal_event_set_start($this->_stream, $dates[0], $dates[1], $dates[2], + $dates[3], $dates[4], $dates[5]); + mcal_event_set_end($this->_stream, $dates[0], $dates[1], $dates[2], + $dates[3], $dates[4], $dates[5]); + mcal_append_event($this->_stream); + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/trunk/docs/include/PEAR/Log/mdb2.php b/trunk/docs/include/PEAR/Log/mdb2.php new file mode 100644 index 0000000..65f1f7b --- /dev/null +++ b/trunk/docs/include/PEAR/Log/mdb2.php @@ -0,0 +1,358 @@ + + * @author Jon Parise + * @since Log 1.9.0 + * @package Log + */ +class Log_mdb2 extends Log +{ + /** + * Variable containing the DSN information. + * @var mixed + * @access private + */ + var $_dsn = ''; + + /** + * Array containing our set of DB configuration options. + * @var array + * @access private + */ + var $_options = array('persistent' => true); + + /** + * Object holding the database handle. + * @var object + * @access private + */ + var $_db = null; + + /** + * Resource holding the prepared statement handle. + * @var resource + * @access private + */ + var $_statement = null; + + /** + * Flag indicating that we're using an existing database connection. + * @var boolean + * @access private + */ + var $_existingConnection = false; + + /** + * String holding the database table to use. + * @var string + * @access private + */ + var $_table = 'log_table'; + + /** + * String holding the name of the ID sequence. + * @var string + * @access private + */ + var $_sequence = 'log_id'; + + /** + * Maximum length of the $ident string. This corresponds to the size of + * the 'ident' column in the SQL table. + * @var integer + * @access private + */ + var $_identLimit = 16; + + /** + * Set of field types used in the database table. + * @var array + * @access private + */ + var $_types = array( + 'id' => 'integer', + 'logtime' => 'timestamp', + 'ident' => 'text', + 'priority' => 'text', + 'message' => 'clob' + ); + + /** + * Constructs a new sql logging object. + * + * @param string $name The target SQL table. + * @param string $ident The identification field. + * @param array $conf The connection configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_mdb2($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_table = $name; + $this->_mask = Log::UPTO($level); + + /* If an options array was provided, use it. */ + if (isset($conf['options']) && is_array($conf['options'])) { + $this->_options = $conf['options']; + } + + /* If a specific sequence name was provided, use it. */ + if (!empty($conf['sequence'])) { + $this->_sequence = $conf['sequence']; + } + + /* If a specific sequence name was provided, use it. */ + if (isset($conf['identLimit'])) { + $this->_identLimit = $conf['identLimit']; + } + + /* Now that the ident limit is confirmed, set the ident string. */ + $this->setIdent($ident); + + /* If an existing database connection was provided, use it. */ + if (isset($conf['db'])) { + $this->_db = &$conf['db']; + $this->_existingConnection = true; + $this->_opened = true; + } elseif (isset($conf['singleton'])) { + $this->_db = &MDB2::singleton($conf['singleton'], $this->_options); + $this->_existingConnection = true; + $this->_opened = true; + } else { + $this->_dsn = $conf['dsn']; + } + } + + /** + * Opens a connection to the database, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * + * @return boolean True on success, false on failure. + * @access public + */ + function open() + { + if (!$this->_opened) { + /* Use the DSN and options to create a database connection. */ + $this->_db = &MDB2::connect($this->_dsn, $this->_options); + if (PEAR::isError($this->_db)) { + return false; + } + + /* Create a prepared statement for repeated use in log(). */ + if (!$this->_prepareStatement()) { + return false; + } + + /* We now consider out connection open. */ + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the connection to the database if it is still open and we were + * the ones that opened it. It is the caller's responsible to close an + * existing connection that was passed to us via $conf['db']. + * + * @return boolean True on success, false on failure. + * @access public + */ + function close() + { + /* If we have a statement object, free it. */ + if (is_object($this->_statement)) { + $this->_statement->free(); + $this->_statement = null; + } + + /* If we opened the database connection, disconnect it. */ + if ($this->_opened && !$this->_existingConnection) { + $this->_opened = false; + return $this->_db->disconnect(); + } + + return ($this->_opened === false); + } + + /** + * Sets this Log instance's identification string. Note that this + * SQL-specific implementation will limit the length of the $ident string + * to sixteen (16) characters. + * + * @param string $ident The new identification string. + * + * @access public + * @since Log 1.8.5 + */ + function setIdent($ident) + { + $this->_ident = substr($ident, 0, $this->_identLimit); + } + + /** + * Inserts $message to the currently open database. Calls open(), + * if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* If we don't already have a statement object, create one. */ + if (!is_object($this->_statement) && !$this->_prepareStatement()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build our set of values for this log entry. */ + $values = array( + 'id' => $this->_db->nextId($this->_sequence), + 'logtime' => MDB2_Date::mdbNow(), + 'ident' => $this->_ident, + 'priority' => $priority, + 'message' => $message + ); + + /* Execute the SQL query for this log entry insertion. */ + $this->_db->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = &$this->_statement->execute($values); + $this->_db->popExpect(); + + /* Attempt to handle any errors. */ + if (PEAR::isError($result)) { + /* We can only handle MDB2_ERROR_NOSUCHTABLE errors. */ + if ($result->getCode() != MDB2_ERROR_NOSUCHTABLE) { + return false; + } + + /* Attempt to create the target table. */ + if (!$this->_createTable()) { + return false; + } + + /* Recreate our prepared statement resource. */ + $this->_statement->free(); + if (!$this->_prepareStatement()) { + return false; + } + + /* Attempt to re-execute the insertion query. */ + $result = $this->_statement->execute($values); + if (PEAR::isError($result)) { + return false; + } + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Create the log table in the database. + * + * @return boolean True on success or false on failure. + * @access private + */ + function _createTable() + { + $this->_db->loadModule('Manager'); + $result = $this->_db->manager->createTable( + $this->_table, + array( + 'id' => array('type' => $this->_types['id']), + 'logtime' => array('type' => $this->_types['logtime']), + 'ident' => array('type' => $this->_types['ident']), + 'priority' => array('type' => $this->_types['priority']), + 'message' => array('type' => $this->_types['message']) + ) + ); + if (PEAR::isError($result)) { + return false; + } + + $result = $this->_db->manager->createIndex( + $this->_table, + 'unique_id', + array('fields' => array('id' => true), 'unique' => true) + ); + if (PEAR::isError($result)) { + return false; + } + + return true; + } + + /** + * Prepare the SQL insertion statement. + * + * @return boolean True if the statement was successfully created. + * + * @access private + * @since Log 1.9.0 + */ + function _prepareStatement() + { + $this->_statement = &$this->_db->prepare( + 'INSERT INTO ' . $this->_table . + ' (id, logtime, ident, priority, message)' . + ' VALUES(:id, :logtime, :ident, :priority, :message)', + $this->_types); + + /* Return success if we didn't generate an error. */ + return (PEAR::isError($this->_statement) === false); + } +} diff --git a/trunk/docs/include/PEAR/Log/null.php b/trunk/docs/include/PEAR/Log/null.php new file mode 100644 index 0000000..9a645b2 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/null.php @@ -0,0 +1,67 @@ + + * @since Log 1.8.2 + * @package Log + * + * @example null.php Using the null handler. + */ +class Log_null extends Log +{ + /** + * Constructs a new Log_null object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_null($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + } + + /** + * Simply consumes the log event. The message will still be passed + * along to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/trunk/docs/include/PEAR/Log/observer.php b/trunk/docs/include/PEAR/Log/observer.php new file mode 100644 index 0000000..23a5470 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/observer.php @@ -0,0 +1,125 @@ + + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + * + * @example observer_mail.php An example Log_observer implementation. + */ +class Log_observer +{ + /** + * Instance-specific unique identification number. + * + * @var integer + * @access private + */ + var $_id = 0; + + /** + * The minimum priority level of message that we want to hear about. + * PEAR_LOG_EMERG is the highest priority, so we will only hear messages + * with an integer priority value less than or equal to ours. It defaults + * to PEAR_LOG_INFO, which listens to everything except PEAR_LOG_DEBUG. + * + * @var string + * @access private + */ + var $_priority = PEAR_LOG_INFO; + + /** + * Creates a new basic Log_observer instance. + * + * @param integer $priority The highest priority at which to receive + * log event notifications. + * + * @access public + */ + function Log_observer($priority = PEAR_LOG_INFO) + { + $this->_id = md5(microtime()); + $this->_priority = $priority; + } + + /** + * Attempts to return a new concrete Log_observer instance of the requested + * type. + * + * @param string $type The type of concreate Log_observer subclass + * to return. + * @param integer $priority The highest priority at which to receive + * log event notifications. + * @param array $conf Optional associative array of additional + * configuration values. + * + * @return object The newly created concrete Log_observer + * instance, or null on an error. + */ + function &factory($type, $priority = PEAR_LOG_INFO, $conf = array()) + { + $type = strtolower($type); + $class = 'Log_observer_' . $type; + + /* Support both the new-style and old-style file naming conventions. */ + if (file_exists(dirname(__FILE__) . '/observer_' . $type . '.php')) { + $classfile = 'Log/observer_' . $type . '.php'; + $newstyle = true; + } else { + $classfile = 'Log/' . $type . '.php'; + $newstyle = false; + } + + /* Issue a warning if the old-style conventions are being used. */ + if (!$newstyle) + { + trigger_error('Using old-style Log_observer conventions', + E_USER_WARNING); + } + + /* + * Attempt to include our version of the named class, but don't treat + * a failure as fatal. The caller may have already included their own + * version of the named class. + */ + @include_once $classfile; + + /* If the class exists, return a new instance of it. */ + if (class_exists($class)) { + /* Support both new-style and old-style construction. */ + if ($newstyle) { + $object =& new $class($priority, $conf); + } else { + $object =& new $class($priority); + } + return $object; + } + + return null; + } + + /** + * This is a stub method to make sure that Log_Observer classes do + * something when they are notified of a message. The default behavior + * is to just print the message, which is obviously not desireable in + * practically any situation - which is why you need to override this + * method. :) + * + * @param array $event A hash describing the log event. + */ + function notify($event) + { + print_r($event); + } +} diff --git a/trunk/docs/include/PEAR/Log/sql.php b/trunk/docs/include/PEAR/Log/sql.php new file mode 100644 index 0000000..b076bf8 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/sql.php @@ -0,0 +1,287 @@ + + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + * + * @example sql.php Using the SQL handler. + */ +class Log_sql extends Log +{ + /** + * Variable containing the DSN information. + * @var mixed + * @access private + */ + var $_dsn = ''; + + /** + * String containing the SQL insertion statement. + * + * @var string + * @access private + */ + var $_sql = ''; + + /** + * Array containing our set of DB configuration options. + * @var array + * @access private + */ + var $_options = array('persistent' => true); + + /** + * Object holding the database handle. + * @var object + * @access private + */ + var $_db = null; + + /** + * Resource holding the prepared statement handle. + * @var resource + * @access private + */ + var $_statement = null; + + /** + * Flag indicating that we're using an existing database connection. + * @var boolean + * @access private + */ + var $_existingConnection = false; + + /** + * String holding the database table to use. + * @var string + * @access private + */ + var $_table = 'log_table'; + + /** + * String holding the name of the ID sequence. + * @var string + * @access private + */ + var $_sequence = 'log_id'; + + /** + * Maximum length of the $ident string. This corresponds to the size of + * the 'ident' column in the SQL table. + * @var integer + * @access private + */ + var $_identLimit = 16; + + + /** + * Constructs a new sql logging object. + * + * @param string $name The target SQL table. + * @param string $ident The identification field. + * @param array $conf The connection configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_sql($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_table = $name; + $this->_mask = Log::UPTO($level); + + /* Now that we have a table name, assign our SQL statement. */ + if (!empty($this->_sql)) { + $this->_sql = $conf['sql']; + } else { + $this->_sql = 'INSERT INTO ' . $this->_table . + ' (id, logtime, ident, priority, message)' . + ' VALUES(?, CURRENT_TIMESTAMP, ?, ?, ?)'; + } + + /* If an options array was provided, use it. */ + if (isset($conf['options']) && is_array($conf['options'])) { + $this->_options = $conf['options']; + } + + /* If a specific sequence name was provided, use it. */ + if (!empty($conf['sequence'])) { + $this->_sequence = $conf['sequence']; + } + + /* If a specific sequence name was provided, use it. */ + if (isset($conf['identLimit'])) { + $this->_identLimit = $conf['identLimit']; + } + + /* Now that the ident limit is confirmed, set the ident string. */ + $this->setIdent($ident); + + /* If an existing database connection was provided, use it. */ + if (isset($conf['db'])) { + $this->_db = &$conf['db']; + $this->_existingConnection = true; + $this->_opened = true; + } else { + $this->_dsn = $conf['dsn']; + } + } + + /** + * Opens a connection to the database, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * + * @return boolean True on success, false on failure. + * @access public + */ + function open() + { + if (!$this->_opened) { + /* Use the DSN and options to create a database connection. */ + $this->_db = &DB::connect($this->_dsn, $this->_options); + if (DB::isError($this->_db)) { + return false; + } + + /* Create a prepared statement for repeated use in log(). */ + if (!$this->_prepareStatement()) { + return false; + } + + /* We now consider out connection open. */ + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the connection to the database if it is still open and we were + * the ones that opened it. It is the caller's responsible to close an + * existing connection that was passed to us via $conf['db']. + * + * @return boolean True on success, false on failure. + * @access public + */ + function close() + { + if ($this->_opened && !$this->_existingConnection) { + $this->_opened = false; + $this->_db->freePrepared($this->_statement); + return $this->_db->disconnect(); + } + + return ($this->_opened === false); + } + + /** + * Sets this Log instance's identification string. Note that this + * SQL-specific implementation will limit the length of the $ident string + * to sixteen (16) characters. + * + * @param string $ident The new identification string. + * + * @access public + * @since Log 1.8.5 + */ + function setIdent($ident) + { + $this->_ident = substr($ident, 0, $this->_identLimit); + } + + /** + * Inserts $message to the currently open database. Calls open(), + * if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* If we don't already have our statement object yet, create it. */ + if (!is_object($this->_statement) && !$this->_prepareStatement()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + /* Build our set of values for this log entry. */ + $id = $this->_db->nextId($this->_sequence); + $values = array($id, $this->_ident, $priority, $message); + + /* Execute the SQL query for this log entry insertion. */ + $result =& $this->_db->execute($this->_statement, $values); + if (DB::isError($result)) { + return false; + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Prepare the SQL insertion statement. + * + * @return boolean True if the statement was successfully created. + * + * @access private + * @since Log 1.9.1 + */ + function _prepareStatement() + { + $this->_statement = $this->_db->prepare($this->_sql); + + /* Return success if we didn't generate an error. */ + return (DB::isError($this->_statement) === false); + } +} diff --git a/trunk/docs/include/PEAR/Log/sqlite.php b/trunk/docs/include/PEAR/Log/sqlite.php new file mode 100644 index 0000000..007eef7 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/sqlite.php @@ -0,0 +1,225 @@ + + * @author Jon Parise + * @since Log 1.8.3 + * @package Log + * + * @example sqlite.php Using the Sqlite handler. + */ +class Log_sqlite extends Log +{ + /** + * Array containing the connection defaults + * @var array + * @access private + */ + var $_options = array('mode' => 0666, + 'persistent' => false); + + /** + * Object holding the database handle. + * @var object + * @access private + */ + var $_db = null; + + /** + * Flag indicating that we're using an existing database connection. + * @var boolean + * @access private + */ + var $_existingConnection = false; + + /** + * String holding the database table to use. + * @var string + * @access private + */ + var $_table = 'log_table'; + + + /** + * Constructs a new sql logging object. + * + * @param string $name The target SQL table. + * @param string $ident The identification field. + * @param mixed $conf Can be an array of configuration options used + * to open a new database connection + * or an already opened sqlite connection. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_sqlite($name, $ident = '', &$conf, $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_table = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (is_array($conf)) { + foreach ($conf as $k => $opt) { + $this->_options[$k] = $opt; + } + } else { + // If an existing database connection was provided, use it. + $this->_db =& $conf; + $this->_existingConnection = true; + } + } + + /** + * Opens a connection to the database, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * + * @return boolean True on success, false on failure. + * @access public + */ + function open() + { + if (is_resource($this->_db)) { + $this->_opened = true; + return $this->_createTable(); + } else { + /* Set the connection function based on the 'persistent' option. */ + if (empty($this->_options['persistent'])) { + $connectFunction = 'sqlite_open'; + } else { + $connectFunction = 'sqlite_popen'; + } + + /* Attempt to connect to the database. */ + if ($this->_db = $connectFunction($this->_options['filename'], + (int)$this->_options['mode'], + $error)) { + $this->_opened = true; + return $this->_createTable(); + } + } + + return $this->_opened; + } + + /** + * Closes the connection to the database if it is still open and we were + * the ones that opened it. It is the caller's responsible to close an + * existing connection that was passed to us via $conf['db']. + * + * @return boolean True on success, false on failure. + * @access public + */ + function close() + { + /* We never close existing connections. */ + if ($this->_existingConnection) { + return false; + } + + if ($this->_opened) { + $this->_opened = false; + sqlite_close($this->_db); + } + + return ($this->_opened === false); + } + + /** + * Inserts $message to the currently open database. Calls open(), + * if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + // Extract the string representation of the message. + $message = $this->_extractMessage($message); + + // Build the SQL query for this log entry insertion. + $q = sprintf('INSERT INTO [%s] (logtime, ident, priority, message) ' . + "VALUES ('%s', '%s', %d, '%s')", + $this->_table, + strftime('%Y-%m-%d %H:%M:%S', time()), + sqlite_escape_string($this->_ident), + $priority, + sqlite_escape_string($message)); + if (!($res = @sqlite_unbuffered_query($this->_db, $q))) { + return false; + } + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Checks whether the log table exists and creates it if necessary. + * + * @return boolean True on success or false on failure. + * @access private + */ + function _createTable() + { + $q = "SELECT name FROM sqlite_master WHERE name='" . $this->_table . + "' AND type='table'"; + + $res = sqlite_query($this->_db, $q); + + if (sqlite_num_rows($res) == 0) { + $q = 'CREATE TABLE [' . $this->_table . '] (' . + 'id INTEGER PRIMARY KEY NOT NULL, ' . + 'logtime NOT NULL, ' . + 'ident CHAR(16) NOT NULL, ' . + 'priority INT NOT NULL, ' . + 'message)'; + + if (!($res = sqlite_unbuffered_query($this->_db, $q))) { + return false; + } + } + + return true; + } + +} diff --git a/trunk/docs/include/PEAR/Log/syslog.php b/trunk/docs/include/PEAR/Log/syslog.php new file mode 100644 index 0000000..91a1f15 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/syslog.php @@ -0,0 +1,160 @@ + + * @since Horde 1.3 + * @since Log 1.0 + * @package Log + * + * @example syslog.php Using the syslog handler. + */ +class Log_syslog extends Log +{ + /** + * Integer holding the log facility to use. + * @var string + * @access private + */ + var $_name = LOG_SYSLOG; + + /** + * Constructs a new syslog object. + * + * @param string $name The syslog facility. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_syslog($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + /* Ensure we have a valid integer value for $name. */ + if (empty($name) || !is_int($name)) { + $name = LOG_SYSLOG; + } + + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + } + + /** + * Opens a connection to the system logger, if it has not already + * been opened. This is implicitly called by log(), if necessary. + * @access public + */ + function open() + { + if (!$this->_opened) { + openlog($this->_ident, LOG_PID, $this->_name); + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the connection to the system logger, if it is open. + * @access public + */ + function close() + { + if ($this->_opened) { + closelog(); + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Sends $message to the currently open syslog connection. Calls + * open() if necessary. Also passes the message along to any Log_observer + * instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param int $priority (optional) The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* If the connection isn't open and can't be opened, return failure. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + if (!syslog($this->_toSyslog($priority), $message)) { + return false; + } + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + + /** + * Converts a PEAR_LOG_* constant into a syslog LOG_* constant. + * + * This function exists because, under Windows, not all of the LOG_* + * constants have unique values. Instead, the PEAR_LOG_* were introduced + * for global use, with the conversion to the LOG_* constants kept local to + * to the syslog driver. + * + * @param int $priority PEAR_LOG_* value to convert to LOG_* value. + * + * @return The LOG_* representation of $priority. + * + * @access private + */ + function _toSyslog($priority) + { + static $priorities = array( + PEAR_LOG_EMERG => LOG_EMERG, + PEAR_LOG_ALERT => LOG_ALERT, + PEAR_LOG_CRIT => LOG_CRIT, + PEAR_LOG_ERR => LOG_ERR, + PEAR_LOG_WARNING => LOG_WARNING, + PEAR_LOG_NOTICE => LOG_NOTICE, + PEAR_LOG_INFO => LOG_INFO, + PEAR_LOG_DEBUG => LOG_DEBUG + ); + + /* If we're passed an unknown priority, default to LOG_INFO. */ + if (!is_int($priority) || !in_array($priority, $priorities)) { + return LOG_INFO; + } + + return $priorities[$priority]; + } + +} diff --git a/trunk/docs/include/PEAR/Log/win.php b/trunk/docs/include/PEAR/Log/win.php new file mode 100644 index 0000000..0052672 --- /dev/null +++ b/trunk/docs/include/PEAR/Log/win.php @@ -0,0 +1,255 @@ + + * @since Log 1.7.0 + * @package Log + * + * @example win.php Using the window handler. + */ +class Log_win extends Log +{ + /** + * The name of the output window. + * @var string + * @access private + */ + var $_name = 'LogWindow'; + + /** + * The title of the output window. + * @var string + * @access private + */ + var $_title = 'Log Output Window'; + + /** + * Mapping of log priorities to colors. + * @var array + * @access private + */ + var $_colors = array( + PEAR_LOG_EMERG => 'red', + PEAR_LOG_ALERT => 'orange', + PEAR_LOG_CRIT => 'yellow', + PEAR_LOG_ERR => 'green', + PEAR_LOG_WARNING => 'blue', + PEAR_LOG_NOTICE => 'indigo', + PEAR_LOG_INFO => 'violet', + PEAR_LOG_DEBUG => 'black' + ); + + /** + * String buffer that holds line that are pending output. + * @var array + * @access private + */ + var $_buffer = array(); + + /** + * Constructs a new Log_win object. + * + * @param string $name Ignored. + * @param string $ident The identity string. + * @param array $conf The configuration array. + * @param int $level Log messages up to and including this level. + * @access public + */ + function Log_win($name, $ident = '', $conf = array(), + $level = PEAR_LOG_DEBUG) + { + $this->_id = md5(microtime()); + $this->_name = $name; + $this->_ident = $ident; + $this->_mask = Log::UPTO($level); + + if (isset($conf['title'])) { + $this->_title = $conf['title']; + } + if (isset($conf['colors']) && is_array($conf['colors'])) { + $this->_colors = $conf['colors']; + } + + register_shutdown_function(array(&$this, '_Log_win')); + } + + /** + * Destructor + */ + function _Log_win() + { + if ($this->_opened || (count($this->_buffer) > 0)) { + $this->close(); + } + } + + /** + * The first time open() is called, it will open a new browser window and + * prepare it for output. + * + * This is implicitly called by log(), if necessary. + * + * @access public + */ + function open() + { + if (!$this->_opened) { + $win = $this->_name; + + if (!empty($this->_ident)) { + $identHeader = "$win.document.writeln('Ident')"; + } else { + $identHeader = ''; + } + + echo <<< END_OF_SCRIPT + +END_OF_SCRIPT; + $this->_opened = true; + } + + return $this->_opened; + } + + /** + * Closes the output stream if it is open. If there are still pending + * lines in the output buffer, the output window will be opened so that + * the buffer can be drained. + * + * @access public + */ + function close() + { + /* + * If there are still lines waiting to be written, open the output + * window so that we can drain the buffer. + */ + if (!$this->_opened && (count($this->_buffer) > 0)) { + $this->open(); + } + + if ($this->_opened) { + $this->_writeln(''); + $this->_writeln(''); + $this->_opened = false; + } + + return ($this->_opened === false); + } + + /** + * Writes a single line of text to the output window. + * + * @param string $line The line of text to write. + * + * @access private + */ + function _writeln($line) + { + /* Add this line to our output buffer. */ + $this->_buffer[] = $line; + + /* Buffer the output until this page's headers have been sent. */ + if (!headers_sent()) { + return; + } + + /* If we haven't already opened the output window, do so now. */ + if (!$this->_opened && !$this->open()) { + return false; + } + + /* Drain the buffer to the output window. */ + $win = $this->_name; + foreach ($this->_buffer as $line) { + echo "\n"; + } + + /* Now that the buffer has been drained, clear it. */ + $this->_buffer = array(); + } + + /** + * Logs $message to the output window. The message is also passed along + * to any Log_observer instances that are observing this Log. + * + * @param mixed $message String or object containing the message to log. + * @param string $priority The priority of the message. Valid + * values are: PEAR_LOG_EMERG, PEAR_LOG_ALERT, + * PEAR_LOG_CRIT, PEAR_LOG_ERR, PEAR_LOG_WARNING, + * PEAR_LOG_NOTICE, PEAR_LOG_INFO, and PEAR_LOG_DEBUG. + * @return boolean True on success or false on failure. + * @access public + */ + function log($message, $priority = null) + { + /* If a priority hasn't been specified, use the default value. */ + if ($priority === null) { + $priority = $this->_priority; + } + + /* Abort early if the priority is above the maximum logging level. */ + if (!$this->_isMasked($priority)) { + return false; + } + + /* Extract the string representation of the message. */ + $message = $this->_extractMessage($message); + + list($usec, $sec) = explode(' ', microtime()); + + /* Build the output line that contains the log entry row. */ + $line = ''; + $line .= sprintf('%s.%s', + strftime('%T', $sec), substr($usec, 2, 2)); + if (!empty($this->_ident)) { + $line .= '' . $this->_ident . ''; + } + $line .= '' . ucfirst($this->priorityToString($priority)) . ''; + $line .= sprintf('%s', + $this->_colors[$priority], + preg_replace('/\r\n|\n|\r/', '
', $message)); + $line .= ''; + + $this->_writeln($line); + + $this->_announce(array('priority' => $priority, 'message' => $message)); + + return true; + } + +} diff --git a/trunk/docs/include/PEAR/MDB2.php b/trunk/docs/include/PEAR/MDB2.php new file mode 100644 index 0000000..3e59a99 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2.php @@ -0,0 +1,4211 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: MDB2.php,v 1.255 2006/08/20 21:28:53 lsmith Exp $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +require_once 'PEAR.php'; + +// {{{ Error constants + +/** + * The method mapErrorCode in each MDB2_dbtype implementation maps + * native error codes to one of these. + * + * If you add an error code here, make sure you also add a textual + * version of it in MDB2::errorMessage(). + */ + +define('MDB2_OK', true); +define('MDB2_ERROR', -1); +define('MDB2_ERROR_SYNTAX', -2); +define('MDB2_ERROR_CONSTRAINT', -3); +define('MDB2_ERROR_NOT_FOUND', -4); +define('MDB2_ERROR_ALREADY_EXISTS', -5); +define('MDB2_ERROR_UNSUPPORTED', -6); +define('MDB2_ERROR_MISMATCH', -7); +define('MDB2_ERROR_INVALID', -8); +define('MDB2_ERROR_NOT_CAPABLE', -9); +define('MDB2_ERROR_TRUNCATED', -10); +define('MDB2_ERROR_INVALID_NUMBER', -11); +define('MDB2_ERROR_INVALID_DATE', -12); +define('MDB2_ERROR_DIVZERO', -13); +define('MDB2_ERROR_NODBSELECTED', -14); +define('MDB2_ERROR_CANNOT_CREATE', -15); +define('MDB2_ERROR_CANNOT_DELETE', -16); +define('MDB2_ERROR_CANNOT_DROP', -17); +define('MDB2_ERROR_NOSUCHTABLE', -18); +define('MDB2_ERROR_NOSUCHFIELD', -19); +define('MDB2_ERROR_NEED_MORE_DATA', -20); +define('MDB2_ERROR_NOT_LOCKED', -21); +define('MDB2_ERROR_VALUE_COUNT_ON_ROW', -22); +define('MDB2_ERROR_INVALID_DSN', -23); +define('MDB2_ERROR_CONNECT_FAILED', -24); +define('MDB2_ERROR_EXTENSION_NOT_FOUND',-25); +define('MDB2_ERROR_NOSUCHDB', -26); +define('MDB2_ERROR_ACCESS_VIOLATION', -27); +define('MDB2_ERROR_CANNOT_REPLACE', -28); +define('MDB2_ERROR_CONSTRAINT_NOT_NULL',-29); +define('MDB2_ERROR_DEADLOCK', -30); +define('MDB2_ERROR_CANNOT_ALTER', -31); +define('MDB2_ERROR_MANAGER', -32); +define('MDB2_ERROR_MANAGER_PARSE', -33); +define('MDB2_ERROR_LOADMODULE', -34); +define('MDB2_ERROR_INSUFFICIENT_DATA', -35); +// }}} +// {{{ Verbose constants +/** + * These are just helper constants to more verbosely express parameters to prepare() + */ + +define('MDB2_PREPARE_MANIP', false); +define('MDB2_PREPARE_RESULT', null); + +// }}} +// {{{ Fetchmode constants + +/** + * This is a special constant that tells MDB2 the user hasn't specified + * any particular get mode, so the default should be used. + */ +define('MDB2_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ +define('MDB2_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ +define('MDB2_FETCHMODE_ASSOC', 2); + +/** + * Column data as object properties + */ +define('MDB2_FETCHMODE_OBJECT', 3); + +/** + * For multi-dimensional results: normally the first level of arrays + * is the row number, and the second level indexed by column number or name. + * MDB2_FETCHMODE_FLIPPED switches this order, so the first level of arrays + * is the column name, and the second level the row number. + */ +define('MDB2_FETCHMODE_FLIPPED', 4); + +// }}} +// {{{ Portability mode constants + +/** + * Portability: turn off all portability features. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_NONE', 0); + +/** + * Portability: convert names of tables and fields to case defined in the + * "field_case" option when using the query*(), fetch*() and tableInfo() methods. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_FIX_CASE', 1); + +/** + * Portability: right trim the data output by query*() and fetch*(). + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_RTRIM', 2); + +/** + * Portability: force reporting the number of rows deleted. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_DELETE_COUNT', 4); + +/** + * Portability: not needed in MDB2 (just left here for compatibility to DB) + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_NUMROWS', 8); + +/** + * Portability: makes certain error messages in certain drivers compatible + * with those from other DBMS's. + * + * + mysql, mysqli: change unique/primary key constraints + * MDB2_ERROR_ALREADY_EXISTS -> MDB2_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to MDB2_ERROR_NOSUCHFIELD. + * + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_ERRORS', 16); + +/** + * Portability: convert empty values to null strings in data output by + * query*() and fetch*(). + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_EMPTY_TO_NULL', 32); + +/** + * Portability: removes database/table qualifiers from associative indexes + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES', 64); + +/** + * Portability: turn on all portability features. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_ALL', 127); + +// }}} +// {{{ Globals for class instance tracking + +/** + * These are global variables that are used to track the various class instances + */ + +$GLOBALS['_MDB2_databases'] = array(); +$GLOBALS['_MDB2_dsninfo_default'] = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false, + 'mode' => false, +); + +// }}} +// {{{ class MDB2 + +/** + * The main 'MDB2' class is simply a container class with some static + * methods for creating DB objects as well as some utility functions + * common to all parts of DB. + * + * The object model of MDB2 is as follows (indentation means inheritance): + * + * MDB2 The main MDB2 class. This is simply a utility class + * with some 'static' methods for creating MDB2 objects as + * well as common utility functions for other MDB2 classes. + * + * MDB2_Driver_Common The base for each MDB2 implementation. Provides default + * | implementations (in OO lingo virtual methods) for + * | the actual DB implementations as well as a bunch of + * | query utility functions. + * | + * +-MDB2_Driver_mysql The MDB2 implementation for MySQL. Inherits MDB2_Driver_Common. + * When calling MDB2::factory or MDB2::connect for MySQL + * connections, the object returned is an instance of this + * class. + * +-MDB2_Driver_pgsql The MDB2 implementation for PostGreSQL. Inherits MDB2_Driver_Common. + * When calling MDB2::factory or MDB2::connect for PostGreSQL + * connections, the object returned is an instance of this + * class. + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2 +{ + // {{{ function setOptions(&$db, $options) + + /** + * set option array in an exiting database object + * + * @param MDB2_Driver_Common MDB2 object + * @param array An associative array of option names and their values. + * + * @return mixed MDB2_OK or a PEAR Error object + * + * @access public + */ + function setOptions(&$db, $options) + { + if (is_array($options)) { + foreach ($options as $option => $value) { + $test = $db->setOption($option, $value); + if (PEAR::isError($test)) { + return $test; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ function classExists($classname) + + /** + * Checks if a class exists without triggering __autoload + * + * @param string classname + * + * @return bool true success and false on error + * + * @access public + */ + function classExists($classname) + { + if (version_compare(phpversion(), "5.0", ">=")) { + return class_exists($classname, false); + } + return class_exists($classname); + } + + // }}} + // {{{ function loadClass($class_name, $debug) + + /** + * Loads a PEAR class. + * + * @param string classname to load + * @param bool if errors should be suppressed + * + * @return bool true success or false on failure + * + * @access public + */ + function loadClass($class_name, $debug) + { + if (!MDB2::classExists($class_name)) { + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + if ($debug) { + $include = include_once($file_name); + } else { + $include = @include_once($file_name); + } + if (!$include) { + if (!MDB2::fileExists($file_name)) { + $msg = "unable to find package '$class_name' file '$file_name'"; + } else { + $msg = "unable to load class '$class_name' from file '$file_name'"; + } + $err =& MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, $msg); + return $err; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function &factory($dsn, $options = false) + + /** + * Create a new MDB2 object for the specified database type + * + * IMPORTANT: In order for MDB2 to work properly it is necessary that + * you make sure that you work with a reference of the original + * object instead of a copy (this is a PHP4 quirk). + * + * For example: + * $db =& MDB2::factory($dsn); + * ^^ + * And not: + * $db = MDB2::factory($dsn); + * + * @param mixed 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 object, or false on error + * + * @access public + */ + function &factory($dsn, $options = false) + { + $dsninfo = MDB2::parseDSN($dsn); + if (empty($dsninfo['phptype'])) { + $err =& MDB2::raiseError(MDB2_ERROR_NOT_FOUND, + null, null, 'no RDBMS driver specified'); + return $err; + } + $class_name = 'MDB2_Driver_'.$dsninfo['phptype']; + + $debug = (!empty($options['debug'])); + $err = MDB2::loadClass($class_name, $debug); + if (PEAR::isError($err)) { + return $err; + } + + $db =& new $class_name(); + $db->setDSN($dsninfo); + $err = MDB2::setOptions($db, $options); + if (PEAR::isError($err)) { + return $err; + } + + return $db; + } + + // }}} + // {{{ function &connect($dsn, $options = false) + + /** + * Create a new MDB2 connection object and connect to the specified + * database + * + * IMPORTANT: In order for MDB2 to work properly it is necessary that + * you make sure that you work with a reference of the original + * object instead of a copy (this is a PHP4 quirk). + * + * For example: + * $db =& MDB2::connect($dsn); + * ^^ + * And not: + * $db = MDB2::connect($dsn); + * ^^ + * + * @param mixed 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 connection object, or a MDB2 + * error object on error + * + * @access public + * @see MDB2::parseDSN + */ + function &connect($dsn, $options = false) + { + $db =& MDB2::factory($dsn, $options); + if (PEAR::isError($db)) { + return $db; + } + + $err = $db->connect(); + if (PEAR::isError($err)) { + $dsn = $db->getDSN('string', 'xxx'); + $db->disconnect(); + $err->addUserInfo($dsn); + return $err; + } + + return $db; + } + + // }}} + // {{{ function &singleton($dsn = null, $options = false) + + /** + * Returns a MDB2 connection with the requested DSN. + * A new MDB2 connection object is only created if no object with the + * requested DSN exists yet. + * + * IMPORTANT: In order for MDB2 to work properly it is necessary that + * you make sure that you work with a reference of the original + * object instead of a copy (this is a PHP4 quirk). + * + * For example: + * $db =& MDB2::singleton($dsn); + * ^^ + * And not: + * $db = MDB2::singleton($dsn); + * ^^ + * + * @param mixed 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 connection object, or a MDB2 + * error object on error + * + * @access public + * @see MDB2::parseDSN + */ + function &singleton($dsn = null, $options = false) + { + if ($dsn) { + $dsninfo = MDB2::parseDSN($dsn); + $dsninfo = array_merge($GLOBALS['_MDB2_dsninfo_default'], $dsninfo); + $keys = array_keys($GLOBALS['_MDB2_databases']); + for ($i=0, $j=count($keys); $i<$j; ++$i) { + $tmp_dsn = $GLOBALS['_MDB2_databases'][$keys[$i]]->getDSN('array'); + if (count(array_diff($tmp_dsn, $dsninfo)) == 0) { + MDB2::setOptions($GLOBALS['_MDB2_databases'][$keys[$i]], $options); + return $GLOBALS['_MDB2_databases'][$keys[$i]]; + } + } + } elseif (is_array($GLOBALS['_MDB2_databases']) && reset($GLOBALS['_MDB2_databases'])) { + $db =& $GLOBALS['_MDB2_databases'][key($GLOBALS['_MDB2_databases'])]; + return $db; + } + $db =& MDB2::factory($dsn, $options); + return $db; + } + + // }}} + // {{{ function loadFile($file) + + /** + * load a file (like 'Date') + * + * @param string name of the file in the MDB2 directory (without '.php') + * + * @return string name of the file that was included + * + * @access public + */ + function loadFile($file) + { + $file_name = 'MDB2'.DIRECTORY_SEPARATOR.$file.'.php'; + if (!MDB2::fileExists($file_name)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'unable to find: '.$file_name); + } + if (!include_once($file_name)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'unable to load driver class: '.$file_name); + } + return $file_name; + } + + // }}} + // {{{ function apiVersion() + + /** + * Return the MDB2 API version + * + * @return string the MDB2 API version number + * + * @access public + */ + function apiVersion() + { + return '@package_version@'; + } + + // }}} + // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param mixed int error code + * + * @param int error mode, see PEAR_Error docs + * + * @param mixed If error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * + * @param string Extra debug information. Defaults to the last + * query and native error code. + * + * @param object a PEAR error object + * + * @return PEAR_Error instance of a PEAR Error object + * + * @access private + * @see PEAR_Error + */ + function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + { + $err =& PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); + return $err; + } + + // }}} + // {{{ function isError($data, $code = null) + + /** + * Tell whether a value is a MDB2 error. + * + * @param mixed the value to test + * @param int if is an error object, return true + * only if $code is a string and + * $db->getMessage() == $code or + * $code is an integer and $db->getCode() == $code + * + * @return bool true if parameter is an error + * + * @access public + */ + function isError($data, $code = null) + { + if (is_a($data, 'MDB2_Error')) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() === $code; + } else { + $code = (array)$code; + return in_array($data->getCode(), $code); + } + } + return false; + } + + // }}} + // {{{ function isConnection($value) + + /** + * Tell whether a value is a MDB2 connection + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 connection + * + * @access public + */ + function isConnection($value) + { + return is_a($value, 'MDB2_Driver_Common'); + } + + // }}} + // {{{ function isResult($value) + + /** + * Tell whether a value is a MDB2 result + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 result + * + * @access public + */ + function isResult($value) + { + return is_a($value, 'MDB2_Result'); + } + + // }}} + // {{{ function isResultCommon($value) + + /** + * Tell whether a value is a MDB2 result implementing the common interface + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 result implementing the common interface + * + * @access public + */ + function isResultCommon($value) + { + return is_a($value, 'MDB2_Result_Common'); + } + + // }}} + // {{{ function isStatement($value) + + /** + * Tell whether a value is a MDB2 statement interface + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 statement interface + * + * @access public + */ + function isStatement($value) + { + return is_a($value, 'MDB2_Statement'); + } + + // }}} + // {{{ function errorMessage($value = null) + + /** + * Return a textual error message for a MDB2 error code + * + * @param int|array integer error code, + null to get the current error code-message map, + or an array with a new error code-message map + * + * @return string error message, or false if the error code was + * not recognized + * + * @access public + */ + function errorMessage($value = null) + { + static $errorMessages; + + if (is_array($value)) { + $errorMessages = $value; + return MDB2_OK; + } + + if (!isset($errorMessages)) { + $errorMessages = array( + MDB2_OK => 'no error', + MDB2_ERROR => 'unknown error', + MDB2_ERROR_ALREADY_EXISTS => 'already exists', + MDB2_ERROR_CANNOT_CREATE => 'can not create', + MDB2_ERROR_CANNOT_ALTER => 'can not alter', + MDB2_ERROR_CANNOT_REPLACE => 'can not replace', + MDB2_ERROR_CANNOT_DELETE => 'can not delete', + MDB2_ERROR_CANNOT_DROP => 'can not drop', + MDB2_ERROR_CONSTRAINT => 'constraint violation', + MDB2_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + MDB2_ERROR_DIVZERO => 'division by zero', + MDB2_ERROR_INVALID => 'invalid', + MDB2_ERROR_INVALID_DATE => 'invalid date or time', + MDB2_ERROR_INVALID_NUMBER => 'invalid number', + MDB2_ERROR_MISMATCH => 'mismatch', + MDB2_ERROR_NODBSELECTED => 'no database selected', + MDB2_ERROR_NOSUCHFIELD => 'no such field', + MDB2_ERROR_NOSUCHTABLE => 'no such table', + MDB2_ERROR_NOT_CAPABLE => 'MDB2 backend not capable', + MDB2_ERROR_NOT_FOUND => 'not found', + MDB2_ERROR_NOT_LOCKED => 'not locked', + MDB2_ERROR_SYNTAX => 'syntax error', + MDB2_ERROR_UNSUPPORTED => 'not supported', + MDB2_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + MDB2_ERROR_INVALID_DSN => 'invalid DSN', + MDB2_ERROR_CONNECT_FAILED => 'connect failed', + MDB2_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + MDB2_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + MDB2_ERROR_NOSUCHDB => 'no such database', + MDB2_ERROR_ACCESS_VIOLATION => 'insufficient permissions', + MDB2_ERROR_LOADMODULE => 'error while including on demand module', + MDB2_ERROR_TRUNCATED => 'truncated', + MDB2_ERROR_DEADLOCK => 'deadlock detected', + ); + } + + if (is_null($value)) { + return $errorMessages; + } + + if (PEAR::isError($value)) { + $value = $value->getCode(); + } + + return isset($errorMessages[$value]) ? + $errorMessages[$value] : $errorMessages[MDB2_ERROR]; + } + + // }}} + // {{{ function parseDSN($dsn) + + /** + * Parse a data source name. + * + * Additional keys can be added by appending a URI query string to the + * end of the DSN. + * + * The format of the supplied DSN is in its fullest form: + * + * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true + * + * + * Most variations are allowed: + * + * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 + * phptype://username:password@hostspec/database_name + * phptype://username:password@hostspec + * phptype://username@hostspec + * phptype://hostspec/database + * phptype://hostspec + * phptype(dbsyntax) + * phptype + * + * + * @param string Data Source Name to be parsed + * + * @return array an associative array with the following keys: + * + phptype: Database backend used in PHP (mysql, odbc etc.) + * + dbsyntax: Database used with regards to SQL syntax etc. + * + protocol: Communication protocol to use (tcp, unix etc.) + * + hostspec: Host specification (hostname[:port]) + * + database: Database to use on the DBMS server + * + username: User name for login + * + password: Password for login + * + * @access public + * @author Tomas V.V.Cox + */ + function parseDSN($dsn) + { + $parsed = $GLOBALS['_MDB2_dsninfo_default']; + + if (is_array($dsn)) { + $dsn = array_merge($parsed, $dsn); + if (!$dsn['dbsyntax']) { + $dsn['dbsyntax'] = $dsn['phptype']; + } + return $dsn; + } + + // Find phptype and dbsyntax + if (($pos = strpos($dsn, '://')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 3); + } else { + $str = $dsn; + $dsn = null; + } + + // Get phptype and dbsyntax + // $str => phptype(dbsyntax) + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { + $parsed['phptype'] = $arr[1]; + $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + if (!count($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strrpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = rawurldecode(substr($str, 0, $pos)); + $parsed['password'] = rawurldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = rawurldecode($str); + } + } + + // Find protocol and hostspec + + // $dsn => proto(proto_opts)/database + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + $proto = $match[1]; + $proto_opts = $match[2] ? $match[2] : false; + $dsn = $match[3]; + + // $dsn => protocol+hostspec/database (old format) + } else { + if (strpos($dsn, '+') !== false) { + list($proto, $dsn) = explode('+', $dsn, 2); + } + if (strpos($dsn, '/') !== false) { + list($proto_opts, $dsn) = explode('/', $dsn, 2); + } else { + $proto_opts = $dsn; + $dsn = null; + } + } + + // process the different protocol options + $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; + $proto_opts = rawurldecode($proto_opts); + if (strpos($proto_opts, ':') !== false) { + list($proto_opts, $parsed['port']) = explode(':', $proto_opts); + } + if ($parsed['protocol'] == 'tcp') { + $parsed['hostspec'] = $proto_opts; + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if ($dsn) { + // /database + if (($pos = strpos($dsn, '?')) === false) { + $parsed['database'] = $dsn; + // /database?param1=value1¶m2=value2 + } else { + $parsed['database'] = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 1); + if (strpos($dsn, '&') !== false) { + $opts = explode('&', $dsn); + } else { // database?param1=value1 + $opts = array($dsn); + } + foreach ($opts as $opt) { + list($key, $value) = explode('=', $opt); + if (!isset($parsed[$key])) { + // don't allow params overwrite + $parsed[$key] = rawurldecode($value); + } + } + } + } + + return $parsed; + } + + // }}} + // {{{ function fileExists($file) + + /** + * Checks if a file exists in the include path + * + * @param string filename + * + * @return bool true success and false on error + * + * @access public + */ + function fileExists($file) + { + // safe_mode does notwork with is_readable() + if (ini_get('safe_mode')) { + $fp = @fopen($file, 'r', true); + if (is_resource($fp)) { + @fclose($fp); + return true; + } + } else { + $dirs = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($dirs as $dir) { + if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) { + return true; + } + } + } + return false; + } + // }}} +} + +// }}} +// {{{ class MDB2_Error extends PEAR_Error + +/** + * MDB2_Error implements a class for reporting portable database error + * messages. + * + * @package MDB2 + * @category Database + * @author Stig Bakken + */ +class MDB2_Error extends PEAR_Error +{ + // {{{ constructor: function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null) + + /** + * MDB2_Error constructor. + * + * @param mixed MDB2 error code, or string with error message. + * @param int what 'error mode' to operate in + * @param int what error level to use for $mode & PEAR_ERROR_TRIGGER + * @param smixed additional debug info, such as the last query + */ + function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null) + { + $this->PEAR_Error('MDB2 Error: '.MDB2::errorMessage($code), $code, + $mode, $level, $debuginfo); + } + + // }}} +} + +// }}} +// {{{ class MDB2_Driver_Common extends PEAR + +/** + * MDB2_Driver_Common: Base class that is extended by each MDB2 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Common extends PEAR +{ + // {{{ Variables (Properties) + + /** + * index of the MDB2 object within the $GLOBALS['_MDB2_databases'] array + * @var int + * @access public + */ + var $db_index = 0; + + /** + * DSN used for the next query + * @var array + * @access protected + */ + var $dsn = array(); + + /** + * DSN that was used to create the current connection + * @var array + * @access protected + */ + var $connected_dsn = array(); + + /** + * connection resource + * @var mixed + * @access protected + */ + var $connection = 0; + + /** + * if the current opened connection is a persistent connection + * @var bool + * @access protected + */ + var $opened_persistent; + + /** + * the name of the database for the next query + * @var string + * @access protected + */ + var $database_name = ''; + + /** + * the name of the database currently selected + * @var string + * @access protected + */ + var $connected_database_name = ''; + + /** + * server version information + * @var string + * @access protected + */ + var $connected_server_info = ''; + + /** + * list of all supported features of the given driver + * @var array + * @access public + */ + var $supported = array( + 'sequences' => false, + 'indexes' => false, + 'affected_rows' => false, + 'summary_functions' => false, + 'order_by_text' => false, + 'transactions' => false, + 'savepoints' => false, + 'current_id' => false, + 'limit_queries' => false, + 'LOBs' => false, + 'replace' => false, + 'sub_selects' => false, + 'auto_increment' => false, + 'primary_key' => false, + 'result_introspection' => false, + 'prepared_statements' => false, + 'identifier_quoting' => false, + 'pattern_escaping' => false, + ); + + /** + * Array of supported options that can be passed to the MDB2 instance. + * + * The options can be set during object creation, using + * MDB2::connect(), MDB2::factory() or MDB2::singleton(). The options can + * also be set after the object is created, using MDB2::setOptions() or + * MDB2_Driver_Common::setOption(). + * The list of available option includes: + *
    + *
  • $options['ssl'] -> boolean: determines if ssl should be used for connections
  • + *
  • $options['field_case'] -> CASE_LOWER|CASE_UPPER: determines what case to force on field/table names
  • + *
  • $options['disable_query'] -> boolean: determines if queries should be executed
  • + *
  • $options['result_class'] -> string: class used for result sets
  • + *
  • $options['buffered_result_class'] -> string: class used for buffered result sets
  • + *
  • $options['result_wrap_class'] -> string: class used to wrap result sets into
  • + *
  • $options['result_buffering'] -> boolean should results be buffered or not?
  • + *
  • $options['fetch_class'] -> string: class to use when fetch mode object is used
  • + *
  • $options['persistent'] -> boolean: persistent connection?
  • + *
  • $options['debug'] -> integer: numeric debug level
  • + *
  • $options['debug_handler'] -> string: function/method that captures debug messages
  • + *
  • $options['debug_expanded_output'] -> bool: BC option to determine if more context information should be send to the debug handler
  • + *
  • $options['default_text_field_length'] -> integer: default text field length to use
  • + *
  • $options['lob_buffer_length'] -> integer: LOB buffer length
  • + *
  • $options['log_line_break'] -> string: line-break format
  • + *
  • $options['idxname_format'] -> string: pattern for index name
  • + *
  • $options['seqname_format'] -> string: pattern for sequence name
  • + *
  • $options['savepoint_format'] -> string: pattern for auto generated savepoint names
  • + *
  • $options['seqcol_name'] -> string: sequence column name
  • + *
  • $options['quote_identifier'] -> boolean: if identifier quoting should be done when check_option is used
  • + *
  • $options['use_transactions'] -> boolean: if transaction use should be enabled
  • + *
  • $options['decimal_places'] -> integer: number of decimal places to handle
  • + *
  • $options['portability'] -> integer: portability constant
  • + *
  • $options['modules'] -> array: short to long module name mapping for __call()
  • + *
  • $options['emulate_prepared'] -> boolean: force prepared statements to be emulated
  • + *
  • $options['datatype_map'] -> array: map user defined datatypes to other primitive datatypes
  • + *
  • $options['datatype_map_callback'] -> array: callback function/method that should be called
  • + *
+ * + * @var array + * @access public + * @see MDB2::connect() + * @see MDB2::factory() + * @see MDB2::singleton() + * @see MDB2_Driver_Common::setOption() + */ + var $options = array( + 'ssl' => false, + 'field_case' => CASE_LOWER, + 'disable_query' => false, + 'result_class' => 'MDB2_Result_%s', + 'buffered_result_class' => 'MDB2_BufferedResult_%s', + 'result_wrap_class' => false, + 'result_buffering' => true, + 'fetch_class' => 'stdClass', + 'persistent' => false, + 'debug' => 0, + 'debug_handler' => 'MDB2_defaultDebugOutput', + 'debug_expanded_output' => false, + 'default_text_field_length' => 4096, + 'lob_buffer_length' => 8192, + 'log_line_break' => "\n", + 'idxname_format' => '%s_idx', + 'seqname_format' => '%s_seq', + 'savepoint_format' => 'MDB2_SAVEPOINT_%s', + 'seqcol_name' => 'sequence', + 'quote_identifier' => false, + 'use_transactions' => true, + 'decimal_places' => 2, + 'portability' => MDB2_PORTABILITY_ALL, + 'modules' => array( + 'ex' => 'Extended', + 'dt' => 'Datatype', + 'mg' => 'Manager', + 'rv' => 'Reverse', + 'na' => 'Native', + 'fc' => 'Function', + ), + 'emulate_prepared' => false, + 'datatype_map' => array(), + 'datatype_map_callback' => array(), + ); + + /** + * escape character + * @var string + * @access protected + */ + var $escape_quotes = "'"; + + /** + * escape character for pattern + * @var string + * @access protected + */ + var $escape_pattern = ''; + + /** + * comparision wildcards + * @var array + * @access protected + */ + var $wildcards = array('%', '_'); + + /** + * escape character + * @var string + * @access protected + */ + var $escape_identifier = '"'; + + /** + * column alias keyword + * @var string + * @access protected + */ + var $as_keyword = ' AS '; + + /** + * warnings + * @var array + * @access protected + */ + var $warnings = array(); + + /** + * string with the debugging information + * @var string + * @access public + */ + var $debug_output = ''; + + /** + * determine if there is an open transaction + * @var bool + * @access protected + */ + var $in_transaction = false; + + /** + * the smart transaction nesting depth + * @var int + * @access protected + */ + var $nested_transaction_counter = null; + + /** + * the first error that occured inside a nested transaction + * @var MDB2_Error|bool + * @access protected + */ + var $has_transaction_error = false; + + /** + * result offset used in the next query + * @var int + * @access protected + */ + var $offset = 0; + + /** + * result limit used in the next query + * @var int + * @access protected + */ + var $limit = 0; + + /** + * Database backend used in PHP (mysql, odbc etc.) + * @var string + * @access protected + */ + var $phptype; + + /** + * Database used with regards to SQL syntax etc. + * @var string + * @access protected + */ + var $dbsyntax; + + /** + * the last query sent to the driver + * @var string + * @access public + */ + var $last_query; + + /** + * the default fetchmode used + * @var int + * @access protected + */ + var $fetchmode = MDB2_FETCHMODE_ORDERED; + + /** + * array of module instances + * @var array + * @access protected + */ + var $modules = array(); + + /** + * determines of the PHP4 destructor emulation has been enabled yet + * @var array + * @access protected + */ + var $destructor_registered = true; + + // }}} + // {{{ constructor: function __construct() + + /** + * Constructor + */ + function __construct() + { + end($GLOBALS['_MDB2_databases']); + $db_index = key($GLOBALS['_MDB2_databases']) + 1; + $GLOBALS['_MDB2_databases'][$db_index] = &$this; + $this->db_index = $db_index; + } + + // }}} + // {{{ function MDB2_Driver_Common() + + /** + * PHP 4 Constructor + */ + function MDB2_Driver_Common() + { + $this->destructor_registered = false; + $this->__construct(); + } + + // }}} + // {{{ destructor: function __destruct() + + /** + * Destructor + */ + function __destruct() + { + $this->disconnect(false); + } + + // }}} + // {{{ function free() + + /** + * Free the internal references so that the instance can be destroyed + * + * @return bool true on success, false if result is invalid + * + * @access public + */ + function free() + { + unset($GLOBALS['_MDB2_databases'][$this->db_index]); + unset($this->db_index); + return MDB2_OK; + } + + // }}} + // {{{ function __toString() + + /** + * String conversation + * + * @return string representation of the object + * + * @access public + */ + function __toString() + { + $info = get_class($this); + $info.= ': (phptype = '.$this->phptype.', dbsyntax = '.$this->dbsyntax.')'; + if ($this->connection) { + $info.= ' [connected]'; + } + return $info; + } + + // }}} + // {{{ function errorInfo($error = null) + + /** + * This method is used to collect information about an error + * + * @param mixed error codeor ressource + * + * @return array with MDB2 errorcode, native error code, native message + * + * @access public + */ + function errorInfo($error = null) + { + return array($error, null, null); + } + + // }}} + // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param mixed integer error code, or a PEAR error object (all other + * parameters are ignored if this parameter is an object + * @param int error mode, see PEAR_Error docs + * @param mixed If error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string Extra debug information. Defaults to the last + * query and native error code. + * @param string name of the method that triggered the error + * + * @return PEAR_Error instance of a PEAR Error object + * + * @access public + * @see PEAR_Error + */ + function &raiseError($code = null, $mode = null, $options = null, $userinfo = null, $method = null) + { + $userinfo = "[Error message: $userinfo]\n"; + // The error is yet a MDB2 error object + if (PEAR::isError($code)) { + // because we use the static PEAR::raiseError, our global + // handler should be used if it is set + if (is_null($mode) && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + if (is_null($userinfo)) { + $userinfo = $code->getUserinfo(); + } + $code = $code->getCode(); + } elseif (isset($this->connection)) { + if (!empty($this->last_query)) { + $userinfo.= "[Last query: {$this->last_query}]\n"; + } + $native_errno = $native_msg = null; + list($code, $native_errno, $native_msg) = $this->errorInfo($code); + if (!is_null($native_errno) && $native_errno !== '') { + $userinfo.= "[Native code: $native_errno]\n"; + } + if (!is_null($native_msg) && $native_msg !== '') { + $userinfo.= "[Native message: ". strip_tags($native_msg) ."]\n"; + } + if (!is_null($method)) { + $userinfo = $method.': '.$userinfo; + } + } + + $err =& PEAR::raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); + if ($err->getMode() !== PEAR_ERROR_RETURN + && isset($this->nested_transaction_counter) && !$this->has_transaction_error) { + $this->has_transaction_error =& $err; + } + return $err; + } + + // }}} + // {{{ function resetWarnings() + + /** + * reset the warning array + * + * @return void + * + * @access public + */ + function resetWarnings() + { + $this->warnings = array(); + } + + // }}} + // {{{ function getWarnings() + + /** + * Get all warnings in reverse order. + * This means that the last warning is the first element in the array + * + * @return array with warnings + * + * @access public + * @see resetWarnings() + */ + function getWarnings() + { + return array_reverse($this->warnings); + } + + // }}} + // {{{ function setFetchMode($fetchmode, $object_class = 'stdClass') + + /** + * Sets which fetch mode should be used by default on queries + * on this connection + * + * @param int MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC + * or MDB2_FETCHMODE_OBJECT + * @param string the class name of the object to be returned + * by the fetch methods when the + * MDB2_FETCHMODE_OBJECT mode is selected. + * If no class is specified by default a cast + * to object from the assoc array row will be + * done. There is also the possibility to use + * and extend the 'MDB2_row' class. + * + * @return mixed MDB2_OK or MDB2 Error Object + * + * @access public + * @see MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC, MDB2_FETCHMODE_OBJECT + */ + function setFetchMode($fetchmode, $object_class = 'stdClass') + { + switch ($fetchmode) { + case MDB2_FETCHMODE_OBJECT: + $this->options['fetch_class'] = $object_class; + case MDB2_FETCHMODE_ORDERED: + case MDB2_FETCHMODE_ASSOC: + $this->fetchmode = $fetchmode; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'invalid fetchmode mode', __FUNCTION__); + } + + return MDB2_OK; + } + + // }}} + // {{{ function setOption($option, $value) + + /** + * set the option for the db class + * + * @param string option name + * @param mixed value for the option + * + * @return mixed MDB2_OK or MDB2 Error Object + * + * @access public + */ + function setOption($option, $value) + { + if (array_key_exists($option, $this->options)) { + $this->options[$option] = $value; + return MDB2_OK; + } + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown option $option", __FUNCTION__); + } + + // }}} + // {{{ function getOption($option) + + /** + * Returns the value of an option + * + * @param string option name + * + * @return mixed the option value or error object + * + * @access public + */ + function getOption($option) + { + if (array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown option $option", __FUNCTION__); + } + + // }}} + // {{{ function debug($message, $scope = '', $is_manip = null) + + /** + * set a debug message + * + * @param string message that should be appended to the debug variable + * @param string usually the method name that triggered the debug call: + * for example 'query', 'prepare', 'execute', 'parameters', + * 'beginTransaction', 'commit', 'rollback' + * @param array contains context information about the debug() call + * common keys are: is_manip, time, result etc. + * + * @return void + * + * @access public + */ + function debug($message, $scope = '', $context = array()) + { + if ($this->options['debug'] && $this->options['debug_handler']) { + if (!$this->options['debug_expanded_output']) { + if (!empty($context['when']) && $context['when'] !== 'pre') { + return null; + } + $context = empty($context['is_manip']) ? false : $context['is_manip']; + } + return call_user_func_array($this->options['debug_handler'], array(&$this, $scope, $message, $context)); + } + return null; + } + + // }}} + // {{{ function getDebugOutput() + + /** + * output debug info + * + * @return string content of the debug_output class variable + * + * @access public + */ + function getDebugOutput() + { + return $this->debug_output; + } + + // }}} + // {{{ function escape($text) + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + if ($this->escape_quotes !== "'") { + $text = str_replace($this->escape_quotes, $this->escape_quotes.$this->escape_quotes, $text); + } + + if ($escape_wildcards) { + $text = $this->escapePattern($text); + } + $text = str_replace("'", $this->escape_quotes . "'", $text); + return $text; + } + + // }}} + // {{{ function escapePattern($text) + + /** + * Quotes pattern (% and _) characters in a string) + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @param string the input string to quote + * + * @return string quoted string + * + * @access public + */ + function escapePattern($text) + { + if ($this->escape_pattern) { + $text = str_replace($this->escape_pattern, $this->escape_pattern . $this->escape_pattern, $text); + foreach ($this->wildcards as $wildcard) { + $text = str_replace($wildcard, $this->escape_pattern . $wildcard, $text); + } + } + return $text; + } + + // }}} + // {{{ function quoteIdentifier($str, $check_option = false) + + /** + * Quote a string so it can be safely used as a table or column name + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + pgsql + * + sqlite + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string identifier name to be quoted + * @param bool check the 'quote_identifier' option + * + * @return string quoted identifier string + * + * @access public + */ + function quoteIdentifier($str, $check_option = false) + { + if ($check_option && !$this->options['quote_identifier']) { + return $str; + } + $str = str_replace($this->escape_identifier, $this->escape_identifier.$this->escape_identifier, $str); + return $this->escape_identifier . $str . $this->escape_identifier; + } + + // }}} + // {{{ function getAsKeyword() + + /** + * Gets the string to alias column + * + * @return string to use when aliasing a column + */ + function getAsKeyword() + { + return $this->as_keyword; + } + + // }}} + // {{{ function getConnection() + + /** + * Returns a native connection + * + * @return mixed a valid MDB2 connection object, + * or a MDB2 error object on error + * + * @access public + */ + function getConnection() + { + $result = $this->connect(); + if (PEAR::isError($result)) { + return $result; + } + return $this->connection; + } + + // }}} + // {{{ function _fixResultArrayValues(&$row, $mode) + + /** + * Do all necessary conversions on result arrays to fix DBMS quirks + * + * @param array the array to be fixed (passed by reference) + * @param array bit-wise addition of the required portability modes + * + * @return void + * + * @access protected + */ + function _fixResultArrayValues(&$row, $mode) + { + switch ($mode) { + case MDB2_PORTABILITY_EMPTY_TO_NULL: + foreach ($row as $key => $value) { + if ($value === '') { + $row[$key] = null; + } + } + break; + case MDB2_PORTABILITY_RTRIM: + foreach ($row as $key => $value) { + if (is_string($value)) { + $row[$key] = rtrim($value); + } + } + break; + case MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES: + $tmp_row = array(); + foreach ($row as $key => $value) { + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL): + foreach ($row as $key => $value) { + if ($value === '') { + $row[$key] = null; + } elseif (is_string($value)) { + $row[$key] = rtrim($value); + } + } + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if (is_string($value)) { + $value = rtrim($value); + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if ($value === '') { + $value = null; + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if ($value === '') { + $value = null; + } elseif (is_string($value)) { + $value = rtrim($value); + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + } + } + + // }}} + // {{{ function &loadModule($module, $property = null, $phptype_specific = null) + + /** + * loads a module + * + * @param string name of the module that should be loaded + * (only used for error messages) + * @param string name of the property into which the class will be loaded + * @param bool if the class to load for the module + is specific to the phptype + * @param object on success a reference to the given module is returned + * and on failure a PEAR error + * + * @access public + */ + + function &loadModule($module, $property = null, $phptype_specific = null) + { + if (!$property) { + $property = strtolower($module); + } + + if (!isset($this->{$property})) { + $version = $phptype_specific; + if ($phptype_specific !== false) { + $version = true; + $class_name = 'MDB2_Driver_'.$module.'_'.$this->phptype; + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + } + if ($phptype_specific === false + || (!MDB2::classExists($class_name) && !MDB2::fileExists($file_name)) + ) { + $version = false; + $class_name = 'MDB2_'.$module; + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + } + + $err = MDB2::loadClass($class_name, $this->getOption('debug')); + if (PEAR::isError($err)) { + return $err; + } + + // load modul in a specific version + if ($version) { + if (method_exists($class_name, 'getClassName')) { + $class_name_new = call_user_func(array($class_name, 'getClassName'), $this->db_index); + if ($class_name != $class_name_new) { + $class_name = $class_name_new; + $err = MDB2::loadClass($class_name, $this->getOption('debug')); + if (PEAR::isError($err)) { + return $err; + } + } + } + } + + if (!class_exists($class_name)) { + $err =& $this->raiseError(MDB2_ERROR_LOADMODULE, null, null, + "unable to load module '$module' into property '$property'", __FUNCTION__); + return $err; + } + $this->{$property} =& new $class_name($this->db_index); + $this->modules[$module] =& $this->{$property}; + if ($version) { + // this will be used in the connect method to determine if the module + // needs to be loaded with a different version if the server + // version changed in between connects + $this->loaded_version_modules[] = $property; + } + } + + return $this->{$property}; + } + + // }}} + // {{{ function __call($method, $params) + + /** + * Calls a module method using the __call magic method + * + * @param string Method name. + * @param array Arguments. + * + * @return mixed Returned value. + */ + function __call($method, $params) + { + $module = null; + if (preg_match('/^([a-z]+)([A-Z])(.*)$/', $method, $match) + && isset($this->options['modules'][$match[1]]) + ) { + $module = $this->options['modules'][$match[1]]; + $method = strtolower($match[2]).$match[3]; + if (!isset($this->modules[$module]) || !is_object($this->modules[$module])) { + $result =& $this->loadModule($module); + if (PEAR::isError($result)) { + return $result; + } + } + } else { + foreach ($this->modules as $key => $foo) { + if (is_object($this->modules[$key]) + && method_exists($this->modules[$key], $method) + ) { + $module = $key; + break; + } + } + } + if (!is_null($module)) { + return call_user_func_array(array(&$this->modules[$module], $method), $params); + } + trigger_error(sprintf('Call to undefined function: %s::%s().', get_class($this), $method), E_USER_ERROR); + } + + // }}} + // {{{ function beginTransaction($savepoint = null) + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + + // }}} + // {{{ function commit($savepoint = null) + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'commiting transactions is not supported', __FUNCTION__); + } + + // }}} + // {{{ function rollback($savepoint = null) + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'rolling back transactions is not supported', __FUNCTION__); + } + + // }}} + // {{{ function inTransaction($ignore_nested = false) + + /** + * If a transaction is currently open. + * + * @param bool if the nested transaction count should be ignored + * @return int|bool - an integer with the nesting depth is returned if a + * nested transaction is open + * - true is returned for a normal open transaction + * - false is returned if no transaction is open + * + * @access public + */ + function inTransaction($ignore_nested = false) + { + if (!$ignore_nested && isset($this->nested_transaction_counter)) { + return $this->nested_transaction_counter; + } + return $this->in_transaction; + } + + // }}} + // {{{ function setTransactionIsolation($isolation) + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @param array some transaction options: + * 'wait' => 'WAIT' | 'NO WAIT' + * 'rw' => 'READ WRITE' | 'READ ONLY' + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation, $options = array()) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level setting is not supported', __FUNCTION__); + } + + // }}} + // {{{ function beginNestedTransaction($savepoint = false) + + /** + * Start a nested transaction. + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @return mixed MDB2_OK on success/savepoint name, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function beginNestedTransaction() + { + if ($this->in_transaction) { + ++$this->nested_transaction_counter; + $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter); + if ($this->supports('savepoints') && $savepoint) { + return $this->beginTransaction($savepoint); + } + return MDB2_OK; + } + $this->has_transaction_error = false; + $result = $this->beginTransaction(); + $this->nested_transaction_counter = 1; + return $result; + } + + // }}} + // {{{ function completeNestedTransaction($force_rollback = false, $release = false) + + /** + * Finish a nested transaction by rolling back if an error occured or + * committing otherwise. + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @param bool if the transaction should be rolled back regardless + * even if no error was set within the nested transaction + * @return mixed MDB_OK on commit/counter decrementing, false on rollback + * and a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function completeNestedTransaction($force_rollback = false) + { + if ($this->nested_transaction_counter > 1) { + $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter); + if ($this->supports('savepoints') && $savepoint) { + if ($force_rollback || $this->has_transaction_error) { + $result = $this->rollback($savepoint); + if (!PEAR::isError($result)) { + $result = false; + $this->has_transaction_error = false; + } + } else { + $result = $this->commit($savepoint); + } + } else { + $result = MDB2_OK; + } + --$this->nested_transaction_counter; + return $result; + } + + $this->nested_transaction_counter = null; + $result = MDB2_OK; + + // transaction has not yet been rolled back + if ($this->in_transaction) { + if ($force_rollback || $this->has_transaction_error) { + $result = $this->rollback(); + if (!PEAR::isError($result)) { + $result = false; + } + } else { + $result = $this->commit(); + } + } + $this->has_transaction_error = false; + return $result; + } + + // }}} + // {{{ function failNestedTransaction($error = null, $immediately = false) + + /** + * Force setting nested transaction to failed. + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @param mixed value to return in getNestededTransactionError() + * @param bool if the transaction should be rolled back immediately + * @return bool MDB2_OK + * + * @access public + * @since 2.1.1 + */ + function failNestedTransaction($error = null, $immediately = false) + { + if (is_null($error)) { + $error = $this->has_transaction_error ? $this->has_transaction_error : true; + } elseif (!$error) { + $error = true; + } + $this->has_transaction_error = $error; + if (!$immediately) { + return MDB2_OK; + } + return $this->rollback(); + } + + // }}} + // {{{ function getNestedTransactionError() + + /** + * The first error that occured since the transaction start. + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @return MDB2_Error|bool MDB2 error object if an error occured or false. + * + * @access public + * @since 2.1.1 + */ + function getNestedTransactionError() + { + return $this->has_transaction_error; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + function connect() + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ setCharset($charset, $connection = null) + + /** + * Set the charset on the current connection + * + * @param string charset + * @param resource connection handle + * + * @return true on success, MDB2 Error Object on failure + */ + function setCharset($charset, $connection = null) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function disconnect($force = true) + + /** + * Log out and disconnect from the database. + * + * @param bool if the disconnect should be forced even if the + * connection is opened persistently + * + * @return mixed true on success, false if not connected and error + * object on error + * + * @access public + */ + function disconnect($force = true) + { + $this->connection = 0; + $this->connected_dsn = array(); + $this->connected_database_name = ''; + $this->opened_persistent = null; + $this->connected_server_info = ''; + $this->in_transaction = null; + $this->nested_transaction_counter = 0; + return MDB2_OK; + } + + // }}} + // {{{ function setDatabase($name) + + /** + * Select a different database + * + * @param string name of the database that should be selected + * + * @return string name of the database previously connected to + * + * @access public + */ + function setDatabase($name) + { + $previous_database_name = (isset($this->database_name)) ? $this->database_name : ''; + $this->database_name = $name; + $this->disconnect(false); + return $previous_database_name; + } + + // }}} + // {{{ function getDatabase() + + /** + * Get the current database + * + * @return string name of the database + * + * @access public + */ + function getDatabase() + { + return $this->database_name; + } + + // }}} + // {{{ function setDSN($dsn) + + /** + * set the DSN + * + * @param mixed DSN string or array + * + * @return MDB2_OK + * + * @access public + */ + function setDSN($dsn) + { + $dsn_default = $GLOBALS['_MDB2_dsninfo_default']; + $dsn = MDB2::parseDSN($dsn); + if (array_key_exists('database', $dsn)) { + $this->database_name = $dsn['database']; + unset($dsn['database']); + } + $this->dsn = array_merge($dsn_default, $dsn); + return $this->disconnect(false); + } + + // }}} + // {{{ function getDSN($type = 'string', $hidepw = false) + + /** + * return the DSN as a string + * + * @param string format to return ("array", "string") + * @param string string to hide the password with + * + * @return mixed DSN in the chosen type + * + * @access public + */ + function getDSN($type = 'string', $hidepw = false) + { + $dsn = array_merge($GLOBALS['_MDB2_dsninfo_default'], $this->dsn); + $dsn['phptype'] = $this->phptype; + $dsn['database'] = $this->database_name; + if ($hidepw) { + $dsn['password'] = $hidepw; + } + switch ($type) { + // expand to include all possible options + case 'string': + $dsn = $dsn['phptype']. + ($dsn['dbsyntax'] ? ('('.$dsn['dbsyntax'].')') : ''). + '://'.$dsn['username'].':'. + $dsn['password'].'@'.$dsn['hostspec']. + ($dsn['port'] ? (':'.$dsn['port']) : ''). + '/'.$dsn['database']; + break; + case 'array': + default: + break; + } + return $dsn; + } + + // }}} + // {{{ function &standaloneQuery($query, $types = null, $is_manip = false) + + /** + * execute a query as database administrator + * + * @param string the SQL query + * @param mixed array that contains the types of the columns in + * the result set + * @param bool if the query is a manipulation query + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function &standaloneQuery($query, $types = null, $is_manip = false) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $result =& $this->_doQuery($query, $is_manip, $connection, false); + if (PEAR::isError($result)) { + return $result; + } + + if ($is_manip) { + $affected_rows = $this->_affectedRows($connection, $result); + return $affected_rows; + } + $result =& $this->_wrapResult($result, $types, true, false, $limit, $offset); + return $result; + } + + // }}} + // {{{ function _modifyQuery($query, $is_manip, $limit, $offset) + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string query to modify + * @param bool if it is a DML query + * @param int limit the number of rows + * @param int start reading from given offset + * + * @return string modified query + * + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + return $query; + } + + // }}} + // {{{ function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) + + /** + * Execute a query + * @param string query + * @param bool if the query is a manipulation query + * @param resource connection handle + * @param string database name + * + * @return result or error object + * + * @access protected + */ + function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $err =& $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $err; + } + + // }}} + // {{{ function _affectedRows($connection, $result = null) + + /** + * Returns the number of rows affected + * + * @param resource result handle + * @param resource connection handle + * + * @return mixed MDB2 Error Object or the number of rows affected + * + * @access private + */ + function _affectedRows($connection, $result = null) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function exec($query) + + /** + * Execute a manipulation query to the database and return any the affected rows + * + * @param string the SQL query + * + * @return mixed affected rows on success, a MDB2 error on failure + * + * @access public + */ + function exec($query) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, true, $limit, $offset); + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $result =& $this->_doQuery($query, true, $connection, $this->database_name); + if (PEAR::isError($result)) { + return $result; + } + + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ function &query($query, $types = null, $result_class = true, $result_wrap_class = false) + + /** + * Send a query to the database and return any results + * + * @param string the SQL query + * @param mixed array that contains the types of the columns in + * the result set + * @param mixed string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * @param object a result handle on success, a MDB2 error on failure + * + * @return mixed an MDB2_Result, a MDB2 error on failure + * + * @access public + */ + function &query($query, $types = null, $result_class = true, $result_wrap_class = false) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, false, $limit, $offset); + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $result =& $this->_doQuery($query, false, $connection, $this->database_name); + if (PEAR::isError($result)) { + return $result; + } + + $result =& $this->_wrapResult($result, $types, $result_class, $result_wrap_class, $limit, $offset); + return $result; + } + + // }}} + // {{{ function &_wrapResult($result, $types = array(), $result_class = true, $result_wrap_class = false, $limit = null, $offset = null) + + /** + * wrap a result set into the correct class + * + * @param resource result handle + * @param mixed array that contains the types of the columns in + * the result set + * @param mixed string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * @param string number of rows to select + * @param string first row to select + * + * @return mixed an MDB2_Result, a MDB2 error on failure + * + * @access protected + */ + function &_wrapResult($result, $types = array(), $result_class = true, + $result_wrap_class = false, $limit = null, $offset = null) + { + if ($types === true) { + if ($this->supports('result_introspection')) { + $this->loadModule('Reverse', null, true); + $tableInfo = $this->reverse->tableInfo($result); + if (PEAR::isError($tableInfo)) { + return $tableInfo; + } + $types = array(); + foreach ($tableInfo as $field) { + $types[] = $field['mdb2type']; + } + } else { + $types = null; + } + } + + if ($result_class === true) { + $result_class = $this->options['result_buffering'] + ? $this->options['buffered_result_class'] : $this->options['result_class']; + } + + if ($result_class) { + $class_name = sprintf($result_class, $this->phptype); + if (!class_exists($class_name)) { + $err =& $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result class does not exist '.$class_name, __FUNCTION__); + return $err; + } + $result =& new $class_name($this, $result, $limit, $offset); + if (!MDB2::isResultCommon($result)) { + $err =& $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result class is not extended from MDB2_Result_Common', __FUNCTION__); + return $err; + } + if (!empty($types)) { + $err = $result->setResultTypes($types); + if (PEAR::isError($err)) { + $result->free(); + return $err; + } + } + } + if ($result_wrap_class === true) { + $result_wrap_class = $this->options['result_wrap_class']; + } + if ($result_wrap_class) { + if (!class_exists($result_wrap_class)) { + $err =& $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result wrap class does not exist '.$result_wrap_class, __FUNCTION__); + return $err; + } + $result =& new $result_wrap_class($result, $this->fetchmode); + } + return $result; + } + + // }}} + // {{{ function getServerVersion($native = false) + + /** + * return version information about the server + * + * @param string determines if the raw version string should be returned + * + * @return mixed array with version information or row string + * + * @access public + */ + function getServerVersion($native = false) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function setLimit($limit, $offset = null) + + /** + * set the range of the next query + * + * @param string number of rows to select + * @param string first row to select + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function setLimit($limit, $offset = null) + { + if (!$this->supports('limit_queries')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'limit is not supported by this driver', __FUNCTION__); + } + $limit = (int)$limit; + if ($limit < 0) { + return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'it was not specified a valid selected range row limit', __FUNCTION__); + } + $this->limit = $limit; + if (!is_null($offset)) { + $offset = (int)$offset; + if ($offset < 0) { + return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'it was not specified a valid first selected range row', __FUNCTION__); + } + $this->offset = $offset; + } + return MDB2_OK; + } + + // }}} + // {{{ function subSelect($query, $type = false) + + /** + * simple subselect emulation: leaves the query untouched for all RDBMS + * that support subselects + * + * @param string the SQL query for the subselect that may only + * return a column + * @param string determines type of the field + * + * @return string the query + * + * @access public + */ + function subSelect($query, $type = false) + { + if ($this->supports('sub_selects') === true) { + return $query; + } + + if (!$this->supports('sub_selects')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $col = $this->queryCol($query, $type); + if (PEAR::isError($col)) { + return $col; + } + if (!is_array($col) || count($col) == 0) { + return 'NULL'; + } + if ($type) { + $this->loadModule('Datatype', null, true); + return $this->datatype->implodeArray($col, $type); + } + return implode(', ', $col); + } + + // }}} + // {{{ function replace($table, $fields) + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the REPLACE query just updates its values instead of + * inserting a new row. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only MySQL and SQLIte implement it natively, this type of + * query isemulated through this method for other DBMS using standard types + * of queries inside a transaction to assure the atomicity of the operation. + * + * @param string name of the table on which the REPLACE query will + * be executed. + * @param array associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. + * The values of the array are also associative arrays that describe + * the values and other properties of the table fields. + * + * Here follows a list of field properties that need to be specified: + * + * value + * Value to be assigned to the specified field. This value may be + * of specified in database independent type format as this + * function can perform the necessary datatype conversions. + * + * Default: this property is required unless the Null property is + * set to 1. + * + * type + * Name of the type of the field. Currently, all types Metabase + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * bool property that indicates that the value for this field + * should be set to null. + * + * The default value for fields missing in INSERT queries may be + * specified the definition of a table. Often, the default value + * is already null, but since the REPLACE may be emulated using + * an UPDATE query, make sure that all fields of the table are + * listed in this function argument array. + * + * Default: 0 + * + * key + * bool property that indicates that this field should be + * handled as a primary key or at least as part of the compound + * unique index of the table that will determine the row that will + * updated if it exists or inserted a new row otherwise. + * + * This function will fail if no key field is specified or if the + * value of a key field is set to null because fields that are + * part of unique index they may not be null. + * + * Default: 0 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function replace($table, $fields) + { + if (!$this->supports('replace')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'replace query is not supported', __FUNCTION__); + } + $count = count($fields); + $condition = $values = array(); + for ($colnum = 0, reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + } + $values[$name] = $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $condition[] = $name . '=' . $value; + } + } + if (empty($condition)) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $result = null; + $in_transaction = $this->in_transaction; + if (!$in_transaction && PEAR::isError($result = $this->beginTransaction())) { + return $result; + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $condition = ' WHERE '.implode(' AND ', $condition); + $query = "DELETE FROM $table$condition"; + $result =& $this->_doQuery($query, true, $connection); + if (!PEAR::isError($result)) { + $affected_rows = $this->_affectedRows($connection, $result); + $insert = implode(', ', array_keys($values)); + $values = implode(', ', $values); + $query = "INSERT INTO $table ($insert) VALUES ($values)"; + $result =& $this->_doQuery($query, true, $connection); + if (!PEAR::isError($result)) { + $affected_rows += $this->_affectedRows($connection, $result);; + } + } + + if (!$in_transaction) { + if (PEAR::isError($result)) { + $this->rollback(); + } else { + $result = $this->commit(); + } + } + + if (PEAR::isError($result)) { + return $result; + } + + return $affected_rows; + } + + // }}} + // {{{ function &prepare($query, $types = null, $result_types = null, $lobs = array()) + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :[a-zA-Z] and are placeholders which can be set using + * bindParam() and the query can be send off using the execute() method. + * + * @param string the query to prepare + * @param mixed array that contains the types of the placeholders + * @param mixed array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed key (field) value (parameter) pair for all lob placeholders + * + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * + * @access public + * @see bindParam, execute + */ + function &prepare($query, $types = null, $result_types = null, $lobs = array()) + { + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (is_null($placeholder_type)) { + $placeholder_type_guess = $query[$p_position]; + } + if (is_int($quote = strpos($query, "'", $position)) && $quote < $p_position) { + if (!is_int($end_quote = strpos($query, "'", $quote + 1))) { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'query with an unterminated text string specified', __FUNCTION__); + return $err; + } + switch ($this->escape_quotes) { + case '': + case "'": + $position = $end_quote + 1; + break; + default: + if ($end_quote == $quote + 1) { + $position = $end_quote + 1; + } else { + if ($query[$end_quote-1] == $this->escape_quotes) { + $position = $end_quote; + } else { + $position = $end_quote + 1; + } + } + break; + } + } elseif ($query[$position] == $placeholder_type_guess) { + if (is_null($placeholder_type)) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + if (!empty($types) && is_array($types)) { + if ($placeholder_type == ':') { + if (is_int(key($types))) { + $types_tmp = $types; + $types = array(); + $count = -1; + } + } else { + $types = array_values($types); + } + } + } + if ($placeholder_type == ':') { + $parameter = preg_replace('/^.{'.($position+1).'}([a-z0-9_]+).*$/si', '\\1', $query); + if ($parameter === '') { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter with an empty name', __FUNCTION__); + return $err; + } elseif (isset($positions[$parameter])) { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter names can only be used once per statement', __FUNCTION__); + return $err; + } + $positions[$parameter] = $p_position; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + // use parameter name in type array + if (isset($count) && isset($types_tmp[++$count])) { + $types[$parameter] = $types_tmp[$count]; + } + } else { + $positions[] = $p_position; + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $class_name = 'MDB2_Statement_'.$this->phptype; + $statement = null; + $obj =& new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ function quote($value, $type = null, $quote = true) + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string text string value that is intended to be converted. + * @param string type to which the value should be converted to + * @param bool escape wildcards + * + * @return string text string that represents the given argument value in + * a DBMS specific format. + * + * @access public + */ + function quote($value, $type = null, $quote = true, $escape_wildcards = false) + { + $result = $this->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + + return $this->datatype->quote($value, $type, $quote, $escape_wildcards); + } + + // }}} + // {{{ function getDeclaration($type, $name, $field) + + /** + * Obtain DBMS specific SQL code portion needed to declare + * of the given type + * + * @param string type to which the value should be converted to + * @param string name the field to be declared. + * @param string definition of the field + * + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * + * @access public + */ + function getDeclaration($type, $name, $field) + { + $result = $this->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + return $this->datatype->getDeclaration($type, $name, $field); + } + + // }}} + // {{{ function compareDefinition($current, $previous) + + /** + * Obtain an array of changes that may need to applied + * + * @param array new definition + * @param array old definition + * + * @return array containing all changes that will need to be applied + * + * @access public + */ + function compareDefinition($current, $previous) + { + $result = $this->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + return $this->datatype->compareDefinition($current, $previous); + } + + // }}} + // {{{ function supports($feature) + + /** + * Tell whether a DB implementation or its backend extension + * supports a given feature. + * + * @param string name of the feature (see the MDB2 class doc) + * + * @return bool|string if this DB implementation supports a given feature + * false means no, true means native, + * 'emulated' means emulated + * + * @access public + */ + function supports($feature) + { + if (array_key_exists($feature, $this->supported)) { + return $this->supported[$feature]; + } + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown support feature $feature", __FUNCTION__); + } + + // }}} + // {{{ function getSequenceName($sqn) + + /** + * adds sequence name formatting to a sequence name + * + * @param string name of the sequence + * + * @return string formatted sequence name + * + * @access public + */ + function getSequenceName($sqn) + { + return sprintf($this->options['seqname_format'], + preg_replace('/[^a-z0-9_\$.]/i', '_', $sqn)); + } + + // }}} + // {{{ function getIndexName($idx) + + /** + * adds index name formatting to a index name + * + * @param string name of the index + * + * @return string formatted index name + * + * @access public + */ + function getIndexName($idx) + { + return sprintf($this->options['idxname_format'], + preg_replace('/[^a-z0-9_\$]/i', '_', $idx)); + } + + // }}} + // {{{ function nextID($seq_name, $ondemand = true) + + /** + * Returns the next free id of a sequence + * + * @param string name of the sequence + * @param bool when true missing sequences are automatic created + * + * @return mixed MDB2 Error Object or id + * + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function lastInsertID($table = null, $field = null) + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string name of the table into which a new row was inserted + * @param string name of the field into which a new row was inserted + * + * @return mixed MDB2 Error Object or id + * + * @access public + */ + function lastInsertID($table = null, $field = null) + { + $seq = $table.(empty($field) ? '' : '_'.$field); + return $this->currID($seq); + } + + // }}} + // {{{ function currID($seq_name) + + /** + * Returns the current id of a sequence + * + * @param string name of the sequence + * + * @return mixed MDB2 Error Object or id + * + * @access public + */ + function currID($seq_name) + { + $this->warnings[] = 'database does not support getting current + sequence value, the sequence value was incremented'; + return $this->nextID($seq_name); + } + + // }}} + // {{{ function queryOne($query, $type = null, $colnum = 0) + + /** + * Execute the specified query, fetch the value from the first column of + * the first row of the result set and then frees + * the result set. + * + * @param string the SELECT query statement to be executed. + * @param string optional argument that specifies the expected + * datatype of the result set field, so that an eventual conversion + * may be performed. The default datatype is text, meaning that no + * conversion is performed + * @param int the column number to fetch + * + * @return mixed MDB2_OK or field value on success, a MDB2 error on failure + * + * @access public + */ + function queryOne($query, $type = null, $colnum = 0) + { + $result = $this->query($query, $type); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $one = $result->fetchOne($colnum); + $result->free(); + return $one; + } + + // }}} + // {{{ function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + + /** + * Execute the specified query, fetch the values from the first + * row of the result set into an array and then frees + * the result set. + * + * @param string the SELECT query statement to be executed. + * @param array optional array argument that specifies a list of + * expected datatypes of the result set columns, so that the eventual + * conversions may be performed. The default list of datatypes is + * empty, meaning that no conversion is performed. + * @param int how the array data should be indexed + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * + * @access public + */ + function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $result = $this->query($query, $types); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $row = $result->fetchRow($fetchmode); + $result->free(); + return $row; + } + + // }}} + // {{{ function queryCol($query, $type = null, $colnum = 0) + + /** + * Execute the specified query, fetch the value from the first column of + * each row of the result set into an array and then frees the result set. + * + * @param string the SELECT query statement to be executed. + * @param string optional argument that specifies the expected + * datatype of the result set field, so that an eventual conversion + * may be performed. The default datatype is text, meaning that no + * conversion is performed + * @param int the row number to fetch + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * + * @access public + */ + function queryCol($query, $type = null, $colnum = 0) + { + $result = $this->query($query, $type); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $col = $result->fetchCol($colnum); + $result->free(); + return $col; + } + + // }}} + // {{{ function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false) + + /** + * Execute the specified query, fetch all the rows of the result set into + * a two dimensional array and then frees the result set. + * + * @param string the SELECT query statement to be executed. + * @param array optional array argument that specifies a list of + * expected datatypes of the result set columns, so that the eventual + * conversions may be performed. The default list of datatypes is + * empty, meaning that no conversion is performed. + * @param int how the array data should be indexed + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * + * @access public + */ + function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, + $rekey = false, $force_array = false, $group = false) + { + $result = $this->query($query, $types); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group); + $result->free(); + return $all; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Result + +/** + * The dummy class that all user space result classes should extend from + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result +{ +} + +// }}} +// {{{ class MDB2_Result_Common extends MDB2_Result + +/** + * The common result class for MDB2 result objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_Common extends MDB2_Result +{ + // {{{ Variables (Properties) + + var $db; + var $result; + var $rownum = -1; + var $types = array(); + var $values = array(); + var $offset; + var $offset_count = 0; + var $limit; + var $column_names; + + // }}} + // {{{ constructor: function __construct(&$db, &$result, $limit = 0, $offset = 0) + + /** + * Constructor + */ + function __construct(&$db, &$result, $limit = 0, $offset = 0) + { + $this->db =& $db; + $this->result =& $result; + $this->offset = $offset; + $this->limit = max(0, $limit - 1); + } + + // }}} + // {{{ function MDB2_Result_Common(&$db, &$result, $limit = 0, $offset = 0) + + /** + * PHP 4 Constructor + */ + function MDB2_Result_Common(&$db, &$result, $limit = 0, $offset = 0) + { + $this->__construct($db, $result, $limit, $offset); + } + + // }}} + // {{{ function setResultTypes($types) + + /** + * Define the list of types to be associated with the columns of a given + * result set. + * + * This function may be called before invoking fetchRow(), fetchOne(), + * fetchCol() and fetchAll() so that the necessary data type + * conversions are performed on the data to be retrieved by them. If this + * function is not called, the type of all result set columns is assumed + * to be text, thus leading to not perform any conversions. + * + * @param array variable that lists the + * data types to be expected in the result set columns. If this array + * contains less types than the number of columns that are returned + * in the result set, the remaining columns are assumed to be of the + * type text. Currently, the types clob and blob are not fully + * supported. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function setResultTypes($types) + { + $load = $this->db->loadModule('Datatype', null, true); + if (PEAR::isError($load)) { + return $load; + } + $types = $this->db->datatype->checkResultTypes($types); + if (PEAR::isError($types)) { + return $types; + } + $this->types = $types; + return MDB2_OK; + } + + // }}} + // {{{ function seek($rownum = 0) + + /** + * Seek to a specific row in a result set + * + * @param int number of the row where the data can be found + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function seek($rownum = 0) + { + $target_rownum = $rownum - 1; + if ($this->rownum > $target_rownum) { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'seeking to previous rows not implemented', __FUNCTION__); + } + while ($this->rownum < $target_rownum) { + $this->fetchRow(); + } + return MDB2_OK; + } + + // }}} + // {{{ function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + + /** + * Fetch and return a row of data + * + * @param int how the array data should be indexed + * @param int number of the row where the data can be found + * + * @return int data array on success, a MDB2 error on failure + * + * @access public + */ + function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + $err =& $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $err; + } + + // }}} + // {{{ function fetchOne($colnum = 0) + + /** + * fetch single column from the next row from a result set + * + * @param int the column number to fetch + * @param int number of the row where the data can be found + * + * @return string data on success, a MDB2 error on failure + * + * @access public + */ + function fetchOne($colnum = 0, $rownum = null) + { + $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC; + $row = $this->fetchRow($fetchmode, $rownum); + if (!is_array($row) || PEAR::isError($row)) { + return $row; + } + if (!array_key_exists($colnum, $row)) { + return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'column is not defined in the result set: '.$colnum, __FUNCTION__); + } + return $row[$colnum]; + } + + // }}} + // {{{ function fetchCol($colnum = 0) + + /** + * Fetch and return a column of data (it uses current for that) + * + * @param int the column number to fetch + * + * @return mixed data array on success, a MDB2 error on failure + * + * @access public + */ + function fetchCol($colnum = 0) + { + $column = array(); + $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC; + $row = $this->fetchRow($fetchmode); + if (is_array($row)) { + if (!array_key_exists($colnum, $row)) { + return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'column is not defined in the result set: '.$colnum, __FUNCTION__); + } + do { + $column[] = $row[$colnum]; + } while (is_array($row = $this->fetchRow($fetchmode))); + } + if (PEAR::isError($row)) { + return $row; + } + return $column; + } + + // }}} + // {{{ function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false) + + /** + * Fetch and return a column of data (it uses fetchRow for that) + * + * @param int $fetchmode the fetch mode to use: + * + MDB2_FETCHMODE_ORDERED + * + MDB2_FETCHMODE_ASSOC + * + MDB2_FETCHMODE_ORDERED | MDB2_FETCHMODE_FLIPPED + * + MDB2_FETCHMODE_ASSOC | MDB2_FETCHMODE_FLIPPED + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return mixed data array on success, a MDB2 error on failure + * + * @access public + * @see getAssoc() + */ + function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, + $force_array = false, $group = false) + { + $all = array(); + $row = $this->fetchRow($fetchmode); + if (PEAR::isError($row)) { + return $row; + } elseif (!$row) { + return $all; + } + + $shift_array = $rekey ? false : null; + if (!is_null($shift_array)) { + if (is_object($row)) { + $colnum = count(get_object_vars($row)); + } else { + $colnum = count($row); + } + if ($colnum < 2) { + return $this->db->raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'rekey feature requires atleast 2 column', __FUNCTION__); + } + $shift_array = (!$force_array && $colnum == 2); + } + + if ($rekey) { + do { + if (is_object($row)) { + $arr = get_object_vars($row); + $key = reset($arr); + unset($row->{$key}); + } else { + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $key = reset($row); + unset($row[key($row)]); + } else { + $key = array_shift($row); + } + if ($shift_array) { + $row = array_shift($row); + } + } + if ($group) { + $all[$key][] = $row; + } else { + $all[$key] = $row; + } + } while (($row = $this->fetchRow($fetchmode))); + } elseif ($fetchmode & MDB2_FETCHMODE_FLIPPED) { + do { + foreach ($row as $key => $val) { + $all[$key][] = $val; + } + } while (($row = $this->fetchRow($fetchmode))); + } else { + do { + $all[] = $row; + } while (($row = $this->fetchRow($fetchmode))); + } + + return $all; + } + + // }}} + // {{{ function rowCount() + /** + * Returns the actual row number that was last fetched (count from 0) + * @return int + * + * @access public + */ + function rowCount() + { + return $this->rownum + 1; + } + + // }}} + // {{{ function numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * + * @access public + */ + function numRows() + { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @param a valid result resource + * + * @return true on success, false if there is no more result set or an error object on failure + * + * @access public + */ + + function nextResult() + { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result or + * from the cache. + * + * @param bool If set to true the values are the column names, + * otherwise the names of the columns are the keys. + * @return mixed Array variable that holds the names of columns or an + * MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * + * @access public + */ + function getColumnNames($reverse = false) + { + if (!isset($this->column_names)) { + $result = $this->_getColumnNames(); + if (PEAR::isError($result)) { + return $result; + } + $this->column_names = $result; + } + if ($reverse) { + return array_reverse($this->column_names); + } + return $this->column_names; + } + + // }}} + // {{{ function _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * + * @access private + */ + function _getColumnNames() + { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + * + * @access public + */ + function numCols() + { + return $this->db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function getResource() + + /** + * return the resource associated with the result object + * + * @return resource + * + * @access public + */ + function getResource() + { + return $this->result; + } + + // }}} + // {{{ function bindColumn($column, &$value, $type = null) + + /** + * Set bind variable to a column. + * + * @param int column number or name + * @param mixed variable reference + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindColumn($column, &$value, $type = null) + { + if (!is_numeric($column)) { + $column_names = $this->getColumnNames(); + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($this->db->options['field_case'] == CASE_LOWER) { + $column = strtolower($column); + } else { + $column = strtoupper($column); + } + } + $column = $column_names[$column]; + } + $this->values[$column] =& $value; + if (!is_null($type)) { + $this->types[$column] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function _assignBindColumns($row) + + /** + * Bind a variable to a value in the result row. + * + * @param array row data + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access private + */ + function _assignBindColumns($row) + { + $row = array_values($row); + foreach ($row as $column => $value) { + if (array_key_exists($column, $this->values)) { + $this->values[$column] = $value; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function free() + + /** + * Free the internal resources associated with result. + * + * @return bool true on success, false if result is invalid + * + * @access public + */ + function free() + { + $this->result = false; + return MDB2_OK; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Row + +/** + * The simple class that accepts row data as an array + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Row +{ + // {{{ constructor: function __construct(&$row) + + /** + * constructor + * + * @param resource row data as array + */ + function __construct(&$row) + { + foreach ($row as $key => $value) { + $this->$key = &$row[$key]; + } + } + + // }}} + // {{{ function MDB2_Row(&$row) + + /** + * PHP 4 Constructor + * + * @param resource row data as array + */ + function MDB2_Row(&$row) + { + $this->__construct($row); + } + + // }}} +} + +// }}} +// {{{ class MDB2_Statement_Common + +/** + * The common statement class for MDB2 statement objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_Common +{ + // {{{ Variables (Properties) + + var $db; + var $statement; + var $query; + var $result_types; + var $types; + var $values = array(); + var $limit; + var $offset; + var $is_manip; + + // }}} + // {{{ constructor: function __construct(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + + /** + * Constructor + */ + function __construct(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + { + $this->db =& $db; + $this->statement =& $statement; + $this->positions = $positions; + $this->query = $query; + $this->types = (array)$types; + $this->result_types = (array)$result_types; + $this->limit = $limit; + $this->is_manip = $is_manip; + $this->offset = $offset; + } + + // }}} + // {{{ function MDB2_Statement_Common(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + + /** + * PHP 4 Constructor + */ + function MDB2_Statement_Common(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + { + $this->__construct($db, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + } + + // }}} + // {{{ function bindValue($parameter, &$value, $type = null) + + /** + * Set the value of a parameter of a prepared query. + * + * @param int the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed value that is meant to be assigned to specified + * parameter. The type of the value depends on the $type argument. + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindValue($parameter, &$value, $type = null) + { + if (!is_numeric($parameter)) { + $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); + } + if (!array_key_exists($parameter, $this->positions)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $this->values[$parameter] = $value; + if (!is_null($type)) { + $this->types[$parameter] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function bindValueArray($values, $types = null) + + /** + * Set the values of multiple a parameter of a prepared query in bulk. + * + * @param array specifies all necessary information + * for bindValue() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param array specifies the types of the fields + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @see bindParam() + */ + function bindValueArray(&$values, $types = null) + { + $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); + $parameters = array_keys($values); + foreach ($parameters as $key => $parameter) { + $this->bindValue($parameter, $values[$parameter], $types[$key]); + } + return MDB2_OK; + } + + // }}} + // {{{ function bindParam($parameter, &$value, $type = null) + + /** + * Bind a variable to a parameter of a prepared query. + * + * @param int the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed variable that is meant to be bound to specified + * parameter. The type of the value depends on the $type argument. + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindParam($parameter, &$value, $type = null) + { + if (!is_numeric($parameter)) { + $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); + } + if (!array_key_exists($parameter, $this->positions)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $this->values[$parameter] =& $value; + if (!is_null($type)) { + $this->types[$parameter] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function bindParamArray(&$values, $types = null) + + /** + * Bind the variables of multiple a parameter of a prepared query in bulk. + * + * @param array specifies all necessary information + * for bindParam() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param array specifies the types of the fields + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @see bindParam() + */ + function bindParamArray(&$values, $types = null) + { + $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); + $parameters = array_keys($values); + foreach ($parameters as $key => $parameter) { + $this->bindParam($parameter, $values[$parameter], $types[$key]); + } + return MDB2_OK; + } + + // }}} + // {{{ function &execute($values = null, $result_class = true, $result_wrap_class = false) + + /** + * Execute a prepared query statement. + * + * @param array specifies all necessary information + * for bindParam() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param mixed specifies which result class to use + * @param mixed specifies which class to wrap results in + * + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function &execute($values = null, $result_class = true, $result_wrap_class = false) + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + + if (!empty($values)) { + $values = (array)$values; + $this->bindValueArray($values); + } + $result =& $this->_execute($result_class, $result_wrap_class); + return $result; + } + + // }}} + // {{{ function &_execute($result_class = true, $result_wrap_class = false) + + /** + * Execute a prepared query statement helper method. + * + * @param mixed specifies which result class to use + * @param mixed specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer on success, a MDB2 error on failure + * + * @access private + */ + function &_execute($result_class = true, $result_wrap_class = false) + { + $this->last_query = $this->query; + $query = ''; + $last_position = 0; + foreach ($this->positions as $parameter => $current_position) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $query.= substr($this->query, $last_position, $current_position - $last_position); + if (!isset($value)) { + $value_quoted = 'NULL'; + } else { + $type = !empty($this->types[$parameter]) ? $this->types[$parameter] : null; + $value_quoted = $this->db->quote($value, $type); + if (PEAR::isError($value_quoted)) { + return $value_quoted; + } + } + $query.= $value_quoted; + $last_position = $current_position + 1; + } + $query.= substr($this->query, $last_position); + + $this->db->offset = $this->offset; + $this->db->limit = $this->limit; + if ($this->is_manip) { + $result = $this->db->exec($query); + } else { + $result =& $this->db->query($query, $this->result_types, $result_class, $result_wrap_class); + } + return $result; + } + + // }}} + // {{{ function free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function free() + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + + $this->statement = null; + $this->positions = null; + $this->query = null; + $this->types = null; + $this->result_types = null; + $this->limit = null; + $this->is_manip = null; + $this->offset = null; + $this->values = null; + + return MDB2_OK; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Module_Common + +/** + * The common modules class for MDB2 module objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Module_Common +{ + // {{{ Variables (Properties) + + /** + * contains the key to the global MDB2 instance array of the associated + * MDB2 instance + * + * @var int + * @access protected + */ + var $db_index; + + // }}} + // {{{ constructor: function __construct($db_index) + + /** + * Constructor + */ + function __construct($db_index) + { + $this->db_index = $db_index; + } + + // }}} + // {{{ function MDB2_Module_Common($db_index) + + /** + * PHP 4 Constructor + */ + function MDB2_Module_Common($db_index) + { + $this->__construct($db_index); + } + + // }}} + // {{{ function &getDBInstance() + + /** + * Get the instance of MDB2 associated with the module instance + * + * @return object MDB2 instance or a MDB2 error on failure + * + * @access public + */ + function &getDBInstance() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $result =& $GLOBALS['_MDB2_databases'][$this->db_index]; + } else { + $result =& MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'could not find MDB2 instance'); + } + return $result; + } + + // }}} +} + +// }}} +// {{{ function MDB2_closeOpenTransactions() + +/** + * Close any open transactions form persistent connections + * + * @return void + * + * @access public + */ + +function MDB2_closeOpenTransactions() +{ + reset($GLOBALS['_MDB2_databases']); + while (next($GLOBALS['_MDB2_databases'])) { + $key = key($GLOBALS['_MDB2_databases']); + if ($GLOBALS['_MDB2_databases'][$key]->opened_persistent + && $GLOBALS['_MDB2_databases'][$key]->in_transaction + ) { + $GLOBALS['_MDB2_databases'][$key]->rollback(); + } + } +} + +// }}} +// {{{ function MDB2_defaultDebugOutput(&$db, $scope, $message, $is_manip = null) + +/** + * default debug output handler + * + * @param object reference to an MDB2 database object + * @param string usually the method name that triggered the debug call: + * for example 'query', 'prepare', 'execute', 'parameters', + * 'beginTransaction', 'commit', 'rollback' + * @param string message that should be appended to the debug variable + * @param array contains context information about the debug() call + * common keys are: is_manip, time, result etc. + * + * @return void|string optionally return a modified message, this allows + * rewriting a query before being issued or prepared + * + * @access public + */ +function MDB2_defaultDebugOutput(&$db, $scope, $message, $context = array()) +{ + $db->debug_output.= $scope.'('.$db->db_index.'): '; + $db->debug_output.= $message.$db->getOption('log_line_break'); + return $message; +} + +// }}} +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Date.php b/trunk/docs/include/PEAR/MDB2/Date.php new file mode 100644 index 0000000..ce84654 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Date.php @@ -0,0 +1,183 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Date.php,v 1.10 2006/03/01 12:15:32 lsmith Exp $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Several methods to convert the MDB2 native timestamp format (ISO based) + * to and from data structures that are convenient to worth with in side of php. + * For more complex date arithmetic please take a look at the Date package in PEAR + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Date +{ + // {{{ mdbNow() + + /** + * return the current datetime + * + * @return string current datetime in the MDB2 format + * @access public + */ + function mdbNow() + { + return date('Y-m-d H:i:s'); + } + // }}} + + // {{{ mdbToday() + + /** + * return the current date + * + * @return string current date in the MDB2 format + * @access public + */ + function mdbToday() + { + return date('Y-m-d'); + } + // }}} + + // {{{ mdbTime() + + /** + * return the current time + * + * @return string current time in the MDB2 format + * @access public + */ + function mdbTime() + { + return date('H:i:s'); + } + // }}} + + // {{{ date2Mdbstamp() + + /** + * convert a date into a MDB2 timestamp + * + * @param int hour of the date + * @param int minute of the date + * @param int second of the date + * @param int month of the date + * @param int day of the date + * @param int year of the date + * + * @return string a valid MDB2 timestamp + * @access public + */ + function date2Mdbstamp($hour = null, $minute = null, $second = null, + $month = null, $day = null, $year = null) + { + return MDB2_Date::unix2Mdbstamp(mktime($hour, $minute, $second, $month, $day, $year, -1)); + } + // }}} + + // {{{ unix2Mdbstamp() + + /** + * convert a unix timestamp into a MDB2 timestamp + * + * @param int a valid unix timestamp + * + * @return string a valid MDB2 timestamp + * @access public + */ + function unix2Mdbstamp($unix_timestamp) + { + return date('Y-m-d H:i:s', $unix_timestamp); + } + // }}} + + // {{{ mdbstamp2Unix() + + /** + * convert a MDB2 timestamp into a unix timestamp + * + * @param int a valid MDB2 timestamp + * @return string unix timestamp with the time stored in the MDB2 format + * + * @access public + */ + function mdbstamp2Unix($mdb_timestamp) + { + $arr = MDB2_Date::mdbstamp2Date($mdb_timestamp); + + return mktime($arr['hour'], $arr['minute'], $arr['second'], $arr['month'], $arr['day'], $arr['year'], -1); + } + // }}} + + // {{{ mdbstamp2Date() + + /** + * convert a MDB2 timestamp into an array containing all + * values necessary to pass to php's date() function + * + * @param int a valid MDB2 timestamp + * + * @return array with the time split + * @access public + */ + function mdbstamp2Date($mdb_timestamp) + { + list($arr['year'], $arr['month'], $arr['day'], $arr['hour'], $arr['minute'], $arr['second']) = + sscanf($mdb_timestamp, "%04u-%02u-%02u %02u:%02u:%02u"); + return $arr; + } + // }}} +} + +?> diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Datatype/.Common.php.swp b/trunk/docs/include/PEAR/MDB2/Driver/Datatype/.Common.php.swp new file mode 100644 index 0000000..5548d9c Binary files /dev/null and b/trunk/docs/include/PEAR/MDB2/Driver/Datatype/.Common.php.swp differ diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Datatype/Common.php b/trunk/docs/include/PEAR/MDB2/Driver/Datatype/Common.php new file mode 100644 index 0000000..63ced04 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Datatype/Common.php @@ -0,0 +1,1672 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.109 2006/08/20 09:48:43 lsmith Exp $ + +require_once 'MDB2/LOB.php'; + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * MDB2_Driver_Common: Base class that is extended by each MDB2 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_Common extends MDB2_Module_Common +{ + var $valid_types = array( + 'text' => '', + 'boolean' => true, + 'integer' => 0, + 'decimal' => 0.0, + 'float' => 0.0, + 'timestamp' => '1970-01-01 00:00:00', + 'time' => '00:00:00', + 'date' => '1970-01-01', + 'clob' => '', + 'blob' => '', + ); + + /** + * contains all LOB objects created with this MDB2 instance + * @var array + * @access protected + */ + var $lobs = array(); + + // }}} + // {{{ getValidTypes() + + /** + * Get the list of valid types + * + * This function returns an array of valid types as keys with the values + * being possible default values for all native datatypes and mapped types + * for custom datatypes. + * + * @return mixed array on success, a MDB2 error on failure + * @access public + */ + function getValidTypes() + { + $types = $this->valid_types; + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (!empty($db->options['datatype_map'])) { + foreach ($db->options['datatype_map'] as $type => $mapped_type) { + if (array_key_exists($mapped_type, $types)) { + $types[$type] = $types[$mapped_type]; + } elseif (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'mapped_type' => $mapped_type); + $default = call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + $types[$type] = $default; + } + } + } + return $types; + } + + // }}} + // {{{ checkResultTypes() + + /** + * Define the list of types to be associated with the columns of a given + * result set. + * + * This function may be called before invoking fetchRow(), fetchOne() + * fetchCole() and fetchAll() so that the necessary data type + * conversions are performed on the data to be retrieved by them. If this + * function is not called, the type of all result set columns is assumed + * to be text, thus leading to not perform any conversions. + * + * @param string $types array variable that lists the + * data types to be expected in the result set columns. If this array + * contains less types than the number of columns that are returned + * in the result set, the remaining columns are assumed to be of the + * type text. Currently, the types clob and blob are not fully + * supported. + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function checkResultTypes($types) + { + $types = is_array($types) ? $types : array($types); + foreach ($types as $key => $type) { + if (!isset($this->valid_types[$type])) { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (empty($db->options['datatype_map'][$type])) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + $type.' for '.$key.' is not a supported column type', __FUNCTION__); + } + } + } + return $types; + } + + // }}} + // {{{ _baseConvertResult() + + /** + * general type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + switch ($type) { + case 'text': + if ($rtrim) { + $value = rtrim($value); + } + return $value; + case 'integer': + return intval($value); + case 'boolean': + return !empty($value); + case 'decimal': + return $value; + case 'float': + return doubleval($value); + case 'date': + return $value; + case 'time': + return $value; + case 'timestamp': + return $value; + case 'clob': + case 'blob': + $this->lobs[] = array( + 'buffer' => null, + 'position' => 0, + 'lob_index' => null, + 'endOfLOB' => false, + 'resource' => $value, + 'value' => null, + 'loaded' => false, + ); + end($this->lobs); + $lob_index = key($this->lobs); + $this->lobs[$lob_index]['lob_index'] = $lob_index; + return fopen('MDB2LOB://'.$lob_index.'@'.$this->db_index, 'r+'); + } + + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_INVALID, null, null, + 'attempt to convert result value to an unknown type :' . $type, __FUNCTION__); + } + + // }}} + // {{{ convertResult() + + /** + * convert a value to a RDBMS indepdenant MDB2 type + * + * @param mixed $value value to be converted + * @param string $type specifies which type to convert to + * @param bool $rtrim if to rtrim text values or not + * @return mixed converted value + * @access public + */ + function convertResult($value, $type, $rtrim = true) + { + if (is_null($value)) { + return null; + } + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'value' => $value, 'rtrim' => $rtrim); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + return $this->_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ convertResultRow() + + /** + * convert a result row + * + * @param array $types + * @param array $row specifies the types to convert to + * @param bool $rtrim if to rtrim text values or not + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function convertResultRow($types, $row, $rtrim = true) + { + reset($types); + $current_column = -1; + foreach ($row as $key => $value) { + ++$current_column; + if (!isset($value)) { + continue; + } + if (isset($types[$current_column])) { + $type = $types[$current_column]; + } elseif (isset($types[$key])) { + $type = $types[$key]; + } elseif (current($types)) { + $type = current($types); + next($types); + } else { + continue; + } + $value = $this->convertResult($row[$key], $type, $rtrim); + if (PEAR::isError($value)) { + return $value; + } + $row[$key] = $value; + } + return $row; + } + + // }}} + // {{{ getDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare + * of the given type + * + * @param string $type type to which the value should be converted to + * @param string $name name the field to be declared. + * @param string $field definition of the field + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getDeclaration($type, $name, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'name' => $name, 'field' => $field); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + if (!method_exists($this, "_get{$type}Declaration")) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'type not defined: '.$type, __FUNCTION__); + } + return $this->{"_get{$type}Declaration"}($name, $field); + } + + // }}} + // {{{ getTypeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getTypeDeclaration($field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + switch ($field['type']) { + case 'text': + $length = !empty($field['length']) ? $field['length'] : $db->options['default_text_field_length']; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + return 'TEXT'; + case 'blob': + return 'TEXT'; + case 'integer': + return 'INT'; + case 'boolean': + return 'INT'; + case 'date': + return 'CHAR ('.strlen('YYYY-MM-DD').')'; + case 'time': + return 'CHAR ('.strlen('HH:MM:SS').')'; + case 'timestamp': + return 'CHAR ('.strlen('YYYY-MM-DD HH:MM:SS').')'; + case 'float': + return 'TEXT'; + case 'decimal': + return 'TEXT'; + } + return ''; + } + + // }}} + // {{{ _getDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a generic type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDeclaration($name, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) + ? null : $this->valid_types[$field['type']]; + if ($field['default'] === '' + && ($db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) + ) { + $field['default'] = ' '; + } + } + $default = ' DEFAULT '.$this->quote($field['default'], $field['type']); + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$default.$notnull; + } + + // }}} + // {{{ _getIntegerDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field should be + * declared as unsigned integer if possible. + * + * default + * Integer value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getIntegerDeclaration($name, $field) + { + if (!empty($field['unsigned'])) { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTextDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getTextDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getCLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an character + * large object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function _getCLOBDeclaration($name, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an binary large + * object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getBLOBDeclaration($name, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBooleanDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a boolean type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Boolean value to be used as default for this field. + * + * notnullL + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getBooleanDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getDateDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a date type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Date value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDateDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTimestampDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a timestamp + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Timestamp value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getTimestampDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTimeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a time + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Time value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getTimeDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getFloatDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a float type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Float value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getFloatDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getDecimalDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a decimal type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Decimal value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDecimalDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ compareDefinition() + + /** + * Obtain an array of changes that may need to applied + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access public + */ + function compareDefinition($current, $previous) + { + $type = !empty($current['type']) ? $current['type'] : null; + + if (!method_exists($this, "_compare{$type}Definition")) { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'type "'.$current['type'].'" is not yet supported', __FUNCTION__); + } + + if (empty($previous['type']) || $previous['type'] != $type) { + return $current; + } + + $change = $this->{"_compare{$type}Definition"}($current, $previous); + + if ($previous['type'] != $type) { + $change['type'] = true; + } + + $previous_notnull = !empty($previous['notnull']) ? $previous['notnull'] : false; + $notnull = !empty($current['notnull']) ? $current['notnull'] : false; + if ($previous_notnull != $notnull) { + $change['notnull'] = true; + } + + $previous_default = array_key_exists('default', $previous) ? $previous['default'] : + ($previous_notnull ? '' : null); + $default = array_key_exists('default', $current) ? $current['default'] : + ($notnull ? '' : null); + if ($previous_default !== $default) { + $change['default'] = true; + } + + return $change; + } + + // }}} + // {{{ _compareIntegerDefinition() + + /** + * Obtain an array of changes that may need to applied to an integer field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareIntegerDefinition($current, $previous) + { + $change = array(); + $previous_unsigned = !empty($previous['unsigned']) ? $previous['unsigned'] : false; + $unsigned = !empty($current['unsigned']) ? $current['unsigned'] : false; + if ($previous_unsigned != $unsigned) { + $change['unsigned'] = true; + } + $previous_autoincrement = !empty($previous['autoincrement']) ? $previous['autoincrement'] : false; + $autoincrement = !empty($current['autoincrement']) ? $current['autoincrement'] : false; + if ($previous_autoincrement != $autoincrement) { + $change['autoincrement'] = true; + } + return $change; + } + + // }}} + // {{{ _compareTextDefinition() + + /** + * Obtain an array of changes that may need to applied to an text field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTextDefinition($current, $previous) + { + $change = array(); + $previous_length = !empty($previous['length']) ? $previous['length'] : 0; + $length = !empty($current['length']) ? $current['length'] : 0; + if ($previous_length != $length) { + $change['length'] = true; + } + $previous_fixed = !empty($previous['fixed']) ? $previous['fixed'] : 0; + $fixed = !empty($current['fixed']) ? $current['fixed'] : 0; + if ($previous_fixed != $fixed) { + $change['fixed'] = true; + } + return $change; + } + + // }}} + // {{{ _compareCLOBDefinition() + + /** + * Obtain an array of changes that may need to applied to an CLOB field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareCLOBDefinition($current, $previous) + { + return $this->_compareTextDefinition($current, $previous); + } + + // }}} + // {{{ _compareBLOBDefinition() + + /** + * Obtain an array of changes that may need to applied to an BLOB field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareBLOBDefinition($current, $previous) + { + return $this->_compareTextDefinition($current, $previous); + } + + // }}} + // {{{ _compareDateDefinition() + + /** + * Obtain an array of changes that may need to applied to an date field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareDateDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareTimeDefinition() + + /** + * Obtain an array of changes that may need to applied to an time field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTimeDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareTimestampDefinition() + + /** + * Obtain an array of changes that may need to applied to an timestamp field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTimestampDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareBooleanDefinition() + + /** + * Obtain an array of changes that may need to applied to an boolean field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareBooleanDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareFloatDefinition() + + /** + * Obtain an array of changes that may need to applied to an float field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareFloatDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareDecimalDefinition() + + /** + * Obtain an array of changes that may need to applied to an decimal field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareDecimalDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ quote() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param string $type type to which the value should be converted to + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access public + */ + function quote($value, $type = null, $quote = true, $escape_wildcards = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (is_null($value) + || ($value === '' && $db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) + ) { + if (!$quote) { + return null; + } + return 'NULL'; + } + + if (is_null($type)) { + switch (gettype($value)) { + case 'integer': + $type = 'integer'; + break; + case 'double': + // todo: default to decimal as float is quite unusual + // $type = 'float'; + $type = 'decimal'; + break; + case 'boolean': + $type = 'boolean'; + break; + case 'array': + $value = serialize($value); + case 'object': + $type = 'text'; + break; + default: + if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $value)) { + $type = 'timestamp'; + } elseif (preg_match('/^\d{2}:\d{2}$/', $value)) { + $type = 'time'; + } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { + $type = 'date'; + } else { + $type = 'text'; + } + break; + } + } elseif (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'value' => $value, 'quote' => $quote, 'escape_wildcards' => $escape_wildcards); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + if (!method_exists($this, "_quote{$type}")) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'type not defined: '.$type, __FUNCTION__); + } + $value = $this->{"_quote{$type}"}($value, $quote, $escape_wildcards); + if ($quote && $escape_wildcards && $db->escape_quotes !== $db->escape_pattern) { + $value.= $this->patternEscapeString(); + } + return $value; + } + + // }}} + // {{{ _quoteInteger() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteInteger($value, $quote, $escape_wildcards) + { + return (int)$value; + } + + // }}} + // {{{ _quoteText() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that already contains any DBMS specific + * escaped character sequences. + * @access protected + */ + function _quoteText($value, $quote, $escape_wildcards) + { + if (!$quote) { + return $value; + } + + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $value = $db->escape($value, $escape_wildcards); + return "'".$value."'"; + } + + // }}} + // {{{ _readFile() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _readFile($value) + { + $close = false; + if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + $close = true; + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + } + + if (is_resource($value)) { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $fp = $value; + $value = ''; + while (!@feof($fp)) { + $value.= @fread($fp, $db->options['lob_buffer_length']); + } + if ($close) { + @fclose($fp); + } + } + + return $value; + } + + // }}} + // {{{ _quoteLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteLOB($value, $quote, $escape_wildcards) + { + $value = $this->_readFile($value); + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteCLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteCLOB($value, $quote, $escape_wildcards) + { + return $this->_quoteLOB($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + return $this->_quoteLOB($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteBoolean() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBoolean($value, $quote, $escape_wildcards) + { + return ($value ? 1 : 0); + } + + // }}} + // {{{ _quoteDate() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDate($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_DATE') { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('date'); + } + return 'CURRENT_DATE'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTimestamp() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTimestamp($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_TIMESTAMP') { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('timestamp'); + } + return 'CURRENT_TIMESTAMP'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTime() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTime($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_TIME') { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (isset($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('time'); + } + return 'CURRENT_TIME'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteFloat() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteFloat($value, $quote, $escape_wildcards) + { + if (preg_match('/^(.*)e([-+])(\d+)$/i', $value, $matches)) { + $decimal = $this->_quoteDecimal($matches[1], $quote, $escape_wildcards); + $sign = $matches[2]; + $exponent = str_pad($matches[3], 2, '0', STR_PAD_LEFT); + $value = $decimal.'E'.$sign.$exponent; + } else { + $value = $this->_quoteDecimal($value, $quote, $escape_wildcards); + } + return $value; + } + + // }}} + // {{{ _quoteDecimal() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDecimal($value, $quote, $escape_wildcards) + { + $value = (string)$value; + if (preg_match('/[^.0-9]/', $value)) { + if (strpos($value, ',')) { + // 1000,00 + if (!strpos($value, '.')) { + // convert the last "," to a "." + $value = strrev(str_replace(',', '.', strrev($value))); + // 1.000,00 + } elseif (strpos($value, '.') && strpos($value, '.') < strpos($value, ',')) { + $value = str_replace('.', '', $value); + // convert the last "," to a "." + $value = strrev(str_replace(',', '.', strrev($value))); + // 1,000.00 + } else { + $value = str_replace(',', '', $value); + } + } + } + return $value; + } + + // }}} + // {{{ writeLOBToFile() + + /** + * retrieve LOB from the database + * + * @param resource $lob stream handle + * @param string $file name of the file into which the LOb should be fetched + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function writeLOBToFile($lob, $file) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (preg_match('/^(\w+:\/\/)(.*)$/', $file, $match)) { + if ($match[1] == 'file://') { + $file = $match[2]; + } + } + + $fp = @fopen($file, 'wb'); + while (!@feof($lob)) { + $result = @fread($lob, $db->options['lob_buffer_length']); + $read = strlen($result); + if (@fwrite($fp, $result, $read) != $read) { + @fclose($fp); + return $db->raiseError(MDB2_ERROR, null, null, + 'could not write to the output file', __FUNCTION__); + } + } + @fclose($fp); + return MDB2_OK; + } + + // }}} + // {{{ _retrieveLOB() + + /** + * retrieve LOB from the database + * + * @param array $lob array + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _retrieveLOB(&$lob) + { + if (is_null($lob['value'])) { + $lob['value'] = $lob['resource']; + } + $lob['loaded'] = true; + return MDB2_OK; + } + + // }}} + // {{{ readLOB() + + /** + * Read data from large object input stream. + * + * @param resource $lob stream handle + * @param string $data reference to a variable that will hold data + * to be read from the large object input stream + * @param integer $length value that indicates the largest ammount ofdata + * to be read from the large object input stream. + * @return mixed the effective number of bytes read from the large object + * input stream on sucess or an MDB2 error object. + * @access public + * @see endOfLOB() + */ + function _readLOB($lob, $length) + { + return substr($lob['value'], $lob['position'], $length); + } + + // }}} + // {{{ _endOfLOB() + + /** + * Determine whether it was reached the end of the large object and + * therefore there is no more data to be read for the its input stream. + * + * @param array $lob array + * @return mixed true or false on success, a MDB2 error on failure + * @access protected + */ + function _endOfLOB($lob) + { + return $lob['endOfLOB']; + } + + // }}} + // {{{ destroyLOB() + + /** + * Free any resources allocated during the lifetime of the large object + * handler object. + * + * @param resource $lob stream handle + * @access public + */ + function destroyLOB($lob) + { + $lob_data = stream_get_meta_data($lob); + $lob_index = $lob_data['wrapper_data']->lob_index; + fclose($lob); + if (isset($this->lobs[$lob_index])) { + $this->_destroyLOB($this->lobs[$lob_index]); + unset($this->lobs[$lob_index]); + } + return MDB2_OK; + } + + // }}} + // {{{ _destroyLOB() + + /** + * Free any resources allocated during the lifetime of the large object + * handler object. + * + * @param array $lob array + * @access private + */ + function _destroyLOB(&$lob) + { + return MDB2_OK; + } + + // }}} + // {{{ implodeArray() + + /** + * apply a type to all values of an array and return as a comma seperated string + * useful for generating IN statements + * + * @access public + * + * @param array $array data array + * @param string $type determines type of the field + * + * @return string comma seperated values + */ + function implodeArray($array, $type = false) + { + if (!is_array($array) || empty($array)) { + return 'NULL'; + } + if ($type) { + foreach ($array as $value) { + $return[] = $this->quote($value, $type); + } + } else { + $return = $array; + } + return implode(', ', $return); + } + + // }}} + // {{{ matchPattern() + + /** + * build a pattern matching string + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @access public + * + * @param array $pattern even keys are strings, odd are patterns (% and _) + * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) + * @param string $field optional field name that is being matched against + * (might be required when emulating ILIKE) + * + * @return string SQL pattern + */ + function matchPattern($pattern, $operator = null, $field = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (!is_null($operator)) { + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + if (is_null($field)) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'case insensitive LIKE matching requires passing the field name', __FUNCTION__); + } + $db->loadModule('Function', null, true); + $match = $db->function->lower($field).' '.'LIKE '; + break; + // case sensitive + case 'LIKE': + $match = is_null($field) ? 'LIKE ' : $field.' LIKE '; + break; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'not a supported operator type:'. $operator, __FUNCTION__); + } + } + $match.= "'"; + foreach ($pattern as $key => $value) { + if ($key % 2) { + $match.= $value; + } else { + if ($operator === 'ILIKE') { + $value = strtolower($value); + } + $match.= $db->escapePattern($db->escape($value)); + } + } + $match.= "'"; + $match.= $this->patternEscapeString(); + return $match; + } + + // }}} + // {{{ patternEscapeString() + + /** + * build string to define pattern escape character + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @access public + * + * @return string define pattern escape character + */ + function patternEscapeString() + { + return ''; + } + + // }}} + // {{{ mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types, length, sign, fixed + * @access public + */ + function mapNativeDatatype($field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ mapPrepareDatatype() + + /** + * Maps an mdb2 datatype to mysqli prepare type + * + * @param string $type + * @return string + * @access public + */ + function mapPrepareDatatype($type) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + return $type; + } +} + +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Datatype/mysql.php b/trunk/docs/include/PEAR/MDB2/Driver/Datatype/mysql.php new file mode 100644 index 0000000..c73022d --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Datatype/mysql.php @@ -0,0 +1,426 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.51 2006/08/12 15:58:19 lsmith Exp $ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_mysql extends MDB2_Driver_Datatype_Common +{ + // }}} + // {{{ getTypeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getTypeDeclaration($field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + switch ($field['type']) { + case 'text': + if (empty($field['length']) && array_key_exists('default', $field)) { + $field['length'] = $db->varchar_max_length; + } + $length = !empty($field['length']) ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYTEXT'; + } elseif ($length <= 65535) { + return 'TEXT'; + } elseif ($length <= 16777215) { + return 'MEDIUMTEXT'; + } + } + return 'LONGTEXT'; + case 'blob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYBLOB'; + } elseif ($length <= 65535) { + return 'BLOB'; + } elseif ($length <= 16777215) { + return 'MEDIUMBLOB'; + } + } + return 'LONGBLOB'; + case 'integer': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 1) { + return 'TINYINT'; + } elseif ($length == 2) { + return 'SMALLINT'; + } elseif ($length == 3) { + return 'MEDIUMINT'; + } elseif ($length == 4) { + return 'INT'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INT'; + case 'boolean': + return 'TINYINT(1)'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME'; + case 'timestamp': + return 'DATETIME'; + case 'float': + return 'DOUBLE'; + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + return 'DECIMAL('.$length.','.$db->options['decimal_places'].')'; + } + return ''; + } + + // }}} + // {{{ _getIntegerDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field + * should be declared as unsigned integer if + * possible. + * + * default + * Integer value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getIntegerDeclaration($name, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $default = $autoinc = '';; + if (!empty($field['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT PRIMARY KEY'; + } elseif (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : 0; + } + $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc; + } + + // }}} + // {{{ matchPattern() + + /** + * build a pattern matching string + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change signature at + * any time until labelled as non-experimental + * + * @access public + * + * @param array $pattern even keys are strings, odd are patterns (% and _) + * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) + * @param string $field optional field name that is being matched against + * (might be required when emulating ILIKE) + * + * @return string SQL pattern + */ + function matchPattern($pattern, $operator = null, $field = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (!is_null($operator)) { + $field = is_null($field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'LIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'LIKE BINARY '; + break; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'not a supported operator type:'. $operator, __FUNCTION__); + } + } + $match.= "'"; + foreach ($pattern as $key => $value) { + if ($key % 2) { + $match.= $value; + } else { + $match.= $db->escapePattern($db->escape($value)); + } + } + $match.= "'"; + $match.= $this->patternEscapeString(); + return $match; + } + + // }}} + // {{{ mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types, length, sign, fixed + * @access public + */ + function mapNativeDatatype($field) + { + $db_type = strtolower($field['type']); + $db_type = strtok($db_type, '(), '); + if ($db_type == 'national') { + $db_type = strtok('(), '); + } + if (!empty($field['length'])) { + $length = $field['length']; + $decimal = ''; + } else { + $length = strtok('(), '); + $decimal = strtok('(), '); + } + $type = array(); + $unsigned = $fixed = null; + switch ($db_type) { + case 'tinyint': + $type[] = 'integer'; + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 1; + break; + case 'smallint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 2; + break; + case 'mediumint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 3; + break; + case 'int': + case 'integer': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 4; + break; + case 'bigint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 8; + break; + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + case 'text': + case 'varchar': + $fixed = false; + case 'string': + case 'char': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($db_type, 'text')) { + $type[] = 'clob'; + if ($decimal == 'binary') { + $type[] = 'blob'; + } + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'enum': + $type[] = 'text'; + preg_match_all('/\'.+\'/U', $field['type'], $matches); + $length = 0; + $fixed = false; + if (is_array($matches)) { + foreach ($matches[0] as $value) { + $length = max($length, strlen($value)-2); + } + if ($length == '1' && count($matches[0]) == 2) { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + } + $type[] = 'integer'; + case 'set': + $fixed = false; + $type[] = 'text'; + $type[] = 'integer'; + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'real': + $type[] = 'float'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + break; + case 'unknown': + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + $type[] = 'blob'; + $length = null; + break; + case 'year': + $type[] = 'integer'; + $type[] = 'date'; + $length = null; + break; + default: + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unknown database attribute type: '.$db_type, __FUNCTION__); + } + + return array($type, $length, $unsigned, $fixed); + } +} + +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Function/Common.php b/trunk/docs/include/PEAR/MDB2/Driver/Function/Common.php new file mode 100644 index 0000000..c64cd78 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Function/Common.php @@ -0,0 +1,210 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.16 2006/08/07 20:15:21 lsmith Exp $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Base class for the function modules that is extended by each MDB2 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_Common extends MDB2_Module_Common +{ + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $error =& $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} + // {{{ functionTable() + + /** + * return string for internal table used when calling only a function + * + * @return string for internal table used when calling only a function + * @access public + */ + function functionTable() + { + return ''; + } + + // }}} + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time: + * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) + * - CURRENT_DATE (date, DATE type) + * - CURRENT_TIME (time, TIME type) + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'CURRENT_TIME'; + case 'date': + return 'CURRENT_DATE'; + case 'timestamp': + default: + return 'CURRENT_TIMESTAMP'; + } + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (!is_null($length)) { + return "SUBSTRING($value FROM $position FOR $length)"; + } + return "SUBSTRING($value FROM $position)"; + } + + // }}} + // {{{ concat() + + /** + * Returns string to concatenate two or more string parameters + * + * @param string $value1 + * @param string $value2 + * @param string $values... + * @return string to concatenate two strings + * @access public + */ + function concat($value1, $value2) + { + $args = func_get_args(); + return "(".implode(' || ', $args).")"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return 'RAND()'; + } + + // }}} + // {{{ lower() + + /** + * return string to call a function to lower the case of an expression + * + * @param string $expression + * @return return string to lower case of an expression + * @access public + */ + function lower($expression) + { + return "LOWER($expression)"; + } + + // }}} + // {{{ upper() + + /** + * return string to call a function to upper the case of an expression + * + * @param string $expression + * @return return string to upper case of an expression + * @access public + */ + function upper($expression) + { + return "UPPER($expression)"; + } + + // }}} +} +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Function/mysql.php b/trunk/docs/include/PEAR/MDB2/Driver/Function/mysql.php new file mode 100644 index 0000000..d57cac9 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Function/mysql.php @@ -0,0 +1,104 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.10 2006/06/12 21:48:43 lsmith Exp $ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 MySQL driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_mysql extends MDB2_Driver_Function_Common +{ + // }}} + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'CALL '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ concat() + + /** + * Returns string to concatenate two or more string parameters + * + * @param string $value1 + * @param string $value2 + * @param string $values... + * @return string to concatenate two strings + * @access public + **/ + function concat($value1, $value2) + { + $args = func_get_args(); + return "CONCAT(".implode(', ', $args).")"; + } +} +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Manager/Common.php b/trunk/docs/include/PEAR/MDB2/Driver/Manager/Common.php new file mode 100644 index 0000000..90516ba --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Manager/Common.php @@ -0,0 +1,805 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.56 2006/08/21 16:18:10 nrf Exp $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Base class for the management modules that is extended by each MDB2 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_Common extends MDB2_Module_Common +{ + // }}} + // {{{ getFieldDeclarationList() + + /** + * Get declaration of a number of field in bulk + * + * @param string $fields a multidimensional associative array. + * The first dimension determines the field name, while the second + * dimension is keyed with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Boolean value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * + * @return mixed string on success, a MDB2 error on failure + * @access public + */ + function getFieldDeclarationList($fields) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!is_array($fields) || empty($fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'missing any fields', __FUNCTION__); + } + + foreach ($fields as $field_name => $field) { + $query = $db->getDeclaration($field['type'], $field_name, $field); + if (PEAR::isError($query)) { + return $query; + } + $query_fields[] = $query; + } + return implode(', ', $query_fields); + } + + // }}} + // {{{ _fixSequenceName() + + /** + * Removes any formatting in an sequence name using the 'seqname_format' option + * + * @param string $sqn string that containts name of a potential sequence + * @param bool $check if only formatted sequences should be returned + * @return string name of the sequence with possible formatting removed + * @access protected + */ + function _fixSequenceName($sqn, $check = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $seq_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['seqname_format']).'$/i'; + $seq_name = preg_replace($seq_pattern, '\\1', $sqn); + if ($seq_name && !strcasecmp($sqn, $db->getSequenceName($seq_name))) { + return $seq_name; + } + if ($check) { + return false; + } + return $sqn; + } + + // }}} + // {{{ _fixIndexName() + + /** + * Removes any formatting in an index name using the 'idxname_format' option + * + * @param string $idx string that containts name of anl index + * @return string name of the index with possible formatting removed + * @access protected + */ + function _fixIndexName($idx) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $idx_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['idxname_format']).'$/i'; + $idx_name = preg_replace($idx_pattern, '\\1', $idx); + if ($idx_name && !strcasecmp($idx, $db->getIndexName($idx_name))) { + return $idx_name; + } + return $idx; + } + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($database) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($database) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + + $name = $db->quoteIdentifier($name, true); + $query = "CREATE TABLE $name ($query_fields)"; + return $db->exec($query); + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DROP TABLE $name"); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the Metabase parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the Metabase parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implementedd', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all references views should be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + **/ + function listTableViews($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableTriggers() + /** + * This function will be called to get all triggers of the + * current database ($db->getDatabase()) + * + * @access public + * @param string $table The name of the table from the + * previous database to query against. + * @return mixed Array on success or MDB2 error on failure + */ + function listTableTriggers($table = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a tables in the current database + * + * @param string $table name of table that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table ADD CONSTRAINT $name"; + if (!empty($definition['primary'])) { + $query.= ' PRIMARY KEY'; + } elseif (!empty($definition['unique'])) { + $query.= ' UNIQUE'; + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("ALTER TABLE $table DROP CONSTRAINT $name"); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } +} + +?> diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Manager/mysql.php b/trunk/docs/include/PEAR/MDB2/Driver/Manager/mysql.php new file mode 100644 index 0000000..2d15dc1 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Manager/mysql.php @@ -0,0 +1,915 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.84 2006/08/21 16:39:37 lsmith Exp $ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 MySQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_mysql extends MDB2_Driver_Manager_Common +{ + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = "CREATE DATABASE $name"; + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = "DROP DATABASE $name"; + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'character_set' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + $name = $db->quoteIdentifier($name, true); + $query = "CREATE TABLE $name ($query_fields)"; + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + if (!empty($options_strings)) { + $query.= ' '.implode(' ', $options_strings); + } + return $db->exec($query); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the Metabase parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the Metabase parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $query = ''; + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $query .= 'RENAME TO ' . $change_name; + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); + } + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'DROP ' . $field_name; + } + } + + $rename = array(); + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $rename[$field['name']] = $field_name; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$field_name])) { + $old_field_name = $rename[$field_name]; + unset($rename[$field_name]); + } else { + $old_field_name = $field_name; + } + $old_field_name = $db->quoteIdentifier($old_field_name, true); + $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + } + } + + if (!empty($rename) && is_array($rename)) { + foreach ($rename as $rename_name => $renamed_field) { + if ($query) { + $query.= ', '; + } + $field = $changes['rename'][$renamed_field]; + $renamed_field = $db->quoteIdentifier($renamed_field, true); + $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']); + } + } + + if (!$query) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("ALTER TABLE $name $query"); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->queryCol('SHOW DATABASES'); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->queryCol('SELECT DISTINCT USER FROM USER'); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW /*!50002 FULL*/ TABLES"; + if (!is_null($database)) { + $query .= " FROM $database"; + } + $query.= "/*!50002 WHERE Table_type = 'BASE TABLE'*/"; + + $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table) { + if (!$this->_fixSequenceName($table[0], true)) { + $result[] = $table[0]; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listViews() + + /** + * list the views in the database + * + * @param string database, the current is default + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + **/ + function listViews($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW FULL TABLES'; + if (!is_null($database)) { + $query.= " FROM $database"; + } + $query.= " WHERE Table_type = 'VIEW'"; + + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a tables in the current database + * + * @param string $table name of table that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $result = $db->queryCol("SHOW COLUMNS FROM $table"); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @author Leoncx + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * 'length' => 10 + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field => $fieldinfo) { + if (!empty($fieldinfo['length'])) { + $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')'; + } else { + $fields[] = $db->quoteIdentifier($field, true); + } + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name ON $table"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table"; + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) { + $result[$index] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $type = ''; + $name = $db->quoteIdentifier($db->getIndexName($name), true); + if (!empty($definition['primary'])) { + $type = 'PRIMARY'; + $name = 'KEY'; + } elseif (!empty($definition['unique'])) { + $type = 'UNIQUE'; + } + + $table = $db->quoteIdentifier($table, true); + $query = "ALTER TABLE $table ADD $type $name"; + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + if ($primary || strtolower($name) == 'primary') { + $query = "ALTER TABLE $table DROP PRIMARY KEY"; + } else { + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table DROP INDEX $name"; + } + return $db->exec($query); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table"; + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if (!$index_data[$non_unique]) { + if ($index_data[$key_name] !== 'PRIMARY') { + $index = $this->_fixIndexName($index_data[$key_name]); + } else { + $index = 'PRIMARY'; + } + if (!empty($index)) { + $result[$index] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); + + $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))"; + $query.= strlen($db->options['default_table_type']) ? ' TYPE='.$db->options['default_table_type'] : ''; + $res = $db->exec($query); + + if (PEAR::isError($res)) { + return $res; + } + + if ($start == 1) { + return MDB2_OK; + } + + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'; + $res = $db->exec($query); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + + // Handle error + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW TABLES"; + if (!is_null($database)) { + $query .= " FROM $database"; + } + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} +?> diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Native/Common.php b/trunk/docs/include/PEAR/MDB2/Driver/Native/Common.php new file mode 100644 index 0000000..3197d6d --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Native/Common.php @@ -0,0 +1,58 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.1 2006/06/18 21:59:05 lsmith Exp $ +// + +/** + * Base class for the natuve modules that is extended by each MDB2 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_Common extends MDB2_Module_Common +{ +} +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Native/mysql.php b/trunk/docs/include/PEAR/MDB2/Driver/Native/mysql.php new file mode 100644 index 0000000..90ff068 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Native/mysql.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.9 2006/06/18 21:59:05 lsmith Exp $ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 MySQL driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_mysql extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Reverse/Common.php b/trunk/docs/include/PEAR/MDB2/Driver/Reverse/Common.php new file mode 100644 index 0000000..23bbb3b --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Reverse/Common.php @@ -0,0 +1,423 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.28 2006/08/15 11:24:51 lsmith Exp $ +// + +/** + * @package MDB2 + * @category Database + */ + +/** + * These are constants for the tableInfo-function + * they are bitwised or'ed. so if there are more constants to be defined + * in the future, adjust MDB2_TABLEINFO_FULL accordingly + */ + +define('MDB2_TABLEINFO_ORDER', 1); +define('MDB2_TABLEINFO_ORDERTABLE', 2); +define('MDB2_TABLEINFO_FULL', 3); + +/** + * Base class for the schema reverse engineering module that is extended by each MDB2 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_Common extends MDB2_Module_Common +{ + // }}} + // {{{ getTableFieldDefinition() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of table that should be used in method + * @param string $fields name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the stucture of an index into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table, $index) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the stucture of an constraints into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table, $index) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getSequenceDefinition() + + /** + * Get the stucture of a sequence into an array + * + * @param string $sequence name of sequence that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getSequenceDefinition($sequence) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $start = $db->currId($sequence); + if (PEAR::isError($start)) { + return $start; + } + if ($db->supports('current_id')) { + $start++; + } else { + $db->warnings[] = 'database does not support getting current + sequence value, the sequence value was incremented'; + } + $definition = array(); + if ($start != 1) { + $definition = array('start' => $start); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the stucture of an trigger into an array + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which $mode + * you select. The sample output below is based on this query: + *
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * 
+ * + *
    + *
  • + * + * null (default) + *
    +     *   [0] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   [1] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldPhone
    +     *       [type] => string
    +     *       [len] => 20
    +     *       [flags] =>
    +     *   )
    +     *   [2] => Array (
    +     *       [table] => tblBar
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   
    + * + *
  • + * + * MDB2_TABLEINFO_ORDER + * + *

    In addition to the information found in the default output, + * a notation of the number of columns is provided by the + * num_fields element while the order + * element provides an array with the column names as the keys and + * their location index number (corresponding to the keys in the + * the default output) as the values.

    + * + *

    If a result set has identical field names, the last one is + * used.

    + * + *
    +     *   [num_fields] => 3
    +     *   [order] => Array (
    +     *       [fldId] => 2
    +     *       [fldTrans] => 1
    +     *   )
    +     *   
    + * + *
  • + * + * MDB2_TABLEINFO_ORDERTABLE + * + *

    Similar to MDB2_TABLEINFO_ORDER but adds more + * dimensions to the array in which the table names are keys and + * the field names are sub-keys. This is helpful for queries that + * join tables which have identical field names.

    + * + *
    +     *   [num_fields] => 3
    +     *   [ordertable] => Array (
    +     *       [tblFoo] => Array (
    +     *           [fldId] => 0
    +     *           [fldPhone] => 1
    +     *       )
    +     *       [tblBar] => Array (
    +     *           [fldId] => 2
    +     *       )
    +     *   )
    +     *   
    + * + *
  • + *
+ * + * The flags element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + primary_key + * + unique_key + * + multiple_key + * + not_null + * + * Most DBMS's only provide the table and flags + * elements if $result is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has MDB2_PORTABILITY_FIX_CASE + * turned on, the names of tables and fields will be lower or upper cased. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * MDB2_TABLEINFO_ORDERTABLE, + * MDB2_TABLEINFO_ORDER or + * MDB2_TABLEINFO_FULL (which does both). + * These are bitwise, so the first two can be + * combined using |. + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::setOption() + */ + function tableInfo($result, $mode = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!is_string($result)) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $db->loadModule('Manager', null, true); + $fields = $db->manager->listTableFields($result); + if (PEAR::isError($fields)) { + return $fields; + } + + $flags = array(); + + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + + $indexes = $db->manager->listTableIndexes($result); + if (PEAR::isError($indexes)) { + $db->setOption('idxname_format', $idxname_format); + return $indexes; + } + + foreach ($indexes as $index) { + $definition = $this->getTableIndexDefinition($result, $index); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + if (count($definition['fields']) > 1) { + foreach ($definition['fields'] as $field => $sort) { + $flags[$field] = 'multiple_key'; + } + } + } + + $constraints = $db->manager->listTableConstraints($result); + if (PEAR::isError($constraints)) { + return $constraints; + } + + foreach ($constraints as $constraint) { + $definition = $this->getTableConstraintDefinition($result, $constraint); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $flag = !empty($definition['primary']) + ? 'primary_key' : (!empty($definition['unique']) + ? 'unique_key' : false); + if ($flag) { + foreach ($definition['fields'] as $field => $sort) { + if (empty($flags[$field]) || $flags[$field] != 'primary_key') { + $flags[$field] = $flag; + } + } + } + } + + if ($mode) { + $res['num_fields'] = count($fields); + } + + foreach ($fields as $i => $field) { + $definition = $this->getTableFieldDefinition($result, $field); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $res[$i] = $definition[0]; + $res[$i]['name'] = $field; + $res[$i]['table'] = $result; + $res[$i]['type'] = preg_replace('/^([a-z]+).*$/i', '\\1', trim($definition[0]['nativetype'])); + // 'primary_key', 'unique_key', 'multiple_key' + $res[$i]['flags'] = empty($flags[$field]) ? '' : $flags[$field]; + // not_null', 'unsigned', 'auto_increment', 'default_[rawencodedvalue]' + if (!empty($res[$i]['notnull'])) { + $res[$i]['flags'].= ' not_null'; + } + if (!empty($res[$i]['unsigned'])) { + $res[$i]['flags'].= ' unsigned'; + } + if (!empty($res[$i]['auto_increment'])) { + $res[$i]['flags'].= ' autoincrement'; + } + if (!empty($res[$i]['default'])) { + $res[$i]['flags'].= ' default_'.rawurlencode($res[$i]['default']); + } + + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + $db->setOption('idxname_format', $idxname_format); + return $res; + } +} +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Driver/Reverse/mysql.php b/trunk/docs/include/PEAR/MDB2/Driver/Reverse/mysql.php new file mode 100644 index 0000000..122056e --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/Reverse/mysql.php @@ -0,0 +1,369 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.59 2006/08/12 15:44:03 lsmith Exp $ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 MySQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_mysql extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table, $field_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + $table = $db->quoteIdentifier($table, true); + $query = "SHOW COLUMNS FROM $table LIKE ".$db->quote($field_name); + $columns = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($columns)) { + return $columns; + } + foreach ($columns as $column) { + $column = array_change_key_case($column, CASE_LOWER); + $column['name'] = $column['field']; + unset($column['field']); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column['name'] = strtolower($column['name']); + } else { + $column['name'] = strtoupper($column['name']); + } + } else { + $column = array_change_key_case($column, $db->options['field_case']); + } + if ($field_name == $column['name']) { + list($types, $length, $unsigned, $fixed) = $db->datatype->mapNativeDatatype($column); + $notnull = false; + if (empty($column['null']) || $column['null'] !== 'YES') { + $notnull = true; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if (is_null($default) && $notnull) { + $default = ''; + } + } + $autoincrement = false; + if (!empty($column['extra']) && $column['extra'] == 'auto_increment') { + $autoincrement = true; + } + + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + if ($length > 0) { + $definition[0]['length'] = $length; + } + if (!is_null($unsigned)) { + $definition[0]['unsigned'] = $unsigned; + } + if (!is_null($fixed)) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the stucture of an index into an array + * + * @param string $table name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table, $index_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $index_name = $db->getIndexName($index_name); + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = ".$db->quote($index_name)." */"; + $result = $db->query($query); + if (PEAR::isError($result)) { + return $result; + } + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $key_name = $row['key_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + } else { + $key_name = strtoupper($key_name); + } + } + if ($index_name == $key_name) { + if (!$row['non_unique']) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + $column_name = $row['column_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column_name = strtolower($column_name); + } else { + $column_name = strtoupper($column_name); + } + } + $definition['fields'][$column_name] = array(); + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' + ? 'ascending' : 'descending'); + } + } + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the stucture of a constraint into an array + * + * @param string $table name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table, $index_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (strtolower($index_name) != 'primary') { + $index_name = $db->getIndexName($index_name); + } + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = ".$db->quote($index_name)." */"; + $result = $db->query($query); + if (PEAR::isError($result)) { + return $result; + } + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $key_name = $row['key_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + } else { + $key_name = strtoupper($key_name); + } + } + if ($index_name == $key_name) { + if ($row['non_unique']) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table constraint', __FUNCTION__); + } + if ($row['key_name'] == 'PRIMARY') { + $definition['primary'] = true; + } else { + $definition['unique'] = true; + } + $column_name = $row['column_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column_name = strtolower($column_name); + } else { + $column_name = strtoupper($column_name); + } + } + $definition['fields'][$column_name] = array(); + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' + ? 'ascending' : 'descending'); + } + } + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table constraint', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::setOption() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $id = MDB2::isResultCommon($result) ? $result->getResource() : $result; + if (!is_resource($id)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Could not generate result ressource', __FUNCTION__); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($id, $i)), + 'name' => $case_func(@mysql_field_name($id, $i)), + 'type' => @mysql_field_type($id, $i), + 'length' => @mysql_field_len($id, $i), + 'flags' => @mysql_field_flags($id, $i), + ); + if ($res[$i]['type'] == 'string') { + $res[$i]['type'] = 'char'; + } elseif ($res[$i]['type'] == 'unknown') { + $res[$i]['type'] = 'decimal'; + } + $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); + if (PEAR::isError($mdb2type_info)) { + return $mdb2type_info; + } + $res[$i]['mdb2type'] = $mdb2type_info[0][0]; + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } +} +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/Driver/mysql.php b/trunk/docs/include/PEAR/MDB2/Driver/mysql.php new file mode 100644 index 0000000..6524d12 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Driver/mysql.php @@ -0,0 +1,1416 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.157 2006/08/20 19:38:26 lsmith Exp $ +// + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_mysql extends MDB2_Driver_Common +{ + // {{{ properties + var $escape_quotes = "\\"; + + var $escape_pattern = "\\"; + + var $escape_identifier = '`'; + + var $start_transaction = false; + + var $varchar_max_length = 255; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'mysql'; + $this->dbsyntax = 'mysql'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = false; + $this->supported['savepoints'] = false; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = true; + $this->supported['sub_selects'] = 'emulated'; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = true; + + $this->options['default_table_type'] = ''; + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + if ($this->connection) { + $native_code = @mysql_errno($this->connection); + $native_msg = @mysql_error($this->connection); + } else { + $native_code = @mysql_errno(); + $native_msg = @mysql_error(); + } + if (is_null($error)) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 1004 => MDB2_ERROR_CANNOT_CREATE, + 1005 => MDB2_ERROR_CANNOT_CREATE, + 1006 => MDB2_ERROR_CANNOT_CREATE, + 1007 => MDB2_ERROR_ALREADY_EXISTS, + 1008 => MDB2_ERROR_CANNOT_DROP, + 1022 => MDB2_ERROR_ALREADY_EXISTS, + 1044 => MDB2_ERROR_ACCESS_VIOLATION, + 1046 => MDB2_ERROR_NODBSELECTED, + 1048 => MDB2_ERROR_CONSTRAINT, + 1049 => MDB2_ERROR_NOSUCHDB, + 1050 => MDB2_ERROR_ALREADY_EXISTS, + 1051 => MDB2_ERROR_NOSUCHTABLE, + 1054 => MDB2_ERROR_NOSUCHFIELD, + 1061 => MDB2_ERROR_ALREADY_EXISTS, + 1062 => MDB2_ERROR_ALREADY_EXISTS, + 1064 => MDB2_ERROR_SYNTAX, + 1091 => MDB2_ERROR_NOT_FOUND, + 1100 => MDB2_ERROR_NOT_LOCKED, + 1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 1142 => MDB2_ERROR_ACCESS_VIOLATION, + 1146 => MDB2_ERROR_NOSUCHTABLE, + 1216 => MDB2_ERROR_CONSTRAINT, + 1217 => MDB2_ERROR_CONSTRAINT, + ); + } + if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) { + $ecode_map[1022] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL; + $ecode_map[1062] = MDB2_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS; + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + if ($escape_wildcards) { + $text = $this->escapePattern($text); + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $text = @mysql_real_escape_string($text, $connection); + return $text; + } + + // }}} + // {{{ + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } elseif ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 1'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $server_info = $this->getServerVersion(); + if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { + return MDB2_OK; + } + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + + $result =& $this->_doQuery('COMMIT', true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 0'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $query = 'ROLLBACK'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 0'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + switch ($isolation) { + case 'READ UNCOMMITTED': + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + function connect() + { + if (is_resource($this->connection)) { + if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + $params = array(); + if ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix') { + $params[0] = ':' . $this->dsn['socket']; + } else { + $params[0] = $this->dsn['hostspec'] ? $this->dsn['hostspec'] + : 'localhost'; + if ($this->dsn['port']) { + $params[0].= ':' . $this->dsn['port']; + } + } + $params[] = $this->dsn['username'] ? $this->dsn['username'] : null; + $params[] = $this->dsn['password'] ? $this->dsn['password'] : null; + if (!$this->options['persistent']) { + if (isset($this->dsn['new_link']) + && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true) + ) { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($this->dsn['client_flags']) + ? $this->dsn['client_flags'] : null; + } + + $connect_function = $this->options['persistent'] ? 'mysql_pconnect' : 'mysql_connect'; + + @ini_set('track_errors', true); + $php_errormsg = ''; + $connection = @call_user_func_array($connect_function, $params); + @ini_restore('track_errors'); + if (!$connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + $err, __FUNCTION__); + } else { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + $php_errormsg, __FUNCTION__); + } + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (PEAR::isError($result)) { + return $result; + } + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = ''; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + $this->supported['transactions'] = $this->options['use_transactions']; + if ($this->options['default_table_type']) { + switch (strtoupper($this->options['default_table_type'])) { + case 'BLACKHOLE': + case 'MEMORY': + case 'ARCHIVE': + case 'CSV': + case 'HEAP': + case 'ISAM': + case 'MERGE': + case 'MRG_ISAM': + case 'ISAM': + case 'MRG_MYISAM': + case 'MYISAM': + $this->supported['transactions'] = false; + $this->warnings[] = $default_table_type. + ' is not a supported default table type'; + break; + } + } + + $this->supported['sub_selects'] = 'emulated'; + $this->supported['prepared_statements'] = 'emulated'; + $this->start_transaction = false; + $this->varchar_max_length = 255; + + $server_info = $this->getServerVersion(); + if (is_array($server_info)) { + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.0', '<')) { + $this->supported['sub_selects'] = true; + $this->supported['prepared_statements'] = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.14', '<') + || !version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.1', '<') + ) { + $this->supported['savepoints'] = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.11', '<')) { + $this->start_transaction = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { + $this->varchar_max_length = 65535; + } + } + + return MDB2_OK; + } + // }}} + // {{{ setCharset() + + /** + * Set the charset on the current connection + * + * @param string charset + * @param resource connection handle + * + * @return true on success, MDB2 Error Object on failure + */ + function setCharset($charset, $connection = null) + { + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + + $query = "SET character_set_client = '".mysql_real_escape_string($charset, $connection)."'"; + $result = @mysql_query($query, $connection); + if (!$result) { + return $this->raiseError(null, null, null, + 'Unable to set client charset: '.$charset, __FUNCTION__); + } + + return MDB2_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @param boolean $force if the disconnect should be forced even if the + * connection is opened persistently + * @return mixed true on success, false if not connected and error + * object on error + * @access public + */ + function disconnect($force = true) + { + if (is_resource($this->connection)) { + if ($this->in_transaction) { + $dsn = $this->dsn; + $database_name = $this->database_name; + $persistent = $this->options['persistent']; + $this->dsn = $this->connected_dsn; + $this->database_name = $this->connected_database_name; + $this->options['persistent'] = $this->opened_persistent; + $this->rollback(); + $this->dsn = $dsn; + $this->database_name = $database_name; + $this->options['persistent'] = $persistent; + } + + if (!$this->opened_persistent || $force) { + @mysql_close($this->connection); + } + } + return parent::disconnect($force); + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $is_manip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + if (is_null($database_name)) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@mysql_select_db($database_name, $connection)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $database_name; + } + } + + $function = $this->options['result_buffering'] + ? 'mysql_query' : 'mysql_unbuffered_query'; + $result = @$function($query, $connection); + if (!$result) { + $err =& $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + return @mysql_affected_rows($connection); + } + + // }}} + // {{{ _modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param boolean $is_manip if it is a DML query + * @param integer $limit limit the number of rows + * @param integer $offset start reading from given offset + * @return string modified query + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + if ($limit > 0 + && !preg_match('/LIMIT\s*\d(\s*(,|OFFSET)\s*\d+)?/i', $query) + ) { + $query = rtrim($query); + if (substr($query, -1) == ';') { + $query = substr($query, 0, -1); + } + if ($is_manip) { + return $query . " LIMIT $limit"; + } else { + return $query . " LIMIT $offset, $limit"; + } + } + return $query; + } + + // }}} + // {{{ getServerVersion() + + /** + * return version information about the server + * + * @param string $native determines if the raw version string should be returned + * @return mixed array/string with version information or MDB2 error object + * @access public + */ + function getServerVersion($native = false) + { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $server_info = @mysql_get_server_info($connection); + } + if (!$server_info) { + return $this->raiseError(null, null, null, + 'Could not get server information', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + if (isset($tmp[2]) && strpos($tmp[2], '-')) { + $tmp2 = explode('-', @$tmp[2], 2); + } else { + $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null; + $tmp2[1] = null; + } + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => $tmp2[0], + 'extra' => $tmp2[1], + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :[a-zA-Z] and are placeholders which can be set using + * bindParam() and the query can be send off using the execute() method. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function &prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared'] + || $this->supported['prepared_statements'] !== true + ) { + $obj =& parent::prepare($query, $types, $result_types, $lobs); + return $obj; + } + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (is_null($placeholder_type)) { + $placeholder_type_guess = $query[$p_position]; + } + if (is_int($quote = strpos($query, "'", $position)) && $quote < $p_position) { + if (!is_int($end_quote = strpos($query, "'", $quote + 1))) { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'query with an unterminated text string specified', __FUNCTION__); + return $err; + } + switch ($this->escape_quotes) { + case '': + case "'": + $position = $end_quote + 1; + break; + default: + if ($end_quote == $quote + 1) { + $position = $end_quote + 1; + } else { + if ($query[$end_quote-1] == $this->escape_quotes) { + $position = $end_quote; + } else { + $position = $end_quote + 1; + } + } + break; + } + } elseif ($query[$position] == $placeholder_type_guess) { + if (is_null($placeholder_type)) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + } + if ($placeholder_type == ':') { + $parameter = preg_replace('/^.{'.($position+1).'}([a-z0-9_]+).*$/si', '\\1', $query); + if ($parameter === '') { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter with an empty name', __FUNCTION__); + return $err; + } elseif (isset($positions[$parameter])) { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter names can only be used once per statement', __FUNCTION__); + return $err; + } + $positions[$parameter] = $p_position; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + } else { + $positions[] = $p_position; + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $statement_name = 'MDB2_Statement_'.$this->phptype.'_'.md5(time() + rand()); + $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text'); + $statement =& $this->_doQuery($query, true, $connection); + if (PEAR::isError($statement)) { + return $statement; + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj =& new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ replace() + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the REPLACE query just updates its values instead of + * inserting a new row. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only MySQL implements it natively, this type of query is + * emulated through this method for other DBMS using standard types of + * queries inside a transaction to assure the atomicity of the operation. + * + * @access public + * + * @param string $table name of the table on which the REPLACE query will + * be executed. + * @param array $fields associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. The + * values of the array are also associative arrays that describe the + * values and other properties of the table fields. + * + * Here follows a list of field properties that need to be specified: + * + * value: + * Value to be assigned to the specified field. This value may be + * of specified in database independent type format as this + * function can perform the necessary datatype conversions. + * + * Default: + * this property is required unless the Null property + * is set to 1. + * + * type + * Name of the type of the field. Currently, all types Metabase + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * Boolean property that indicates that the value for this field + * should be set to null. + * + * The default value for fields missing in INSERT queries may be + * specified the definition of a table. Often, the default value + * is already null, but since the REPLACE may be emulated using + * an UPDATE query, make sure that all fields of the table are + * listed in this function argument array. + * + * Default: 0 + * + * key + * Boolean property that indicates that this field should be + * handled as a primary key or at least as part of the compound + * unique index of the table that will determine the row that will + * updated if it exists or inserted a new row otherwise. + * + * This function will fail if no key field is specified or if the + * value of a key field is set to null because fields that are + * part of unique index they may not be null. + * + * Default: 0 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function replace($table, $fields) + { + $count = count($fields); + $query = $values = ''; + $keys = $colnum = 0; + for (reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + if ($colnum > 0) { + $query .= ','; + $values.= ','; + } + $query.= $name; + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $result =& $this->_doQuery($query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result =& $this->_doQuery($query, true); + $this->popExpect(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + // Since we are creating the sequence on demand + // we know the first id = 1 so initialize the + // sequence at 2 + $result = $this->manager->createSequence($seq_name, 2); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + // First ID of a newly created sequence is 1 + return 1; + } + } + return $result; + } + $value = $this->lastInsertID(); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051 + return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer'); + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; + return $this->queryOne($query, 'integer'); + } +} + +/** + * MDB2 MySQL result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_mysql extends MDB2_Result_Common +{ + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @mysql_fetch_assoc($this->result); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + $row = @mysql_fetch_row($this->result); + } + + if (!$row) { + if ($this->result === false) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, false); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $row = &new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @mysql_field_name($this->result, $column); + $columns[$column_name] = $column; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_change_key_case($columns, $this->db->options['field_case']); + } + return $columns; + } + + // }}} + // {{{ numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + * @access public + */ + function numCols() + { + $cols = @mysql_num_fields($this->result); + if (is_null($cols)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return boolean true on success, false if result is invalid + * @access public + */ + function free() + { + if (is_resource($this->result) && $this->db->connection) { + $free = @mysql_free_result($this->result); + if ($free === false) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 MySQL buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_mysql extends MDB2_Result_mysql +{ + // }}} + // {{{ seek() + + /** + * Seek to a specific row in a result set + * + * @param int $rownum number of the row where the data can be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function seek($rownum = 0) + { + if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @mysql_num_rows($this->result); + if (is_null($rows)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } +} + +/** + * MDB2 MySQL statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_mysql extends MDB2_Statement_Common +{ + // {{{ _execute() + + /** + * Execute a prepared query statement helper method. + * + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function &_execute($result_class = true, $result_wrap_class = false) + { + if (is_null($this->statement)) { + $result =& parent::_execute($result_class, $result_wrap_class); + return $result; + } + $this->db->last_query = $this->query; + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); + if ($this->db->getOption('disable_query')) { + $result = $this->is_manip ? 0 : null; + return $result; + } + + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = 'EXECUTE '.$this->statement; + if (!empty($this->positions)) { + $parameters = array(); + foreach ($this->positions as $parameter => $current_position) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if (is_resource($value) || $type == 'clob' || $type == 'blob') { + if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + $close = true; + } + if (is_resource($value)) { + $data = ''; + while (!@feof($value)) { + $data.= @fread($value, $this->db->options['lob_buffer_length']); + } + if ($close) { + @fclose($value); + } + $value = $data; + } + } + $param_query = 'SET @'.$parameter.' = '.$this->db->quote($value, $type); + $result = $this->db->_doQuery($param_query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + } + $query.= ' USING @'.implode(', @', array_keys($this->positions)); + } + + $result = $this->db->_doQuery($query, $this->is_manip, $connection); + if (PEAR::isError($result)) { + return $result; + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $result); + return $affected_rows; + } + + $result =& $this->db->_wrapResult($result, $this->result_types, + $result_class, $result_wrap_class, $this->limit, $this->offset); + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function free() + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if (!is_null($this->statement)) { + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $query = 'DEALLOCATE PREPARE '.$this->statement; + $result = $this->db->_doQuery($query, true, $connection); + } + + parent::free(); + return $result; + } +} +?> diff --git a/trunk/docs/include/PEAR/MDB2/Extended.php b/trunk/docs/include/PEAR/MDB2/Extended.php new file mode 100644 index 0000000..408673f --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Extended.php @@ -0,0 +1,704 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Extended.php,v 1.52 2006/08/18 21:53:43 lsmith Exp $ + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Used by autoPrepare() + */ +define('MDB2_AUTOQUERY_INSERT', 1); +define('MDB2_AUTOQUERY_UPDATE', 2); +define('MDB2_AUTOQUERY_DELETE', 3); +define('MDB2_AUTOQUERY_SELECT', 4); + +/** + * MDB2_Extended: class which adds several high level methods to MDB2 + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Extended extends MDB2_Module_Common +{ + // {{{ autoPrepare() + + /** + * Generate an insert, update or delete query and call prepare() on it + * + * @param string table + * @param array the fields names + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * @param array that contains the types of the placeholders + * @param mixed array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * + * @return resource handle for the query + * @see buildManipSQL + * @access public + */ + function autoPrepare($table, $table_fields, $mode = MDB2_AUTOQUERY_INSERT, + $where = false, $types = null, $result_types = MDB2_PREPARE_MANIP) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (PEAR::isError($query)) { + return $query; + } + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + return $db->prepare($query, $types, $result_types); + } + // }}} + + // {{{ autoExecute() + + /** + * Generate an insert, update or delete query and call prepare() and execute() on it + * + * @param string name of the table + * @param array assoc ($key=>$value) where $key is a field name and $value its value + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * @param array that contains the types of the placeholders + * @param string which specifies which result class to use + * @param mixed array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * + * @return bool|MDB2_Error true on success, a MDB2 error on failure + * @see buildManipSQL + * @see autoPrepare + * @access public + */ + function &autoExecute($table, $fields_values, $mode = MDB2_AUTOQUERY_INSERT, + $where = false, $types = null, $result_class = true, $result_types = MDB2_PREPARE_MANIP) + { + $fields_values = (array)$fields_values; + if ($mode == MDB2_AUTOQUERY_SELECT) { + if (is_array($result_types)) { + $keys = array_keys($result_types); + } else { + $keys = $result_types = array(); + } + } else { + $keys = array_keys($fields_values); + } + $params = array_values($fields_values); + if (empty($params)) { + $query = $this->buildManipSQL($table, $keys, $mode, $where); + + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if ($mode == MDB2_AUTOQUERY_SELECT) { + $result =& $db->query($query, $result_types, $result_class); + } else { + $result =& $db->exec($query); + } + } else { + $stmt = $this->autoPrepare($table, $keys, $mode, $where, $types, $result_types); + if (PEAR::isError($stmt)) { + return $stmt; + } + $result =& $stmt->execute($params, $result_class); + $stmt->free(); + } + return $result; + } + // }}} + + // {{{ buildManipSQL() + + /** + * Make automaticaly an sql query for prepare() + * + * Example : buildManipSQL('table_sql', array('field1', 'field2', 'field3'), MDB2_AUTOQUERY_INSERT) + * will return the string : INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) + * NB : - This belongs more to a SQL Builder class, but this is a simple facility + * - Be carefull ! If you don't give a $where param with an UPDATE/DELETE query, all + * the records of the table will be updated/deleted ! + * + * @param string name of the table + * @param ordered array containing the fields names + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * + * @return string sql query for prepare() + * @access public + */ + function buildManipSQL($table, $table_fields, $mode, $where = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($table_fields) && $db->options['quote_identifier']) { + foreach ($table_fields as $key => $field) { + $table_fields[$key] = $db->quoteIdentifier($field); + } + } + + if ($where !== false && !is_null($where)) { + if (is_array($where)) { + $where = implode(' AND ', $where); + } + $where = ' WHERE '.$where; + } + + switch ($mode) { + case MDB2_AUTOQUERY_INSERT: + if (empty($table_fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Insert requires table fields', __FUNCTION__); + } + $cols = implode(', ', $table_fields); + $values = '?'.str_repeat(', ?', (count($table_fields) - 1)); + return 'INSERT INTO '.$table.' ('.$cols.') VALUES ('.$values.')'; + break; + case MDB2_AUTOQUERY_UPDATE: + if (empty($table_fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Update requires table fields', __FUNCTION__); + } + $set = implode(' = ?, ', $table_fields).' = ?'; + $sql = 'UPDATE '.$table.' SET '.$set.$where; + return $sql; + break; + case MDB2_AUTOQUERY_DELETE: + $sql = 'DELETE FROM '.$table.$where; + return $sql; + break; + case MDB2_AUTOQUERY_SELECT: + $cols = is_array($table_fields) ? implode(', ', $table_fields) : '*'; + $sql = 'SELECT '.$cols.' FROM '.$table.$where; + return $sql; + break; + } + return $db->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'Non existant mode', __FUNCTION__); + } + // }}} + + // {{{ limitQuery() + + /** + * Generates a limited query + * + * @param string query + * @param array that contains the types of the columns in the result set + * @param integer the numbers of rows to fetch + * @param integer the row to start to fetching + * @param string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * + * @return MDB2_Result|MDB2_Error result set on success, a MDB2 error on failure + * @access public + */ + function &limitQuery($query, $types, $limit, $offset = 0, $result_class = true, + $result_wrap_class = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->setLimit($limit, $offset); + if (PEAR::isError($result)) { + return $result; + } + $result =& $db->query($query, $types, $result_class, $result_wrap_class); + return $result; + } + // }}} + + // {{{ execParam() + + /** + * Execute a parameterized DML statement. + * + * @param string the SQL query + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * + * @return int|MDB2_Error affected rows on success, a MDB2 error on failure + * @access public + */ + function execParam($query, $params = array(), $param_types = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->exec($query); + } + + $stmt = $db->prepare($query, $param_types, MDB2_PREPARE_MANIP); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (PEAR::isError($result)) { + return $result; + } + + $stmt->free(); + return $result; + } + // }}} + + // {{{ getOne() + + /** + * Fetch the first column of the first row of data returned from a query. + * Takes care of doing the query and freeing the results when finished. + * + * @param string the SQL query + * @param string that contains the type of the column in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int|string which column to return + * + * @return scalar|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getOne($query, $type = null, $params = array(), + $param_types = null, $colnum = 0) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + settype($type, 'array'); + if (empty($params)) { + return $db->queryOne($query, $type, $colnum); + } + + $stmt = $db->prepare($query, $param_types, $type); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $one = $result->fetchOne($colnum); + $stmt->free(); + $result->free(); + return $one; + } + // }}} + + // {{{ getRow() + + /** + * Fetch the first row of data returned from a query. Takes care + * of doing the query and freeing the results when finished. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int the fetch mode to use + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getRow($query, $types = null, $params = array(), + $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryRow($query, $types, $fetchmode); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $row = $result->fetchRow($fetchmode); + $stmt->free(); + $result->free(); + return $row; + } + // }}} + + // {{{ getCol() + + /** + * Fetch a single column from a result set and return it as an + * indexed array. + * + * @param string the SQL query + * @param string that contains the type of the column in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int|string which column to return + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getCol($query, $type = null, $params = array(), + $param_types = null, $colnum = 0) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + settype($type, 'array'); + if (empty($params)) { + return $db->queryCol($query, $type, $colnum); + } + + $stmt = $db->prepare($query, $param_types, $type); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $col = $result->fetchCol($colnum); + $stmt->free(); + $result->free(); + return $col; + } + // }}} + + // {{{ getAll() + + /** + * Fetch all the rows returned from a query. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int the fetch mode to use + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool $force_array used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool $group if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getAll($query, $types = null, $params = array(), + $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, + $rekey = false, $force_array = false, $group = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryAll($query, $types, $fetchmode, $rekey, $force_array, $group); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group); + $stmt->free(); + $result->free(); + return $all; + } + // }}} + + // {{{ getAssoc() + + /** + * Fetch the entire result set of a query and return it as an + * associative array using the first column as the key. + * + * If the result set contains more than two columns, the value + * will be an array of the values from column 2-n. If the result + * set contains only two columns, the returned value will be a + * scalar with the value of the second column (unless forced to an + * array with the $force_array parameter). A MDB2 error code is + * returned on errors. If the result set contains fewer than two + * columns, a MDB2_ERROR_TRUNCATED error is returned. + * + * For example, if the table 'mytable' contains: + * + * ID TEXT DATE + * -------------------------------- + * 1 'one' 944679408 + * 2 'two' 944679408 + * 3 'three' 944679408 + * + * Then the call getAssoc('SELECT id,text FROM mytable') returns: + * array( + * '1' => 'one', + * '2' => 'two', + * '3' => 'three', + * ) + * + * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + * array( + * '1' => array('one', '944679408'), + * '2' => array('two', '944679408'), + * '3' => array('three', '944679408') + * ) + * + * If the more than one row occurs with the same value in the + * first column, the last row overwrites all previous ones by + * default. Use the $group parameter if you don't want to + * overwrite like this. Example: + * + * getAssoc('SELECT category,id,name FROM mytable', null, null + * MDB2_FETCHMODE_ASSOC, false, true) returns: + * array( + * '1' => array(array('id' => '4', 'name' => 'number four'), + * array('id' => '6', 'name' => 'number six') + * ), + * '9' => array(array('id' => '4', 'name' => 'number four'), + * array('id' => '6', 'name' => 'number six') + * ) + * ) + * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param bool $force_array used only when the query returns + * exactly two columns. If TRUE, the values of the returned array + * will be one-element arrays instead of scalars. + * @param bool $group if TRUE, the values of the returned array + * is wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getAssoc($query, $types = null, $params = array(), $param_types = null, + $fetchmode = MDB2_FETCHMODE_DEFAULT, $force_array = false, $group = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryAll($query, $types, $fetchmode, true, $force_array, $group); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, true, $force_array, $group); + $stmt->free(); + $result->free(); + return $all; + } + // }}} + + // {{{ executeMultiple() + + /** + * This function does several execute() calls on the same statement handle. + * $params must be an array indexed numerically from 0, one execute call is + * done for every 'row' in the array. + * + * If an error occurs during execute(), executeMultiple() does not execute + * the unfinished rows, but rather returns that error. + * + * @param resource query handle from prepare() + * @param array numeric array containing the data to insert into the query + * + * @return bool|MDB2_Error true on success, a MDB2 error on failure + * @access public + * @see prepare(), execute() + */ + function executeMultiple(&$stmt, $params = null) + { + for ($i = 0, $j = count($params); $i < $j; $i++) { + $result = $stmt->execute($params[$i]); + if (PEAR::isError($result)) { + return $result; + } + } + return MDB2_OK; + } + // }}} + + // {{{ getBeforeID() + + /** + * Returns the next free id of a sequence if the RDBMS + * does not support auto increment + * + * @param string name of the table into which a new row was inserted + * @param string name of the field into which a new row was inserted + * @param bool when true the sequence is automatic created, if it not exists + * @param bool if the returned value should be quoted + * + * @return int|MDB2_Error id on success, a MDB2 error on failure + * @access public + */ + function getBeforeID($table, $field = null, $ondemand = true, $quote = true) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($db->supports('auto_increment') !== true) { + $seq = $table.(empty($field) ? '' : '_'.$field); + $id = $db->nextID($seq, $ondemand); + if (!$quote || PEAR::isError($id)) { + return $id; + } + return $db->quote($id, 'integer'); + } elseif (!$quote) { + return null; + } + return 'NULL'; + } + // }}} + + // {{{ getAfterID() + + /** + * Returns the autoincrement ID if supported or $id + * + * @param mixed value as returned by getBeforeId() + * @param string name of the table into which a new row was inserted + * @param string name of the field into which a new row was inserted + * + * @return int|MDB2_Error id on success, a MDB2 error on failure + * @access public + */ + function getAfterID($id, $table, $field = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($db->supports('auto_increment') !== true) { + return $id; + } + return $db->lastInsertID($table, $field); + } + // }}} +} +?> diff --git a/trunk/docs/include/PEAR/MDB2/Iterator.php b/trunk/docs/include/PEAR/MDB2/Iterator.php new file mode 100644 index 0000000..ca5e7b6 --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/Iterator.php @@ -0,0 +1,259 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Iterator.php,v 1.22 2006/05/06 14:03:41 lsmith Exp $ + +/** + * PHP5 Iterator + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Iterator implements Iterator +{ + protected $fetchmode; + protected $result; + protected $row; + + // {{{ constructor + + /** + * Constructor + */ + public function __construct($result, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $this->result = $result; + $this->fetchmode = $fetchmode; + } + // }}} + + // {{{ seek() + + /** + * Seek forward to a specific row in a result set + * + * @param int number of the row where the data can be found + * + * @return void + * @access public + */ + public function seek($rownum) + { + $this->row = null; + if ($this->result) { + $this->result->seek($rownum); + } + } + // }}} + + // {{{ next() + + /** + * Fetch next row of data + * + * @return void + * @access public + */ + public function next() + { + $this->row = null; + } + // }}} + + // {{{ current() + + /** + * return a row of data + * + * @return void + * @access public + */ + public function current() + { + if (is_null($this->row)) { + $row = $this->result->fetchRow($this->fetchmode); + if (PEAR::isError($row)) { + $row = false; + } + $this->row = $row; + } + return $this->row; + } + // }}} + + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return bool true/false, false is also returned on failure + * @access public + */ + public function valid() + { + return (bool)$this->current(); + } + // }}} + + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function free() + { + if ($this->result) { + return $this->result->free(); + } + $this->result = false; + $this->row = null; + return false; + } + // }}} + + // {{{ key() + + /** + * Returns the row number + * + * @return int|bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function key() + { + if ($this->result) { + return $this->result->rowCount(); + } + return false; + } + // }}} + + // {{{ rewind() + + /** + * Seek to the first row in a result set + * + * @return void + * @access public + */ + public function rewind() + { + } + // }}} + + // {{{ destructor + + /** + * Destructor + */ + public function __destruct() + { + $this->free(); + } + // }}} +} + +/** + * PHP5 buffered Iterator + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedIterator extends MDB2_Iterator implements SeekableIterator +{ + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function valid() + { + if ($this->result) { + return $this->result->valid(); + } + return false; + } + // }}} + + // {{{count() + + /** + * Returns the number of rows in a result object + * + * @return int|MDB2_Error number of rows, false|MDB2_Error if result is invalid + * @access public + */ + public function count() + { + if ($this->result) { + return $this->result->numRows(); + } + return false; + } + // }}} + + // {{{ rewind() + + /** + * Seek to the first row in a result set + * + * @return void + * @access public + */ + public function rewind() + { + $this->seek(0); + } + // }}} +} + +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/MDB2/LOB.php b/trunk/docs/include/PEAR/MDB2/LOB.php new file mode 100644 index 0000000..317e02f --- /dev/null +++ b/trunk/docs/include/PEAR/MDB2/LOB.php @@ -0,0 +1,245 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: LOB.php,v 1.33 2006/05/31 14:38:06 lsmith Exp $ + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +require_once 'MDB2.php'; + +/** + * MDB2_LOB: user land stream wrapper implementation for LOB support + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_LOB +{ + /** + * contains the key to the global MDB2 instance array of the associated + * MDB2 instance + * + * @var integer + * @access protected + */ + var $db_index; + + /** + * contains the key to the global MDB2_LOB instance array of the associated + * MDB2_LOB instance + * + * @var integer + * @access protected + */ + var $lob_index; + + // {{{ stream_open() + + /** + * open stream + * + * @param string specifies the URL that was passed to fopen() + * @param string the mode used to open the file + * @param int holds additional flags set by the streams API + * @param string not used + * + * @return bool + * @access public + */ + function stream_open($path, $mode, $options, &$opened_path) + { + if (!preg_match('/^rb?\+?$/', $mode)) { + return false; + } + $url = parse_url($path); + if (empty($url['host'])) { + return false; + } + $this->db_index = (int)$url['host']; + if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + return false; + } + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $this->lob_index = (int)$url['user']; + if (!isset($db->datatype->lobs[$this->lob_index])) { + return false; + } + return true; + } + // }}} + + // {{{ stream_read() + + /** + * read stream + * + * @param int number of bytes to read + * + * @return string + * @access public + */ + function stream_read($count) + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $db->datatype->_retrieveLOB($db->datatype->lobs[$this->lob_index]); + + $data = $db->datatype->_readLOB($db->datatype->lobs[$this->lob_index], $count); + $length = strlen($data); + if ($length == 0) { + $db->datatype->lobs[$this->lob_index]['endOfLOB'] = true; + } + $db->datatype->lobs[$this->lob_index]['position'] += $length; + return $data; + } + } + // }}} + + // {{{ stream_write() + + /** + * write stream, note implemented + * + * @param string data + * + * @return int + * @access public + */ + function stream_write($data) + { + return 0; + } + // }}} + + // {{{ stream_tell() + + /** + * return the current position + * + * @return int current position + * @access public + */ + function stream_tell() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + return $db->datatype->lobs[$this->lob_index]['position']; + } + } + // }}} + + // {{{ stream_eof() + + /** + * Check if stream reaches EOF + * + * @return bool + * @access public + */ + function stream_eof() + { + if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + return true; + } + + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $result = $db->datatype->_endOfLOB($db->datatype->lobs[$this->lob_index]); + if (version_compare(phpversion(), "5.0", ">=") + && version_compare(phpversion(), "5.1", "<") + ) { + return !$result; + } + return $result; + } + // }}} + + // {{{ stream_seek() + + /** + * Seek stream, not implemented + * + * @param int offset + * @param int whence + * + * @return bool + * @access public + */ + function stream_seek($offset, $whence) + { + return false; + } + // }}} + + // {{{ stream_close() + + /** + * close stream + * + * @access public + */ + function stream_close() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + if (isset($db->datatype->lobs[$this->lob_index])) { + $db->datatype->_destroyLOB($db->datatype->lobs[$this->lob_index]); + unset($db->datatype->lobs[$this->lob_index]); + } + } + } + // }}} +} + +// register streams wrapper +if (!stream_wrapper_register("MDB2LOB", "MDB2_LOB")) { + MDB2::raiseError(); + return false; +} + +?> diff --git a/trunk/docs/include/PEAR/Mail/mime.php b/trunk/docs/include/PEAR/Mail/mime.php new file mode 100644 index 0000000..302a212 --- /dev/null +++ b/trunk/docs/include/PEAR/Mail/mime.php @@ -0,0 +1,713 @@ + | +// | Tomas V.V.Cox (port to PEAR) | +// +-----------------------------------------------------------------------+ +// +// $Id: mime.php,v 1.39 2005/06/13 21:24:16 cipri Exp $ + +require_once('PEAR.php'); +require_once('Mail/mimePart.php'); + +/** + * Mime mail composer class. Can handle: text and html bodies, embedded html + * images and attachments. + * Documentation and examples of this class are avaible here: + * http://pear.php.net/manual/ + * + * @notes This class is based on HTML Mime Mail class from + * Richard Heyes which was based also + * in the mime_mail.class by Tobias Ratschiller and + * Sascha Schumann + * + * @author Richard Heyes + * @author Tomas V.V.Cox + * @package Mail + * @access public + */ +class Mail_mime +{ + /** + * Contains the plain text part of the email + * @var string + */ + var $_txtbody; + /** + * Contains the html part of the email + * @var string + */ + var $_htmlbody; + /** + * contains the mime encoded text + * @var string + */ + var $_mime; + /** + * contains the multipart content + * @var string + */ + var $_multipart; + /** + * list of the attached images + * @var array + */ + var $_html_images = array(); + /** + * list of the attachements + * @var array + */ + var $_parts = array(); + /** + * Build parameters + * @var array + */ + var $_build_params = array(); + /** + * Headers for the mail + * @var array + */ + var $_headers = array(); + /** + * End Of Line sequence (for serialize) + * @var string + */ + var $_eol; + + + /** + * Constructor function + * + * @access public + */ + function Mail_mime($crlf = "\r\n") + { + $this->_setEOL($crlf); + $this->_build_params = array( + 'text_encoding' => '7bit', + 'html_encoding' => 'quoted-printable', + '7bit_wrap' => 998, + 'html_charset' => 'ISO-8859-1', + 'text_charset' => 'ISO-8859-1', + 'head_charset' => 'ISO-8859-1' + ); + } + + /** + * Wakeup (unserialize) - re-sets EOL constant + * + * @access private + */ + function __wakeup() + { + $this->_setEOL($this->_eol); + } + + /** + * Accessor function to set the body text. Body text is used if + * it's not an html mail being sent or else is used to fill the + * text/plain part that emails clients who don't support + * html should show. + * + * @param string $data Either a string or + * the file name with the contents + * @param bool $isfile If true the first param should be treated + * as a file name, else as a string (default) + * @param bool $append If true the text or file is appended to + * the existing body, else the old body is + * overwritten + * @return mixed true on success or PEAR_Error object + * @access public + */ + function setTXTBody($data, $isfile = false, $append = false) + { + if (!$isfile) { + if (!$append) { + $this->_txtbody = $data; + } else { + $this->_txtbody .= $data; + } + } else { + $cont = $this->_file2str($data); + if (PEAR::isError($cont)) { + return $cont; + } + if (!$append) { + $this->_txtbody = $cont; + } else { + $this->_txtbody .= $cont; + } + } + return true; + } + + /** + * Adds a html part to the mail + * + * @param string $data Either a string or the file name with the + * contents + * @param bool $isfile If true the first param should be treated + * as a file name, else as a string (default) + * @return mixed true on success or PEAR_Error object + * @access public + */ + function setHTMLBody($data, $isfile = false) + { + if (!$isfile) { + $this->_htmlbody = $data; + } else { + $cont = $this->_file2str($data); + if (PEAR::isError($cont)) { + return $cont; + } + $this->_htmlbody = $cont; + } + + return true; + } + + /** + * Adds an image to the list of embedded images. + * + * @param string $file The image file name OR image data itself + * @param string $c_type The content type + * @param string $name The filename of the image. + * Only use if $file is the image data + * @param bool $isfilename Whether $file is a filename or not + * Defaults to true + * @return mixed true on success or PEAR_Error object + * @access public + */ + function addHTMLImage($file, $c_type='application/octet-stream', + $name = '', $isfilename = true) + { + $filedata = ($isfilename === true) ? $this->_file2str($file) + : $file; + if ($isfilename === true) { + $filename = ($name == '' ? basename($file) : basename($name)); + } else { + $filename = basename($name); + } + if (PEAR::isError($filedata)) { + return $filedata; + } + $this->_html_images[] = array( + 'body' => $filedata, + 'name' => $filename, + 'c_type' => $c_type, + 'cid' => md5(uniqid(time())) + ); + return true; + } + + /** + * Adds a file to the list of attachments. + * + * @param string $file The file name of the file to attach + * OR the file data itself + * @param string $c_type The content type + * @param string $name The filename of the attachment + * Only use if $file is the file data + * @param bool $isFilename Whether $file is a filename or not + * Defaults to true + * @return mixed true on success or PEAR_Error object + * @access public + */ + function addAttachment($file, $c_type = 'application/octet-stream', + $name = '', $isfilename = true, + $encoding = 'base64') + { + $filedata = ($isfilename === true) ? $this->_file2str($file) + : $file; + if ($isfilename === true) { + // Force the name the user supplied, otherwise use $file + $filename = (!empty($name)) ? $name : $file; + } else { + $filename = $name; + } + if (empty($filename)) { + return PEAR::raiseError( + 'The supplied filename for the attachment can\'t be empty' + ); + } + $filename = basename($filename); + if (PEAR::isError($filedata)) { + return $filedata; + } + + $this->_parts[] = array( + 'body' => $filedata, + 'name' => $filename, + 'c_type' => $c_type, + 'encoding' => $encoding + ); + return true; + } + + /** + * Get the contents of the given file name as string + * + * @param string $file_name path of file to process + * @return string contents of $file_name + * @access private + */ + function &_file2str($file_name) + { + if (!is_readable($file_name)) { + return PEAR::raiseError('File is not readable ' . $file_name); + } + if (!$fd = fopen($file_name, 'rb')) { + return PEAR::raiseError('Could not open ' . $file_name); + } + $filesize = filesize($file_name); + if ($filesize == 0){ + $cont = ""; + }else{ + $cont = fread($fd, $filesize); + } + fclose($fd); + return $cont; + } + + /** + * Adds a text subpart to the mimePart object and + * returns it during the build process. + * + * @param mixed The object to add the part to, or + * null if a new object is to be created. + * @param string The text to add. + * @return object The text mimePart object + * @access private + */ + function &_addTextPart(&$obj, $text) + { + $params['content_type'] = 'text/plain'; + $params['encoding'] = $this->_build_params['text_encoding']; + $params['charset'] = $this->_build_params['text_charset']; + if (is_object($obj)) { + return $obj->addSubpart($text, $params); + } else { + return new Mail_mimePart($text, $params); + } + } + + /** + * Adds a html subpart to the mimePart object and + * returns it during the build process. + * + * @param mixed The object to add the part to, or + * null if a new object is to be created. + * @return object The html mimePart object + * @access private + */ + function &_addHtmlPart(&$obj) + { + $params['content_type'] = 'text/html'; + $params['encoding'] = $this->_build_params['html_encoding']; + $params['charset'] = $this->_build_params['html_charset']; + if (is_object($obj)) { + return $obj->addSubpart($this->_htmlbody, $params); + } else { + return new Mail_mimePart($this->_htmlbody, $params); + } + } + + /** + * Creates a new mimePart object, using multipart/mixed as + * the initial content-type and returns it during the + * build process. + * + * @return object The multipart/mixed mimePart object + * @access private + */ + function &_addMixedPart() + { + $params['content_type'] = 'multipart/mixed'; + return new Mail_mimePart('', $params); + } + + /** + * Adds a multipart/alternative part to a mimePart + * object (or creates one), and returns it during + * the build process. + * + * @param mixed The object to add the part to, or + * null if a new object is to be created. + * @return object The multipart/mixed mimePart object + * @access private + */ + function &_addAlternativePart(&$obj) + { + $params['content_type'] = 'multipart/alternative'; + if (is_object($obj)) { + return $obj->addSubpart('', $params); + } else { + return new Mail_mimePart('', $params); + } + } + + /** + * Adds a multipart/related part to a mimePart + * object (or creates one), and returns it during + * the build process. + * + * @param mixed The object to add the part to, or + * null if a new object is to be created + * @return object The multipart/mixed mimePart object + * @access private + */ + function &_addRelatedPart(&$obj) + { + $params['content_type'] = 'multipart/related'; + if (is_object($obj)) { + return $obj->addSubpart('', $params); + } else { + return new Mail_mimePart('', $params); + } + } + + /** + * Adds an html image subpart to a mimePart object + * and returns it during the build process. + * + * @param object The mimePart to add the image to + * @param array The image information + * @return object The image mimePart object + * @access private + */ + function &_addHtmlImagePart(&$obj, $value) + { + $params['content_type'] = $value['c_type']; + $params['encoding'] = 'base64'; + $params['disposition'] = 'inline'; + $params['dfilename'] = $value['name']; + $params['cid'] = $value['cid']; + $obj->addSubpart($value['body'], $params); + } + + /** + * Adds an attachment subpart to a mimePart object + * and returns it during the build process. + * + * @param object The mimePart to add the image to + * @param array The attachment information + * @return object The image mimePart object + * @access private + */ + function &_addAttachmentPart(&$obj, $value) + { + $params['content_type'] = $value['c_type']; + $params['encoding'] = $value['encoding']; + $params['disposition'] = 'attachment'; + $params['dfilename'] = $value['name']; + $obj->addSubpart($value['body'], $params); + } + + /** + * Builds the multipart message from the list ($this->_parts) and + * returns the mime content. + * + * @param array Build parameters that change the way the email + * is built. Should be associative. Can contain: + * text_encoding - What encoding to use for plain text + * Default is 7bit + * html_encoding - What encoding to use for html + * Default is quoted-printable + * 7bit_wrap - Number of characters before text is + * wrapped in 7bit encoding + * Default is 998 + * html_charset - The character set to use for html. + * Default is iso-8859-1 + * text_charset - The character set to use for text. + * Default is iso-8859-1 + * head_charset - The character set to use for headers. + * Default is iso-8859-1 + * @return string The mime content + * @access public + */ + function &get($build_params = null) + { + if (isset($build_params)) { + while (list($key, $value) = each($build_params)) { + $this->_build_params[$key] = $value; + } + } + + if (!empty($this->_html_images) AND isset($this->_htmlbody)) { + foreach ($this->_html_images as $value) { + $regex = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' . preg_quote($value['name'], '#') . + '\3#'; + $rep = '\1\2=\3cid:' . $value['cid'] .'\3'; + $this->_htmlbody = preg_replace($regex, $rep, + $this->_htmlbody + ); + } + } + + $null = null; + $attachments = !empty($this->_parts) ? true : false; + $html_images = !empty($this->_html_images) ? true : false; + $html = !empty($this->_htmlbody) ? true : false; + $text = (!$html AND !empty($this->_txtbody)) ? true : false; + + switch (true) { + case $text AND !$attachments: + $message =& $this->_addTextPart($null, $this->_txtbody); + break; + + case !$text AND !$html AND $attachments: + $message =& $this->_addMixedPart(); + for ($i = 0; $i < count($this->_parts); $i++) { + $this->_addAttachmentPart($message, $this->_parts[$i]); + } + break; + + case $text AND $attachments: + $message =& $this->_addMixedPart(); + $this->_addTextPart($message, $this->_txtbody); + for ($i = 0; $i < count($this->_parts); $i++) { + $this->_addAttachmentPart($message, $this->_parts[$i]); + } + break; + + case $html AND !$attachments AND !$html_images: + if (isset($this->_txtbody)) { + $message =& $this->_addAlternativePart($null); + $this->_addTextPart($message, $this->_txtbody); + $this->_addHtmlPart($message); + } else { + $message =& $this->_addHtmlPart($null); + } + break; + + case $html AND !$attachments AND $html_images: + if (isset($this->_txtbody)) { + $message =& $this->_addAlternativePart($null); + $this->_addTextPart($message, $this->_txtbody); + $related =& $this->_addRelatedPart($message); + } else { + $message =& $this->_addRelatedPart($null); + $related =& $message; + } + $this->_addHtmlPart($related); + for ($i = 0; $i < count($this->_html_images); $i++) { + $this->_addHtmlImagePart($related, $this->_html_images[$i]); + } + break; + + case $html AND $attachments AND !$html_images: + $message =& $this->_addMixedPart(); + if (isset($this->_txtbody)) { + $alt =& $this->_addAlternativePart($message); + $this->_addTextPart($alt, $this->_txtbody); + $this->_addHtmlPart($alt); + } else { + $this->_addHtmlPart($message); + } + for ($i = 0; $i < count($this->_parts); $i++) { + $this->_addAttachmentPart($message, $this->_parts[$i]); + } + break; + + case $html AND $attachments AND $html_images: + $message =& $this->_addMixedPart(); + if (isset($this->_txtbody)) { + $alt =& $this->_addAlternativePart($message); + $this->_addTextPart($alt, $this->_txtbody); + $rel =& $this->_addRelatedPart($alt); + } else { + $rel =& $this->_addRelatedPart($message); + } + $this->_addHtmlPart($rel); + for ($i = 0; $i < count($this->_html_images); $i++) { + $this->_addHtmlImagePart($rel, $this->_html_images[$i]); + } + for ($i = 0; $i < count($this->_parts); $i++) { + $this->_addAttachmentPart($message, $this->_parts[$i]); + } + break; + + } + + if (isset($message)) { + $output = $message->encode(); + $this->_headers = array_merge($this->_headers, + $output['headers']); + return $output['body']; + + } else { + return false; + } + } + + /** + * Returns an array with the headers needed to prepend to the email + * (MIME-Version and Content-Type). Format of argument is: + * $array['header-name'] = 'header-value'; + * + * @param array $xtra_headers Assoc array with any extra headers. + * Optional. + * @return array Assoc array with the mime headers + * @access public + */ + function &headers($xtra_headers = null) + { + // Content-Type header should already be present, + // So just add mime version header + $headers['MIME-Version'] = '1.0'; + if (isset($xtra_headers)) { + $headers = array_merge($headers, $xtra_headers); + } + $this->_headers = array_merge($headers, $this->_headers); + + return $this->_encodeHeaders($this->_headers); + } + + /** + * Get the text version of the headers + * (usefull if you want to use the PHP mail() function) + * + * @param array $xtra_headers Assoc array with any extra headers. + * Optional. + * @return string Plain text headers + * @access public + */ + function txtHeaders($xtra_headers = null) + { + $headers = $this->headers($xtra_headers); + $ret = ''; + foreach ($headers as $key => $val) { + $ret .= "$key: $val" . MAIL_MIME_CRLF; + } + return $ret; + } + + /** + * Sets the Subject header + * + * @param string $subject String to set the subject to + * access public + */ + function setSubject($subject) + { + $this->_headers['Subject'] = $subject; + } + + /** + * Set an email to the From (the sender) header + * + * @param string $email The email direction to add + * @access public + */ + function setFrom($email) + { + $this->_headers['From'] = $email; + } + + /** + * Add an email to the Cc (carbon copy) header + * (multiple calls to this method are allowed) + * + * @param string $email The email direction to add + * @access public + */ + function addCc($email) + { + if (isset($this->_headers['Cc'])) { + $this->_headers['Cc'] .= ", $email"; + } else { + $this->_headers['Cc'] = $email; + } + } + + /** + * Add an email to the Bcc (blank carbon copy) header + * (multiple calls to this method are allowed) + * + * @param string $email The email direction to add + * @access public + */ + function addBcc($email) + { + if (isset($this->_headers['Bcc'])) { + $this->_headers['Bcc'] .= ", $email"; + } else { + $this->_headers['Bcc'] = $email; + } + } + + /** + * Encodes a header as per RFC2047 + * + * @param string $input The header data to encode + * @return string Encoded data + * @access private + */ + function _encodeHeaders($input) + { + foreach ($input as $hdr_name => $hdr_value) { + preg_match_all('/(\w*[\x80-\xFF]+\w*)/', $hdr_value, $matches); + foreach ($matches[1] as $value) { + $replacement = preg_replace('/([\x80-\xFF])/e', + '"=" . + strtoupper(dechex(ord("\1")))', + $value); + $hdr_value = str_replace($value, '=?' . + $this->_build_params['head_charset'] . + '?Q?' . $replacement . '?=', + $hdr_value); + } + $input[$hdr_name] = $hdr_value; + } + + return $input; + } + + /** + * Set the object's end-of-line and define the constant if applicable + * + * @param string $eol End Of Line sequence + * @access private + */ + function _setEOL($eol) + { + $this->_eol = $eol; + if (!defined('MAIL_MIME_CRLF')) { + define('MAIL_MIME_CRLF', $this->_eol, true); + } + } + + + +} // End of class +?> diff --git a/trunk/docs/include/PEAR/Mail/mimeDecode.php b/trunk/docs/include/PEAR/Mail/mimeDecode.php new file mode 100644 index 0000000..d5187d9 --- /dev/null +++ b/trunk/docs/include/PEAR/Mail/mimeDecode.php @@ -0,0 +1,837 @@ + | +// +-----------------------------------------------------------------------+ + +require_once 'PEAR.php'; + +/** +* +----------------------------- IMPORTANT ------------------------------+ +* | Usage of this class compared to native php extensions such as | +* | mailparse or imap, is slow and may be feature deficient. If available| +* | you are STRONGLY recommended to use the php extensions. | +* +----------------------------------------------------------------------+ +* +* Mime Decoding class +* +* This class will parse a raw mime email and return +* the structure. Returned structure is similar to +* that returned by imap_fetchstructure(). +* +* USAGE: (assume $input is your raw email) +* +* $decode = new Mail_mimeDecode($input, "\r\n"); +* $structure = $decode->decode(); +* print_r($structure); +* +* Or statically: +* +* $params['input'] = $input; +* $structure = Mail_mimeDecode::decode($params); +* print_r($structure); +* +* TODO: +* o Implement multipart/appledouble +* o UTF8: ??? + + > 4. We have also found a solution for decoding the UTF-8 + > headers. Therefore I made the following function: + > + > function decode_utf8($txt) { + > $trans=array("Å‘"=>"õ","ű"=>"û","Å"=>"Õ","Å°" + =>"Û"); + > $txt=strtr($txt,$trans); + > return(utf8_decode($txt)); + > } + > + > And I have inserted the following line to the class: + > + > if (strtolower($charset)=="utf-8") $text=decode_utf8($text); + > + > ... before the following one in the "_decodeHeader" function: + > + > $input = str_replace($encoded, $text, $input); + > + > This way from now on it can easily decode the UTF-8 headers too. + +* +* @author Richard Heyes +* @version $Revision: 1.46 $ +* @package Mail +*/ +class Mail_mimeDecode extends PEAR +{ + /** + * The raw email to decode + * @var string + */ + var $_input; + + /** + * The header part of the input + * @var string + */ + var $_header; + + /** + * The body part of the input + * @var string + */ + var $_body; + + /** + * If an error occurs, this is used to store the message + * @var string + */ + var $_error; + + /** + * Flag to determine whether to include bodies in the + * returned object. + * @var boolean + */ + var $_include_bodies; + + /** + * Flag to determine whether to decode bodies + * @var boolean + */ + var $_decode_bodies; + + /** + * Flag to determine whether to decode headers + * @var boolean + */ + var $_decode_headers; + + /** + * Constructor. + * + * Sets up the object, initialise the variables, and splits and + * stores the header and body of the input. + * + * @param string The input to decode + * @access public + */ + function Mail_mimeDecode($input) + { + list($header, $body) = $this->_splitBodyHeader($input); + + $this->_input = $input; + $this->_header = $header; + $this->_body = $body; + $this->_decode_bodies = false; + $this->_include_bodies = true; + } + + /** + * Begins the decoding process. If called statically + * it will create an object and call the decode() method + * of it. + * + * @param array An array of various parameters that determine + * various things: + * include_bodies - Whether to include the body in the returned + * object. + * decode_bodies - Whether to decode the bodies + * of the parts. (Transfer encoding) + * decode_headers - Whether to decode headers + * input - If called statically, this will be treated + * as the input + * @return object Decoded results + * @access public + */ + function decode($params = null) + { + // determine if this method has been called statically + $isStatic = !(isset($this) && get_class($this) == __CLASS__); + + // Have we been called statically? + // If so, create an object and pass details to that. + if ($isStatic AND isset($params['input'])) { + + $obj = new Mail_mimeDecode($params['input']); + $structure = $obj->decode($params); + + // Called statically but no input + } elseif ($isStatic) { + return PEAR::raiseError('Called statically and no input given'); + + // Called via an object + } else { + $this->_include_bodies = isset($params['include_bodies']) ? + $params['include_bodies'] : false; + $this->_decode_bodies = isset($params['decode_bodies']) ? + $params['decode_bodies'] : false; + $this->_decode_headers = isset($params['decode_headers']) ? + $params['decode_headers'] : false; + + $structure = $this->_decode($this->_header, $this->_body); + if ($structure === false) { + $structure = $this->raiseError($this->_error); + } + } + + return $structure; + } + + /** + * Performs the decoding. Decodes the body string passed to it + * If it finds certain content-types it will call itself in a + * recursive fashion + * + * @param string Header section + * @param string Body section + * @return object Results of decoding process + * @access private + */ + function _decode($headers, $body, $default_ctype = 'text/plain') + { + $return = new stdClass; + $return->headers = array(); + $headers = $this->_parseHeaders($headers); + + foreach ($headers as $value) { + if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { + $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); + $return->headers[strtolower($value['name'])][] = $value['value']; + + } elseif (isset($return->headers[strtolower($value['name'])])) { + $return->headers[strtolower($value['name'])][] = $value['value']; + + } else { + $return->headers[strtolower($value['name'])] = $value['value']; + } + } + + reset($headers); + while (list($key, $value) = each($headers)) { + $headers[$key]['name'] = strtolower($headers[$key]['name']); + switch ($headers[$key]['name']) { + + case 'content-type': + $content_type = $this->_parseHeaderValue($headers[$key]['value']); + + if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { + $return->ctype_primary = $regs[1]; + $return->ctype_secondary = $regs[2]; + } + + if (isset($content_type['other'])) { + while (list($p_name, $p_value) = each($content_type['other'])) { + $return->ctype_parameters[$p_name] = $p_value; + } + } + break; + + case 'content-disposition': + $content_disposition = $this->_parseHeaderValue($headers[$key]['value']); + $return->disposition = $content_disposition['value']; + if (isset($content_disposition['other'])) { + while (list($p_name, $p_value) = each($content_disposition['other'])) { + $return->d_parameters[$p_name] = $p_value; + } + } + break; + + case 'content-transfer-encoding': + $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']); + break; + } + } + + if (isset($content_type)) { + switch (strtolower($content_type['value'])) { + case 'text/plain': + $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null; + break; + + case 'text/html': + $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null; + break; + + case 'multipart/parallel': + case 'multipart/report': // RFC1892 + case 'multipart/signed': // PGP + case 'multipart/digest': + case 'multipart/alternative': + case 'multipart/related': + case 'multipart/mixed': + if(!isset($content_type['other']['boundary'])){ + $this->_error = 'No boundary found for ' . $content_type['value'] . ' part'; + return false; + } + + $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain'; + + $parts = $this->_boundarySplit($body, $content_type['other']['boundary']); + for ($i = 0; $i < count($parts); $i++) { + list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]); + $part = $this->_decode($part_header, $part_body, $default_ctype); + if($part === false) + $part = $this->raiseError($this->_error); + $return->parts[] = $part; + } + break; + + case 'message/rfc822': + $obj = &new Mail_mimeDecode($body); + $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies, + 'decode_bodies' => $this->_decode_bodies, + 'decode_headers' => $this->_decode_headers)); + unset($obj); + break; + + default: + if(!isset($content_transfer_encoding['value'])) + $content_transfer_encoding['value'] = '7bit'; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null; + break; + } + + } else { + $ctype = explode('/', $default_ctype); + $return->ctype_primary = $ctype[0]; + $return->ctype_secondary = $ctype[1]; + $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null; + } + + return $return; + } + + /** + * Given the output of the above function, this will return an + * array of references to the parts, indexed by mime number. + * + * @param object $structure The structure to go through + * @param string $mime_number Internal use only. + * @return array Mime numbers + */ + function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '') + { + $return = array(); + if (!empty($structure->parts)) { + if ($mime_number != '') { + $structure->mime_id = $prepend . $mime_number; + $return[$prepend . $mime_number] = &$structure; + } + for ($i = 0; $i < count($structure->parts); $i++) { + + + if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') { + $prepend = $prepend . $mime_number . '.'; + $_mime_number = ''; + } else { + $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1)); + } + + $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend); + foreach ($arr as $key => $val) { + $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key]; + } + } + } else { + if ($mime_number == '') { + $mime_number = '1'; + } + $structure->mime_id = $prepend . $mime_number; + $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure; + } + + return $return; + } + + /** + * Given a string containing a header and body + * section, this function will split them (at the first + * blank line) and return them. + * + * @param string Input to split apart + * @return array Contains header and body section + * @access private + */ + function _splitBodyHeader($input) + { + if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) { + return array($match[1], $match[2]); + } + $this->_error = 'Could not split header and body'; + return false; + } + + /** + * Parse headers given in $input and return + * as assoc array. + * + * @param string Headers to parse + * @return array Contains parsed headers + * @access private + */ + function _parseHeaders($input) + { + + if ($input !== '') { + // Unfold the input + $input = preg_replace("/\r?\n/", "\r\n", $input); + $input = preg_replace("/\r\n(\t| )+/", ' ', $input); + $headers = explode("\r\n", trim($input)); + + foreach ($headers as $value) { + $hdr_name = substr($value, 0, $pos = strpos($value, ':')); + $hdr_value = substr($value, $pos+1); + if($hdr_value[0] == ' ') + $hdr_value = substr($hdr_value, 1); + + $return[] = array( + 'name' => $hdr_name, + 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value + ); + } + } else { + $return = array(); + } + + return $return; + } + + /** + * Function to parse a header value, + * extract first part, and any secondary + * parts (after ;) This function is not as + * robust as it could be. Eg. header comments + * in the wrong place will probably break it. + * + * @param string Header value to parse + * @return array Contains parsed result + * @access private + */ + function _parseHeaderValue($input) + { + + if (($pos = strpos($input, ';')) !== false) { + + $return['value'] = trim(substr($input, 0, $pos)); + $input = trim(substr($input, $pos+1)); + + if (strlen($input) > 0) { + + // This splits on a semi-colon, if there's no preceeding backslash + // Now works with quoted values; had to glue the \; breaks in PHP + // the regex is already bordering on incomprehensible + $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/'; + preg_match_all($splitRegex, $input, $matches); + $parameters = array(); + for ($i=0; $i_quotedPrintableDecode($input); + break; + + case 'base64': + return base64_decode($input); + break; + + default: + return $input; + } + } + + /** + * Given a quoted-printable string, this + * function will decode and return it. + * + * @param string Input body to decode + * @return string Decoded body + * @access private + */ + function _quotedPrintableDecode($input) + { + // Remove soft line breaks + $input = preg_replace("/=\r?\n/", '', $input); + + // Replace encoded characters + $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input); + + return $input; + } + + /** + * Checks the input for uuencoded files and returns + * an array of them. Can be called statically, eg: + * + * $files =& Mail_mimeDecode::uudecode($some_text); + * + * It will check for the begin 666 ... end syntax + * however and won't just blindly decode whatever you + * pass it. + * + * @param string Input body to look for attahcments in + * @return array Decoded bodies, filenames and permissions + * @access public + * @author Unknown + */ + function &uudecode($input) + { + // Find all uuencoded sections + preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches); + + for ($j = 0; $j < count($matches[3]); $j++) { + + $str = $matches[3][$j]; + $filename = $matches[2][$j]; + $fileperm = $matches[1][$j]; + + $file = ''; + $str = preg_split("/\r?\n/", trim($str)); + $strlen = count($str); + + for ($i = 0; $i < $strlen; $i++) { + $pos = 1; + $d = 0; + $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077); + + while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) { + $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); + $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); + $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); + $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20); + $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); + + $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); + + $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077)); + + $pos += 4; + $d += 3; + } + + if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) { + $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); + $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); + $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); + $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); + + $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); + + $pos += 3; + $d += 2; + } + + if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) { + $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); + $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); + $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); + + } + } + $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file); + } + + return $files; + } + + /** + * getSendArray() returns the arguments required for Mail::send() + * used to build the arguments for a mail::send() call + * + * Usage: + * $mailtext = Full email (for example generated by a template) + * $decoder = new Mail_mimeDecode($mailtext); + * $parts = $decoder->getSendArray(); + * if (!PEAR::isError($parts) { + * list($recipents,$headers,$body) = $parts; + * $mail = Mail::factory('smtp'); + * $mail->send($recipents,$headers,$body); + * } else { + * echo $parts->message; + * } + * @return mixed array of recipeint, headers,body or Pear_Error + * @access public + * @author Alan Knowles + */ + function getSendArray() + { + // prevent warning if this is not set + $this->_decode_headers = FALSE; + $headerlist =$this->_parseHeaders($this->_header); + $to = ""; + if (!$headerlist) { + return $this->raiseError("Message did not contain headers"); + } + foreach($headerlist as $item) { + $header[$item['name']] = $item['value']; + switch (strtolower($item['name'])) { + case "to": + case "cc": + case "bcc": + $to = ",".$item['value']; + default: + break; + } + } + if ($to == "") { + return $this->raiseError("Message did not contain any recipents"); + } + $to = substr($to,1); + return array($to,$header,$this->_body); + } + + /** + * Returns a xml copy of the output of + * Mail_mimeDecode::decode. Pass the output in as the + * argument. This function can be called statically. Eg: + * + * $output = $obj->decode(); + * $xml = Mail_mimeDecode::getXML($output); + * + * The DTD used for this should have been in the package. Or + * alternatively you can get it from cvs, or here: + * http://www.phpguru.org/xmail/xmail.dtd. + * + * @param object Input to convert to xml. This should be the + * output of the Mail_mimeDecode::decode function + * @return string XML version of input + * @access public + */ + function getXML($input) + { + $crlf = "\r\n"; + $output = '' . $crlf . + '' . $crlf . + '' . $crlf . + Mail_mimeDecode::_getXML($input) . + ''; + + return $output; + } + + /** + * Function that does the actual conversion to xml. Does a single + * mimepart at a time. + * + * @param object Input to convert to xml. This is a mimepart object. + * It may or may not contain subparts. + * @param integer Number of tabs to indent + * @return string XML version of input + * @access private + */ + function _getXML($input, $indent = 1) + { + $htab = "\t"; + $crlf = "\r\n"; + $output = ''; + $headers = @(array)$input->headers; + + foreach ($headers as $hdr_name => $hdr_value) { + + // Multiple headers with this name + if (is_array($headers[$hdr_name])) { + for ($i = 0; $i < count($hdr_value); $i++) { + $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent); + } + + // Only one header of this sort + } else { + $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent); + } + } + + if (!empty($input->parts)) { + for ($i = 0; $i < count($input->parts); $i++) { + $output .= $crlf . str_repeat($htab, $indent) . '' . $crlf . + Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) . + str_repeat($htab, $indent) . '' . $crlf; + } + } elseif (isset($input->body)) { + $output .= $crlf . str_repeat($htab, $indent) . 'body . ']]>' . $crlf; + } + + return $output; + } + + /** + * Helper function to _getXML(). Returns xml of a header. + * + * @param string Name of header + * @param string Value of header + * @param integer Number of tabs to indent + * @return string XML version of input + * @access private + */ + function _getXML_helper($hdr_name, $hdr_value, $indent) + { + $htab = "\t"; + $crlf = "\r\n"; + $return = ''; + + $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value); + $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name))); + + // Sort out any parameters + if (!empty($new_hdr_value['other'])) { + foreach ($new_hdr_value['other'] as $paramname => $paramvalue) { + $params[] = str_repeat($htab, $indent) . $htab . '' . $crlf . + str_repeat($htab, $indent) . $htab . $htab . '' . htmlspecialchars($paramname) . '' . $crlf . + str_repeat($htab, $indent) . $htab . $htab . '' . htmlspecialchars($paramvalue) . '' . $crlf . + str_repeat($htab, $indent) . $htab . '' . $crlf; + } + + $params = implode('', $params); + } else { + $params = ''; + } + + $return = str_repeat($htab, $indent) . '
' . $crlf . + str_repeat($htab, $indent) . $htab . '' . htmlspecialchars($new_hdr_name) . '' . $crlf . + str_repeat($htab, $indent) . $htab . '' . htmlspecialchars($new_hdr_value['value']) . '' . $crlf . + $params . + str_repeat($htab, $indent) . '
' . $crlf; + + return $return; + } + +} // End of class +?> diff --git a/trunk/docs/include/PEAR/Mail/mimePart.php b/trunk/docs/include/PEAR/Mail/mimePart.php new file mode 100644 index 0000000..6edb2ef --- /dev/null +++ b/trunk/docs/include/PEAR/Mail/mimePart.php @@ -0,0 +1,351 @@ + | +// +-----------------------------------------------------------------------+ + +/** +* +* Raw mime encoding class +* +* What is it? +* This class enables you to manipulate and build +* a mime email from the ground up. +* +* Why use this instead of mime.php? +* mime.php is a userfriendly api to this class for +* people who aren't interested in the internals of +* mime mail. This class however allows full control +* over the email. +* +* Eg. +* +* // Since multipart/mixed has no real body, (the body is +* // the subpart), we set the body argument to blank. +* +* $params['content_type'] = 'multipart/mixed'; +* $email = new Mail_mimePart('', $params); +* +* // Here we add a text part to the multipart we have +* // already. Assume $body contains plain text. +* +* $params['content_type'] = 'text/plain'; +* $params['encoding'] = '7bit'; +* $text = $email->addSubPart($body, $params); +* +* // Now add an attachment. Assume $attach is +* the contents of the attachment +* +* $params['content_type'] = 'application/zip'; +* $params['encoding'] = 'base64'; +* $params['disposition'] = 'attachment'; +* $params['dfilename'] = 'example.zip'; +* $attach =& $email->addSubPart($body, $params); +* +* // Now build the email. Note that the encode +* // function returns an associative array containing two +* // elements, body and headers. You will need to add extra +* // headers, (eg. Mime-Version) before sending. +* +* $email = $message->encode(); +* $email['headers'][] = 'Mime-Version: 1.0'; +* +* +* Further examples are available at http://www.phpguru.org +* +* TODO: +* - Set encode() to return the $obj->encoded if encode() +* has already been run. Unless a flag is passed to specifically +* re-build the message. +* +* @author Richard Heyes +* @version $Revision: 1.13 $ +* @package Mail +*/ + +class Mail_mimePart { + + /** + * The encoding type of this part + * @var string + */ + var $_encoding; + + /** + * An array of subparts + * @var array + */ + var $_subparts; + + /** + * The output of this part after being built + * @var string + */ + var $_encoded; + + /** + * Headers for this part + * @var array + */ + var $_headers; + + /** + * The body of this part (not encoded) + * @var string + */ + var $_body; + + /** + * Constructor. + * + * Sets up the object. + * + * @param $body - The body of the mime part if any. + * @param $params - An associative array of parameters: + * content_type - The content type for this part eg multipart/mixed + * encoding - The encoding to use, 7bit, 8bit, base64, or quoted-printable + * cid - Content ID to apply + * disposition - Content disposition, inline or attachment + * dfilename - Optional filename parameter for content disposition + * description - Content description + * charset - Character set to use + * @access public + */ + function Mail_mimePart($body = '', $params = array()) + { + if (!defined('MAIL_MIMEPART_CRLF')) { + define('MAIL_MIMEPART_CRLF', defined('MAIL_MIME_CRLF') ? MAIL_MIME_CRLF : "\r\n", TRUE); + } + + foreach ($params as $key => $value) { + switch ($key) { + case 'content_type': + $headers['Content-Type'] = $value . (isset($charset) ? '; charset="' . $charset . '"' : ''); + break; + + case 'encoding': + $this->_encoding = $value; + $headers['Content-Transfer-Encoding'] = $value; + break; + + case 'cid': + $headers['Content-ID'] = '<' . $value . '>'; + break; + + case 'disposition': + $headers['Content-Disposition'] = $value . (isset($dfilename) ? '; filename="' . $dfilename . '"' : ''); + break; + + case 'dfilename': + if (isset($headers['Content-Disposition'])) { + $headers['Content-Disposition'] .= '; filename="' . $value . '"'; + } else { + $dfilename = $value; + } + break; + + case 'description': + $headers['Content-Description'] = $value; + break; + + case 'charset': + if (isset($headers['Content-Type'])) { + $headers['Content-Type'] .= '; charset="' . $value . '"'; + } else { + $charset = $value; + } + break; + } + } + + // Default content-type + if (!isset($headers['Content-Type'])) { + $headers['Content-Type'] = 'text/plain'; + } + + //Default encoding + if (!isset($this->_encoding)) { + $this->_encoding = '7bit'; + } + + // Assign stuff to member variables + $this->_encoded = array(); + $this->_headers = $headers; + $this->_body = $body; + } + + /** + * encode() + * + * Encodes and returns the email. Also stores + * it in the encoded member variable + * + * @return An associative array containing two elements, + * body and headers. The headers element is itself + * an indexed array. + * @access public + */ + function encode() + { + $encoded =& $this->_encoded; + + if (!empty($this->_subparts)) { + srand((double)microtime()*1000000); + $boundary = '=_' . md5(rand() . microtime()); + $this->_headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF . "\t" . 'boundary="' . $boundary . '"'; + + // Add body parts to $subparts + for ($i = 0; $i < count($this->_subparts); $i++) { + $headers = array(); + $tmp = $this->_subparts[$i]->encode(); + foreach ($tmp['headers'] as $key => $value) { + $headers[] = $key . ': ' . $value; + } + $subparts[] = implode(MAIL_MIMEPART_CRLF, $headers) . MAIL_MIMEPART_CRLF . MAIL_MIMEPART_CRLF . $tmp['body']; + } + + $encoded['body'] = '--' . $boundary . MAIL_MIMEPART_CRLF . + implode('--' . $boundary . MAIL_MIMEPART_CRLF, $subparts) . + '--' . $boundary.'--' . MAIL_MIMEPART_CRLF; + + } else { + $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding) . MAIL_MIMEPART_CRLF; + } + + // Add headers to $encoded + $encoded['headers'] =& $this->_headers; + + return $encoded; + } + + /** + * &addSubPart() + * + * Adds a subpart to current mime part and returns + * a reference to it + * + * @param $body The body of the subpart, if any. + * @param $params The parameters for the subpart, same + * as the $params argument for constructor. + * @return A reference to the part you just added. It is + * crucial if using multipart/* in your subparts that + * you use =& in your script when calling this function, + * otherwise you will not be able to add further subparts. + * @access public + */ + function &addSubPart($body, $params) + { + $this->_subparts[] = new Mail_mimePart($body, $params); + return $this->_subparts[count($this->_subparts) - 1]; + } + + /** + * _getEncodedData() + * + * Returns encoded data based upon encoding passed to it + * + * @param $data The data to encode. + * @param $encoding The encoding type to use, 7bit, base64, + * or quoted-printable. + * @access private + */ + function _getEncodedData($data, $encoding) + { + switch ($encoding) { + case '8bit': + case '7bit': + return $data; + break; + + case 'quoted-printable': + return $this->_quotedPrintableEncode($data); + break; + + case 'base64': + return rtrim(chunk_split(base64_encode($data), 76, MAIL_MIMEPART_CRLF)); + break; + + default: + return $data; + } + } + + /** + * quoteadPrintableEncode() + * + * Encodes data to quoted-printable standard. + * + * @param $input The data to encode + * @param $line_max Optional max line length. Should + * not be more than 76 chars + * + * @access private + */ + function _quotedPrintableEncode($input , $line_max = 76) + { + $lines = preg_split("/\r?\n/", $input); + $eol = MAIL_MIMEPART_CRLF; + $escape = '='; + $output = ''; + + while(list(, $line) = each($lines)){ + + $linlen = strlen($line); + $newline = ''; + + for ($i = 0; $i < $linlen; $i++) { + $char = substr($line, $i, 1); + $dec = ord($char); + + if (($dec == 32) AND ($i == ($linlen - 1))){ // convert space at eol only + $char = '=20'; + + } elseif(($dec == 9) AND ($i == ($linlen - 1))) { // convert tab at eol only + $char = '=09'; + } elseif($dec == 9) { + ; // Do nothing if a tab. + } elseif(($dec == 61) OR ($dec < 32 ) OR ($dec > 126)) { + $char = $escape . strtoupper(sprintf('%02s', dechex($dec))); + } + + if ((strlen($newline) + strlen($char)) >= $line_max) { // MAIL_MIMEPART_CRLF is not counted + $output .= $newline . $escape . $eol; // soft line break; " =\r\n" is okay + $newline = ''; + } + $newline .= $char; + } // end of for + $output .= $newline . $eol; + } + $output = substr($output, 0, -1 * strlen($eol)); // Don't want last crlf + return $output; + } +} // End of class +?> diff --git a/trunk/docs/include/PEAR/Mail/xmail.dtd b/trunk/docs/include/PEAR/Mail/xmail.dtd new file mode 100644 index 0000000..9779796 --- /dev/null +++ b/trunk/docs/include/PEAR/Mail/xmail.dtd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/trunk/docs/include/PEAR/Mail/xmail.xsl b/trunk/docs/include/PEAR/Mail/xmail.xsl new file mode 100644 index 0000000..0e4ffe6 --- /dev/null +++ b/trunk/docs/include/PEAR/Mail/xmail.xsl @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + : + + + + + + + + ; + + =" + + " + + + + + + + + + + + + + + + + + + + + -- + + + + ---- + + + + + + + + + + + \ No newline at end of file diff --git a/trunk/docs/include/PEAR/Net/DIME.php b/trunk/docs/include/PEAR/Net/DIME.php new file mode 100644 index 0000000..6c54208 --- /dev/null +++ b/trunk/docs/include/PEAR/Net/DIME.php @@ -0,0 +1,629 @@ + | +// | Ralf Hofmann | +// +----------------------------------------------------------------------+ +// +// $Id: DIME.php,v 1.5 2002/09/29 01:55:16 shane Exp $ +// + +require_once 'PEAR.php'; +/** + * + * DIME Encoding/Decoding + * + * What is it? + * This class enables you to manipulate and build + * a DIME encapsulated message. + * + * http://www.ietf.org/internet-drafts/draft-nielsen-dime-02.txt + * + * 09/18/02 Ralf - A huge number of changes to be compliant + * with the DIME Specification Release 17 June 2002 + * + * TODO: lots of stuff needs to be tested. + * Definitily have to go through DIME spec and + * make things work right, most importantly, sec 3.3 + * make examples, document + * + * see test/dime_mesage_test.php for example of usage + * + * @author Shane Caraveo , + * Ralf Hofmann + * @version $Revision: 1.5 $ + * @package Net_DIME + */ +define('NET_DIME_TYPE_UNCHANGED',0x00); +define('NET_DIME_TYPE_MEDIA',0x01); +define('NET_DIME_TYPE_URI',0x02); +define('NET_DIME_TYPE_UNKNOWN',0x03); +define('NET_DIME_TYPE_NONE',0x04); + +define('NET_DIME_VERSION',0x0001); + +define('NET_DIME_RECORD_HEADER',12); + +define('NET_DIME_FLAGS', 0); +define('NET_DIME_OPTS_LEN', 1); +define('NET_DIME_ID_LEN', 2); +define('NET_DIME_TYPE_LEN', 3); +define('NET_DIME_DATA_LEN', 4); +define('NET_DIME_OPTS', 5); +define('NET_DIME_ID', 6); +define('NET_DIME_TYPE', 7); +define('NET_DIME_DATA', 8); + +class Net_DIME_Record extends PEAR +{ + // these are used to hold the padded length + var $OPTS_LENGTH = 0; + var $ID_LENGTH = 0; + var $TYPE_LENGTH = 0; + var $DATA_LENGTH = 0; + var $_haveOpts = FALSE; + var $_haveID = FALSE; + var $_haveType = FALSE; + var $_haveData = FALSE; + var $debug = FALSE; + var $padstr = "\0"; + /** + * Elements + * [NET_DIME_FLAGS], 16 bits: VERSION:MB:ME:CF:TYPE_T + * [NET_DIME_OPTS_LEN], 16 bits: OPTIONS_LENGTH + * [NET_DIME_ID_LEN], 16 bits: ID_LENGTH + * [NET_DIME_TYPE_LEN], 16 bits: TYPE_LENGTH + * [NET_DIME_DATA_LEN], 32 bits: DATA_LENGTH + * [NET_DIME_OPTS] : OPTIONS + * [NET_DIME_ID] : ID + * [NET_DIME_TYPE] : TYPE + * [NET_DIME_DATA] : DATA + */ + var $Elements = array(NET_DIME_FLAGS => 0, NET_DIME_OPTS_LEN => 0, + NET_DIME_ID_LEN => 0, NET_DIME_TYPE_LEN => 0, + NET_DIME_DATA_LEN => 0, + NET_DIME_OPTS => '', + NET_DIME_ID => '', + NET_DIME_TYPE => '', + NET_DIME_DATA => ''); + + function Net_DIME_Record($debug = FALSE) + { + $this->debug = $debug; + if ($debug) $this->padstr = '*'; + } + + function setMB() + { + $this->Elements[NET_DIME_FLAGS] |= 0x0400; + } + + function setME() + { + $this->Elements[NET_DIME_FLAGS] |= 0x0200; + } + + function setCF() + { + $this->Elements[NET_DIME_FLAGS] |= 0x0100; + } + + function isChunk() + { + return $this->Elements[NET_DIME_FLAGS] & 0x0100; + } + + function isEnd() + { + return $this->Elements[NET_DIME_FLAGS] & 0x0200; + } + + function isStart() + { + return $this->Elements[NET_DIME_FLAGS] & 0x0400; + } + + function getID() + { + return $this->Elements[NET_DIME_ID]; + } + + function getType() + { + return $this->Elements[NET_DIME_TYPE]; + } + + function getData() + { + return $this->Elements[NET_DIME_DATA]; + } + + function getDataLength() + { + return $this->Elements[NET_DIME_DATA_LEN]; + } + + function setType($typestring, $type=NET_DIME_TYPE_UNKNOWN) + { + $typelen = strlen($typestring) & 0xFFFF; + $type = $type << 4; + $this->Elements[NET_DIME_FLAGS] = ($this->Elements[NET_DIME_FLAGS] & 0xFF0F) | $type; + $this->Elements[NET_DIME_TYPE_LEN] = $typelen; + $this->TYPE_LENGTH = $this->_getPadLength($typelen); + $this->Elements[NET_DIME_TYPE] = $typestring; + } + + function generateID() + { + $id = md5(time()); + $this->setID($id); + return $id; + } + + function setID($id) + { + $idlen = strlen($id) & 0xFFFF; + $this->Elements[NET_DIME_ID_LEN] = $idlen; + $this->ID_LENGTH = $this->_getPadLength($idlen); + $this->Elements[NET_DIME_ID] = $id; + } + + function setData($data, $size=0) + { + $datalen = $size?$size:strlen($data); + $this->Elements[NET_DIME_DATA_LEN] = $datalen; + $this->DATA_LENGTH = $this->_getPadLength($datalen); + $this->Elements[NET_DIME_DATA] = $data; + } + + function encode() + { + // insert version + $this->Elements[NET_DIME_FLAGS] = ($this->Elements[NET_DIME_FLAGS] & 0x07FF) | (NET_DIME_VERSION << 11); + + // the real dime encoding + $format = '%c%c%c%c%c%c%c%c%c%c%c%c'. + '%'.$this->OPTS_LENGTH.'s'. + '%'.$this->ID_LENGTH.'s'. + '%'.$this->TYPE_LENGTH.'s'. + '%'.$this->DATA_LENGTH.'s'; + return sprintf($format, + ($this->Elements[NET_DIME_FLAGS]&0x0000FF00)>>8, + ($this->Elements[NET_DIME_FLAGS]&0x000000FF), + ($this->Elements[NET_DIME_OPTS_LEN]&0x0000FF00)>>8, + ($this->Elements[NET_DIME_OPTS_LEN]&0x000000FF), + ($this->Elements[NET_DIME_ID_LEN]&0x0000FF00)>>8, + ($this->Elements[NET_DIME_ID_LEN]&0x000000FF), + ($this->Elements[NET_DIME_TYPE_LEN]&0x0000FF00)>>8, + ($this->Elements[NET_DIME_TYPE_LEN]&0x000000FF), + ($this->Elements[NET_DIME_DATA_LEN]&0xFF000000)>>24, + ($this->Elements[NET_DIME_DATA_LEN]&0x00FF0000)>>16, + ($this->Elements[NET_DIME_DATA_LEN]&0x0000FF00)>>8, + ($this->Elements[NET_DIME_DATA_LEN]&0x000000FF), + str_pad($this->Elements[NET_DIME_OPTS], $this->OPTS_LENGTH, $this->padstr), + str_pad($this->Elements[NET_DIME_ID], $this->ID_LENGTH, $this->padstr), + str_pad($this->Elements[NET_DIME_TYPE], $this->TYPE_LENGTH, $this->padstr), + str_pad($this->Elements[NET_DIME_DATA], $this->DATA_LENGTH, $this->padstr)); + } + + function _getPadLength($len) + { + $pad = 0; + if ($len) { + $pad = $len % 4; + if ($pad) $pad = 4 - $pad; + } + return $len + $pad; + } + + function decode(&$data) + { + // REAL DIME decoding + $this->Elements[NET_DIME_FLAGS] = (hexdec(bin2hex($data[0]))<<8) + hexdec(bin2hex($data[1])); + $this->Elements[NET_DIME_OPTS_LEN] = (hexdec(bin2hex($data[2]))<<8) + hexdec(bin2hex($data[3])); + $this->Elements[NET_DIME_ID_LEN] = (hexdec(bin2hex($data[4]))<<8) + hexdec(bin2hex($data[5])); + $this->Elements[NET_DIME_TYPE_LEN] = (hexdec(bin2hex($data[6]))<<8) + hexdec(bin2hex($data[7])); + $this->Elements[NET_DIME_DATA_LEN] = (hexdec(bin2hex($data[8]))<<24) + + (hexdec(bin2hex($data[9]))<<16) + + (hexdec(bin2hex($data[10]))<<8) + + hexdec(bin2hex($data[11])); + $p = 12; + + $version = (($this->Elements[NET_DIME_FLAGS]>>11) & 0x001F); + + if ($version == NET_DIME_VERSION) + { + $this->OPTS_LENGTH = $this->_getPadLength($this->Elements[NET_DIME_OPTS_LEN]); + $this->ID_LENGTH = $this->_getPadLength($this->Elements[NET_DIME_ID_LEN]); + $this->TYPE_LENGTH = $this->_getPadLength($this->Elements[NET_DIME_TYPE_LEN]); + $this->DATA_LENGTH = $this->_getPadLength($this->Elements[NET_DIME_DATA_LEN]); + + $datalen = strlen($data); + $this->Elements[NET_DIME_OPTS] = substr($data,$p,$this->Elements[NET_DIME_OPTS_LEN]); + $this->_haveOpts = (strlen($this->Elements[NET_DIME_OPTS]) == $this->Elements[NET_DIME_OPTS_LEN]); + if ($this->_haveOpts) { + $p += $this->OPTS_LENGTH; + $this->Elements[NET_DIME_ID] = substr($data,$p,$this->Elements[NET_DIME_ID_LEN]); + $this->_haveID = (strlen($this->Elements[NET_DIME_ID]) == $this->Elements[NET_DIME_ID_LEN]); + if ($this->_haveID) { + $p += $this->ID_LENGTH; + $this->Elements[NET_DIME_TYPE] = substr($data,$p,$this->Elements[NET_DIME_TYPE_LEN]); + $this->_haveType = (strlen($this->Elements[NET_DIME_TYPE]) == $this->Elements[NET_DIME_TYPE_LEN]); + if ($this->_haveType) { + $p += $this->TYPE_LENGTH; + $this->Elements[NET_DIME_DATA] = substr($data,$p,$this->Elements[NET_DIME_DATA_LEN]); + $this->_haveData = (strlen($this->Elements[NET_DIME_DATA]) == $this->Elements[NET_DIME_DATA_LEN]); + if ($this->_haveData) { + $p += $this->DATA_LENGTH; + } else { + $p += strlen($this->Elements[NET_DIME_DATA]); + } + } else { + $p += strlen($this->Elements[NET_DIME_TYPE]); + } + } else { + $p += strlen($this->Elements[NET_DIME_ID]); + } + } else { + $p += strlen($this->Elements[NET_DIME_OPTS]); + } + } + return substr($data, $p); + } + + function addData(&$data) + { + $datalen = strlen($data); + $p = 0; + if (!$this->_haveOpts) { + $have = strlen($this->Elements[NET_DIME_OPTS]); + $this->Elements[NET_DIME_OPTS] .= substr($data,$p,$this->Elements[NET_DIME_OPTS_LEN]-$have); + $this->_haveOpts = (strlen($this->Elements[NET_DIME_OPTS]) == $this->Elements[DIME_OTPS_LEN]); + if (!$this->_haveOpts) return NULL; + $p += $this->OPTS_LENGTH-$have; + } + if (!$this->_haveID) { + $have = strlen($this->Elements[NET_DIME_ID]); + $this->Elements[NET_DIME_ID] .= substr($data,$p,$this->Elements[NET_DIME_ID_LEN]-$have); + $this->_haveID = (strlen($this->Elements[NET_DIME_ID]) == $this->Elements[NET_DIME_ID_LEN]); + if (!$this->_haveID) return NULL; + $p += $this->ID_LENGTH-$have; + } + if (!$this->_haveType && $p < $datalen) { + $have = strlen($this->Elements[NET_DIME_TYPE]); + $this->Elements[NET_DIME_TYPE] .= substr($data,$p,$this->Elements[NET_DIME_TYPE_LEN]-$have); + $this->_haveType = (strlen($this->Elements[NET_DIME_TYPE]) == $this->Elements[NET_DIME_TYPE_LEN]); + if (!$this->_haveType) return NULL; + $p += $this->TYPE_LENGTH-$have; + } + if (!$this->_haveData && $p < $datalen) { + $have = strlen($this->Elements[NET_DIME_DATA]); + $this->Elements[NET_DIME_DATA] .= substr($data,$p,$this->Elements[NET_DIME_DATA_LEN]-$have); + $this->_haveData = (strlen($this->Elements[NET_DIME_DATA]) == $this->Elements[NET_DIME_DATA_LEN]); + if (!$this->_haveData) return NULL; + $p += $this->DATA_LENGTH-$have; + } + return substr($data,$p); + } +} + + +class Net_DIME_Message extends PEAR +{ + + var $record_size = 4096; + #var $records =array(); + var $parts = array(); + var $currentPart = -1; + var $stream = NULL; + var $_currentRecord; + var $_proc = array(); + var $type; + var $typestr; + var $mb = 1; + var $me = 0; + var $cf = 0; + var $id = NULL; + var $debug = FALSE; + /** + * constructor + * + * this currently takes a file pointer as provided + * by fopen + * + * TODO: integrate with the php streams stuff + */ + function Net_DIME_Message($stream=NULL, $record_size = 4096, $debug = FALSE) + { + $this->stream = $stream; + $this->record_size = $record_size; + $this->debug = $debug; + } + + function _makeRecord(&$data, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN) + { + $record = new Net_DIME_Record($this->debug); + if ($this->mb) { + $record->setMB(); + // all subsequent records are not message begin! + $this->mb = 0; + } + if ($this->me) $record->setME(); + if ($this->cf) $record->setCF(); + $record->setData($data); + $record->setType($typestr,$type); + if ($id) $record->setID($id); + #if ($this->debug) { + # print str_replace('\0','*',$record->encode()); + #} + return $record->encode(); + } + + function startChunk(&$data, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN) + { + $this->me = 0; + $this->cf = 1; + $this->type = $type; + $this->typestr = $typestr; + if ($id) { + $this->id = $id; + } else { + $this->id = md5(time()); + } + return $this->_makeRecord($data, $this->typestr, $this->id, $this->type); + } + + function doChunk(&$data) + { + $this->me = 0; + $this->cf = 1; + return $this->_makeRecord($data, NULL, NULL, NET_DIME_TYPE_UNCHANGED); + } + + function endChunk() + { + $this->cf = 0; + $data = NULL; + $rec = $this->_makeRecord($data, NULL, NULL, NET_DIME_TYPE_UNCHANGED); + $this->id = 0; + $this->cf = 0; + $this->id = 0; + $this->type = NET_DIME_TYPE_UNKNOWN; + $this->typestr = NULL; + return $rec; + } + + function endMessage() + { + $this->me = 1; + $data = NULL; + $rec = $this->_makeRecord($data, NULL, NULL, NET_DIME_TYPE_NONE); + $this->me = 0; + $this->mb = 1; + $this->id = 0; + return $rec; + } + + /** + * sendRecord + * + * given a chunk of data, it creates DIME records + * and writes them to the stream + * + */ + function sendData(&$data, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN) + { + $len = strlen($data); + if ($len > $this->record_size) { + $chunk = substr($data, 0, $this->record_size); + $p = $this->record_size; + $rec = $this->startChunk($chunk,$typestr,$id,$type); + fwrite($this->stream, $rec); + while ($p < $len) { + $chunk = substr($data, $p, $this->record_size); + $p += $this->record_size; + $rec = $this->doChunk($chunk); + fwrite($this->stream, $rec); + } + $rec = $this->endChunk(); + fwrite($this->stream, $rec); + return; + } + $rec = $this->_makeRecord($data, $typestr,$id,$type); + fwrite($this->stream, $rec); + } + + function sendEndMessage() + { + $rec = $this->endMessage(); + fwrite($this->stream, $rec); + } + + /** + * sendFile + * + * given a filename, it reads the file, + * creates records and writes them to the stream + * + */ + function sendFile($filename, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN) + { + $f = fopen($filename, "rb"); + if ($f) { + if ($data = fread($f, $this->record_size)) { + $this->startChunk($data,$typestr,$id,$type); + } + while ($data = fread($f, $this->record_size)) { + $this->doChunk($data,$typestr,$id,$type); + } + $this->endChunk(); + fclose($f); + } + } + + /** + * encodeData + * + * given data, encode it in DIME + * + */ + function encodeData($data, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN) + { + $len = strlen($data); + $resp = ''; + if ($len > $this->record_size) { + $chunk = substr($data, 0, $this->record_size); + $p = $this->record_size; + $resp .= $this->startChunk($chunk,$typestr,$id,$type); + while ($p < $len) { + $chunk = substr($data, $p, $this->record_size); + $p += $this->record_size; + $resp .= $this->doChunk($chunk); + } + $resp .= $this->endChunk(); + } else { + $resp .= $this->_makeRecord($data, $typestr,$id,$type); + } + return $resp; + } + + /** + * sendFile + * + * given a filename, it reads the file, + * creates records and writes them to the stream + * + */ + function encodeFile($filename, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN) + { + $f = fopen($filename, "rb"); + if ($f) { + if ($data = fread($f, $this->record_size)) { + $resp = $this->startChunk($data,$typestr,$id,$type); + } + while ($data = fread($f, $this->record_size)) { + $resp = $this->doChunk($data,$typestr,$id,$type); + } + $resp = $this->endChunk(); + fclose($f); + } + return $resp; + } + + /** + * _processData + * + * creates Net_DIME_Records from provided data + * + */ + function _processData(&$data) + { + $leftover = NULL; + if (!$this->_currentRecord) { + $this->_currentRecord = new Net_DIME_Record($this->debug); + $data = $this->_currentRecord->decode($data); + } else { + $data = $this->_currentRecord->addData($data); + } + + if ($this->_currentRecord->_haveData) { + if (count($this->parts)==0 && !$this->_currentRecord->isStart()) { + // raise an error! + return PEAR::raiseError('First Message is not a DIME begin record!'); + } + + if ($this->_currentRecord->isEnd() && $this->_currentRecord->getDataLength()==0) { + return NULL; + } + + if ($this->currentPart < 0 && !$this->_currentRecord->isChunk()) { + $this->parts[] = array(); + $this->currentPart = count($this->parts)-1; + $this->parts[$this->currentPart]['id'] = $this->_currentRecord->getID(); + $this->parts[$this->currentPart]['type'] = $this->_currentRecord->getType(); + $this->parts[$this->currentPart]['data'] = $this->_currentRecord->getData(); + $this->currentPart = -1; + } else { + if ($this->currentPart < 0) { + $this->parts[] = array(); + $this->currentPart = count($this->parts)-1; + $this->parts[$this->currentPart]['id'] = $this->_currentRecord->getID(); + $this->parts[$this->currentPart]['type'] = $this->_currentRecord->getType(); + $this->parts[$this->currentPart]['data'] = $this->_currentRecord->getData(); + } else { + $this->parts[$this->currentPart]['data'] .= $this->_currentRecord->getData(); + if (!$this->_currentRecord->isChunk()) { + // we reached the end of the chunk + $this->currentPart = -1; + } + } + } + #$this->records[] = $this->_currentRecord; + if (!$this->_currentRecord->isEnd()) $this->_currentRecord = NULL; + } + return NULL; + } + + /** + * decodeData + * + * decodes a DIME encrypted string of data + * + */ + function decodeData(&$data) { + while (strlen($data) >= NET_DIME_RECORD_HEADER) { + $err = $this->_processData($data); + if (PEAR::isError($err)) { + return $err; + } + } + } + + /** + * read + * + * reads the stream and creates + * an array of records + * + * it can accept the start of a previously read buffer + * this is usefull in situations where you need to read + * headers before discovering that the data is DIME encoded + * such as in the case of reading an HTTP response. + */ + function read($buf=NULL) + { + while ($data = fread($this->stream, 8192)) { + if ($buf) { + $data = $buf.$data; + $buf = NULL; + } + if ($this->debug) + echo "read: ".strlen($data)." bytes\n"; + $err = $this->decodeData($data); + if (PEAR::isError($err)) { + return $err; + } + + // store any leftover data to be used again + // should be < NET_DIME_RECORD_HEADER bytes + $buf = $data; + } + if (!$this->_currentRecord || !$this->_currentRecord->isEnd()) { + return PEAR::raiseError('reached stream end without end record'); + } + return NULL; + } +} +?> \ No newline at end of file diff --git a/trunk/docs/include/PEAR/Net/Socket.php b/trunk/docs/include/PEAR/Net/Socket.php new file mode 100644 index 0000000..4b009dd --- /dev/null +++ b/trunk/docs/include/PEAR/Net/Socket.php @@ -0,0 +1,554 @@ + | +// | Chuck Hagenbuch | +// +----------------------------------------------------------------------+ +// +// $Id: Socket.php,v 1.28 2006/12/13 21:32:03 cweiske Exp $ + +require_once 'PEAR.php'; + +define('NET_SOCKET_READ', 1); +define('NET_SOCKET_WRITE', 2); +define('NET_SOCKET_ERROR', 4); + +/** + * Generalized Socket class. + * + * @version 1.1 + * @author Stig Bakken + * @author Chuck Hagenbuch + */ +class Net_Socket extends PEAR { + + /** + * Socket file pointer. + * @var resource $fp + */ + var $fp = null; + + /** + * Whether the socket is blocking. Defaults to true. + * @var boolean $blocking + */ + var $blocking = true; + + /** + * Whether the socket is persistent. Defaults to false. + * @var boolean $persistent + */ + var $persistent = false; + + /** + * The IP address to connect to. + * @var string $addr + */ + var $addr = ''; + + /** + * The port number to connect to. + * @var integer $port + */ + var $port = 0; + + /** + * Number of seconds to wait on socket connections before assuming + * there's no more data. Defaults to no timeout. + * @var integer $timeout + */ + var $timeout = false; + + /** + * Number of bytes to read at a time in readLine() and + * readAll(). Defaults to 2048. + * @var integer $lineLength + */ + var $lineLength = 2048; + + /** + * Connect to the specified port. If called when the socket is + * already connected, it disconnects and connects again. + * + * @param string $addr IP address or host name. + * @param integer $port TCP port number. + * @param boolean $persistent (optional) Whether the connection is + * persistent (kept open between requests + * by the web server). + * @param integer $timeout (optional) How long to wait for data. + * @param array $options See options for stream_context_create. + * + * @access public + * + * @return boolean | PEAR_Error True on success or a PEAR_Error on failure. + */ + function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null) + { + if (is_resource($this->fp)) { + @fclose($this->fp); + $this->fp = null; + } + + if (!$addr) { + return $this->raiseError('$addr cannot be empty'); + } elseif (strspn($addr, '.0123456789') == strlen($addr) || + strstr($addr, '/') !== false) { + $this->addr = $addr; + } else { + $this->addr = @gethostbyname($addr); + } + + $this->port = $port % 65536; + + if ($persistent !== null) { + $this->persistent = $persistent; + } + + if ($timeout !== null) { + $this->timeout = $timeout; + } + + $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; + $errno = 0; + $errstr = ''; + if ($options && function_exists('stream_context_create')) { + if ($this->timeout) { + $timeout = $this->timeout; + } else { + $timeout = 0; + } + $context = stream_context_create($options); + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context); + } else { + if ($this->timeout) { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout); + } else { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr); + } + } + + if (!$fp) { + return $this->raiseError($errstr, $errno); + } + + $this->fp = $fp; + + return $this->setBlocking($this->blocking); + } + + /** + * Disconnects from the peer, closes the socket. + * + * @access public + * @return mixed true on success or an error object otherwise + */ + function disconnect() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + @fclose($this->fp); + $this->fp = null; + return true; + } + + /** + * Find out if the socket is in blocking mode. + * + * @access public + * @return boolean The current blocking mode. + */ + function isBlocking() + { + return $this->blocking; + } + + /** + * Sets whether the socket connection should be blocking or + * not. A read call to a non-blocking socket will return immediately + * if there is no data available, whereas it will block until there + * is data for blocking sockets. + * + * @param boolean $mode True for blocking sockets, false for nonblocking. + * @access public + * @return mixed true on success or an error object otherwise + */ + function setBlocking($mode) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $this->blocking = $mode; + socket_set_blocking($this->fp, $this->blocking); + return true; + } + + /** + * Sets the timeout value on socket descriptor, + * expressed in the sum of seconds and microseconds + * + * @param integer $seconds Seconds. + * @param integer $microseconds Microseconds. + * @access public + * @return mixed true on success or an error object otherwise + */ + function setTimeout($seconds, $microseconds) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_set_timeout($this->fp, $seconds, $microseconds); + } + + /** + * Sets the file buffering size on the stream. + * See php's stream_set_write_buffer for more information. + * + * @param integer $size Write buffer size. + * @access public + * @return mixed on success or an PEAR_Error object otherwise + */ + function setWriteBuffer($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $returned = stream_set_write_buffer($this->fp, $code); + if ($returned == 0) { + return true; + } + return $this->raiseError('Cannot set write buffer.'); + } + + /** + * Returns information about an existing socket resource. + * Currently returns four entries in the result array: + * + *

+ * timed_out (bool) - The socket timed out waiting for data
+ * blocked (bool) - The socket was blocked
+ * eof (bool) - Indicates EOF event
+ * unread_bytes (int) - Number of bytes left in the socket buffer
+ *

+ * + * @access public + * @return mixed Array containing information about existing socket resource or an error object otherwise + */ + function getStatus() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return socket_get_status($this->fp); + } + + /** + * Get a specified line of data + * + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function gets($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fgets($this->fp, $size); + } + + /** + * Read a specified amount of data. This is guaranteed to return, + * and has the added benefit of getting everything in one fread() + * chunk; if you know the size of the data you're getting + * beforehand, this is definitely the way to go. + * + * @param integer $size The number of bytes to read from the socket. + * @access public + * @return $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + function read($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fread($this->fp, $size); + } + + /** + * Write a specified amount of data. + * + * @param string $data Data to write. + * @param integer $blocksize Amount of data to write at once. + * NULL means all at once. + * + * @access public + * @return mixed true on success or an error object otherwise + */ + function write($data, $blocksize = null) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + if (is_null($blocksize) && !OS_WINDOWS) { + return fwrite($this->fp, $data); + } else { + if (is_null($blocksize)) { + $blocksize = 1024; + } + + $pos = 0; + $size = strlen($data); + while ($pos < $size) { + $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); + if ($written === false) { + return false; + } + $pos += $written; + } + + return $pos; + } + } + + /** + * Write a line of data to the socket, followed by a trailing "\r\n". + * + * @access public + * @return mixed fputs result, or an error + */ + function writeLine($data) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return fwrite($this->fp, $data . "\r\n"); + } + + /** + * Tests for end-of-file on a socket descriptor. + * + * Also returns true if the socket is disconnected. + * + * @access public + * @return bool + */ + function eof() + { + return (!is_resource($this->fp) || feof($this->fp)); + } + + /** + * Reads a byte of data + * + * @access public + * @return 1 byte of data from the socket, or a PEAR_Error if + * not connected. + */ + function readByte() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return ord(@fread($this->fp, 1)); + } + + /** + * Reads a word of data + * + * @access public + * @return 1 word of data from the socket, or a PEAR_Error if + * not connected. + */ + function readWord() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 2); + return (ord($buf[0]) + (ord($buf[1]) << 8)); + } + + /** + * Reads an int of data + * + * @access public + * @return integer 1 int of data from the socket, or a PEAR_Error if + * not connected. + */ + function readInt() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + return (ord($buf[0]) + (ord($buf[1]) << 8) + + (ord($buf[2]) << 16) + (ord($buf[3]) << 24)); + } + + /** + * Reads a zero-terminated string of data + * + * @access public + * @return string, or a PEAR_Error if + * not connected. + */ + function readString() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $string = ''; + while (($char = @fread($this->fp, 1)) != "\x00") { + $string .= $char; + } + return $string; + } + + /** + * Reads an IP Address and returns it in a dot formated string + * + * @access public + * @return Dot formated string, or a PEAR_Error if + * not connected. + */ + function readIPAddress() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + return sprintf("%s.%s.%s.%s", ord($buf[0]), ord($buf[1]), + ord($buf[2]), ord($buf[3])); + } + + /** + * Read until either the end of the socket or a newline, whichever + * comes first. Strips the trailing newline from the returned data. + * + * @access public + * @return All available data up to a newline, without that + * newline, or until the end of the socket, or a PEAR_Error if + * not connected. + */ + function readLine() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + ob_start(); + $line = ''; + $timeout = time() + $this->timeout; + while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { + $line .= fgets($this->fp, $this->lineLength); + if (substr($line, -1) == "\n") { + ob_end_clean(); + return rtrim($line, "\r\n"); + } + } + ob_end_clean(); + return $line; + } + + /** + * Read until the socket closes, or until there is no more data in + * the inner PHP buffer. If the inner buffer is empty, in blocking + * mode we wait for at least 1 byte of data. Therefore, in + * blocking mode, if there is no data at all to be read, this + * function will never exit (unless the socket is closed on the + * remote end). + * + * @access public + * + * @return string All data until the socket closes, or a PEAR_Error if + * not connected. + */ + function readAll() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $data = ''; + while (!feof($this->fp)) { + $data .= @fread($this->fp, $this->lineLength); + } + return $data; + } + + /** + * Runs the equivalent of the select() system call on the socket + * with a timeout specified by tv_sec and tv_usec. + * + * @param integer $state Which of read/write/error to check for. + * @param integer $tv_sec Number of seconds for timeout. + * @param integer $tv_usec Number of microseconds for timeout. + * + * @access public + * @return False if select fails, integer describing which of read/write/error + * are ready, or PEAR_Error if not connected. + */ + function select($state, $tv_sec, $tv_usec = 0) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $read = null; + $write = null; + $except = null; + if ($state & NET_SOCKET_READ) { + $read[] = $this->fp; + } + if ($state & NET_SOCKET_WRITE) { + $write[] = $this->fp; + } + if ($state & NET_SOCKET_ERROR) { + $except[] = $this->fp; + } + if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) { + return false; + } + + $result = 0; + if (count($read)) { + $result |= NET_SOCKET_READ; + } + if (count($write)) { + $result |= NET_SOCKET_WRITE; + } + if (count($except)) { + $result |= NET_SOCKET_ERROR; + } + return $result; + } + +} diff --git a/trunk/docs/include/PEAR/Net/URL.php b/trunk/docs/include/PEAR/Net/URL.php new file mode 100644 index 0000000..f5ee706 --- /dev/null +++ b/trunk/docs/include/PEAR/Net/URL.php @@ -0,0 +1,410 @@ + | +// +-----------------------------------------------------------------------+ +// +// $Id: URL.php,v 1.1 2005/08/01 06:20:45 dancoulter Exp $ +// +// Net_URL Class + +class Net_URL +{ + /** + * Full url + * @var string + */ + var $url; + + /** + * Protocol + * @var string + */ + var $protocol; + + /** + * Username + * @var string + */ + var $username; + + /** + * Password + * @var string + */ + var $password; + + /** + * Host + * @var string + */ + var $host; + + /** + * Port + * @var integer + */ + var $port; + + /** + * Path + * @var string + */ + var $path; + + /** + * Query string + * @var array + */ + var $querystring; + + /** + * Anchor + * @var string + */ + var $anchor; + + /** + * Whether to use [] + * @var bool + */ + var $useBrackets; + + /** + * PHP4 Constructor + * + * @see __construct() + */ + //function Net_URL($url = null, $useBrackets = true) + //{ + // $this->__construct($url, $useBrackets); + //} + + /** + * PHP5 Constructor + * + * Parses the given url and stores the various parts + * Defaults are used in certain cases + * + * @param string $url Optional URL + * @param bool $useBrackets Whether to use square brackets when + * multiple querystrings with the same name + * exist + */ + function __construct($url = null, $useBrackets = true) + { + $HTTP_SERVER_VARS = !empty($_SERVER) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS']; + + $this->useBrackets = $useBrackets; + $this->url = $url; + $this->user = ''; + $this->pass = ''; + $this->host = ''; + $this->port = 80; + $this->path = ''; + $this->querystring = array(); + $this->anchor = ''; + + // Only use defaults if not an absolute URL given + if (!preg_match('/^[a-z0-9]+:\/\//i', $url)) { + + $this->protocol = (@$HTTP_SERVER_VARS['HTTPS'] == 'on' ? 'https' : 'http'); + + /** + * Figure out host/port + */ + if (!empty($HTTP_SERVER_VARS['HTTP_HOST']) AND preg_match('/^(.*)(:([0-9]+))?$/U', $HTTP_SERVER_VARS['HTTP_HOST'], $matches)) { + $host = $matches[1]; + if (!empty($matches[3])) { + $port = $matches[3]; + } else { + $port = $this->getStandardPort($this->protocol); + } + } + + $this->user = ''; + $this->pass = ''; + $this->host = !empty($host) ? $host : (isset($HTTP_SERVER_VARS['SERVER_NAME']) ? $HTTP_SERVER_VARS['SERVER_NAME'] : 'localhost'); + $this->port = !empty($port) ? $port : (isset($HTTP_SERVER_VARS['SERVER_PORT']) ? $HTTP_SERVER_VARS['SERVER_PORT'] : $this->getStandardPort($this->protocol)); + $this->path = !empty($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : '/'; + $this->querystring = isset($HTTP_SERVER_VARS['QUERY_STRING']) ? $this->_parseRawQuerystring($HTTP_SERVER_VARS['QUERY_STRING']) : null; + $this->anchor = ''; + } + + // Parse the url and store the various parts + if (!empty($url)) { + $urlinfo = parse_url($url); + + // Default querystring + $this->querystring = array(); + + foreach ($urlinfo as $key => $value) { + switch ($key) { + case 'scheme': + $this->protocol = $value; + $this->port = $this->getStandardPort($value); + break; + + case 'user': + case 'pass': + case 'host': + case 'port': + $this->$key = $value; + break; + + case 'path': + if ($value{0} == '/') { + $this->path = $value; + } else { + $path = dirname($this->path) == DIRECTORY_SEPARATOR ? '' : dirname($this->path); + $this->path = sprintf('%s/%s', $path, $value); + } + break; + + case 'query': + $this->querystring = $this->_parseRawQueryString($value); + break; + + case 'fragment': + $this->anchor = $value; + break; + } + } + } + } + + /** + * Returns full url + * + * @return string Full url + * @access public + */ + function getURL() + { + $querystring = $this->getQueryString(); + + $this->url = $this->protocol . '://' + . $this->user . (!empty($this->pass) ? ':' : '') + . $this->pass . (!empty($this->user) ? '@' : '') + . $this->host . ($this->port == $this->getStandardPort($this->protocol) ? '' : ':' . $this->port) + . $this->path + . (!empty($querystring) ? '?' . $querystring : '') + . (!empty($this->anchor) ? '#' . $this->anchor : ''); + + return $this->url; + } + + /** + * Adds a querystring item + * + * @param string $name Name of item + * @param string $value Value of item + * @param bool $preencoded Whether value is urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + if ($preencoded) { + $this->querystring[$name] = $value; + } else { + $this->querystring[$name] = is_array($value) ? array_map('rawurlencode', $value): rawurlencode($value); + } + } + + /** + * Removes a querystring item + * + * @param string $name Name of item + * @access public + */ + function removeQueryString($name) + { + if (isset($this->querystring[$name])) { + unset($this->querystring[$name]); + } + } + + /** + * Sets the querystring to literally what you supply + * + * @param string $querystring The querystring data. Should be of the format foo=bar&x=y etc + * @access public + */ + function addRawQueryString($querystring) + { + $this->querystring = $this->_parseRawQueryString($querystring); + } + + /** + * Returns flat querystring + * + * @return string Querystring + * @access public + */ + function getQueryString() + { + if (!empty($this->querystring)) { + foreach ($this->querystring as $name => $value) { + if (is_array($value)) { + foreach ($value as $k => $v) { + $querystring[] = $this->useBrackets ? sprintf('%s[%s]=%s', $name, $k, $v) : ($name . '=' . $v); + } + } elseif (!is_null($value)) { + $querystring[] = $name . '=' . $value; + } else { + $querystring[] = $name; + } + } + $querystring = implode(ini_get('arg_separator.output'), $querystring); + } else { + $querystring = ''; + } + + return $querystring; + } + + /** + * Parses raw querystring and returns an array of it + * + * @param string $querystring The querystring to parse + * @return array An array of the querystring data + * @access private + */ + function _parseRawQuerystring($querystring) + { + $parts = preg_split('/[' . preg_quote(ini_get('arg_separator.input'), '/') . ']/', $querystring, -1, PREG_SPLIT_NO_EMPTY); + $return = array(); + + foreach ($parts as $part) { + if (strpos($part, '=') !== false) { + $value = substr($part, strpos($part, '=') + 1); + $key = substr($part, 0, strpos($part, '=')); + } else { + $value = null; + $key = $part; + } + if (substr($key, -2) == '[]') { + $key = substr($key, 0, -2); + if (@!is_array($return[$key])) { + $return[$key] = array(); + $return[$key][] = $value; + } else { + $return[$key][] = $value; + } + } elseif (!$this->useBrackets AND !empty($return[$key])) { + $return[$key] = (array)$return[$key]; + $return[$key][] = $value; + } else { + $return[$key] = $value; + } + } + + return $return; + } + + /** + * Resolves //, ../ and ./ from a path and returns + * the result. Eg: + * + * /foo/bar/../boo.php => /foo/boo.php + * /foo/bar/../../boo.php => /boo.php + * /foo/bar/.././/boo.php => /foo/boo.php + * + * This method can also be called statically. + * + * @param string $url URL path to resolve + * @return string The result + */ + function resolvePath($path) + { + $path = explode('/', str_replace('//', '/', $path)); + + for ($i=0; $i 1 OR ($i == 1 AND $path[0] != '') ) ) { + unset($path[$i]); + unset($path[$i-1]); + $path = array_values($path); + $i -= 2; + + } elseif ($path[$i] == '..' AND $i == 1 AND $path[0] == '') { + unset($path[$i]); + $path = array_values($path); + $i--; + + } else { + continue; + } + } + + return implode('/', $path); + } + + /** + * Returns the standard port number for a protocol + * + * @param string $scheme The protocol to lookup + * @return integer Port number or NULL if no scheme matches + * + * @author Philippe Jausions + */ + function getStandardPort($scheme) + { + switch (strtolower($scheme)) { + case 'http': return 80; + case 'https': return 443; + case 'ftp': return 21; + case 'imap': return 143; + case 'imaps': return 993; + case 'pop3': return 110; + case 'pop3s': return 995; + default: return null; + } + } + + /** + * Forces the URL to a particular protocol + * + * @param string $protocol Protocol to force the URL to + * @param integer $port Optional port (standard port is used by default) + */ + function setProtocol($protocol, $port = null) + { + $this->protocol = $protocol; + $this->port = is_null($port) ? $this->getStandardPort() : $port; + } + +} +?> diff --git a/trunk/docs/include/PEAR/PEAR.php b/trunk/docs/include/PEAR/PEAR.php new file mode 100644 index 0000000..a29bf8b --- /dev/null +++ b/trunk/docs/include/PEAR/PEAR.php @@ -0,0 +1,1095 @@ + + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: PEAR.php,v 1.97 2006/01/06 04:47:36 cellog Exp $ + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ +define('PEAR_ZE2', (function_exists('version_compare') && + version_compare(zend_version(), "2-dev", "ge"))); + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +// instant backwards compatibility +if (!defined('PATH_SEPARATOR')) { + if (OS_WINDOWS) { + define('PATH_SEPARATOR', ';'); + } else { + define('PATH_SEPARATOR', ':'); + } +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.4.6 + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + // {{{ properties + + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + // }}} + + // {{{ constructor + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function PEAR($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + if ($error_class !== null) { + $this->_error_class = $error_class; + } + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = &$this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + // }}} + // {{{ destructor + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + // }}} + // {{{ getStaticProperty() + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @access public + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + function &getStaticProperty($class, $var) + { + static $properties; + return $properties[$class][$var]; + } + + // }}} + // {{{ registerShutdownFunc() + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @access public + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + */ + function registerShutdownFunc($func, $args = array()) + { + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + // }}} + // {{{ isError() + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * @access public + * @return bool true if parameter is an error + */ + function isError($data, $code = null) + { + if (is_a($data, 'PEAR_Error')) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } else { + return $data->getCode() == $code; + } + } + return false; + } + + // }}} + // {{{ setErrorHandling() + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + + function setErrorHandling($mode = null, $options = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $setmode = &$this->_default_error_mode; + $setoptions = &$this->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + // }}} + // {{{ expectError() + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return sizeof($this->_expected_errors); + } + + // }}} + // {{{ popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + // }}} + // {{{ _checkDelExpect() + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + + foreach ($this->_expected_errors AS $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + return $deleted; + } + + // }}} + // {{{ delExpect() + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; + // we walk through it trying to unset all + // values + foreach($error_code as $key => $error) { + if ($this->_checkDelExpect($error)) { + $deleted = true; + } else { + $deleted = false; + } + } + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } else { + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + } else { + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + } + + // }}} + // {{{ raiseError() + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @access public + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + function &raiseError($message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp))) { + $mode = PEAR_ERROR_RETURN; + } + } + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if (isset($this) && isset($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif (isset($this) && isset($this->_error_class)) { + $ec = $this->_error_class; + } else { + $ec = 'PEAR_Error'; + } + if ($skipmsg) { + $a = &new $ec($code, $mode, $options, $userinfo); + return $a; + } else { + $a = &new $ec($message, $code, $mode, $options, $userinfo); + return $a; + } + } + + // }}} + // {{{ throwError() + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param string $message + * + */ + function &throwError($message = null, + $code = null, + $userinfo = null) + { + if (isset($this) && is_a($this, 'PEAR')) { + $a = &$this->raiseError($message, $code, null, null, $userinfo); + return $a; + } else { + $a = &PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + } + + // }}} + function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + // {{{ pushErrorHandling() + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + function pushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if (isset($this) && is_a($this, 'PEAR')) { + $def_mode = &$this->_default_error_mode; + $def_options = &$this->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + // }}} + // {{{ popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + function popErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if (isset($this) && is_a($this, 'PEAR')) { + $this->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + // }}} + // {{{ loadExtension() + + /** + * OS independant PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + function loadExtension($ext) + { + if (!extension_loaded($ext)) { + // if either returns true dl() will produce a FATAL error, stop that + if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) { + return false; + } + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } + return true; + } + + // }}} +} + +// {{{ _PEAR_call_destructors() + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + if (@PEAR::getStaticProperty('PEAR', 'destructlifo')) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +// }}} +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Gregory Beaver + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.4.6 + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + // {{{ properties + + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + // }}} + // {{{ constructor + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function PEAR_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + if (function_exists("debug_backtrace")) { + if (@!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) { + $this->backtrace = debug_backtrace(); + } + } + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + $this->level = $options; + $this->callback = null; + } + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + printf($format, $this->getMessage()); + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + die(sprintf($format, $msg)); + } + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + } + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + // }}} + // {{{ getMode() + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() { + return $this->mode; + } + + // }}} + // {{{ getCallback() + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() { + return $this->callback; + } + + // }}} + // {{{ getMessage() + + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + + // }}} + // {{{ getCode() + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + // }}} + // {{{ getType() + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + // }}} + // {{{ getUserInfo() + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + // }}} + // {{{ getDebugInfo() + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + // }}} + // {{{ getBacktrace() + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + // }}} + // {{{ addUserInfo() + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + // }}} + // {{{ toString() + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } + + // }}} +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/trunk/docs/include/application.php b/trunk/docs/include/application.php new file mode 100644 index 0000000..306c5e2 --- /dev/null +++ b/trunk/docs/include/application.php @@ -0,0 +1,146 @@ +quote($this->council_reference); + $authority_id = $db->quote($this->authority_id); + + $sql = "select application_id + from application + where council_reference = $council_reference + and authority_id = $authority_id"; + + if(sizeof($db->getAll($sql)) >0){ + $exists = true; + } + return $exists; + } + + //Save + function save(){ + + $db = DB::connect(DB_CONNECTION_STRING); + + $council_reference = $db->quote($this->council_reference); + $address = $db->quote($this->address); + $postcode = $db->quote($this->postcode); + $description = $db->quote($this->description); + $info_url = $db->quote($this->info_url); + $info_tinyurl = $db->quote($this->info_tinyurl); + $comment_url = $db->quote($this->comment_url); + $comment_tinyurl = $db->quote($this->comment_tinyurl); + $authority_id = $db->quote($this->authority_id); + $x = $db->quote($this->x); + $y = $db->quote($this->y); + $date_scraped = $db->quote($this->date_scraped); + $map_url = $db->quote($this->map_url); + + $sql ="insert into application + ( + council_reference, + address, + postcode, + description, + info_url, + info_tinyurl, + comment_url, + comment_tinyurl, + authority_id, + x, + y, + date_scraped, + map_url + ) + values( + $council_reference, + $address, + $postcode, + $description, + $info_url, + $info_tinyurl, + $comment_url, + $comment_tinyurl, + $authority_id, + $x, + $y, + $date_scraped, + $map_url + )"; + + $db->query($sql); + + } + + } + + class Applications{ + + function query($x,$y,$d) { + $db = DB::connect(DB_CONNECTION_STRING); + $sql = "select council_reference, address, postcode, description, info_url, comment_url, map_url, x, y, full_name + from application + inner join authority on application.authority_id = authority.authority_id + where application.x > " . $db->quote($x - $d) . " and application.x < " . $db->quote($x + $d) . + " and application.y > " . $db->quote($y - $d) . " and application.y < " . $db->quote($y + $d) . + " order by date_scraped desc limit 100"; + + $application_results = $db->getAll($sql); + + $applications = array(); + if (sizeof($application_results) > 0) { + for ($i=0; $i < sizeof($application_results); $i++) { + $application = new application(); + $application->council_reference = $application_results[$i][0]; + $application->address = $application_results[$i][1]; + $application->postcode = $application_results[$i][2]; + $application->description = $application_results[$i][3]; + $application->info_url = $application_results[$i][4]; + $application->comment_url = $application_results[$i][5]; + $application->map_url = $application_results[$i][6]; + $application->x = $application_results[$i][7]; + $application->y = $application_results[$i][8]; + $application->authority_name = $application_results[$i][9]; + + $os = new OSRef($application->x, $application->y); + $latlng = $os->toLatLng(); + $application->lat = $latlng->lat; + $application->lon = $latlng->lng; + array_push($applications, $application); + } + } + + return $applications; + } + } + +?> diff --git a/trunk/docs/include/config.php b/trunk/docs/include/config.php new file mode 100644 index 0000000..b7a0bdb --- /dev/null +++ b/trunk/docs/include/config.php @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/trunk/docs/include/config.php.live b/trunk/docs/include/config.php.live new file mode 100644 index 0000000..ba89f98 --- /dev/null +++ b/trunk/docs/include/config.php.live @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/trunk/docs/include/config.php.svn b/trunk/docs/include/config.php.svn new file mode 100644 index 0000000..35b2a7d --- /dev/null +++ b/trunk/docs/include/config.php.svn @@ -0,0 +1,43 @@ + \ No newline at end of file diff --git a/trunk/docs/include/phpcoord.php b/trunk/docs/include/phpcoord.php new file mode 100644 index 0000000..e8e1a64 --- /dev/null +++ b/trunk/docs/include/phpcoord.php @@ -0,0 +1,776 @@ +lat = $lat; + $this->lng = $lng; + } + + + /** + * Return a string representation of this LatLng object + * + * @return a string representation of this LatLng object + */ + function toString() { + return "(" . $this->lat . ", " . $this->lng . ")"; + } + + + /** + * Calculate the surface distance between this LatLng object and the one + * passed in as a parameter. + * + * @param to a LatLng object to measure the surface distance to + * @return the surface distance + */ + function distance($to) { + $er = 6366.707; + + $latFrom = deg2rad($this->lat); + $latTo = deg2rad($to->lat); + $lngFrom = deg2rad($this->lng); + $lngTo = deg2rad($to->lng); + + $x1 = $er * cos($lngFrom) * sin($latFrom); + $y1 = $er * sin($lngFrom) * sin($latFrom); + $z1 = $er * cos($latFrom); + + $x2 = $er * cos($lngTo) * sin($latTo); + $y2 = $er * sin($lngTo) * sin($latTo); + $z2 = $er * cos($latTo); + + $d = acos(sin($latFrom)*sin($latTo) + cos($latFrom)*cos($latTo)*cos($lngTo-$lngFrom)) * $er; + + return $d; + } + + + /** + * Convert this LatLng object from OSGB36 datum to WGS84 datum. + */ + function OSGB36ToWGS84() { + $airy1830 = new RefEll(6377563.396, 6356256.909); + $a = $airy1830->maj; + $b = $airy1830->min; + $eSquared = $airy1830->ecc; + $phi = deg2rad($this->lat); + $lambda = deg2rad($this->lng); + $v = $a / (sqrt(1 - $eSquared * sinSquared($phi))); + $H = 0; // height + $x = ($v + $H) * cos($phi) * cos($lambda); + $y = ($v + $H) * cos($phi) * sin($lambda); + $z = ((1 - $eSquared) * $v + $H) * sin($phi); + + $tx = 446.448; + $ty = -124.157; + $tz = 542.060; + $s = -0.0000204894; + $rx = deg2rad( 0.00004172222); + $ry = deg2rad( 0.00006861111); + $rz = deg2rad( 0.00023391666); + + $xB = $tx + ($x * (1 + $s)) + (-$rx * $y) + ($ry * $z); + $yB = $ty + ($rz * $x) + ($y * (1 + $s)) + (-$rx * $z); + $zB = $tz + (-$ry * $x) + ($rx * $y) + ($z * (1 + $s)); + + $wgs84 = new RefEll(6378137.000, 6356752.3141); + $a = $wgs84->maj; + $b = $wgs84->min; + $eSquared = $wgs84->ecc; + + $lambdaB = rad2deg(atan($yB / $xB)); + $p = sqrt(($xB * $xB) + ($yB * $yB)); + $phiN = atan($zB / ($p * (1 - $eSquared))); + for ($i = 1; $i < 10; $i++) { + $v = $a / (sqrt(1 - $eSquared * sinSquared($phiN))); + $phiN1 = atan(($zB + ($eSquared * $v * sin($phiN))) / $p); + $phiN = $phiN1; + } + + $phiB = rad2deg($phiN); + + $this->lat = $phiB; + $this->lng = $lambdaB; + } + + + /** + * Convert this LatLng object from WGS84 datum to OSGB36 datum. + */ + function WGS84ToOSGB36() { + $wgs84 = new RefEll(6378137.000, 6356752.3141); + $a = $wgs84->maj; + $b = $wgs84->min; + $eSquared = $wgs84->ecc; + $phi = deg2rad($this->lat); + $lambda = deg2rad($this->lng); + $v = $a / (sqrt(1 - $eSquared * sinSquared($phi))); + $H = 0; // height + $x = ($v + $H) * cos($phi) * cos($lambda); + $y = ($v + $H) * cos($phi) * sin($lambda); + $z = ((1 - $eSquared) * $v + $H) * sin($phi); + + $tx = -446.448; + $ty = 124.157; + $tz = -542.060; + $s = 0.0000204894; + $rx = deg2rad(-0.00004172222); + $ry = deg2rad(-0.00006861111); + $rz = deg2rad(-0.00023391666); + + $xB = $tx + ($x * (1 + $s)) + (-$rx * $y) + ($ry * $z); + $yB = $ty + ($rz * $x) + ($y * (1 + $s)) + (-$rx * $z); + $zB = $tz + (-$ry * $x) + ($rx * $y) + ($z * (1 + $s)); + + $airy1830 = new RefEll(6377563.396, 6356256.909); + $a = $airy1830->maj; + $b = $airy1830->min; + $eSquared = $airy1830->ecc; + + $lambdaB = rad2deg(atan($yB / $xB)); + $p = sqrt(($xB * $xB) + ($yB * $yB)); + $phiN = atan($zB / ($p * (1 - $eSquared))); + for ($i = 1; $i < 10; $i++) { + $v = $a / (sqrt(1 - $eSquared * sinSquared($phiN))); + $phiN1 = atan(($zB + ($eSquared * $v * sin($phiN))) / $p); + $phiN = $phiN1; + } + + $phiB = rad2deg($phiN); + + $this->lat = $phiB; + $this->lng = $lambdaB; + } + + + /** + * Convert this LatLng object into an OSGB grid reference. Note that this + * function does not take into account the bounds of the OSGB grid - + * beyond the bounds of the OSGB grid, the resulting OSRef object has no + * meaning + * + * @return the converted OSGB grid reference + */ + function toOSRef() { + $airy1830 = new RefEll(6377563.396, 6356256.909); + $OSGB_F0 = 0.9996012717; + $N0 = -100000.0; + $E0 = 400000.0; + $phi0 = deg2rad(49.0); + $lambda0 = deg2rad(-2.0); + $a = $airy1830->maj; + $b = $airy1830->min; + $eSquared = $airy1830->ecc; + $phi = deg2rad($this->lat); + $lambda = deg2rad($this->lng); + $E = 0.0; + $N = 0.0; + $n = ($a - $b) / ($a + $b); + $v = $a * $OSGB_F0 * pow(1.0 - $eSquared * sinSquared($phi), -0.5); + $rho = + $a * $OSGB_F0 * (1.0 - $eSquared) * pow(1.0 - $eSquared * sinSquared($phi), -1.5); + $etaSquared = ($v / $rho) - 1.0; + $M = + ($b * $OSGB_F0) + * (((1 + $n + ((5.0 / 4.0) * $n * $n) + ((5.0 / 4.0) * $n * $n * $n)) + * ($phi - $phi0)) + - (((3 * $n) + (3 * $n * $n) + ((21.0 / 8.0) * $n * $n * $n)) + * sin($phi - $phi0) + * cos($phi + $phi0)) + + ((((15.0 / 8.0) * $n * $n) + ((15.0 / 8.0) * $n * $n * $n)) + * sin(2.0 * ($phi - $phi0)) + * cos(2.0 * ($phi + $phi0))) + - (((35.0 / 24.0) * $n * $n * $n) + * sin(3.0 * ($phi - $phi0)) + * cos(3.0 * ($phi + $phi0)))); + $I = $M + $N0; + $II = ($v / 2.0) * sin($phi) * cos($phi); + $III = + ($v / 24.0) + * sin($phi) + * pow(cos($phi), 3.0) + * (5.0 - tanSquared($phi) + (9.0 * $etaSquared)); + $IIIA = + ($v / 720.0) + * sin($phi) + * pow(cos($phi), 5.0) + * (61.0 - (58.0 * tanSquared($phi)) + pow(tan($phi), 4.0)); + $IV = $v * cos($phi); + $V = ($v / 6.0) * pow(cos($phi), 3.0) * (($v / $rho) - tanSquared($phi)); + $VI = + ($v / 120.0) + * pow(cos($phi), 5.0) + * (5.0 + - (18.0 * tanSquared($phi)) + + (pow(tan($phi), 4.0)) + + (14 * $etaSquared) + - (58 * tanSquared($phi) * $etaSquared)); + + $N = + $I + + ($II * pow($lambda - $lambda0, 2.0)) + + ($III * pow($lambda - $lambda0, 4.0)) + + ($IIIA * pow($lambda - $lambda0, 6.0)); + $E = + $E0 + + ($IV * ($lambda - $lambda0)) + + ($V * pow($lambda - $lambda0, 3.0)) + + ($VI * pow($lambda - $lambda0, 5.0)); + + return new OSRef($E, $N); + } + + + /** + * Convert a latitude and longitude to an UTM reference + * + * @return the converted UTM reference + */ + function toUTMRef() { + $wgs84 = new RefEll(6378137, 6356752.314); + $UTM_F0 = 0.9996; + $a = $wgs84->maj; + $eSquared = $wgs84->ecc; + $longitude = $this->lng; + $latitude = $this->lat; + + $latitudeRad = $latitude * (pi() / 180.0); + $longitudeRad = $longitude * (pi() / 180.0); + $longitudeZone = (int) (($longitude + 180.0) / 6.0) + 1; + + // Special zone for Norway + if ($latitude >= 56.0 + && $latitude < 64.0 + && $longitude >= 3.0 + && $longitude < 12.0) { + $longitudeZone = 32; + } + + // Special zones for Svalbard + if ($latitude >= 72.0 && $latitude < 84.0) { + if ($longitude >= 0.0 && $longitude < 9.0) { + $longitudeZone = 31; + } else if ($longitude >= 9.0 && $longitude < 21.0) { + $longitudeZone = 33; + } else if ($longitude >= 21.0 && $longitude < 33.0) { + $longitudeZone = 35; + } else if ($longitude >= 33.0 && $longitude < 42.0) { + $longitudeZone = 37; + } + } + + $longitudeOrigin = ($longitudeZone - 1) * 6 - 180 + 3; + $longitudeOriginRad = $longitudeOrigin * (pi() / 180.0); + + $UTMZone = getUTMLatitudeZoneLetter($latitude); + + $ePrimeSquared = ($eSquared) / (1 - $eSquared); + + $n = $a / sqrt(1 - $eSquared * sin($latitudeRad) * sin($latitudeRad)); + $t = tan($latitudeRad) * tan($latitudeRad); + $c = $ePrimeSquared * cos($latitudeRad) * cos($latitudeRad); + $A = cos($latitudeRad) * ($longitudeRad - $longitudeOriginRad); + + $M = + $a + * ((1 + - $eSquared / 4 + - 3 * $eSquared * $eSquared / 64 + - 5 * $eSquared * $eSquared * $eSquared / 256) + * $latitudeRad + - (3 * $eSquared / 8 + + 3 * $eSquared * $eSquared / 32 + + 45 * $eSquared * $eSquared * $eSquared / 1024) + * sin(2 * $latitudeRad) + + (15 * $eSquared * $eSquared / 256 + + 45 * $eSquared * $eSquared * $eSquared / 1024) + * sin(4 * $latitudeRad) + - (35 * $eSquared * $eSquared * $eSquared / 3072) + * sin(6 * $latitudeRad)); + + $UTMEasting = + (double) ($UTM_F0 + * $n + * ($A + + (1 - $t + $c) * pow($A, 3.0) / 6 + + (5 - 18 * $t + $t * $t + 72 * $c - 58 * $ePrimeSquared) + * pow($A, 5.0) + / 120) + + 500000.0); + + $UTMNorthing = + (double) ($UTM_F0 + * ($M + + $n + * tan($latitudeRad) + * ($A * $A / 2 + + (5 - $t + (9 * $c) + (4 * $c * $c)) * pow($A, 4.0) / 24 + + (61 - (58 * $t) + ($t * $t) + (600 * $c) - (330 * $ePrimeSquared)) + * pow($A, 6.0) + / 720))); + + // Adjust for the southern hemisphere + if ($latitude < 0) { + $UTMNorthing += 10000000.0; + } + + return new UTMRef($UTMEasting, $UTMNorthing, $UTMZone, $longitudeZone); + } + } + + + // =================================================================== OSRef + + // References given with OSRef are accurate to 1m. + class OSRef { + + var $easting; + var $northing; + + + /** + * Create a new OSRef object representing an OSGB grid reference. Note + * that the parameters for this constructor require eastings and + * northings with 1m accuracy and need to be absolute with respect to + * the whole of the British Grid. For example, to create an OSRef + * object from the six-figure grid reference TG514131, the easting would + * be 651400 and the northing would be 313100. + * + * Grid references with accuracy greater than 1m can be represented + * using floating point values for the easting and northing. For example, + * a value representing an easting or northing accurate to 1mm would be + * given as 651400.0001. + * + * @param easting the easting of the reference (with 1m accuracy) + * @param northing the northing of the reference (with 1m accuracy) + */ + function OSRef($easting, $northing) { + $this->easting = $easting; + $this->northing = $northing; + } + + + /** + * Convert this grid reference into a string showing the exact values + * of the easting and northing. + * + * @return + */ + function toString() { + return "(" . $this->easting . ", " . $this->northing . ")"; + } + + + /** + * Convert this grid reference into a string using a standard six-figure + * grid reference including the two-character designation for the 100km + * square. e.g. TG514131. + * + * @return + */ + function toSixFigureString() { + $hundredkmE = floor($this->easting / 100000); + $hundredkmN = floor($this->northing / 100000); + $firstLetter = ""; + if ($hundredkmN < 5) { + if ($hundredkmE < 5) { + $firstLetter = "S"; + } else { + $firstLetter = "T"; + } + } else if ($hundredkmN < 10) { + if ($hundredkmE < 5) { + $firstLetter = "N"; + } else { + $firstLetter = "O"; + } + } else { + $firstLetter = "H"; + } + + $secondLetter = ""; + $index = 65 + ((4 - ($hundredkmN % 5)) * 5) + ($hundredkmE % 5); + $ti = $index; + if ($index >= 73) $index++; + $secondLetter = chr($index); + + $e = floor(($this->easting - (100000 * $hundredkmE)) / 100); + $n = floor(($this->northing - (100000 * $hundredkmN)) / 100); + $es = $e; + if ($e < 100) $es = "0$es"; + if ($e < 10) $es = "0$es"; + $ns = $n; + if ($n < 100) $ns = "0$ns"; + if ($n < 10) $ns = "0$ns"; + + return $firstLetter . $secondLetter . $es . $ns; + } + + + /** + * Convert this grid reference into a latitude and longitude + * + * @return + */ + function toLatLng() { + $airy1830 = new RefEll(6377563.396, 6356256.909); + $OSGB_F0 = 0.9996012717; + $N0 = -100000.0; + $E0 = 400000.0; + $phi0 = deg2rad(49.0); + $lambda0 = deg2rad(-2.0); + $a = $airy1830->maj; + $b = $airy1830->min; + $eSquared = $airy1830->ecc; + $phi = 0.0; + $lambda = 0.0; + $E = $this->easting; + $N = $this->northing; + $n = ($a - $b) / ($a + $b); + $M = 0.0; + $phiPrime = (($N - $N0) / ($a * $OSGB_F0)) + $phi0; + do { + $M = + ($b * $OSGB_F0) + * (((1 + $n + ((5.0 / 4.0) * $n * $n) + ((5.0 / 4.0) * $n * $n * $n)) + * ($phiPrime - $phi0)) + - (((3 * $n) + (3 * $n * $n) + ((21.0 / 8.0) * $n * $n * $n)) + * sin($phiPrime - $phi0) + * cos($phiPrime + $phi0)) + + ((((15.0 / 8.0) * $n * $n) + ((15.0 / 8.0) * $n * $n * $n)) + * sin(2.0 * ($phiPrime - $phi0)) + * cos(2.0 * ($phiPrime + $phi0))) + - (((35.0 / 24.0) * $n * $n * $n) + * sin(3.0 * ($phiPrime - $phi0)) + * cos(3.0 * ($phiPrime + $phi0)))); + $phiPrime += ($N - $N0 - $M) / ($a * $OSGB_F0); + } while (($N - $N0 - $M) >= 0.001); + $v = $a * $OSGB_F0 * pow(1.0 - $eSquared * sinSquared($phiPrime), -0.5); + $rho = + $a + * $OSGB_F0 + * (1.0 - $eSquared) + * pow(1.0 - $eSquared * sinSquared($phiPrime), -1.5); + $etaSquared = ($v / $rho) - 1.0; + $VII = tan($phiPrime) / (2 * $rho * $v); + $VIII = + (tan($phiPrime) / (24.0 * $rho * pow($v, 3.0))) + * (5.0 + + (3.0 * tanSquared($phiPrime)) + + $etaSquared + - (9.0 * tanSquared($phiPrime) * $etaSquared)); + $IX = + (tan($phiPrime) / (720.0 * $rho * pow($v, 5.0))) + * (61.0 + + (90.0 * tanSquared($phiPrime)) + + (45.0 * tanSquared($phiPrime) * tanSquared($phiPrime))); + $X = sec($phiPrime) / $v; + $XI = + (sec($phiPrime) / (6.0 * $v * $v * $v)) + * (($v / $rho) + (2 * tanSquared($phiPrime))); + $XII = + (sec($phiPrime) / (120.0 * pow($v, 5.0))) + * (5.0 + + (28.0 * tanSquared($phiPrime)) + + (24.0 * tanSquared($phiPrime) * tanSquared($phiPrime))); + $XIIA = + (sec($phiPrime) / (5040.0 * pow($v, 7.0))) + * (61.0 + + (662.0 * tanSquared($phiPrime)) + + (1320.0 * tanSquared($phiPrime) * tanSquared($phiPrime)) + + (720.0 + * tanSquared($phiPrime) + * tanSquared($phiPrime) + * tanSquared($phiPrime))); + $phi = + $phiPrime + - ($VII * pow($E - $E0, 2.0)) + + ($VIII * pow($E - $E0, 4.0)) + - ($IX * pow($E - $E0, 6.0)); + $lambda = + $lambda0 + + ($X * ($E - $E0)) + - ($XI * pow($E - $E0, 3.0)) + + ($XII * pow($E - $E0, 5.0)) + - ($XIIA * pow($E - $E0, 7.0)); + + return new LatLng(rad2deg($phi), rad2deg($lambda)); + } + } + + + // ================================================================== UTMRef + + class UTMRef { + + var $easting; + var $northing; + var $latZone; + var $lngZone; + + + /** + * Create a new object representing a UTM reference. + * + * @param easting + * @param northing + * @param latZone + * @param lngZone + */ + function UTMRef($easting, $northing, $latZone, $lngZone) { + $this->easting = $easting; + $this->northing = $northing; + $this->latZone = $latZone; + $this->lngZone = $lngZone; + } + + + /** + * Return a string representation of this UTM reference + * + * @return + */ + function toString() { + return $this->lngZone . $this->latZone . " " . + $this->easting . " " . $this->northing; + } + + + /** + * Convert this UTM reference to a latitude and longitude + * + * @return the converted latitude and longitude + */ + function toLatLng() { + $wgs84 = new RefEll(6378137, 6356752.314); + $UTM_F0 = 0.9996; + $a = $wgs84->maj; + $eSquared = $wgs84->ecc; + $ePrimeSquared = $eSquared / (1.0 - $eSquared); + $e1 = (1 - sqrt(1 - $eSquared)) / (1 + sqrt(1 - $eSquared)); + $x = $this->easting - 500000.0;; + $y = $this->northing; + $zoneNumber = $this->lngZone; + $zoneLetter = $this->latZone; + + $longitudeOrigin = ($zoneNumber - 1.0) * 6.0 - 180.0 + 3.0; + + // Correct y for southern hemisphere + if ((ord($zoneLetter) - ord("N")) < 0) { + $y -= 10000000.0; + } + + $m = $y / $UTM_F0; + $mu = + $m + / ($a + * (1.0 + - $eSquared / 4.0 + - 3.0 * $eSquared * $eSquared / 64.0 + - 5.0 + * pow($eSquared, 3.0) + / 256.0)); + + $phi1Rad = + $mu + + (3.0 * $e1 / 2.0 - 27.0 * pow($e1, 3.0) / 32.0) * sin(2.0 * $mu) + + (21.0 * $e1 * $e1 / 16.0 - 55.0 * pow($e1, 4.0) / 32.0) + * sin(4.0 * $mu) + + (151.0 * pow($e1, 3.0) / 96.0) * sin(6.0 * $mu); + + $n = + $a + / sqrt(1.0 - $eSquared * sin($phi1Rad) * sin($phi1Rad)); + $t = tan($phi1Rad) * tan($phi1Rad); + $c = $ePrimeSquared * cos($phi1Rad) * cos($phi1Rad); + $r = + $a + * (1.0 - $eSquared) + / pow( + 1.0 - $eSquared * sin($phi1Rad) * sin($phi1Rad), + 1.5); + $d = $x / ($n * $UTM_F0); + + $latitude = ( + $phi1Rad + - ($n * tan($phi1Rad) / $r) + * ($d * $d / 2.0 + - (5.0 + + (3.0 * $t) + + (10.0 * $c) + - (4.0 * $c * $c) + - (9.0 * $ePrimeSquared)) + * pow($d, 4.0) + / 24.0 + + (61.0 + + (90.0 * $t) + + (298.0 * $c) + + (45.0 * $t * $t) + - (252.0 * $ePrimeSquared) + - (3.0 * $c * $c)) + * pow($d, 6.0) + / 720.0)) * (180.0 / pi()); + + $longitude = $longitudeOrigin + ( + ($d + - (1.0 + 2.0 * $t + $c) * pow($d, 3.0) / 6.0 + + (5.0 + - (2.0 * $c) + + (28.0 * $t) + - (3.0 * $c * $c) + + (8.0 * $ePrimeSquared) + + (24.0 * $t * $t)) + * pow($d, 5.0) + / 120.0) + / cos($phi1Rad)) * (180.0 / pi()); + + return new LatLng($latitude, $longitude); + } + } + + + // ================================================================== RefEll + + class RefEll { + + var $maj; + var $min; + var $ecc; + + + /** + * Create a new RefEll object to represent a reference ellipsoid + * + * @param maj the major axis + * @param min the minor axis + */ + function RefEll($maj, $min) { + $this->maj = $maj; + $this->min = $min; + $this->ecc = (($maj * $maj) - ($min * $min)) / ($maj * $maj); + } + } + + + // ================================================== Mathematical Functions + + function sinSquared($x) { + return sin($x) * sin($x); + } + + function cosSquared($x) { + return cos($x) * cos($x); + } + + function tanSquared($x) { + return tan($x) * tan($x); + } + + function sec($x) { + return 1.0 / cos($x); + } + + + /** + * Take a string formatted as a six-figure OS grid reference (e.g. + * "TG514131") and return a reference to an OSRef object that represents + * that grid reference. The first character must be H, N, S, O or T. + * The second character can be any uppercase character from A through Z + * excluding I. + * + * @param ref + * @return + * @since 2.1 + */ + function getOSRefFromSixFigureReference($ref) { + $char1 = substr($ref, 0, 1); + $char2 = substr($ref, 1, 1); + $east = substr($ref, 2, 3) * 100; + $north = substr($ref, 5, 3) * 100; + if ($char1 == 'H') { + $north += 1000000; + } else if ($char1 == 'N') { + $north += 500000; + } else if ($char1 == 'O') { + $north += 500000; + $east += 500000; + } else if ($char1 == 'T') { + $east += 500000; + } + $char2ord = ord($char2); + if ($char2ord > 73) $char2ord--; // Adjust for no I + $nx = (($char2ord - 65) % 5) * 100000; + $ny = (4 - floor(($char2ord - 65) / 5)) * 100000; + return new OSRef($east + $nx, $north + $ny); + } + + + /** + * Work out the UTM latitude zone from the latitude + * + * @param latitude + * @return + */ + function getUTMLatitudeZoneLetter($latitude) { + if ((84 >= $latitude) && ($latitude >= 72)) return "X"; + else if (( 72 > $latitude) && ($latitude >= 64)) return "W"; + else if (( 64 > $latitude) && ($latitude >= 56)) return "V"; + else if (( 56 > $latitude) && ($latitude >= 48)) return "U"; + else if (( 48 > $latitude) && ($latitude >= 40)) return "T"; + else if (( 40 > $latitude) && ($latitude >= 32)) return "S"; + else if (( 32 > $latitude) && ($latitude >= 24)) return "R"; + else if (( 24 > $latitude) && ($latitude >= 16)) return "Q"; + else if (( 16 > $latitude) && ($latitude >= 8)) return "P"; + else if (( 8 > $latitude) && ($latitude >= 0)) return "N"; + else if (( 0 > $latitude) && ($latitude >= -8)) return "M"; + else if (( -8 > $latitude) && ($latitude >= -16)) return "L"; + else if ((-16 > $latitude) && ($latitude >= -24)) return "K"; + else if ((-24 > $latitude) && ($latitude >= -32)) return "J"; + else if ((-32 > $latitude) && ($latitude >= -40)) return "H"; + else if ((-40 > $latitude) && ($latitude >= -48)) return "G"; + else if ((-48 > $latitude) && ($latitude >= -56)) return "F"; + else if ((-56 > $latitude) && ($latitude >= -64)) return "E"; + else if ((-64 > $latitude) && ($latitude >= -72)) return "D"; + else if ((-72 > $latitude) && ($latitude >= -80)) return "C"; + else return 'Z'; + } + +?> \ No newline at end of file diff --git a/trunk/docs/include/scraper_support.php b/trunk/docs/include/scraper_support.php new file mode 100644 index 0000000..a12a219 --- /dev/null +++ b/trunk/docs/include/scraper_support.php @@ -0,0 +1,413 @@ +([0-9]*)<\/th>([^;]*)([^<]*)/"; + + //grab the page + $html = safe_scrape_page($search_url); + + //clean html + $html = str_replace("\r\n","", $html); + + preg_match_all($application_pattern, $html, $application_matches, PREG_PATTERN_ORDER); + + foreach ($application_matches[0] as $application_match){ + + $detail_pattern = "/([^<])*/"; + preg_match_all($detail_pattern, $application_match, $detail_matches, PREG_PATTERN_ORDER); + + $application = new Application(); + + //match the basic details + $application->council_reference = str_replace("", "", $detail_matches[0][0]); + $application->date_received = str_replace("", "", $detail_matches[0][1]); + $application->address = str_replace("", "", $detail_matches[0][2]); + //$application->status = str_replace("", "", $detail_matches[0][4]); + + //match case number + $casenumber_pattern = "/caseno=([^&]*)/"; + preg_match($casenumber_pattern, $application_match, $casenumber_matches); + + $case_number =""; + if(sizeof($casenumber_matches)>0){ + $case_number = str_replace("caseno=","", $casenumber_matches[0]); + } + + //if weve found a caase number, then get the details + if($case_number !=""){ + //Comment and info urls + $application->info_url = $info_url_base . $case_number; + $application->comment_url = $comment_url_base . $case_number; + + //Get the postcode + $postcode_pattern = "/[A-Z][A-Z]?[0-9][A-Z0-9]? ?[0-9][ABDEFGHJLNPQRSTUWXYZ]{2}/"; + + preg_match($postcode_pattern, $application->address, $postcode_matches); + if(isset($postcode_matches[0])){ + $application->postcode = $postcode_matches[0]; + } + + //get full details + $details_html = ""; + $details_html = safe_scrape_page($info_url_base . $case_number); + + //regular expresion and clean + $full_detail_pattern = '/id="desc" rows="[1-9]" cols="80" class="cDetailInput">([^<]*)/'; + preg_match($full_detail_pattern, $details_html, $full_detail_matches); + if (isset($full_detail_matches[0])){ + $application->description = substr($full_detail_matches[0], strpos($full_detail_matches[0], ">") + 1); + } + + //only add it if we have a postcode (bit useless otherwise) + if(is_postcode($application->postcode)){ + array_push($applications, $application); + } + }else{ + error_log("Unable to find case number for an application at " . $search_url); + } + } + + //return + return $applications; +} + +function scrape_applications_wam ($search_url, $info_url_base, $comment_url_base){ + + $applications = array(); + $application_pattern = '/]*>([^<]*)<\/td>]*>([^<]*)<\/a><\/td>]*>([^<]*)<\/td>]*>([^<]*)<\/td>/'; + + + //grab the page + $html = safe_scrape_page($search_url); + + //clean html + $html = str_replace("\r\n","", $html); + + preg_match_all($application_pattern, $html, $application_matches, PREG_SET_ORDER); + + foreach ($application_matches as $application_match){ + if ($application_match[4] != 'Current') { continue; } + + $application = new Application(); + + //match the basic details + $application->council_reference = $application_match[2]; + $case_number = $application_match[2]; + $application->date_received = $application_match[1]; + $application->address = $application_match[3]; + //$application->status = $application_match[4]; + + //if weve found a caase number, then get the details + if($case_number !=""){ + //Comment and info urls + $application->info_url = $info_url_base . $case_number; + $application->comment_url = $comment_url_base . $case_number; + + //Get the postcode + $postcode_pattern = "/[A-Z][A-Z]?[0-9][A-Z0-9]? ?[0-9][ABDEFGHJLNPQRSTUWXYZ]{2}/"; + + preg_match($postcode_pattern, $application->address, $postcode_matches); + if(isset($postcode_matches[0])){ + $application->postcode = $postcode_matches[0]; + } + + //get full details + $details_html = ""; + $details_html = safe_scrape_page($info_url_base . $case_number); + $details_html = str_replace("\r\n","",$details_html); + + //regular expresion and clean + $full_detail_pattern = '/Development:<.*([^<]*)<\/td>/'; + preg_match($full_detail_pattern, $details_html, $full_detail_matches); + if (isset($full_detail_matches[1])){ + $application->description = $full_detail_matches[1]; + } + + //only add it if we have a postcode (bit useless otherwise) + if(is_postcode($application->postcode)){ + + //removed the xy for the moment. It is slowing down the scrape and will be added when the app is parsed anyway (Richard) +/* $xy = postcode_to_location($application->postcode); + $application->x = $xy[0]; + $application->y = $xy[1]; + $os = new OSRef($xy[0],$xy[1]); + $latlon = $os->toLatLng(); + $application->lat = $latlon->lat; + $application->lon = $latlon->lng; +*/ + array_push($applications, $application); + } + }else{ + error_log("Unable to find case number for an application at " . $search_url); + } + + } + + //return + return $applications; +} + +// Council specific scapers +function scrape_applications_islington ($search_url, $info_url_base, $comment_url_base){ + + $applications = array(); + $application_pattern = '/([^<]*)([^<]*)Search Criteria(.*)([^<]*)<(.*)>([^<]*)([^<]*)<\/TD>([^<]*)([^<]*)]*)([^<]*)/'; + + //grab the page + $html = safe_scrape_page($search_url); + + + preg_match_all($application_pattern, $html, $application_matches, PREG_PATTERN_ORDER); + + foreach ($application_matches[0] as $application_match){ + + $application_string = str_replace("\n","", $application_match); + + $reference_pattern = '/Search Results<\/a>">([^<]*)/'; + preg_match_all($reference_pattern, $application_string, $reference_matches, PREG_PATTERN_ORDER); + + $application = new Application(); + + //match the applicaiton number + $application->council_reference = str_replace('Search Results">', "", $reference_matches[0][0]); + + //Comment and info urls + $application->info_url = $info_url_base . $application->council_reference; + //$application->comment_url = $comment_url_base . $case_number; + + //get full details + $details_html = ""; + $details_html = safe_scrape_page($info_url_base . $application->council_reference); +// $details_html = str_replace("\n","", $details_html); +// $details_html = str_replace("\t","", $details_html); + + + //Details + print $details_html;exit; + + //Address + $address_pattern = '/Main location:<\/label><\/td>([^<]*)([^<]*)/'; + $address = ""; + preg_match($address_pattern, $details_html, $address_matches); + if(isset($address_matches[2])){ + $application->address = $address_matches[2]; + } + + //postcode + $postcode_pattern = "/[A-Z][A-Z]?[0-9][A-Z0-9]? ?[0-9][ABDEFGHJLNPQRSTUWXYZ]{2}/"; + preg_match($postcode_pattern, $application->address, $postcode_matches); + if(isset($postcode_matches[0])){ + $application->postcode = $postcode_matches[0]; + } + + //only add it if we have a postcode (bit useless otherwise) + if(is_postcode($application->postcode)){ + array_push($applications, $application); + } + } + + //return + return $applications; +} + //validate postcode + function is_postcode ($postcode){ + $valid = false; + $postcode=str_replace(" ","",$postcode); + if(ereg ('^[a-zA-Z]{1,2}[0-9]{1,2}[a-zA-Z]{0,1}[0-9]{1}[a-zA-Z]{2}$', $postcode)){ + $valid = true; + } + return $valid; + } + + //Tiny url + function tiny_url($url,$length=30){ + + // make nasty big url all small + if (strlen($url) >= $length){ + $tinyurl = @file ("http://tinyurl.com/api-create.php?url=$url"); + + if (is_array($tinyurl)){ + $tinyurl = join ('', $tinyurl); + } else { + $tinyurl = $url; + } + } else { + $tinyurl = $url; + } + + return $tinyurl; + } + + //Google maps url + function googlemap_url_from_postcode($postcode, $zoom = 15){ + $postcode = strtolower(str_replace(" ", "+", $postcode)); + return "http://maps.google.com/maps?q=$postcode&z=$zoom"; + } + + //postcode to location + function postcode_to_location($postcode){ + + $x = 0; + $y = 0; + + $clean_postcode = strtolower($postcode); + $clean_postcode = str_replace(" ","+", $clean_postcode); + $url = "http://www.streetmap.co.uk/newsearch.srf?type=Postcode&name=" . $clean_postcode; + + $html = file_get_contents($url); + $x_pattern = "/var _LocationX=\d*;/"; + $y_pattern = "/var _LocationY=\d*;/"; + + //X + preg_match($x_pattern, $html, $matches); + if(sizeof($matches) >0){ + $x = $matches[0]; + $x = str_replace('var _LocationX=',"", $x); + $x = str_replace(";","", $x); + } + + //Y + preg_match($y_pattern, $html, $matches); + if(sizeof($matches) >0){ + $y = str_replace("var _LocationY=","", $matches[0]); + $y = str_replace(";","", $y); + } + + $return = array(); + + $return[0] = $x; + $return[1] = $y; + return $return; + } + + + + function valid_email ($string) { + $valid = false; + if (!ereg('^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'. + '@'. + '[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'. + '[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$', $string)) { + $valid = false; + } else { + $valid = true; + } + + return $valid; + } + + function alert_size_to_meters($alert_area_size){ + + $area_size_meters = 0; + if ($alert_area_size == "s"){ + $area_size_meters = SMALL_ZONE_SIZE; + }elseif ($alert_area_size == "m"){ + $area_size_meters = MEDIUM_ZONE_SIZE; + }elseif ($alert_area_size == "l"){ + $area_size_meters = LARGE_ZONE_SIZE; + } + return $area_size_meters; + } + + //Send a text email + function send_text_email($to, $from_name, $from_email, $subject, $body){ + + $headers = 'MIME-Version: 1.0' . "\r\n"; + $headers .= 'Content-type: text/plain; charset=iso-8859-1' . "\r\n"; + $headers .= 'From: ' . $from_name. ' <' . $from_email . ">\r\n"; + + mail($to, $subject, $body, $headers); + + } + + // Format a date to mysql format + function mysql_date($date){ + return date("Y-m-d H::i:s", $date); + } + + function safe_scrape_page($url, $method = "GET"){ +error_log(print_r($url, true)); + $page = ""; + for ($i=0; $i < 3; $i++){ + if($page == false){ + if (SCRAPE_METHOD == "PEAR"){ + $page = scrape_page_pear($url, $method); + }else{ + $page = scrape_page_curl($url, $method); + } + } + } + return $page; + } + + function scrape_page_pear($url, $method = "GET"){ + $page = ""; + $request = new HTTP_Request($url, array("method" => $method)); + $request->sendRequest(); + $page = $request->getResponseBody(); + + return $page; + + } + + function scrape_page_curl($url) { + $ch = curl_init($url); + curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE); + curl_setopt($ch,CURLOPT_FOLLOWLOCATION,TRUE); + return curl_exec($ch); + } + + function display_applications($applications, $authority_name, $authority_short_name){ + //smarty + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + $smarty->template_dir = "../templates"; + $smarty->assign("authority_name", $authority_name); + $smarty->assign("authority_short_name", $authority_short_name); + + if (sizeof($applications) > 0){ + $smarty->assign("applications", $applications); + } + + $smarty->display("xml.tpl"); + } + +function get_time_from_get(){ + + //if any get params were passed, overwrite the default date + if (isset($_GET['day'])){ + $day = $_GET['day']; + }else{ + throw_error("No day set in get string"); + } + if (isset($_GET['month'])){ + $month = $_GET['month']; + }else{ + throw_error("No year set in get string"); + } + + if (isset($_GET['year'])){ + $year = $_GET['year']; + }else{ + throw_error("No year set in get string"); + } + + return mktime(0,0,0,$month,$day,$year); + + } + +function throw_error($message){ + throw new exception($message); +} + +?> diff --git a/trunk/docs/include/smarty/Config_File.class.php b/trunk/docs/include/smarty/Config_File.class.php new file mode 100644 index 0000000..da3b732 --- /dev/null +++ b/trunk/docs/include/smarty/Config_File.class.php @@ -0,0 +1,389 @@ + + * @access public + * @package Smarty + */ + +/* $Id: Config_File.class.php,v 1.86 2006/11/30 17:01:28 mohrt Exp $ */ + +/** + * Config file reading class + * @package Smarty + */ +class Config_File { + /**#@+ + * Options + * @var boolean + */ + /** + * Controls whether variables with the same name overwrite each other. + */ + var $overwrite = true; + + /** + * Controls whether config values of on/true/yes and off/false/no get + * converted to boolean values automatically. + */ + var $booleanize = true; + + /** + * Controls whether hidden config sections/vars are read from the file. + */ + var $read_hidden = true; + + /** + * Controls whether or not to fix mac or dos formatted newlines. + * If set to true, \r or \r\n will be changed to \n. + */ + var $fix_newlines = true; + /**#@-*/ + + /** @access private */ + var $_config_path = ""; + var $_config_data = array(); + /**#@-*/ + + /** + * Constructs a new config file class. + * + * @param string $config_path (optional) path to the config files + */ + function Config_File($config_path = NULL) + { + if (isset($config_path)) + $this->set_path($config_path); + } + + + /** + * Set the path where configuration files can be found. + * + * @param string $config_path path to the config files + */ + function set_path($config_path) + { + if (!empty($config_path)) { + if (!is_string($config_path) || !file_exists($config_path) || !is_dir($config_path)) { + $this->_trigger_error_msg("Bad config file path '$config_path'"); + return; + } + if(substr($config_path, -1) != DIRECTORY_SEPARATOR) { + $config_path .= DIRECTORY_SEPARATOR; + } + + $this->_config_path = $config_path; + } + } + + + /** + * Retrieves config info based on the file, section, and variable name. + * + * @param string $file_name config file to get info for + * @param string $section_name (optional) section to get info for + * @param string $var_name (optional) variable to get info for + * @return string|array a value or array of values + */ + function get($file_name, $section_name = NULL, $var_name = NULL) + { + if (empty($file_name)) { + $this->_trigger_error_msg('Empty config file name'); + return; + } else { + $file_name = $this->_config_path . $file_name; + if (!isset($this->_config_data[$file_name])) + $this->load_file($file_name, false); + } + + if (!empty($var_name)) { + if (empty($section_name)) { + return $this->_config_data[$file_name]["vars"][$var_name]; + } else { + if(isset($this->_config_data[$file_name]["sections"][$section_name]["vars"][$var_name])) + return $this->_config_data[$file_name]["sections"][$section_name]["vars"][$var_name]; + else + return array(); + } + } else { + if (empty($section_name)) { + return (array)$this->_config_data[$file_name]["vars"]; + } else { + if(isset($this->_config_data[$file_name]["sections"][$section_name]["vars"])) + return (array)$this->_config_data[$file_name]["sections"][$section_name]["vars"]; + else + return array(); + } + } + } + + + /** + * Retrieves config info based on the key. + * + * @param $file_name string config key (filename/section/var) + * @return string|array same as get() + * @uses get() retrieves information from config file and returns it + */ + function &get_key($config_key) + { + list($file_name, $section_name, $var_name) = explode('/', $config_key, 3); + $result = &$this->get($file_name, $section_name, $var_name); + return $result; + } + + /** + * Get all loaded config file names. + * + * @return array an array of loaded config file names + */ + function get_file_names() + { + return array_keys($this->_config_data); + } + + + /** + * Get all section names from a loaded file. + * + * @param string $file_name config file to get section names from + * @return array an array of section names from the specified file + */ + function get_section_names($file_name) + { + $file_name = $this->_config_path . $file_name; + if (!isset($this->_config_data[$file_name])) { + $this->_trigger_error_msg("Unknown config file '$file_name'"); + return; + } + + return array_keys($this->_config_data[$file_name]["sections"]); + } + + + /** + * Get all global or section variable names. + * + * @param string $file_name config file to get info for + * @param string $section_name (optional) section to get info for + * @return array an array of variables names from the specified file/section + */ + function get_var_names($file_name, $section = NULL) + { + if (empty($file_name)) { + $this->_trigger_error_msg('Empty config file name'); + return; + } else if (!isset($this->_config_data[$file_name])) { + $this->_trigger_error_msg("Unknown config file '$file_name'"); + return; + } + + if (empty($section)) + return array_keys($this->_config_data[$file_name]["vars"]); + else + return array_keys($this->_config_data[$file_name]["sections"][$section]["vars"]); + } + + + /** + * Clear loaded config data for a certain file or all files. + * + * @param string $file_name file to clear config data for + */ + function clear($file_name = NULL) + { + if ($file_name === NULL) + $this->_config_data = array(); + else if (isset($this->_config_data[$file_name])) + $this->_config_data[$file_name] = array(); + } + + + /** + * Load a configuration file manually. + * + * @param string $file_name file name to load + * @param boolean $prepend_path whether current config path should be + * prepended to the filename + */ + function load_file($file_name, $prepend_path = true) + { + if ($prepend_path && $this->_config_path != "") + $config_file = $this->_config_path . $file_name; + else + $config_file = $file_name; + + ini_set('track_errors', true); + $fp = @fopen($config_file, "r"); + if (!is_resource($fp)) { + $this->_trigger_error_msg("Could not open config file '$config_file'"); + return false; + } + + $contents = ($size = filesize($config_file)) ? fread($fp, $size) : ''; + fclose($fp); + + $this->_config_data[$config_file] = $this->parse_contents($contents); + return true; + } + + /** + * Store the contents of a file manually. + * + * @param string $config_file file name of the related contents + * @param string $contents the file-contents to parse + */ + function set_file_contents($config_file, $contents) + { + $this->_config_data[$config_file] = $this->parse_contents($contents); + return true; + } + + /** + * parse the source of a configuration file manually. + * + * @param string $contents the file-contents to parse + */ + function parse_contents($contents) + { + if($this->fix_newlines) { + // fix mac/dos formatted newlines + $contents = preg_replace('!\r\n?!', "\n", $contents); + } + + $config_data = array(); + $config_data['sections'] = array(); + $config_data['vars'] = array(); + + /* reference to fill with data */ + $vars =& $config_data['vars']; + + /* parse file line by line */ + preg_match_all('!^.*\r?\n?!m', $contents, $match); + $lines = $match[0]; + for ($i=0, $count=count($lines); $i<$count; $i++) { + $line = $lines[$i]; + if (empty($line)) continue; + + if ( substr($line, 0, 1) == '[' && preg_match('!^\[(.*?)\]!', $line, $match) ) { + /* section found */ + if (substr($match[1], 0, 1) == '.') { + /* hidden section */ + if ($this->read_hidden) { + $section_name = substr($match[1], 1); + } else { + /* break reference to $vars to ignore hidden section */ + unset($vars); + $vars = array(); + continue; + } + } else { + $section_name = $match[1]; + } + if (!isset($config_data['sections'][$section_name])) + $config_data['sections'][$section_name] = array('vars' => array()); + $vars =& $config_data['sections'][$section_name]['vars']; + continue; + } + + if (preg_match('/^\s*(\.?\w+)\s*=\s*(.*)/s', $line, $match)) { + /* variable found */ + $var_name = rtrim($match[1]); + if (strpos($match[2], '"""') === 0) { + /* handle multiline-value */ + $lines[$i] = substr($match[2], 3); + $var_value = ''; + while ($i<$count) { + if (($pos = strpos($lines[$i], '"""')) === false) { + $var_value .= $lines[$i++]; + } else { + /* end of multiline-value */ + $var_value .= substr($lines[$i], 0, $pos); + break; + } + } + $booleanize = false; + + } else { + /* handle simple value */ + $var_value = preg_replace('/^([\'"])(.*)\1$/', '\2', rtrim($match[2])); + $booleanize = $this->booleanize; + + } + $this->_set_config_var($vars, $var_name, $var_value, $booleanize); + } + /* else unparsable line / means it is a comment / means ignore it */ + } + return $config_data; + } + + /**#@+ @access private */ + /** + * @param array &$container + * @param string $var_name + * @param mixed $var_value + * @param boolean $booleanize determines whether $var_value is converted to + * to true/false + */ + function _set_config_var(&$container, $var_name, $var_value, $booleanize) + { + if (substr($var_name, 0, 1) == '.') { + if (!$this->read_hidden) + return; + else + $var_name = substr($var_name, 1); + } + + if (!preg_match("/^[a-zA-Z_]\w*$/", $var_name)) { + $this->_trigger_error_msg("Bad variable name '$var_name'"); + return; + } + + if ($booleanize) { + if (preg_match("/^(on|true|yes)$/i", $var_value)) + $var_value = true; + else if (preg_match("/^(off|false|no)$/i", $var_value)) + $var_value = false; + } + + if (!isset($container[$var_name]) || $this->overwrite) + $container[$var_name] = $var_value; + else { + settype($container[$var_name], 'array'); + $container[$var_name][] = $var_value; + } + } + + /** + * @uses trigger_error() creates a PHP warning/error + * @param string $error_msg + * @param integer $error_type one of + */ + function _trigger_error_msg($error_msg, $error_type = E_USER_WARNING) + { + trigger_error("Config_File error: $error_msg", $error_type); + } + /**#@-*/ +} + +?> diff --git a/trunk/docs/include/smarty/Smarty.class.php b/trunk/docs/include/smarty/Smarty.class.php new file mode 100644 index 0000000..e11a6dc --- /dev/null +++ b/trunk/docs/include/smarty/Smarty.class.php @@ -0,0 +1,1944 @@ + + * @author Andrei Zmievski + * @package Smarty + * @version 2.6.16 + */ + +/* $Id: Smarty.class.php,v 1.526 2006/11/30 17:01:28 mohrt Exp $ */ + +/** + * DIR_SEP isn't used anymore, but third party apps might + */ +if(!defined('DIR_SEP')) { + define('DIR_SEP', DIRECTORY_SEPARATOR); +} + +/** + * set SMARTY_DIR to absolute path to Smarty library files. + * if not defined, include_path will be used. Sets SMARTY_DIR only if user + * application has not already defined it. + */ + +if (!defined('SMARTY_DIR')) { + define('SMARTY_DIR', dirname(__FILE__) . DIRECTORY_SEPARATOR); +} + +if (!defined('SMARTY_CORE_DIR')) { + define('SMARTY_CORE_DIR', SMARTY_DIR . 'internals' . DIRECTORY_SEPARATOR); +} + +define('SMARTY_PHP_PASSTHRU', 0); +define('SMARTY_PHP_QUOTE', 1); +define('SMARTY_PHP_REMOVE', 2); +define('SMARTY_PHP_ALLOW', 3); + +/** + * @package Smarty + */ +class Smarty +{ + /**#@+ + * Smarty Configuration Section + */ + + /** + * The name of the directory where templates are located. + * + * @var string + */ + var $template_dir = 'templates'; + + /** + * The directory where compiled templates are located. + * + * @var string + */ + var $compile_dir = 'templates_c'; + + /** + * The directory where config files are located. + * + * @var string + */ + var $config_dir = 'configs'; + + /** + * An array of directories searched for plugins. + * + * @var array + */ + var $plugins_dir = array('plugins'); + + /** + * If debugging is enabled, a debug console window will display + * when the page loads (make sure your browser allows unrequested + * popup windows) + * + * @var boolean + */ + var $debugging = false; + + /** + * When set, smarty does uses this value as error_reporting-level. + * + * @var boolean + */ + var $error_reporting = null; + + /** + * This is the path to the debug console template. If not set, + * the default one will be used. + * + * @var string + */ + var $debug_tpl = ''; + + /** + * This determines if debugging is enable-able from the browser. + *
    + *
  • NONE => no debugging control allowed
  • + *
  • URL => enable debugging when SMARTY_DEBUG is found in the URL.
  • + *
+ * @link http://www.foo.dom/index.php?SMARTY_DEBUG + * @var string + */ + var $debugging_ctrl = 'NONE'; + + /** + * This tells Smarty whether to check for recompiling or not. Recompiling + * does not need to happen unless a template or config file is changed. + * Typically you enable this during development, and disable for + * production. + * + * @var boolean + */ + var $compile_check = true; + + /** + * This forces templates to compile every time. Useful for development + * or debugging. + * + * @var boolean + */ + var $force_compile = false; + + /** + * This enables template caching. + *
    + *
  • 0 = no caching
  • + *
  • 1 = use class cache_lifetime value
  • + *
  • 2 = use cache_lifetime in cache file
  • + *
+ * @var integer + */ + var $caching = 0; + + /** + * The name of the directory for cache files. + * + * @var string + */ + var $cache_dir = 'cache'; + + /** + * This is the number of seconds cached content will persist. + *
    + *
  • 0 = always regenerate cache
  • + *
  • -1 = never expires
  • + *
+ * + * @var integer + */ + var $cache_lifetime = 3600; + + /** + * Only used when $caching is enabled. If true, then If-Modified-Since headers + * are respected with cached content, and appropriate HTTP headers are sent. + * This way repeated hits to a cached page do not send the entire page to the + * client every time. + * + * @var boolean + */ + var $cache_modified_check = false; + + /** + * This determines how Smarty handles "" tags in templates. + * possible values: + *
    + *
  • SMARTY_PHP_PASSTHRU -> print tags as plain text
  • + *
  • SMARTY_PHP_QUOTE -> escape tags as entities
  • + *
  • SMARTY_PHP_REMOVE -> remove php tags
  • + *
  • SMARTY_PHP_ALLOW -> execute php tags
  • + *
+ * + * @var integer + */ + var $php_handling = SMARTY_PHP_PASSTHRU; + + /** + * This enables template security. When enabled, many things are restricted + * in the templates that normally would go unchecked. This is useful when + * untrusted parties are editing templates and you want a reasonable level + * of security. (no direct execution of PHP in templates for example) + * + * @var boolean + */ + var $security = false; + + /** + * This is the list of template directories that are considered secure. This + * is used only if {@link $security} is enabled. One directory per array + * element. {@link $template_dir} is in this list implicitly. + * + * @var array + */ + var $secure_dir = array(); + + /** + * These are the security settings for Smarty. They are used only when + * {@link $security} is enabled. + * + * @var array + */ + var $security_settings = array( + 'PHP_HANDLING' => false, + 'IF_FUNCS' => array('array', 'list', + 'isset', 'empty', + 'count', 'sizeof', + 'in_array', 'is_array', + 'true', 'false', 'null'), + 'INCLUDE_ANY' => false, + 'PHP_TAGS' => false, + 'MODIFIER_FUNCS' => array('count'), + 'ALLOW_CONSTANTS' => false + ); + + /** + * This is an array of directories where trusted php scripts reside. + * {@link $security} is disabled during their inclusion/execution. + * + * @var array + */ + var $trusted_dir = array(); + + /** + * The left delimiter used for the template tags. + * + * @var string + */ + var $left_delimiter = '{'; + + /** + * The right delimiter used for the template tags. + * + * @var string + */ + var $right_delimiter = '}'; + + /** + * The order in which request variables are registered, similar to + * variables_order in php.ini E = Environment, G = GET, P = POST, + * C = Cookies, S = Server + * + * @var string + */ + var $request_vars_order = 'EGPCS'; + + /** + * Indicates wether $HTTP_*_VARS[] (request_use_auto_globals=false) + * are uses as request-vars or $_*[]-vars. note: if + * request_use_auto_globals is true, then $request_vars_order has + * no effect, but the php-ini-value "gpc_order" + * + * @var boolean + */ + var $request_use_auto_globals = true; + + /** + * Set this if you want different sets of compiled files for the same + * templates. This is useful for things like different languages. + * Instead of creating separate sets of templates per language, you + * set different compile_ids like 'en' and 'de'. + * + * @var string + */ + var $compile_id = null; + + /** + * This tells Smarty whether or not to use sub dirs in the cache/ and + * templates_c/ directories. sub directories better organized, but + * may not work well with PHP safe mode enabled. + * + * @var boolean + * + */ + var $use_sub_dirs = false; + + /** + * This is a list of the modifiers to apply to all template variables. + * Put each modifier in a separate array element in the order you want + * them applied. example: array('escape:"htmlall"'); + * + * @var array + */ + var $default_modifiers = array(); + + /** + * This is the resource type to be used when not specified + * at the beginning of the resource path. examples: + * $smarty->display('file:index.tpl'); + * $smarty->display('db:index.tpl'); + * $smarty->display('index.tpl'); // will use default resource type + * {include file="file:index.tpl"} + * {include file="db:index.tpl"} + * {include file="index.tpl"} {* will use default resource type *} + * + * @var array + */ + var $default_resource_type = 'file'; + + /** + * The function used for cache file handling. If not set, built-in caching is used. + * + * @var null|string function name + */ + var $cache_handler_func = null; + + /** + * This indicates which filters are automatically loaded into Smarty. + * + * @var array array of filter names + */ + var $autoload_filters = array(); + + /**#@+ + * @var boolean + */ + /** + * This tells if config file vars of the same name overwrite each other or not. + * if disabled, same name variables are accumulated in an array. + */ + var $config_overwrite = true; + + /** + * This tells whether or not to automatically booleanize config file variables. + * If enabled, then the strings "on", "true", and "yes" are treated as boolean + * true, and "off", "false" and "no" are treated as boolean false. + */ + var $config_booleanize = true; + + /** + * This tells whether hidden sections [.foobar] are readable from the + * tempalates or not. Normally you would never allow this since that is + * the point behind hidden sections: the application can access them, but + * the templates cannot. + */ + var $config_read_hidden = false; + + /** + * This tells whether or not automatically fix newlines in config files. + * It basically converts \r (mac) or \r\n (dos) to \n + */ + var $config_fix_newlines = true; + /**#@-*/ + + /** + * If a template cannot be found, this PHP function will be executed. + * Useful for creating templates on-the-fly or other special action. + * + * @var string function name + */ + var $default_template_handler_func = ''; + + /** + * The file that contains the compiler class. This can a full + * pathname, or relative to the php_include path. + * + * @var string + */ + var $compiler_file = 'Smarty_Compiler.class.php'; + + /** + * The class used for compiling templates. + * + * @var string + */ + var $compiler_class = 'Smarty_Compiler'; + + /** + * The class used to load config vars. + * + * @var string + */ + var $config_class = 'Config_File'; + +/**#@+ + * END Smarty Configuration Section + * There should be no need to touch anything below this line. + * @access private + */ + /** + * where assigned template vars are kept + * + * @var array + */ + var $_tpl_vars = array(); + + /** + * stores run-time $smarty.* vars + * + * @var null|array + */ + var $_smarty_vars = null; + + /** + * keeps track of sections + * + * @var array + */ + var $_sections = array(); + + /** + * keeps track of foreach blocks + * + * @var array + */ + var $_foreach = array(); + + /** + * keeps track of tag hierarchy + * + * @var array + */ + var $_tag_stack = array(); + + /** + * configuration object + * + * @var Config_file + */ + var $_conf_obj = null; + + /** + * loaded configuration settings + * + * @var array + */ + var $_config = array(array('vars' => array(), 'files' => array())); + + /** + * md5 checksum of the string 'Smarty' + * + * @var string + */ + var $_smarty_md5 = 'f8d698aea36fcbead2b9d5359ffca76f'; + + /** + * Smarty version number + * + * @var string + */ + var $_version = '2.6.16'; + + /** + * current template inclusion depth + * + * @var integer + */ + var $_inclusion_depth = 0; + + /** + * for different compiled templates + * + * @var string + */ + var $_compile_id = null; + + /** + * text in URL to enable debug mode + * + * @var string + */ + var $_smarty_debug_id = 'SMARTY_DEBUG'; + + /** + * debugging information for debug console + * + * @var array + */ + var $_smarty_debug_info = array(); + + /** + * info that makes up a cache file + * + * @var array + */ + var $_cache_info = array(); + + /** + * default file permissions + * + * @var integer + */ + var $_file_perms = 0644; + + /** + * default dir permissions + * + * @var integer + */ + var $_dir_perms = 0771; + + /** + * registered objects + * + * @var array + */ + var $_reg_objects = array(); + + /** + * table keeping track of plugins + * + * @var array + */ + var $_plugins = array( + 'modifier' => array(), + 'function' => array(), + 'block' => array(), + 'compiler' => array(), + 'prefilter' => array(), + 'postfilter' => array(), + 'outputfilter' => array(), + 'resource' => array(), + 'insert' => array()); + + + /** + * cache serials + * + * @var array + */ + var $_cache_serials = array(); + + /** + * name of optional cache include file + * + * @var string + */ + var $_cache_include = null; + + /** + * indicate if the current code is used in a compiled + * include + * + * @var string + */ + var $_cache_including = false; + + /**#@-*/ + /** + * The class constructor. + */ + function Smarty() + { + $this->assign('SCRIPT_NAME', isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] + : @$GLOBALS['HTTP_SERVER_VARS']['SCRIPT_NAME']); + } + + /** + * assigns values to template variables + * + * @param array|string $tpl_var the template variable name(s) + * @param mixed $value the value to assign + */ + function assign($tpl_var, $value = null) + { + if (is_array($tpl_var)){ + foreach ($tpl_var as $key => $val) { + if ($key != '') { + $this->_tpl_vars[$key] = $val; + } + } + } else { + if ($tpl_var != '') + $this->_tpl_vars[$tpl_var] = $value; + } + } + + /** + * assigns values to template variables by reference + * + * @param string $tpl_var the template variable name + * @param mixed $value the referenced value to assign + */ + function assign_by_ref($tpl_var, &$value) + { + if ($tpl_var != '') + $this->_tpl_vars[$tpl_var] = &$value; + } + + /** + * appends values to template variables + * + * @param array|string $tpl_var the template variable name(s) + * @param mixed $value the value to append + */ + function append($tpl_var, $value=null, $merge=false) + { + if (is_array($tpl_var)) { + // $tpl_var is an array, ignore $value + foreach ($tpl_var as $_key => $_val) { + if ($_key != '') { + if(!@is_array($this->_tpl_vars[$_key])) { + settype($this->_tpl_vars[$_key],'array'); + } + if($merge && is_array($_val)) { + foreach($_val as $_mkey => $_mval) { + $this->_tpl_vars[$_key][$_mkey] = $_mval; + } + } else { + $this->_tpl_vars[$_key][] = $_val; + } + } + } + } else { + if ($tpl_var != '' && isset($value)) { + if(!@is_array($this->_tpl_vars[$tpl_var])) { + settype($this->_tpl_vars[$tpl_var],'array'); + } + if($merge && is_array($value)) { + foreach($value as $_mkey => $_mval) { + $this->_tpl_vars[$tpl_var][$_mkey] = $_mval; + } + } else { + $this->_tpl_vars[$tpl_var][] = $value; + } + } + } + } + + /** + * appends values to template variables by reference + * + * @param string $tpl_var the template variable name + * @param mixed $value the referenced value to append + */ + function append_by_ref($tpl_var, &$value, $merge=false) + { + if ($tpl_var != '' && isset($value)) { + if(!@is_array($this->_tpl_vars[$tpl_var])) { + settype($this->_tpl_vars[$tpl_var],'array'); + } + if ($merge && is_array($value)) { + foreach($value as $_key => $_val) { + $this->_tpl_vars[$tpl_var][$_key] = &$value[$_key]; + } + } else { + $this->_tpl_vars[$tpl_var][] = &$value; + } + } + } + + + /** + * clear the given assigned template variable. + * + * @param string $tpl_var the template variable to clear + */ + function clear_assign($tpl_var) + { + if (is_array($tpl_var)) + foreach ($tpl_var as $curr_var) + unset($this->_tpl_vars[$curr_var]); + else + unset($this->_tpl_vars[$tpl_var]); + } + + + /** + * Registers custom function to be used in templates + * + * @param string $function the name of the template function + * @param string $function_impl the name of the PHP function to register + */ + function register_function($function, $function_impl, $cacheable=true, $cache_attrs=null) + { + $this->_plugins['function'][$function] = + array($function_impl, null, null, false, $cacheable, $cache_attrs); + + } + + /** + * Unregisters custom function + * + * @param string $function name of template function + */ + function unregister_function($function) + { + unset($this->_plugins['function'][$function]); + } + + /** + * Registers object to be used in templates + * + * @param string $object name of template object + * @param object &$object_impl the referenced PHP object to register + * @param null|array $allowed list of allowed methods (empty = all) + * @param boolean $smarty_args smarty argument format, else traditional + * @param null|array $block_functs list of methods that are block format + */ + function register_object($object, &$object_impl, $allowed = array(), $smarty_args = true, $block_methods = array()) + { + settype($allowed, 'array'); + settype($smarty_args, 'boolean'); + $this->_reg_objects[$object] = + array(&$object_impl, $allowed, $smarty_args, $block_methods); + } + + /** + * Unregisters object + * + * @param string $object name of template object + */ + function unregister_object($object) + { + unset($this->_reg_objects[$object]); + } + + + /** + * Registers block function to be used in templates + * + * @param string $block name of template block + * @param string $block_impl PHP function to register + */ + function register_block($block, $block_impl, $cacheable=true, $cache_attrs=null) + { + $this->_plugins['block'][$block] = + array($block_impl, null, null, false, $cacheable, $cache_attrs); + } + + /** + * Unregisters block function + * + * @param string $block name of template function + */ + function unregister_block($block) + { + unset($this->_plugins['block'][$block]); + } + + /** + * Registers compiler function + * + * @param string $function name of template function + * @param string $function_impl name of PHP function to register + */ + function register_compiler_function($function, $function_impl, $cacheable=true) + { + $this->_plugins['compiler'][$function] = + array($function_impl, null, null, false, $cacheable); + } + + /** + * Unregisters compiler function + * + * @param string $function name of template function + */ + function unregister_compiler_function($function) + { + unset($this->_plugins['compiler'][$function]); + } + + /** + * Registers modifier to be used in templates + * + * @param string $modifier name of template modifier + * @param string $modifier_impl name of PHP function to register + */ + function register_modifier($modifier, $modifier_impl) + { + $this->_plugins['modifier'][$modifier] = + array($modifier_impl, null, null, false); + } + + /** + * Unregisters modifier + * + * @param string $modifier name of template modifier + */ + function unregister_modifier($modifier) + { + unset($this->_plugins['modifier'][$modifier]); + } + + /** + * Registers a resource to fetch a template + * + * @param string $type name of resource + * @param array $functions array of functions to handle resource + */ + function register_resource($type, $functions) + { + if (count($functions)==4) { + $this->_plugins['resource'][$type] = + array($functions, false); + + } elseif (count($functions)==5) { + $this->_plugins['resource'][$type] = + array(array(array(&$functions[0], $functions[1]) + ,array(&$functions[0], $functions[2]) + ,array(&$functions[0], $functions[3]) + ,array(&$functions[0], $functions[4])) + ,false); + + } else { + $this->trigger_error("malformed function-list for '$type' in register_resource"); + + } + } + + /** + * Unregisters a resource + * + * @param string $type name of resource + */ + function unregister_resource($type) + { + unset($this->_plugins['resource'][$type]); + } + + /** + * Registers a prefilter function to apply + * to a template before compiling + * + * @param string $function name of PHP function to register + */ + function register_prefilter($function) + { + $_name = (is_array($function)) ? $function[1] : $function; + $this->_plugins['prefilter'][$_name] + = array($function, null, null, false); + } + + /** + * Unregisters a prefilter function + * + * @param string $function name of PHP function + */ + function unregister_prefilter($function) + { + unset($this->_plugins['prefilter'][$function]); + } + + /** + * Registers a postfilter function to apply + * to a compiled template after compilation + * + * @param string $function name of PHP function to register + */ + function register_postfilter($function) + { + $_name = (is_array($function)) ? $function[1] : $function; + $this->_plugins['postfilter'][$_name] + = array($function, null, null, false); + } + + /** + * Unregisters a postfilter function + * + * @param string $function name of PHP function + */ + function unregister_postfilter($function) + { + unset($this->_plugins['postfilter'][$function]); + } + + /** + * Registers an output filter function to apply + * to a template output + * + * @param string $function name of PHP function + */ + function register_outputfilter($function) + { + $_name = (is_array($function)) ? $function[1] : $function; + $this->_plugins['outputfilter'][$_name] + = array($function, null, null, false); + } + + /** + * Unregisters an outputfilter function + * + * @param string $function name of PHP function + */ + function unregister_outputfilter($function) + { + unset($this->_plugins['outputfilter'][$function]); + } + + /** + * load a filter of specified type and name + * + * @param string $type filter type + * @param string $name filter name + */ + function load_filter($type, $name) + { + switch ($type) { + case 'output': + $_params = array('plugins' => array(array($type . 'filter', $name, null, null, false))); + require_once(SMARTY_CORE_DIR . 'core.load_plugins.php'); + smarty_core_load_plugins($_params, $this); + break; + + case 'pre': + case 'post': + if (!isset($this->_plugins[$type . 'filter'][$name])) + $this->_plugins[$type . 'filter'][$name] = false; + break; + } + } + + /** + * clear cached content for the given template and cache id + * + * @param string $tpl_file name of template file + * @param string $cache_id name of cache_id + * @param string $compile_id name of compile_id + * @param string $exp_time expiration time + * @return boolean + */ + function clear_cache($tpl_file = null, $cache_id = null, $compile_id = null, $exp_time = null) + { + + if (!isset($compile_id)) + $compile_id = $this->compile_id; + + if (!isset($tpl_file)) + $compile_id = null; + + $_auto_id = $this->_get_auto_id($cache_id, $compile_id); + + if (!empty($this->cache_handler_func)) { + return call_user_func_array($this->cache_handler_func, + array('clear', &$this, &$dummy, $tpl_file, $cache_id, $compile_id, $exp_time)); + } else { + $_params = array('auto_base' => $this->cache_dir, + 'auto_source' => $tpl_file, + 'auto_id' => $_auto_id, + 'exp_time' => $exp_time); + require_once(SMARTY_CORE_DIR . 'core.rm_auto.php'); + return smarty_core_rm_auto($_params, $this); + } + + } + + + /** + * clear the entire contents of cache (all templates) + * + * @param string $exp_time expire time + * @return boolean results of {@link smarty_core_rm_auto()} + */ + function clear_all_cache($exp_time = null) + { + return $this->clear_cache(null, null, null, $exp_time); + } + + + /** + * test to see if valid cache exists for this template + * + * @param string $tpl_file name of template file + * @param string $cache_id + * @param string $compile_id + * @return string|false results of {@link _read_cache_file()} + */ + function is_cached($tpl_file, $cache_id = null, $compile_id = null) + { + if (!$this->caching) + return false; + + if (!isset($compile_id)) + $compile_id = $this->compile_id; + + $_params = array( + 'tpl_file' => $tpl_file, + 'cache_id' => $cache_id, + 'compile_id' => $compile_id + ); + require_once(SMARTY_CORE_DIR . 'core.read_cache_file.php'); + return smarty_core_read_cache_file($_params, $this); + } + + + /** + * clear all the assigned template variables. + * + */ + function clear_all_assign() + { + $this->_tpl_vars = array(); + } + + /** + * clears compiled version of specified template resource, + * or all compiled template files if one is not specified. + * This function is for advanced use only, not normally needed. + * + * @param string $tpl_file + * @param string $compile_id + * @param string $exp_time + * @return boolean results of {@link smarty_core_rm_auto()} + */ + function clear_compiled_tpl($tpl_file = null, $compile_id = null, $exp_time = null) + { + if (!isset($compile_id)) { + $compile_id = $this->compile_id; + } + $_params = array('auto_base' => $this->compile_dir, + 'auto_source' => $tpl_file, + 'auto_id' => $compile_id, + 'exp_time' => $exp_time, + 'extensions' => array('.inc', '.php')); + require_once(SMARTY_CORE_DIR . 'core.rm_auto.php'); + return smarty_core_rm_auto($_params, $this); + } + + /** + * Checks whether requested template exists. + * + * @param string $tpl_file + * @return boolean + */ + function template_exists($tpl_file) + { + $_params = array('resource_name' => $tpl_file, 'quiet'=>true, 'get_source'=>false); + return $this->_fetch_resource_info($_params); + } + + /** + * Returns an array containing template variables + * + * @param string $name + * @param string $type + * @return array + */ + function &get_template_vars($name=null) + { + if(!isset($name)) { + return $this->_tpl_vars; + } elseif(isset($this->_tpl_vars[$name])) { + return $this->_tpl_vars[$name]; + } else { + // var non-existant, return valid reference + $_tmp = null; + return $_tmp; + } + } + + /** + * Returns an array containing config variables + * + * @param string $name + * @param string $type + * @return array + */ + function &get_config_vars($name=null) + { + if(!isset($name) && is_array($this->_config[0])) { + return $this->_config[0]['vars']; + } else if(isset($this->_config[0]['vars'][$name])) { + return $this->_config[0]['vars'][$name]; + } else { + // var non-existant, return valid reference + $_tmp = null; + return $_tmp; + } + } + + /** + * trigger Smarty error + * + * @param string $error_msg + * @param integer $error_type + */ + function trigger_error($error_msg, $error_type = E_USER_WARNING) + { + trigger_error("Smarty error: $error_msg", $error_type); + } + + + /** + * executes & displays the template results + * + * @param string $resource_name + * @param string $cache_id + * @param string $compile_id + */ + function display($resource_name, $cache_id = null, $compile_id = null) + { + $this->fetch($resource_name, $cache_id, $compile_id, true); + } + + /** + * executes & returns or displays the template results + * + * @param string $resource_name + * @param string $cache_id + * @param string $compile_id + * @param boolean $display + */ + function fetch($resource_name, $cache_id = null, $compile_id = null, $display = false) + { + static $_cache_info = array(); + + $_smarty_old_error_level = $this->debugging ? error_reporting() : error_reporting(isset($this->error_reporting) + ? $this->error_reporting : error_reporting() & ~E_NOTICE); + + if (!$this->debugging && $this->debugging_ctrl == 'URL') { + $_query_string = $this->request_use_auto_globals ? $_SERVER['QUERY_STRING'] : $GLOBALS['HTTP_SERVER_VARS']['QUERY_STRING']; + if (@strstr($_query_string, $this->_smarty_debug_id)) { + if (@strstr($_query_string, $this->_smarty_debug_id . '=on')) { + // enable debugging for this browser session + @setcookie('SMARTY_DEBUG', true); + $this->debugging = true; + } elseif (@strstr($_query_string, $this->_smarty_debug_id . '=off')) { + // disable debugging for this browser session + @setcookie('SMARTY_DEBUG', false); + $this->debugging = false; + } else { + // enable debugging for this page + $this->debugging = true; + } + } else { + $this->debugging = (bool)($this->request_use_auto_globals ? @$_COOKIE['SMARTY_DEBUG'] : @$GLOBALS['HTTP_COOKIE_VARS']['SMARTY_DEBUG']); + } + } + + if ($this->debugging) { + // capture time for debugging info + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $_debug_start_time = smarty_core_get_microtime($_params, $this); + $this->_smarty_debug_info[] = array('type' => 'template', + 'filename' => $resource_name, + 'depth' => 0); + $_included_tpls_idx = count($this->_smarty_debug_info) - 1; + } + + if (!isset($compile_id)) { + $compile_id = $this->compile_id; + } + + $this->_compile_id = $compile_id; + $this->_inclusion_depth = 0; + + if ($this->caching) { + // save old cache_info, initialize cache_info + array_push($_cache_info, $this->_cache_info); + $this->_cache_info = array(); + $_params = array( + 'tpl_file' => $resource_name, + 'cache_id' => $cache_id, + 'compile_id' => $compile_id, + 'results' => null + ); + require_once(SMARTY_CORE_DIR . 'core.read_cache_file.php'); + if (smarty_core_read_cache_file($_params, $this)) { + $_smarty_results = $_params['results']; + if (!empty($this->_cache_info['insert_tags'])) { + $_params = array('plugins' => $this->_cache_info['insert_tags']); + require_once(SMARTY_CORE_DIR . 'core.load_plugins.php'); + smarty_core_load_plugins($_params, $this); + $_params = array('results' => $_smarty_results); + require_once(SMARTY_CORE_DIR . 'core.process_cached_inserts.php'); + $_smarty_results = smarty_core_process_cached_inserts($_params, $this); + } + if (!empty($this->_cache_info['cache_serials'])) { + $_params = array('results' => $_smarty_results); + require_once(SMARTY_CORE_DIR . 'core.process_compiled_include.php'); + $_smarty_results = smarty_core_process_compiled_include($_params, $this); + } + + + if ($display) { + if ($this->debugging) + { + // capture time for debugging info + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $this->_smarty_debug_info[$_included_tpls_idx]['exec_time'] = smarty_core_get_microtime($_params, $this) - $_debug_start_time; + require_once(SMARTY_CORE_DIR . 'core.display_debug_console.php'); + $_smarty_results .= smarty_core_display_debug_console($_params, $this); + } + if ($this->cache_modified_check) { + $_server_vars = ($this->request_use_auto_globals) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS']; + $_last_modified_date = @substr($_server_vars['HTTP_IF_MODIFIED_SINCE'], 0, strpos($_server_vars['HTTP_IF_MODIFIED_SINCE'], 'GMT') + 3); + $_gmt_mtime = gmdate('D, d M Y H:i:s', $this->_cache_info['timestamp']).' GMT'; + if (@count($this->_cache_info['insert_tags']) == 0 + && !$this->_cache_serials + && $_gmt_mtime == $_last_modified_date) { + if (php_sapi_name()=='cgi') + header('Status: 304 Not Modified'); + else + header('HTTP/1.1 304 Not Modified'); + + } else { + header('Last-Modified: '.$_gmt_mtime); + echo $_smarty_results; + } + } else { + echo $_smarty_results; + } + error_reporting($_smarty_old_error_level); + // restore initial cache_info + $this->_cache_info = array_pop($_cache_info); + return true; + } else { + error_reporting($_smarty_old_error_level); + // restore initial cache_info + $this->_cache_info = array_pop($_cache_info); + return $_smarty_results; + } + } else { + $this->_cache_info['template'][$resource_name] = true; + if ($this->cache_modified_check && $display) { + header('Last-Modified: '.gmdate('D, d M Y H:i:s', time()).' GMT'); + } + } + } + + // load filters that are marked as autoload + if (count($this->autoload_filters)) { + foreach ($this->autoload_filters as $_filter_type => $_filters) { + foreach ($_filters as $_filter) { + $this->load_filter($_filter_type, $_filter); + } + } + } + + $_smarty_compile_path = $this->_get_compile_path($resource_name); + + // if we just need to display the results, don't perform output + // buffering - for speed + $_cache_including = $this->_cache_including; + $this->_cache_including = false; + if ($display && !$this->caching && count($this->_plugins['outputfilter']) == 0) { + if ($this->_is_compiled($resource_name, $_smarty_compile_path) + || $this->_compile_resource($resource_name, $_smarty_compile_path)) + { + include($_smarty_compile_path); + } + } else { + ob_start(); + if ($this->_is_compiled($resource_name, $_smarty_compile_path) + || $this->_compile_resource($resource_name, $_smarty_compile_path)) + { + include($_smarty_compile_path); + } + $_smarty_results = ob_get_contents(); + ob_end_clean(); + + foreach ((array)$this->_plugins['outputfilter'] as $_output_filter) { + $_smarty_results = call_user_func_array($_output_filter[0], array($_smarty_results, &$this)); + } + } + + if ($this->caching) { + $_params = array('tpl_file' => $resource_name, + 'cache_id' => $cache_id, + 'compile_id' => $compile_id, + 'results' => $_smarty_results); + require_once(SMARTY_CORE_DIR . 'core.write_cache_file.php'); + smarty_core_write_cache_file($_params, $this); + require_once(SMARTY_CORE_DIR . 'core.process_cached_inserts.php'); + $_smarty_results = smarty_core_process_cached_inserts($_params, $this); + + if ($this->_cache_serials) { + // strip nocache-tags from output + $_smarty_results = preg_replace('!(\{/?nocache\:[0-9a-f]{32}#\d+\})!s' + ,'' + ,$_smarty_results); + } + // restore initial cache_info + $this->_cache_info = array_pop($_cache_info); + } + $this->_cache_including = $_cache_including; + + if ($display) { + if (isset($_smarty_results)) { echo $_smarty_results; } + if ($this->debugging) { + // capture time for debugging info + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $this->_smarty_debug_info[$_included_tpls_idx]['exec_time'] = (smarty_core_get_microtime($_params, $this) - $_debug_start_time); + require_once(SMARTY_CORE_DIR . 'core.display_debug_console.php'); + echo smarty_core_display_debug_console($_params, $this); + } + error_reporting($_smarty_old_error_level); + return; + } else { + error_reporting($_smarty_old_error_level); + if (isset($_smarty_results)) { return $_smarty_results; } + } + } + + /** + * load configuration values + * + * @param string $file + * @param string $section + * @param string $scope + */ + function config_load($file, $section = null, $scope = 'global') + { + require_once($this->_get_plugin_filepath('function', 'config_load')); + smarty_function_config_load(array('file' => $file, 'section' => $section, 'scope' => $scope), $this); + } + + /** + * return a reference to a registered object + * + * @param string $name + * @return object + */ + function &get_registered_object($name) { + if (!isset($this->_reg_objects[$name])) + $this->_trigger_fatal_error("'$name' is not a registered object"); + + if (!is_object($this->_reg_objects[$name][0])) + $this->_trigger_fatal_error("registered '$name' is not an object"); + + return $this->_reg_objects[$name][0]; + } + + /** + * clear configuration values + * + * @param string $var + */ + function clear_config($var = null) + { + if(!isset($var)) { + // clear all values + $this->_config = array(array('vars' => array(), + 'files' => array())); + } else { + unset($this->_config[0]['vars'][$var]); + } + } + + /** + * get filepath of requested plugin + * + * @param string $type + * @param string $name + * @return string|false + */ + function _get_plugin_filepath($type, $name) + { + $_params = array('type' => $type, 'name' => $name); + require_once(SMARTY_CORE_DIR . 'core.assemble_plugin_filepath.php'); + return smarty_core_assemble_plugin_filepath($_params, $this); + } + + /** + * test if resource needs compiling + * + * @param string $resource_name + * @param string $compile_path + * @return boolean + */ + function _is_compiled($resource_name, $compile_path) + { + if (!$this->force_compile && file_exists($compile_path)) { + if (!$this->compile_check) { + // no need to check compiled file + return true; + } else { + // get file source and timestamp + $_params = array('resource_name' => $resource_name, 'get_source'=>false); + if (!$this->_fetch_resource_info($_params)) { + return false; + } + if ($_params['resource_timestamp'] <= filemtime($compile_path)) { + // template not expired, no recompile + return true; + } else { + // compile template + return false; + } + } + } else { + // compiled template does not exist, or forced compile + return false; + } + } + + /** + * compile the template + * + * @param string $resource_name + * @param string $compile_path + * @return boolean + */ + function _compile_resource($resource_name, $compile_path) + { + + $_params = array('resource_name' => $resource_name); + if (!$this->_fetch_resource_info($_params)) { + return false; + } + + $_source_content = $_params['source_content']; + $_cache_include = substr($compile_path, 0, -4).'.inc'; + + if ($this->_compile_source($resource_name, $_source_content, $_compiled_content, $_cache_include)) { + // if a _cache_serial was set, we also have to write an include-file: + if ($this->_cache_include_info) { + require_once(SMARTY_CORE_DIR . 'core.write_compiled_include.php'); + smarty_core_write_compiled_include(array_merge($this->_cache_include_info, array('compiled_content'=>$_compiled_content, 'resource_name'=>$resource_name)), $this); + } + + $_params = array('compile_path'=>$compile_path, 'compiled_content' => $_compiled_content); + require_once(SMARTY_CORE_DIR . 'core.write_compiled_resource.php'); + smarty_core_write_compiled_resource($_params, $this); + + return true; + } else { + return false; + } + + } + + /** + * compile the given source + * + * @param string $resource_name + * @param string $source_content + * @param string $compiled_content + * @return boolean + */ + function _compile_source($resource_name, &$source_content, &$compiled_content, $cache_include_path=null) + { + if (file_exists(SMARTY_DIR . $this->compiler_file)) { + require_once(SMARTY_DIR . $this->compiler_file); + } else { + // use include_path + require_once($this->compiler_file); + } + + + $smarty_compiler = new $this->compiler_class; + + $smarty_compiler->template_dir = $this->template_dir; + $smarty_compiler->compile_dir = $this->compile_dir; + $smarty_compiler->plugins_dir = $this->plugins_dir; + $smarty_compiler->config_dir = $this->config_dir; + $smarty_compiler->force_compile = $this->force_compile; + $smarty_compiler->caching = $this->caching; + $smarty_compiler->php_handling = $this->php_handling; + $smarty_compiler->left_delimiter = $this->left_delimiter; + $smarty_compiler->right_delimiter = $this->right_delimiter; + $smarty_compiler->_version = $this->_version; + $smarty_compiler->security = $this->security; + $smarty_compiler->secure_dir = $this->secure_dir; + $smarty_compiler->security_settings = $this->security_settings; + $smarty_compiler->trusted_dir = $this->trusted_dir; + $smarty_compiler->use_sub_dirs = $this->use_sub_dirs; + $smarty_compiler->_reg_objects = &$this->_reg_objects; + $smarty_compiler->_plugins = &$this->_plugins; + $smarty_compiler->_tpl_vars = &$this->_tpl_vars; + $smarty_compiler->default_modifiers = $this->default_modifiers; + $smarty_compiler->compile_id = $this->_compile_id; + $smarty_compiler->_config = $this->_config; + $smarty_compiler->request_use_auto_globals = $this->request_use_auto_globals; + + if (isset($cache_include_path) && isset($this->_cache_serials[$cache_include_path])) { + $smarty_compiler->_cache_serial = $this->_cache_serials[$cache_include_path]; + } + $smarty_compiler->_cache_include = $cache_include_path; + + + $_results = $smarty_compiler->_compile_file($resource_name, $source_content, $compiled_content); + + if ($smarty_compiler->_cache_serial) { + $this->_cache_include_info = array( + 'cache_serial'=>$smarty_compiler->_cache_serial + ,'plugins_code'=>$smarty_compiler->_plugins_code + ,'include_file_path' => $cache_include_path); + + } else { + $this->_cache_include_info = null; + + } + + return $_results; + } + + /** + * Get the compile path for this resource + * + * @param string $resource_name + * @return string results of {@link _get_auto_filename()} + */ + function _get_compile_path($resource_name) + { + return $this->_get_auto_filename($this->compile_dir, $resource_name, + $this->_compile_id) . '.php'; + } + + /** + * fetch the template info. Gets timestamp, and source + * if get_source is true + * + * sets $source_content to the source of the template, and + * $resource_timestamp to its time stamp + * @param string $resource_name + * @param string $source_content + * @param integer $resource_timestamp + * @param boolean $get_source + * @param boolean $quiet + * @return boolean + */ + + function _fetch_resource_info(&$params) + { + if(!isset($params['get_source'])) { $params['get_source'] = true; } + if(!isset($params['quiet'])) { $params['quiet'] = false; } + + $_return = false; + $_params = array('resource_name' => $params['resource_name']) ; + if (isset($params['resource_base_path'])) + $_params['resource_base_path'] = $params['resource_base_path']; + else + $_params['resource_base_path'] = $this->template_dir; + + if ($this->_parse_resource_name($_params)) { + $_resource_type = $_params['resource_type']; + $_resource_name = $_params['resource_name']; + switch ($_resource_type) { + case 'file': + if ($params['get_source']) { + $params['source_content'] = $this->_read_file($_resource_name); + } + $params['resource_timestamp'] = filemtime($_resource_name); + $_return = is_file($_resource_name); + break; + + default: + // call resource functions to fetch the template source and timestamp + if ($params['get_source']) { + $_source_return = isset($this->_plugins['resource'][$_resource_type]) && + call_user_func_array($this->_plugins['resource'][$_resource_type][0][0], + array($_resource_name, &$params['source_content'], &$this)); + } else { + $_source_return = true; + } + + $_timestamp_return = isset($this->_plugins['resource'][$_resource_type]) && + call_user_func_array($this->_plugins['resource'][$_resource_type][0][1], + array($_resource_name, &$params['resource_timestamp'], &$this)); + + $_return = $_source_return && $_timestamp_return; + break; + } + } + + if (!$_return) { + // see if we can get a template with the default template handler + if (!empty($this->default_template_handler_func)) { + if (!is_callable($this->default_template_handler_func)) { + $this->trigger_error("default template handler function \"$this->default_template_handler_func\" doesn't exist."); + } else { + $_return = call_user_func_array( + $this->default_template_handler_func, + array($_params['resource_type'], $_params['resource_name'], &$params['source_content'], &$params['resource_timestamp'], &$this)); + } + } + } + + if (!$_return) { + if (!$params['quiet']) { + $this->trigger_error('unable to read resource: "' . $params['resource_name'] . '"'); + } + } else if ($_return && $this->security) { + require_once(SMARTY_CORE_DIR . 'core.is_secure.php'); + if (!smarty_core_is_secure($_params, $this)) { + if (!$params['quiet']) + $this->trigger_error('(secure mode) accessing "' . $params['resource_name'] . '" is not allowed'); + $params['source_content'] = null; + $params['resource_timestamp'] = null; + return false; + } + } + return $_return; + } + + + /** + * parse out the type and name from the resource + * + * @param string $resource_base_path + * @param string $resource_name + * @param string $resource_type + * @param string $resource_name + * @return boolean + */ + + function _parse_resource_name(&$params) + { + + // split tpl_path by the first colon + $_resource_name_parts = explode(':', $params['resource_name'], 2); + + if (count($_resource_name_parts) == 1) { + // no resource type given + $params['resource_type'] = $this->default_resource_type; + $params['resource_name'] = $_resource_name_parts[0]; + } else { + if(strlen($_resource_name_parts[0]) == 1) { + // 1 char is not resource type, but part of filepath + $params['resource_type'] = $this->default_resource_type; + $params['resource_name'] = $params['resource_name']; + } else { + $params['resource_type'] = $_resource_name_parts[0]; + $params['resource_name'] = $_resource_name_parts[1]; + } + } + + if ($params['resource_type'] == 'file') { + if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $params['resource_name'])) { + // relative pathname to $params['resource_base_path'] + // use the first directory where the file is found + foreach ((array)$params['resource_base_path'] as $_curr_path) { + $_fullpath = $_curr_path . DIRECTORY_SEPARATOR . $params['resource_name']; + if (file_exists($_fullpath) && is_file($_fullpath)) { + $params['resource_name'] = $_fullpath; + return true; + } + // didn't find the file, try include_path + $_params = array('file_path' => $_fullpath); + require_once(SMARTY_CORE_DIR . 'core.get_include_path.php'); + if(smarty_core_get_include_path($_params, $this)) { + $params['resource_name'] = $_params['new_file_path']; + return true; + } + } + return false; + } else { + /* absolute path */ + return file_exists($params['resource_name']); + } + } elseif (empty($this->_plugins['resource'][$params['resource_type']])) { + $_params = array('type' => $params['resource_type']); + require_once(SMARTY_CORE_DIR . 'core.load_resource_plugin.php'); + smarty_core_load_resource_plugin($_params, $this); + } + + return true; + } + + + /** + * Handle modifiers + * + * @param string|null $modifier_name + * @param array|null $map_array + * @return string result of modifiers + */ + function _run_mod_handler() + { + $_args = func_get_args(); + list($_modifier_name, $_map_array) = array_splice($_args, 0, 2); + list($_func_name, $_tpl_file, $_tpl_line) = + $this->_plugins['modifier'][$_modifier_name]; + + $_var = $_args[0]; + foreach ($_var as $_key => $_val) { + $_args[0] = $_val; + $_var[$_key] = call_user_func_array($_func_name, $_args); + } + return $_var; + } + + /** + * Remove starting and ending quotes from the string + * + * @param string $string + * @return string + */ + function _dequote($string) + { + if ((substr($string, 0, 1) == "'" || substr($string, 0, 1) == '"') && + substr($string, -1) == substr($string, 0, 1)) + return substr($string, 1, -1); + else + return $string; + } + + + /** + * read in a file + * + * @param string $filename + * @return string + */ + function _read_file($filename) + { + if ( file_exists($filename) && ($fd = @fopen($filename, 'rb')) ) { + $contents = ''; + while (!feof($fd)) { + $contents .= fread($fd, 8192); + } + fclose($fd); + return $contents; + } else { + return false; + } + } + + /** + * get a concrete filename for automagically created content + * + * @param string $auto_base + * @param string $auto_source + * @param string $auto_id + * @return string + * @staticvar string|null + * @staticvar string|null + */ + function _get_auto_filename($auto_base, $auto_source = null, $auto_id = null) + { + $_compile_dir_sep = $this->use_sub_dirs ? DIRECTORY_SEPARATOR : '^'; + $_return = $auto_base . DIRECTORY_SEPARATOR; + + if(isset($auto_id)) { + // make auto_id safe for directory names + $auto_id = str_replace('%7C',$_compile_dir_sep,(urlencode($auto_id))); + // split into separate directories + $_return .= $auto_id . $_compile_dir_sep; + } + + if(isset($auto_source)) { + // make source name safe for filename + $_filename = urlencode(basename($auto_source)); + $_crc32 = sprintf('%08X', crc32($auto_source)); + // prepend %% to avoid name conflicts with + // with $params['auto_id'] names + $_crc32 = substr($_crc32, 0, 2) . $_compile_dir_sep . + substr($_crc32, 0, 3) . $_compile_dir_sep . $_crc32; + $_return .= '%%' . $_crc32 . '%%' . $_filename; + } + + return $_return; + } + + /** + * unlink a file, possibly using expiration time + * + * @param string $resource + * @param integer $exp_time + */ + function _unlink($resource, $exp_time = null) + { + if(isset($exp_time)) { + if(time() - @filemtime($resource) >= $exp_time) { + return @unlink($resource); + } + } else { + return @unlink($resource); + } + } + + /** + * returns an auto_id for auto-file-functions + * + * @param string $cache_id + * @param string $compile_id + * @return string|null + */ + function _get_auto_id($cache_id=null, $compile_id=null) { + if (isset($cache_id)) + return (isset($compile_id)) ? $cache_id . '|' . $compile_id : $cache_id; + elseif(isset($compile_id)) + return $compile_id; + else + return null; + } + + /** + * trigger Smarty plugin error + * + * @param string $error_msg + * @param string $tpl_file + * @param integer $tpl_line + * @param string $file + * @param integer $line + * @param integer $error_type + */ + function _trigger_fatal_error($error_msg, $tpl_file = null, $tpl_line = null, + $file = null, $line = null, $error_type = E_USER_ERROR) + { + if(isset($file) && isset($line)) { + $info = ' ('.basename($file).", line $line)"; + } else { + $info = ''; + } + if (isset($tpl_line) && isset($tpl_file)) { + $this->trigger_error('[in ' . $tpl_file . ' line ' . $tpl_line . "]: $error_msg$info", $error_type); + } else { + $this->trigger_error($error_msg . $info, $error_type); + } + } + + + /** + * callback function for preg_replace, to call a non-cacheable block + * @return string + */ + function _process_compiled_include_callback($match) { + $_func = '_smarty_tplfunc_'.$match[2].'_'.$match[3]; + ob_start(); + $_func($this); + $_ret = ob_get_contents(); + ob_end_clean(); + return $_ret; + } + + + /** + * called for included templates + * + * @param string $_smarty_include_tpl_file + * @param string $_smarty_include_vars + */ + + // $_smarty_include_tpl_file, $_smarty_include_vars + + function _smarty_include($params) + { + if ($this->debugging) { + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $debug_start_time = smarty_core_get_microtime($_params, $this); + $this->_smarty_debug_info[] = array('type' => 'template', + 'filename' => $params['smarty_include_tpl_file'], + 'depth' => ++$this->_inclusion_depth); + $included_tpls_idx = count($this->_smarty_debug_info) - 1; + } + + $this->_tpl_vars = array_merge($this->_tpl_vars, $params['smarty_include_vars']); + + // config vars are treated as local, so push a copy of the + // current ones onto the front of the stack + array_unshift($this->_config, $this->_config[0]); + + $_smarty_compile_path = $this->_get_compile_path($params['smarty_include_tpl_file']); + + + if ($this->_is_compiled($params['smarty_include_tpl_file'], $_smarty_compile_path) + || $this->_compile_resource($params['smarty_include_tpl_file'], $_smarty_compile_path)) + { + include($_smarty_compile_path); + } + + // pop the local vars off the front of the stack + array_shift($this->_config); + + $this->_inclusion_depth--; + + if ($this->debugging) { + // capture time for debugging info + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $this->_smarty_debug_info[$included_tpls_idx]['exec_time'] = smarty_core_get_microtime($_params, $this) - $debug_start_time; + } + + if ($this->caching) { + $this->_cache_info['template'][$params['smarty_include_tpl_file']] = true; + } + } + + + /** + * get or set an array of cached attributes for function that is + * not cacheable + * @return array + */ + function &_smarty_cache_attrs($cache_serial, $count) { + $_cache_attrs =& $this->_cache_info['cache_attrs'][$cache_serial][$count]; + + if ($this->_cache_including) { + /* return next set of cache_attrs */ + $_return = current($_cache_attrs); + next($_cache_attrs); + return $_return; + + } else { + /* add a reference to a new set of cache_attrs */ + $_cache_attrs[] = array(); + return $_cache_attrs[count($_cache_attrs)-1]; + + } + + } + + + /** + * wrapper for include() retaining $this + * @return mixed + */ + function _include($filename, $once=false, $params=null) + { + if ($once) { + return include_once($filename); + } else { + return include($filename); + } + } + + + /** + * wrapper for eval() retaining $this + * @return mixed + */ + function _eval($code, $params=null) + { + return eval($code); + } + /**#@-*/ + +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/Smarty_Compiler.class.php b/trunk/docs/include/smarty/Smarty_Compiler.class.php new file mode 100644 index 0000000..2b13a6d --- /dev/null +++ b/trunk/docs/include/smarty/Smarty_Compiler.class.php @@ -0,0 +1,2320 @@ + + * @author Andrei Zmievski + * @version 2.6.16 + * @copyright 2001-2005 New Digital Group, Inc. + * @package Smarty + */ + +/* $Id: Smarty_Compiler.class.php,v 1.386 2006/11/30 17:01:28 mohrt Exp $ */ + +/** + * Template compiling class + * @package Smarty + */ +class Smarty_Compiler extends Smarty { + + // internal vars + /**#@+ + * @access private + */ + var $_folded_blocks = array(); // keeps folded template blocks + var $_current_file = null; // the current template being compiled + var $_current_line_no = 1; // line number for error messages + var $_capture_stack = array(); // keeps track of nested capture buffers + var $_plugin_info = array(); // keeps track of plugins to load + var $_init_smarty_vars = false; + var $_permitted_tokens = array('true','false','yes','no','on','off','null'); + var $_db_qstr_regexp = null; // regexps are setup in the constructor + var $_si_qstr_regexp = null; + var $_qstr_regexp = null; + var $_func_regexp = null; + var $_reg_obj_regexp = null; + var $_var_bracket_regexp = null; + var $_num_const_regexp = null; + var $_dvar_guts_regexp = null; + var $_dvar_regexp = null; + var $_cvar_regexp = null; + var $_svar_regexp = null; + var $_avar_regexp = null; + var $_mod_regexp = null; + var $_var_regexp = null; + var $_parenth_param_regexp = null; + var $_func_call_regexp = null; + var $_obj_ext_regexp = null; + var $_obj_start_regexp = null; + var $_obj_params_regexp = null; + var $_obj_call_regexp = null; + var $_cacheable_state = 0; + var $_cache_attrs_count = 0; + var $_nocache_count = 0; + var $_cache_serial = null; + var $_cache_include = null; + + var $_strip_depth = 0; + var $_additional_newline = "\n"; + + /**#@-*/ + /** + * The class constructor. + */ + function Smarty_Compiler() + { + // matches double quoted strings: + // "foobar" + // "foo\"bar" + $this->_db_qstr_regexp = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + + // matches single quoted strings: + // 'foobar' + // 'foo\'bar' + $this->_si_qstr_regexp = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\''; + + // matches single or double quoted strings + $this->_qstr_regexp = '(?:' . $this->_db_qstr_regexp . '|' . $this->_si_qstr_regexp . ')'; + + // matches bracket portion of vars + // [0] + // [foo] + // [$bar] + $this->_var_bracket_regexp = '\[\$?[\w\.]+\]'; + + // matches numerical constants + // 30 + // -12 + // 13.22 + $this->_num_const_regexp = '(?:\-?\d+(?:\.\d+)?)'; + + // matches $ vars (not objects): + // $foo + // $foo.bar + // $foo.bar.foobar + // $foo[0] + // $foo[$bar] + // $foo[5][blah] + // $foo[5].bar[$foobar][4] + $this->_dvar_math_regexp = '(?:[\+\*\/\%]|(?:-(?!>)))'; + $this->_dvar_math_var_regexp = '[\$\w\.\+\-\*\/\%\d\>\[\]]'; + $this->_dvar_guts_regexp = '\w+(?:' . $this->_var_bracket_regexp + . ')*(?:\.\$?\w+(?:' . $this->_var_bracket_regexp . ')*)*(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?'; + $this->_dvar_regexp = '\$' . $this->_dvar_guts_regexp; + + // matches config vars: + // #foo# + // #foobar123_foo# + $this->_cvar_regexp = '\#\w+\#'; + + // matches section vars: + // %foo.bar% + $this->_svar_regexp = '\%\w+\.\w+\%'; + + // matches all valid variables (no quotes, no modifiers) + $this->_avar_regexp = '(?:' . $this->_dvar_regexp . '|' + . $this->_cvar_regexp . '|' . $this->_svar_regexp . ')'; + + // matches valid variable syntax: + // $foo + // $foo + // #foo# + // #foo# + // "text" + // "text" + $this->_var_regexp = '(?:' . $this->_avar_regexp . '|' . $this->_qstr_regexp . ')'; + + // matches valid object call (one level of object nesting allowed in parameters): + // $foo->bar + // $foo->bar() + // $foo->bar("text") + // $foo->bar($foo, $bar, "text") + // $foo->bar($foo, "foo") + // $foo->bar->foo() + // $foo->bar->foo->bar() + // $foo->bar($foo->bar) + // $foo->bar($foo->bar()) + // $foo->bar($foo->bar($blah,$foo,44,"foo",$foo[0].bar)) + $this->_obj_ext_regexp = '\->(?:\$?' . $this->_dvar_guts_regexp . ')'; + $this->_obj_restricted_param_regexp = '(?:' + . '(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')(?:' . $this->_obj_ext_regexp . '(?:\((?:(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . ')' + . '(?:\s*,\s*(?:' . $this->_var_regexp . '|' . $this->_num_const_regexp . '))*)?\))?)*)'; + $this->_obj_single_param_regexp = '(?:\w+|' . $this->_obj_restricted_param_regexp . '(?:\s*,\s*(?:(?:\w+|' + . $this->_var_regexp . $this->_obj_restricted_param_regexp . ')))*)'; + $this->_obj_params_regexp = '\((?:' . $this->_obj_single_param_regexp + . '(?:\s*,\s*' . $this->_obj_single_param_regexp . ')*)?\)'; + $this->_obj_start_regexp = '(?:' . $this->_dvar_regexp . '(?:' . $this->_obj_ext_regexp . ')+)'; + $this->_obj_call_regexp = '(?:' . $this->_obj_start_regexp . '(?:' . $this->_obj_params_regexp . ')?(?:' . $this->_dvar_math_regexp . '(?:' . $this->_num_const_regexp . '|' . $this->_dvar_math_var_regexp . ')*)?)'; + + // matches valid modifier syntax: + // |foo + // |@foo + // |foo:"bar" + // |foo:$bar + // |foo:"bar":$foobar + // |foo|bar + // |foo:$foo->bar + $this->_mod_regexp = '(?:\|@?\w+(?::(?:\w+|' . $this->_num_const_regexp . '|' + . $this->_obj_call_regexp . '|' . $this->_avar_regexp . '|' . $this->_qstr_regexp .'))*)'; + + // matches valid function name: + // foo123 + // _foo_bar + $this->_func_regexp = '[a-zA-Z_]\w*'; + + // matches valid registered object: + // foo->bar + $this->_reg_obj_regexp = '[a-zA-Z_]\w*->[a-zA-Z_]\w*'; + + // matches valid parameter values: + // true + // $foo + // $foo|bar + // #foo# + // #foo#|bar + // "text" + // "text"|bar + // $foo->bar + $this->_param_regexp = '(?:\s*(?:' . $this->_obj_call_regexp . '|' + . $this->_var_regexp . '|' . $this->_num_const_regexp . '|\w+)(?>' . $this->_mod_regexp . '*)\s*)'; + + // matches valid parenthesised function parameters: + // + // "text" + // $foo, $bar, "text" + // $foo|bar, "foo"|bar, $foo->bar($foo)|bar + $this->_parenth_param_regexp = '(?:\((?:\w+|' + . $this->_param_regexp . '(?:\s*,\s*(?:(?:\w+|' + . $this->_param_regexp . ')))*)?\))'; + + // matches valid function call: + // foo() + // foo_bar($foo) + // _foo_bar($foo,"bar") + // foo123($foo,$foo->bar(),"foo") + $this->_func_call_regexp = '(?:' . $this->_func_regexp . '\s*(?:' + . $this->_parenth_param_regexp . '))'; + } + + /** + * compile a resource + * + * sets $compiled_content to the compiled source + * @param string $resource_name + * @param string $source_content + * @param string $compiled_content + * @return true + */ + function _compile_file($resource_name, $source_content, &$compiled_content) + { + + if ($this->security) { + // do not allow php syntax to be executed unless specified + if ($this->php_handling == SMARTY_PHP_ALLOW && + !$this->security_settings['PHP_HANDLING']) { + $this->php_handling = SMARTY_PHP_PASSTHRU; + } + } + + $this->_load_filters(); + + $this->_current_file = $resource_name; + $this->_current_line_no = 1; + $ldq = preg_quote($this->left_delimiter, '~'); + $rdq = preg_quote($this->right_delimiter, '~'); + + /* un-hide hidden xml open tags */ + $source_content = preg_replace("~<({$ldq}(.*?){$rdq})[?]~s", '< \\1', $source_content); + + // run template source through prefilter functions + if (count($this->_plugins['prefilter']) > 0) { + foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) { + if ($prefilter === false) continue; + if ($prefilter[3] || is_callable($prefilter[0])) { + $source_content = call_user_func_array($prefilter[0], + array($source_content, &$this)); + $this->_plugins['prefilter'][$filter_name][3] = true; + } else { + $this->_trigger_fatal_error("[plugin] prefilter '$filter_name' is not implemented"); + } + } + } + + /* fetch all special blocks */ + $search = "~{$ldq}\*(.*?)\*{$rdq}|{$ldq}\s*literal\s*{$rdq}(.*?){$ldq}\s*/literal\s*{$rdq}|{$ldq}\s*php\s*{$rdq}(.*?){$ldq}\s*/php\s*{$rdq}~s"; + + preg_match_all($search, $source_content, $match, PREG_SET_ORDER); + $this->_folded_blocks = $match; + reset($this->_folded_blocks); + + /* replace special blocks by "{php}" */ + $source_content = preg_replace($search.'e', "'" + . $this->_quote_replace($this->left_delimiter) . 'php' + . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'" + . $this->_quote_replace($this->right_delimiter) + . "'" + , $source_content); + + /* Gather all template tags. */ + preg_match_all("~{$ldq}\s*(.*?)\s*{$rdq}~s", $source_content, $_match); + $template_tags = $_match[1]; + /* Split content by template tags to obtain non-template content. */ + $text_blocks = preg_split("~{$ldq}.*?{$rdq}~s", $source_content); + + /* loop through text blocks */ + for ($curr_tb = 0, $for_max = count($text_blocks); $curr_tb < $for_max; $curr_tb++) { + /* match anything resembling php tags */ + if (preg_match_all('~(<\?(?:\w+|=)?|\?>|language\s*=\s*[\"\']?php[\"\']?)~is', $text_blocks[$curr_tb], $sp_match)) { + /* replace tags with placeholders to prevent recursive replacements */ + $sp_match[1] = array_unique($sp_match[1]); + usort($sp_match[1], '_smarty_sort_length'); + for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) { + $text_blocks[$curr_tb] = str_replace($sp_match[1][$curr_sp],'%%%SMARTYSP'.$curr_sp.'%%%',$text_blocks[$curr_tb]); + } + /* process each one */ + for ($curr_sp = 0, $for_max2 = count($sp_match[1]); $curr_sp < $for_max2; $curr_sp++) { + if ($this->php_handling == SMARTY_PHP_PASSTHRU) { + /* echo php contents */ + $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', ''."\n", $text_blocks[$curr_tb]); + } else if ($this->php_handling == SMARTY_PHP_QUOTE) { + /* quote php tags */ + $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', htmlspecialchars($sp_match[1][$curr_sp]), $text_blocks[$curr_tb]); + } else if ($this->php_handling == SMARTY_PHP_REMOVE) { + /* remove php tags */ + $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', '', $text_blocks[$curr_tb]); + } else { + /* SMARTY_PHP_ALLOW, but echo non php starting tags */ + $sp_match[1][$curr_sp] = preg_replace('~(<\?(?!php|=|$))~i', ''."\n", $sp_match[1][$curr_sp]); + $text_blocks[$curr_tb] = str_replace('%%%SMARTYSP'.$curr_sp.'%%%', $sp_match[1][$curr_sp], $text_blocks[$curr_tb]); + } + } + } + } + + /* Compile the template tags into PHP code. */ + $compiled_tags = array(); + for ($i = 0, $for_max = count($template_tags); $i < $for_max; $i++) { + $this->_current_line_no += substr_count($text_blocks[$i], "\n"); + $compiled_tags[] = $this->_compile_tag($template_tags[$i]); + $this->_current_line_no += substr_count($template_tags[$i], "\n"); + } + if (count($this->_tag_stack)>0) { + list($_open_tag, $_line_no) = end($this->_tag_stack); + $this->_syntax_error("unclosed tag \{$_open_tag} (opened line $_line_no).", E_USER_ERROR, __FILE__, __LINE__); + return; + } + + /* Reformat $text_blocks between 'strip' and '/strip' tags, + removing spaces, tabs and newlines. */ + $strip = false; + for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) { + if ($compiled_tags[$i] == '{strip}') { + $compiled_tags[$i] = ''; + $strip = true; + /* remove leading whitespaces */ + $text_blocks[$i + 1] = ltrim($text_blocks[$i + 1]); + } + if ($strip) { + /* strip all $text_blocks before the next '/strip' */ + for ($j = $i + 1; $j < $for_max; $j++) { + /* remove leading and trailing whitespaces of each line */ + $text_blocks[$j] = preg_replace('![\t ]*[\r\n]+[\t ]*!', '', $text_blocks[$j]); + if ($compiled_tags[$j] == '{/strip}') { + /* remove trailing whitespaces from the last text_block */ + $text_blocks[$j] = rtrim($text_blocks[$j]); + } + $text_blocks[$j] = ""\'", "\\"=>"\\\\")) . "'; ?>"; + if ($compiled_tags[$j] == '{/strip}') { + $compiled_tags[$j] = "\n"; /* slurped by php, but necessary + if a newline is following the closing strip-tag */ + $strip = false; + $i = $j; + break; + } + } + } + } + $compiled_content = ''; + + /* Interleave the compiled contents and text blocks to get the final result. */ + for ($i = 0, $for_max = count($compiled_tags); $i < $for_max; $i++) { + if ($compiled_tags[$i] == '') { + // tag result empty, remove first newline from following text block + $text_blocks[$i+1] = preg_replace('~^(\r\n|\r|\n)~', '', $text_blocks[$i+1]); + } + $compiled_content .= $text_blocks[$i].$compiled_tags[$i]; + } + $compiled_content .= $text_blocks[$i]; + + // remove \n from the end of the file, if any + if (strlen($compiled_content) && (substr($compiled_content, -1) == "\n") ) { + $compiled_content = substr($compiled_content, 0, -1); + } + + if (!empty($this->_cache_serial)) { + $compiled_content = "_cache_serials['".$this->_cache_include."'] = '".$this->_cache_serial."'; ?>" . $compiled_content; + } + + // remove unnecessary close/open tags + $compiled_content = preg_replace('~\?>\n?<\?php~', '', $compiled_content); + + // run compiled template through postfilter functions + if (count($this->_plugins['postfilter']) > 0) { + foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) { + if ($postfilter === false) continue; + if ($postfilter[3] || is_callable($postfilter[0])) { + $compiled_content = call_user_func_array($postfilter[0], + array($compiled_content, &$this)); + $this->_plugins['postfilter'][$filter_name][3] = true; + } else { + $this->_trigger_fatal_error("Smarty plugin error: postfilter '$filter_name' is not implemented"); + } + } + } + + // put header at the top of the compiled template + $template_header = "_version.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n"; + $template_header .= " compiled from ".strtr(urlencode($resource_name), array('%2F'=>'/', '%3A'=>':'))." */ ?>\n"; + + /* Emit code to load needed plugins. */ + $this->_plugins_code = ''; + if (count($this->_plugin_info)) { + $_plugins_params = "array('plugins' => array("; + foreach ($this->_plugin_info as $plugin_type => $plugins) { + foreach ($plugins as $plugin_name => $plugin_info) { + $_plugins_params .= "array('$plugin_type', '$plugin_name', '" . strtr($plugin_info[0], array("'" => "\\'", "\\" => "\\\\")) . "', $plugin_info[1], "; + $_plugins_params .= $plugin_info[2] ? 'true),' : 'false),'; + } + } + $_plugins_params .= '))'; + $plugins_code = "\n"; + $template_header .= $plugins_code; + $this->_plugin_info = array(); + $this->_plugins_code = $plugins_code; + } + + if ($this->_init_smarty_vars) { + $template_header .= "\n"; + $this->_init_smarty_vars = false; + } + + $compiled_content = $template_header . $compiled_content; + return true; + } + + /** + * Compile a template tag + * + * @param string $template_tag + * @return string + */ + function _compile_tag($template_tag) + { + /* Matched comment. */ + if (substr($template_tag, 0, 1) == '*' && substr($template_tag, -1) == '*') + return ''; + + /* Split tag into two three parts: command, command modifiers and the arguments. */ + if(! preg_match('~^(?:(' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp + . '|\/?' . $this->_reg_obj_regexp . '|\/?' . $this->_func_regexp . ')(' . $this->_mod_regexp . '*)) + (?:\s+(.*))?$ + ~xs', $template_tag, $match)) { + $this->_syntax_error("unrecognized tag: $template_tag", E_USER_ERROR, __FILE__, __LINE__); + } + + $tag_command = $match[1]; + $tag_modifier = isset($match[2]) ? $match[2] : null; + $tag_args = isset($match[3]) ? $match[3] : null; + + if (preg_match('~^' . $this->_num_const_regexp . '|' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '$~', $tag_command)) { + /* tag name is a variable or object */ + $_return = $this->_parse_var_props($tag_command . $tag_modifier); + return "" . $this->_additional_newline; + } + + /* If the tag name is a registered object, we process it. */ + if (preg_match('~^\/?' . $this->_reg_obj_regexp . '$~', $tag_command)) { + return $this->_compile_registered_object_tag($tag_command, $this->_parse_attrs($tag_args), $tag_modifier); + } + + switch ($tag_command) { + case 'include': + return $this->_compile_include_tag($tag_args); + + case 'include_php': + return $this->_compile_include_php_tag($tag_args); + + case 'if': + $this->_push_tag('if'); + return $this->_compile_if_tag($tag_args); + + case 'else': + list($_open_tag) = end($this->_tag_stack); + if ($_open_tag != 'if' && $_open_tag != 'elseif') + $this->_syntax_error('unexpected {else}', E_USER_ERROR, __FILE__, __LINE__); + else + $this->_push_tag('else'); + return ''; + + case 'elseif': + list($_open_tag) = end($this->_tag_stack); + if ($_open_tag != 'if' && $_open_tag != 'elseif') + $this->_syntax_error('unexpected {elseif}', E_USER_ERROR, __FILE__, __LINE__); + if ($_open_tag == 'if') + $this->_push_tag('elseif'); + return $this->_compile_if_tag($tag_args, true); + + case '/if': + $this->_pop_tag('if'); + return ''; + + case 'capture': + return $this->_compile_capture_tag(true, $tag_args); + + case '/capture': + return $this->_compile_capture_tag(false); + + case 'ldelim': + return $this->left_delimiter; + + case 'rdelim': + return $this->right_delimiter; + + case 'section': + $this->_push_tag('section'); + return $this->_compile_section_start($tag_args); + + case 'sectionelse': + $this->_push_tag('sectionelse'); + return ""; + break; + + case '/section': + $_open_tag = $this->_pop_tag('section'); + if ($_open_tag == 'sectionelse') + return ""; + else + return ""; + + case 'foreach': + $this->_push_tag('foreach'); + return $this->_compile_foreach_start($tag_args); + break; + + case 'foreachelse': + $this->_push_tag('foreachelse'); + return ""; + + case '/foreach': + $_open_tag = $this->_pop_tag('foreach'); + if ($_open_tag == 'foreachelse') + return ""; + else + return ""; + break; + + case 'strip': + case '/strip': + if (substr($tag_command, 0, 1)=='/') { + $this->_pop_tag('strip'); + if (--$this->_strip_depth==0) { /* outermost closing {/strip} */ + $this->_additional_newline = "\n"; + return '{' . $tag_command . '}'; + } + } else { + $this->_push_tag('strip'); + if ($this->_strip_depth++==0) { /* outermost opening {strip} */ + $this->_additional_newline = ""; + return '{' . $tag_command . '}'; + } + } + return ''; + + case 'php': + /* handle folded tags replaced by {php} */ + list(, $block) = each($this->_folded_blocks); + $this->_current_line_no += substr_count($block[0], "\n"); + /* the number of matched elements in the regexp in _compile_file() + determins the type of folded tag that was found */ + switch (count($block)) { + case 2: /* comment */ + return ''; + + case 3: /* literal */ + return ""\'", "\\"=>"\\\\")) . "'; ?>" . $this->_additional_newline; + + case 4: /* php */ + if ($this->security && !$this->security_settings['PHP_TAGS']) { + $this->_syntax_error("(secure mode) php tags not permitted", E_USER_WARNING, __FILE__, __LINE__); + return; + } + return ''; + } + break; + + case 'insert': + return $this->_compile_insert_tag($tag_args); + + default: + if ($this->_compile_compiler_tag($tag_command, $tag_args, $output)) { + return $output; + } else if ($this->_compile_block_tag($tag_command, $tag_args, $tag_modifier, $output)) { + return $output; + } else if ($this->_compile_custom_tag($tag_command, $tag_args, $tag_modifier, $output)) { + return $output; + } else { + $this->_syntax_error("unrecognized tag '$tag_command'", E_USER_ERROR, __FILE__, __LINE__); + } + + } + } + + + /** + * compile the custom compiler tag + * + * sets $output to the compiled custom compiler tag + * @param string $tag_command + * @param string $tag_args + * @param string $output + * @return boolean + */ + function _compile_compiler_tag($tag_command, $tag_args, &$output) + { + $found = false; + $have_function = true; + + /* + * First we check if the compiler function has already been registered + * or loaded from a plugin file. + */ + if (isset($this->_plugins['compiler'][$tag_command])) { + $found = true; + $plugin_func = $this->_plugins['compiler'][$tag_command][0]; + if (!is_callable($plugin_func)) { + $message = "compiler function '$tag_command' is not implemented"; + $have_function = false; + } + } + /* + * Otherwise we need to load plugin file and look for the function + * inside it. + */ + else if ($plugin_file = $this->_get_plugin_filepath('compiler', $tag_command)) { + $found = true; + + include_once $plugin_file; + + $plugin_func = 'smarty_compiler_' . $tag_command; + if (!is_callable($plugin_func)) { + $message = "plugin function $plugin_func() not found in $plugin_file\n"; + $have_function = false; + } else { + $this->_plugins['compiler'][$tag_command] = array($plugin_func, null, null, null, true); + } + } + + /* + * True return value means that we either found a plugin or a + * dynamically registered function. False means that we didn't and the + * compiler should now emit code to load custom function plugin for this + * tag. + */ + if ($found) { + if ($have_function) { + $output = call_user_func_array($plugin_func, array($tag_args, &$this)); + if($output != '') { + $output = '_push_cacheable_state('compiler', $tag_command) + . $output + . $this->_pop_cacheable_state('compiler', $tag_command) . ' ?>'; + } + } else { + $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__); + } + return true; + } else { + return false; + } + } + + + /** + * compile block function tag + * + * sets $output to compiled block function tag + * @param string $tag_command + * @param string $tag_args + * @param string $tag_modifier + * @param string $output + * @return boolean + */ + function _compile_block_tag($tag_command, $tag_args, $tag_modifier, &$output) + { + if (substr($tag_command, 0, 1) == '/') { + $start_tag = false; + $tag_command = substr($tag_command, 1); + } else + $start_tag = true; + + $found = false; + $have_function = true; + + /* + * First we check if the block function has already been registered + * or loaded from a plugin file. + */ + if (isset($this->_plugins['block'][$tag_command])) { + $found = true; + $plugin_func = $this->_plugins['block'][$tag_command][0]; + if (!is_callable($plugin_func)) { + $message = "block function '$tag_command' is not implemented"; + $have_function = false; + } + } + /* + * Otherwise we need to load plugin file and look for the function + * inside it. + */ + else if ($plugin_file = $this->_get_plugin_filepath('block', $tag_command)) { + $found = true; + + include_once $plugin_file; + + $plugin_func = 'smarty_block_' . $tag_command; + if (!function_exists($plugin_func)) { + $message = "plugin function $plugin_func() not found in $plugin_file\n"; + $have_function = false; + } else { + $this->_plugins['block'][$tag_command] = array($plugin_func, null, null, null, true); + + } + } + + if (!$found) { + return false; + } else if (!$have_function) { + $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__); + return true; + } + + /* + * Even though we've located the plugin function, compilation + * happens only once, so the plugin will still need to be loaded + * at runtime for future requests. + */ + $this->_add_plugin('block', $tag_command); + + if ($start_tag) + $this->_push_tag($tag_command); + else + $this->_pop_tag($tag_command); + + if ($start_tag) { + $output = '_push_cacheable_state('block', $tag_command); + $attrs = $this->_parse_attrs($tag_args); + $_cache_attrs=''; + $arg_list = $this->_compile_arg_list('block', $tag_command, $attrs, $_cache_attrs); + $output .= "$_cache_attrs\$this->_tag_stack[] = array('$tag_command', array(".implode(',', $arg_list).')); '; + $output .= '$_block_repeat=true;' . $this->_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], null, $this, $_block_repeat);'; + $output .= 'while ($_block_repeat) { ob_start(); ?>'; + } else { + $output = '_compile_plugin_call('block', $tag_command).'($this->_tag_stack[count($this->_tag_stack)-1][1], $_block_content, $this, $_block_repeat)'; + if ($tag_modifier != '') { + $this->_parse_modifiers($_out_tag_text, $tag_modifier); + } + $output .= '$_block_repeat=false;echo ' . $_out_tag_text . '; } '; + $output .= " array_pop(\$this->_tag_stack); " . $this->_pop_cacheable_state('block', $tag_command) . '?>'; + } + + return true; + } + + + /** + * compile custom function tag + * + * @param string $tag_command + * @param string $tag_args + * @param string $tag_modifier + * @return string + */ + function _compile_custom_tag($tag_command, $tag_args, $tag_modifier, &$output) + { + $found = false; + $have_function = true; + + /* + * First we check if the custom function has already been registered + * or loaded from a plugin file. + */ + if (isset($this->_plugins['function'][$tag_command])) { + $found = true; + $plugin_func = $this->_plugins['function'][$tag_command][0]; + if (!is_callable($plugin_func)) { + $message = "custom function '$tag_command' is not implemented"; + $have_function = false; + } + } + /* + * Otherwise we need to load plugin file and look for the function + * inside it. + */ + else if ($plugin_file = $this->_get_plugin_filepath('function', $tag_command)) { + $found = true; + + include_once $plugin_file; + + $plugin_func = 'smarty_function_' . $tag_command; + if (!function_exists($plugin_func)) { + $message = "plugin function $plugin_func() not found in $plugin_file\n"; + $have_function = false; + } else { + $this->_plugins['function'][$tag_command] = array($plugin_func, null, null, null, true); + + } + } + + if (!$found) { + return false; + } else if (!$have_function) { + $this->_syntax_error($message, E_USER_WARNING, __FILE__, __LINE__); + return true; + } + + /* declare plugin to be loaded on display of the template that + we compile right now */ + $this->_add_plugin('function', $tag_command); + + $_cacheable_state = $this->_push_cacheable_state('function', $tag_command); + $attrs = $this->_parse_attrs($tag_args); + $_cache_attrs = ''; + $arg_list = $this->_compile_arg_list('function', $tag_command, $attrs, $_cache_attrs); + + $output = $this->_compile_plugin_call('function', $tag_command).'(array('.implode(',', $arg_list)."), \$this)"; + if($tag_modifier != '') { + $this->_parse_modifiers($output, $tag_modifier); + } + + if($output != '') { + $output = '_pop_cacheable_state('function', $tag_command) . "?>" . $this->_additional_newline; + } + + return true; + } + + /** + * compile a registered object tag + * + * @param string $tag_command + * @param array $attrs + * @param string $tag_modifier + * @return string + */ + function _compile_registered_object_tag($tag_command, $attrs, $tag_modifier) + { + if (substr($tag_command, 0, 1) == '/') { + $start_tag = false; + $tag_command = substr($tag_command, 1); + } else { + $start_tag = true; + } + + list($object, $obj_comp) = explode('->', $tag_command); + + $arg_list = array(); + if(count($attrs)) { + $_assign_var = false; + foreach ($attrs as $arg_name => $arg_value) { + if($arg_name == 'assign') { + $_assign_var = $arg_value; + unset($attrs['assign']); + continue; + } + if (is_bool($arg_value)) + $arg_value = $arg_value ? 'true' : 'false'; + $arg_list[] = "'$arg_name' => $arg_value"; + } + } + + if($this->_reg_objects[$object][2]) { + // smarty object argument format + $args = "array(".implode(',', (array)$arg_list)."), \$this"; + } else { + // traditional argument format + $args = implode(',', array_values($attrs)); + if (empty($args)) { + $args = 'null'; + } + } + + $prefix = ''; + $postfix = ''; + $newline = ''; + if(!is_object($this->_reg_objects[$object][0])) { + $this->_trigger_fatal_error("registered '$object' is not an object" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__); + } elseif(!empty($this->_reg_objects[$object][1]) && !in_array($obj_comp, $this->_reg_objects[$object][1])) { + $this->_trigger_fatal_error("'$obj_comp' is not a registered component of object '$object'", $this->_current_file, $this->_current_line_no, __FILE__, __LINE__); + } elseif(method_exists($this->_reg_objects[$object][0], $obj_comp)) { + // method + if(in_array($obj_comp, $this->_reg_objects[$object][3])) { + // block method + if ($start_tag) { + $prefix = "\$this->_tag_stack[] = array('$obj_comp', $args); "; + $prefix .= "\$_block_repeat=true; \$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], null, \$this, \$_block_repeat); "; + $prefix .= "while (\$_block_repeat) { ob_start();"; + $return = null; + $postfix = ''; + } else { + $prefix = "\$_obj_block_content = ob_get_contents(); ob_end_clean(); \$_block_repeat=false;"; + $return = "\$this->_reg_objects['$object'][0]->$obj_comp(\$this->_tag_stack[count(\$this->_tag_stack)-1][1], \$_obj_block_content, \$this, \$_block_repeat)"; + $postfix = "} array_pop(\$this->_tag_stack);"; + } + } else { + // non-block method + $return = "\$this->_reg_objects['$object'][0]->$obj_comp($args)"; + } + } else { + // property + $return = "\$this->_reg_objects['$object'][0]->$obj_comp"; + } + + if($return != null) { + if($tag_modifier != '') { + $this->_parse_modifiers($return, $tag_modifier); + } + + if(!empty($_assign_var)) { + $output = "\$this->assign('" . $this->_dequote($_assign_var) ."', $return);"; + } else { + $output = 'echo ' . $return . ';'; + $newline = $this->_additional_newline; + } + } else { + $output = ''; + } + + return '" . $newline; + } + + /** + * Compile {insert ...} tag + * + * @param string $tag_args + * @return string + */ + function _compile_insert_tag($tag_args) + { + $attrs = $this->_parse_attrs($tag_args); + $name = $this->_dequote($attrs['name']); + + if (empty($name)) { + return $this->_syntax_error("missing insert name", E_USER_ERROR, __FILE__, __LINE__); + } + + if (!preg_match('~^\w+$~', $name)) { + return $this->_syntax_error("'insert: 'name' must be an insert function name", E_USER_ERROR, __FILE__, __LINE__); + } + + if (!empty($attrs['script'])) { + $delayed_loading = true; + } else { + $delayed_loading = false; + } + + foreach ($attrs as $arg_name => $arg_value) { + if (is_bool($arg_value)) + $arg_value = $arg_value ? 'true' : 'false'; + $arg_list[] = "'$arg_name' => $arg_value"; + } + + $this->_add_plugin('insert', $name, $delayed_loading); + + $_params = "array('args' => array(".implode(', ', (array)$arg_list)."))"; + + return "" . $this->_additional_newline; + } + + /** + * Compile {include ...} tag + * + * @param string $tag_args + * @return string + */ + function _compile_include_tag($tag_args) + { + $attrs = $this->_parse_attrs($tag_args); + $arg_list = array(); + + if (empty($attrs['file'])) { + $this->_syntax_error("missing 'file' attribute in include tag", E_USER_ERROR, __FILE__, __LINE__); + } + + foreach ($attrs as $arg_name => $arg_value) { + if ($arg_name == 'file') { + $include_file = $arg_value; + continue; + } else if ($arg_name == 'assign') { + $assign_var = $arg_value; + continue; + } + if (is_bool($arg_value)) + $arg_value = $arg_value ? 'true' : 'false'; + $arg_list[] = "'$arg_name' => $arg_value"; + } + + $output = '_tpl_vars;\n"; + + + $_params = "array('smarty_include_tpl_file' => " . $include_file . ", 'smarty_include_vars' => array(".implode(',', (array)$arg_list)."))"; + $output .= "\$this->_smarty_include($_params);\n" . + "\$this->_tpl_vars = \$_smarty_tpl_vars;\n" . + "unset(\$_smarty_tpl_vars);\n"; + + if (isset($assign_var)) { + $output .= "\$this->assign(" . $assign_var . ", ob_get_contents()); ob_end_clean();\n"; + } + + $output .= ' ?>'; + + return $output; + + } + + /** + * Compile {include ...} tag + * + * @param string $tag_args + * @return string + */ + function _compile_include_php_tag($tag_args) + { + $attrs = $this->_parse_attrs($tag_args); + + if (empty($attrs['file'])) { + $this->_syntax_error("missing 'file' attribute in include_php tag", E_USER_ERROR, __FILE__, __LINE__); + } + + $assign_var = (empty($attrs['assign'])) ? '' : $this->_dequote($attrs['assign']); + $once_var = (empty($attrs['once']) || $attrs['once']=='false') ? 'false' : 'true'; + + $arg_list = array(); + foreach($attrs as $arg_name => $arg_value) { + if($arg_name != 'file' AND $arg_name != 'once' AND $arg_name != 'assign') { + if(is_bool($arg_value)) + $arg_value = $arg_value ? 'true' : 'false'; + $arg_list[] = "'$arg_name' => $arg_value"; + } + } + + $_params = "array('smarty_file' => " . $attrs['file'] . ", 'smarty_assign' => '$assign_var', 'smarty_once' => $once_var, 'smarty_include_vars' => array(".implode(',', $arg_list)."))"; + + return "" . $this->_additional_newline; + } + + + /** + * Compile {section ...} tag + * + * @param string $tag_args + * @return string + */ + function _compile_section_start($tag_args) + { + $attrs = $this->_parse_attrs($tag_args); + $arg_list = array(); + + $output = '_syntax_error("missing section name", E_USER_ERROR, __FILE__, __LINE__); + } + + $output .= "unset(\$this->_sections[$section_name]);\n"; + $section_props = "\$this->_sections[$section_name]"; + + foreach ($attrs as $attr_name => $attr_value) { + switch ($attr_name) { + case 'loop': + $output .= "{$section_props}['loop'] = is_array(\$_loop=$attr_value) ? count(\$_loop) : max(0, (int)\$_loop); unset(\$_loop);\n"; + break; + + case 'show': + if (is_bool($attr_value)) + $show_attr_value = $attr_value ? 'true' : 'false'; + else + $show_attr_value = "(bool)$attr_value"; + $output .= "{$section_props}['show'] = $show_attr_value;\n"; + break; + + case 'name': + $output .= "{$section_props}['$attr_name'] = $attr_value;\n"; + break; + + case 'max': + case 'start': + $output .= "{$section_props}['$attr_name'] = (int)$attr_value;\n"; + break; + + case 'step': + $output .= "{$section_props}['$attr_name'] = ((int)$attr_value) == 0 ? 1 : (int)$attr_value;\n"; + break; + + default: + $this->_syntax_error("unknown section attribute - '$attr_name'", E_USER_ERROR, __FILE__, __LINE__); + break; + } + } + + if (!isset($attrs['show'])) + $output .= "{$section_props}['show'] = true;\n"; + + if (!isset($attrs['loop'])) + $output .= "{$section_props}['loop'] = 1;\n"; + + if (!isset($attrs['max'])) + $output .= "{$section_props}['max'] = {$section_props}['loop'];\n"; + else + $output .= "if ({$section_props}['max'] < 0)\n" . + " {$section_props}['max'] = {$section_props}['loop'];\n"; + + if (!isset($attrs['step'])) + $output .= "{$section_props}['step'] = 1;\n"; + + if (!isset($attrs['start'])) + $output .= "{$section_props}['start'] = {$section_props}['step'] > 0 ? 0 : {$section_props}['loop']-1;\n"; + else { + $output .= "if ({$section_props}['start'] < 0)\n" . + " {$section_props}['start'] = max({$section_props}['step'] > 0 ? 0 : -1, {$section_props}['loop'] + {$section_props}['start']);\n" . + "else\n" . + " {$section_props}['start'] = min({$section_props}['start'], {$section_props}['step'] > 0 ? {$section_props}['loop'] : {$section_props}['loop']-1);\n"; + } + + $output .= "if ({$section_props}['show']) {\n"; + if (!isset($attrs['start']) && !isset($attrs['step']) && !isset($attrs['max'])) { + $output .= " {$section_props}['total'] = {$section_props}['loop'];\n"; + } else { + $output .= " {$section_props}['total'] = min(ceil(({$section_props}['step'] > 0 ? {$section_props}['loop'] - {$section_props}['start'] : {$section_props}['start']+1)/abs({$section_props}['step'])), {$section_props}['max']);\n"; + } + $output .= " if ({$section_props}['total'] == 0)\n" . + " {$section_props}['show'] = false;\n" . + "} else\n" . + " {$section_props}['total'] = 0;\n"; + + $output .= "if ({$section_props}['show']):\n"; + $output .= " + for ({$section_props}['index'] = {$section_props}['start'], {$section_props}['iteration'] = 1; + {$section_props}['iteration'] <= {$section_props}['total']; + {$section_props}['index'] += {$section_props}['step'], {$section_props}['iteration']++):\n"; + $output .= "{$section_props}['rownum'] = {$section_props}['iteration'];\n"; + $output .= "{$section_props}['index_prev'] = {$section_props}['index'] - {$section_props}['step'];\n"; + $output .= "{$section_props}['index_next'] = {$section_props}['index'] + {$section_props}['step'];\n"; + $output .= "{$section_props}['first'] = ({$section_props}['iteration'] == 1);\n"; + $output .= "{$section_props}['last'] = ({$section_props}['iteration'] == {$section_props}['total']);\n"; + + $output .= "?>"; + + return $output; + } + + + /** + * Compile {foreach ...} tag. + * + * @param string $tag_args + * @return string + */ + function _compile_foreach_start($tag_args) + { + $attrs = $this->_parse_attrs($tag_args); + $arg_list = array(); + + if (empty($attrs['from'])) { + return $this->_syntax_error("foreach: missing 'from' attribute", E_USER_ERROR, __FILE__, __LINE__); + } + $from = $attrs['from']; + + if (empty($attrs['item'])) { + return $this->_syntax_error("foreach: missing 'item' attribute", E_USER_ERROR, __FILE__, __LINE__); + } + $item = $this->_dequote($attrs['item']); + if (!preg_match('~^\w+$~', $item)) { + return $this->_syntax_error("'foreach: 'item' must be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__); + } + + if (isset($attrs['key'])) { + $key = $this->_dequote($attrs['key']); + if (!preg_match('~^\w+$~', $key)) { + return $this->_syntax_error("foreach: 'key' must to be a variable name (literal string)", E_USER_ERROR, __FILE__, __LINE__); + } + $key_part = "\$this->_tpl_vars['$key'] => "; + } else { + $key = null; + $key_part = ''; + } + + if (isset($attrs['name'])) { + $name = $attrs['name']; + } else { + $name = null; + } + + $output = '_foreach[$name]"; + $output .= "{$foreach_props} = array('total' => count(\$_from), 'iteration' => 0);\n"; + $output .= "if ({$foreach_props}['total'] > 0):\n"; + $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n"; + $output .= " {$foreach_props}['iteration']++;\n"; + } else { + $output .= "if (count(\$_from)):\n"; + $output .= " foreach (\$_from as $key_part\$this->_tpl_vars['$item']):\n"; + } + $output .= '?>'; + + return $output; + } + + + /** + * Compile {capture} .. {/capture} tags + * + * @param boolean $start true if this is the {capture} tag + * @param string $tag_args + * @return string + */ + + function _compile_capture_tag($start, $tag_args = '') + { + $attrs = $this->_parse_attrs($tag_args); + + if ($start) { + if (isset($attrs['name'])) + $buffer = $attrs['name']; + else + $buffer = "'default'"; + + if (isset($attrs['assign'])) + $assign = $attrs['assign']; + else + $assign = null; + $output = ""; + $this->_capture_stack[] = array($buffer, $assign); + } else { + list($buffer, $assign) = array_pop($this->_capture_stack); + $output = "_smarty_vars['capture'][$buffer] = ob_get_contents(); "; + if (isset($assign)) { + $output .= " \$this->assign($assign, ob_get_contents());"; + } + $output .= "ob_end_clean(); ?>"; + } + + return $output; + } + + /** + * Compile {if ...} tag + * + * @param string $tag_args + * @param boolean $elseif if true, uses elseif instead of if + * @return string + */ + function _compile_if_tag($tag_args, $elseif = false) + { + + /* Tokenize args for 'if' tag. */ + preg_match_all('~(?> + ' . $this->_obj_call_regexp . '(?:' . $this->_mod_regexp . '*)? | # valid object call + ' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)? | # var or quoted string + \-?0[xX][0-9a-fA-F]+|\-?\d+(?:\.\d+)?|\.\d+|!==|===|==|!=|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+|\-|\/|\*|\@ | # valid non-word token + \b\w+\b | # valid word token + \S+ # anything else + )~x', $tag_args, $match); + + $tokens = $match[0]; + + if(empty($tokens)) { + $_error_msg = $elseif ? "'elseif'" : "'if'"; + $_error_msg .= ' statement requires arguments'; + $this->_syntax_error($_error_msg, E_USER_ERROR, __FILE__, __LINE__); + } + + + // make sure we have balanced parenthesis + $token_count = array_count_values($tokens); + if(isset($token_count['(']) && $token_count['('] != $token_count[')']) { + $this->_syntax_error("unbalanced parenthesis in if statement", E_USER_ERROR, __FILE__, __LINE__); + } + + $is_arg_stack = array(); + + for ($i = 0; $i < count($tokens); $i++) { + + $token = &$tokens[$i]; + + switch (strtolower($token)) { + case '!': + case '%': + case '!==': + case '==': + case '===': + case '>': + case '<': + case '!=': + case '<>': + case '<<': + case '>>': + case '<=': + case '>=': + case '&&': + case '||': + case '|': + case '^': + case '&': + case '~': + case ')': + case ',': + case '+': + case '-': + case '*': + case '/': + case '@': + break; + + case 'eq': + $token = '=='; + break; + + case 'ne': + case 'neq': + $token = '!='; + break; + + case 'lt': + $token = '<'; + break; + + case 'le': + case 'lte': + $token = '<='; + break; + + case 'gt': + $token = '>'; + break; + + case 'ge': + case 'gte': + $token = '>='; + break; + + case 'and': + $token = '&&'; + break; + + case 'or': + $token = '||'; + break; + + case 'not': + $token = '!'; + break; + + case 'mod': + $token = '%'; + break; + + case '(': + array_push($is_arg_stack, $i); + break; + + case 'is': + /* If last token was a ')', we operate on the parenthesized + expression. The start of the expression is on the stack. + Otherwise, we operate on the last encountered token. */ + if ($tokens[$i-1] == ')') + $is_arg_start = array_pop($is_arg_stack); + else + $is_arg_start = $i-1; + /* Construct the argument for 'is' expression, so it knows + what to operate on. */ + $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start)); + + /* Pass all tokens from next one until the end to the + 'is' expression parsing function. The function will + return modified tokens, where the first one is the result + of the 'is' expression and the rest are the tokens it + didn't touch. */ + $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1)); + + /* Replace the old tokens with the new ones. */ + array_splice($tokens, $is_arg_start, count($tokens), $new_tokens); + + /* Adjust argument start so that it won't change from the + current position for the next iteration. */ + $i = $is_arg_start; + break; + + default: + if(preg_match('~^' . $this->_func_regexp . '$~', $token) ) { + // function call + if($this->security && + !in_array($token, $this->security_settings['IF_FUNCS'])) { + $this->_syntax_error("(secure mode) '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__); + } + } elseif(preg_match('~^' . $this->_var_regexp . '$~', $token) && (strpos('+-*/^%&|', substr($token, -1)) === false) && isset($tokens[$i+1]) && $tokens[$i+1] == '(') { + // variable function call + $this->_syntax_error("variable function call '$token' not allowed in if statement", E_USER_ERROR, __FILE__, __LINE__); + } elseif(preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . '*)$~', $token)) { + // object or variable + $token = $this->_parse_var_props($token); + } elseif(is_numeric($token)) { + // number, skip it + } else { + $this->_syntax_error("unidentified token '$token'", E_USER_ERROR, __FILE__, __LINE__); + } + break; + } + } + + if ($elseif) + return ''; + else + return ''; + } + + + function _compile_arg_list($type, $name, $attrs, &$cache_code) { + $arg_list = array(); + + if (isset($type) && isset($name) + && isset($this->_plugins[$type]) + && isset($this->_plugins[$type][$name]) + && empty($this->_plugins[$type][$name][4]) + && is_array($this->_plugins[$type][$name][5]) + ) { + /* we have a list of parameters that should be cached */ + $_cache_attrs = $this->_plugins[$type][$name][5]; + $_count = $this->_cache_attrs_count++; + $cache_code = "\$_cache_attrs =& \$this->_smarty_cache_attrs('$this->_cache_serial','$_count');"; + + } else { + /* no parameters are cached */ + $_cache_attrs = null; + } + + foreach ($attrs as $arg_name => $arg_value) { + if (is_bool($arg_value)) + $arg_value = $arg_value ? 'true' : 'false'; + if (is_null($arg_value)) + $arg_value = 'null'; + if ($_cache_attrs && in_array($arg_name, $_cache_attrs)) { + $arg_list[] = "'$arg_name' => (\$this->_cache_including) ? \$_cache_attrs['$arg_name'] : (\$_cache_attrs['$arg_name']=$arg_value)"; + } else { + $arg_list[] = "'$arg_name' => $arg_value"; + } + } + return $arg_list; + } + + /** + * Parse is expression + * + * @param string $is_arg + * @param array $tokens + * @return array + */ + function _parse_is_expr($is_arg, $tokens) + { + $expr_end = 0; + $negate_expr = false; + + if (($first_token = array_shift($tokens)) == 'not') { + $negate_expr = true; + $expr_type = array_shift($tokens); + } else + $expr_type = $first_token; + + switch ($expr_type) { + case 'even': + if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') { + $expr_end++; + $expr_arg = $tokens[$expr_end++]; + $expr = "!(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))"; + } else + $expr = "!(1 & $is_arg)"; + break; + + case 'odd': + if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') { + $expr_end++; + $expr_arg = $tokens[$expr_end++]; + $expr = "(1 & ($is_arg / " . $this->_parse_var_props($expr_arg) . "))"; + } else + $expr = "(1 & $is_arg)"; + break; + + case 'div': + if (@$tokens[$expr_end] == 'by') { + $expr_end++; + $expr_arg = $tokens[$expr_end++]; + $expr = "!($is_arg % " . $this->_parse_var_props($expr_arg) . ")"; + } else { + $this->_syntax_error("expecting 'by' after 'div'", E_USER_ERROR, __FILE__, __LINE__); + } + break; + + default: + $this->_syntax_error("unknown 'is' expression - '$expr_type'", E_USER_ERROR, __FILE__, __LINE__); + break; + } + + if ($negate_expr) { + $expr = "!($expr)"; + } + + array_splice($tokens, 0, $expr_end, $expr); + + return $tokens; + } + + + /** + * Parse attribute string + * + * @param string $tag_args + * @return array + */ + function _parse_attrs($tag_args) + { + + /* Tokenize tag attributes. */ + preg_match_all('~(?:' . $this->_obj_call_regexp . '|' . $this->_qstr_regexp . ' | (?>[^"\'=\s]+) + )+ | + [=] + ~x', $tag_args, $match); + $tokens = $match[0]; + + $attrs = array(); + /* Parse state: + 0 - expecting attribute name + 1 - expecting '=' + 2 - expecting attribute value (not '=') */ + $state = 0; + + foreach ($tokens as $token) { + switch ($state) { + case 0: + /* If the token is a valid identifier, we set attribute name + and go to state 1. */ + if (preg_match('~^\w+$~', $token)) { + $attr_name = $token; + $state = 1; + } else + $this->_syntax_error("invalid attribute name: '$token'", E_USER_ERROR, __FILE__, __LINE__); + break; + + case 1: + /* If the token is '=', then we go to state 2. */ + if ($token == '=') { + $state = 2; + } else + $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__); + break; + + case 2: + /* If token is not '=', we set the attribute value and go to + state 0. */ + if ($token != '=') { + /* We booleanize the token if it's a non-quoted possible + boolean value. */ + if (preg_match('~^(on|yes|true)$~', $token)) { + $token = 'true'; + } else if (preg_match('~^(off|no|false)$~', $token)) { + $token = 'false'; + } else if ($token == 'null') { + $token = 'null'; + } else if (preg_match('~^' . $this->_num_const_regexp . '|0[xX][0-9a-fA-F]+$~', $token)) { + /* treat integer literally */ + } else if (!preg_match('~^' . $this->_obj_call_regexp . '|' . $this->_var_regexp . '(?:' . $this->_mod_regexp . ')*$~', $token)) { + /* treat as a string, double-quote it escaping quotes */ + $token = '"'.addslashes($token).'"'; + } + + $attrs[$attr_name] = $token; + $state = 0; + } else + $this->_syntax_error("'=' cannot be an attribute value", E_USER_ERROR, __FILE__, __LINE__); + break; + } + $last_token = $token; + } + + if($state != 0) { + if($state == 1) { + $this->_syntax_error("expecting '=' after attribute name '$last_token'", E_USER_ERROR, __FILE__, __LINE__); + } else { + $this->_syntax_error("missing attribute value", E_USER_ERROR, __FILE__, __LINE__); + } + } + + $this->_parse_vars_props($attrs); + + return $attrs; + } + + /** + * compile multiple variables and section properties tokens into + * PHP code + * + * @param array $tokens + */ + function _parse_vars_props(&$tokens) + { + foreach($tokens as $key => $val) { + $tokens[$key] = $this->_parse_var_props($val); + } + } + + /** + * compile single variable and section properties token into + * PHP code + * + * @param string $val + * @param string $tag_attrs + * @return string + */ + function _parse_var_props($val) + { + $val = trim($val); + + if(preg_match('~^(' . $this->_obj_call_regexp . '|' . $this->_dvar_regexp . ')(' . $this->_mod_regexp . '*)$~', $val, $match)) { + // $ variable or object + $return = $this->_parse_var($match[1]); + $modifiers = $match[2]; + if (!empty($this->default_modifiers) && !preg_match('~(^|\|)smarty:nodefaults($|\|)~',$modifiers)) { + $_default_mod_string = implode('|',(array)$this->default_modifiers); + $modifiers = empty($modifiers) ? $_default_mod_string : $_default_mod_string . '|' . $modifiers; + } + $this->_parse_modifiers($return, $modifiers); + return $return; + } elseif (preg_match('~^' . $this->_db_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { + // double quoted text + preg_match('~^(' . $this->_db_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match); + $return = $this->_expand_quoted_text($match[1]); + if($match[2] != '') { + $this->_parse_modifiers($return, $match[2]); + } + return $return; + } + elseif(preg_match('~^' . $this->_num_const_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { + // numerical constant + preg_match('~^(' . $this->_num_const_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match); + if($match[2] != '') { + $this->_parse_modifiers($match[1], $match[2]); + return $match[1]; + } + } + elseif(preg_match('~^' . $this->_si_qstr_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { + // single quoted text + preg_match('~^(' . $this->_si_qstr_regexp . ')('. $this->_mod_regexp . '*)$~', $val, $match); + if($match[2] != '') { + $this->_parse_modifiers($match[1], $match[2]); + return $match[1]; + } + } + elseif(preg_match('~^' . $this->_cvar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { + // config var + return $this->_parse_conf_var($val); + } + elseif(preg_match('~^' . $this->_svar_regexp . '(?:' . $this->_mod_regexp . '*)$~', $val)) { + // section var + return $this->_parse_section_prop($val); + } + elseif(!in_array($val, $this->_permitted_tokens) && !is_numeric($val)) { + // literal string + return $this->_expand_quoted_text('"' . strtr($val, array('\\' => '\\\\', '"' => '\\"')) .'"'); + } + return $val; + } + + /** + * expand quoted text with embedded variables + * + * @param string $var_expr + * @return string + */ + function _expand_quoted_text($var_expr) + { + // if contains unescaped $, expand it + if(preg_match_all('~(?:\`(?_dvar_guts_regexp . '(?:' . $this->_obj_ext_regexp . ')*\`)|(?:(?_parse_var(str_replace('`','',$_var)) . ')."'; + } + $var_expr = strtr($var_expr, $_replace); + $_return = preg_replace('~\.""|(?_dvar_math_regexp.'|'.$this->_qstr_regexp.')~', $var_expr, -1, PREG_SPLIT_DELIM_CAPTURE); + + if(count($_math_vars) > 1) { + $_first_var = ""; + $_complete_var = ""; + $_output = ""; + // simple check if there is any math, to stop recursion (due to modifiers with "xx % yy" as parameter) + foreach($_math_vars as $_k => $_math_var) { + $_math_var = $_math_vars[$_k]; + + if(!empty($_math_var) || is_numeric($_math_var)) { + // hit a math operator, so process the stuff which came before it + if(preg_match('~^' . $this->_dvar_math_regexp . '$~', $_math_var)) { + $_has_math = true; + if(!empty($_complete_var) || is_numeric($_complete_var)) { + $_output .= $this->_parse_var($_complete_var); + } + + // just output the math operator to php + $_output .= $_math_var; + + if(empty($_first_var)) + $_first_var = $_complete_var; + + $_complete_var = ""; + } else { + $_complete_var .= $_math_var; + } + } + } + if($_has_math) { + if(!empty($_complete_var) || is_numeric($_complete_var)) + $_output .= $this->_parse_var($_complete_var); + + // get the modifiers working (only the last var from math + modifier is left) + $var_expr = $_complete_var; + } + } + + // prevent cutting of first digit in the number (we _definitly_ got a number if the first char is a digit) + if(is_numeric(substr($var_expr, 0, 1))) + $_var_ref = $var_expr; + else + $_var_ref = substr($var_expr, 1); + + if(!$_has_math) { + + // get [foo] and .foo and ->foo and (...) pieces + preg_match_all('~(?:^\w+)|' . $this->_obj_params_regexp . '|(?:' . $this->_var_bracket_regexp . ')|->\$?\w+|\.\$?\w+|\S+~', $_var_ref, $match); + + $_indexes = $match[0]; + $_var_name = array_shift($_indexes); + + /* Handle $smarty.* variable references as a special case. */ + if ($_var_name == 'smarty') { + /* + * If the reference could be compiled, use the compiled output; + * otherwise, fall back on the $smarty variable generated at + * run-time. + */ + if (($smarty_ref = $this->_compile_smarty_ref($_indexes)) !== null) { + $_output = $smarty_ref; + } else { + $_var_name = substr(array_shift($_indexes), 1); + $_output = "\$this->_smarty_vars['$_var_name']"; + } + } elseif(is_numeric($_var_name) && is_numeric(substr($var_expr, 0, 1))) { + // because . is the operator for accessing arrays thru inidizes we need to put it together again for floating point numbers + if(count($_indexes) > 0) + { + $_var_name .= implode("", $_indexes); + $_indexes = array(); + } + $_output = $_var_name; + } else { + $_output = "\$this->_tpl_vars['$_var_name']"; + } + + foreach ($_indexes as $_index) { + if (substr($_index, 0, 1) == '[') { + $_index = substr($_index, 1, -1); + if (is_numeric($_index)) { + $_output .= "[$_index]"; + } elseif (substr($_index, 0, 1) == '$') { + if (strpos($_index, '.') !== false) { + $_output .= '[' . $this->_parse_var($_index) . ']'; + } else { + $_output .= "[\$this->_tpl_vars['" . substr($_index, 1) . "']]"; + } + } else { + $_var_parts = explode('.', $_index); + $_var_section = $_var_parts[0]; + $_var_section_prop = isset($_var_parts[1]) ? $_var_parts[1] : 'index'; + $_output .= "[\$this->_sections['$_var_section']['$_var_section_prop']]"; + } + } else if (substr($_index, 0, 1) == '.') { + if (substr($_index, 1, 1) == '$') + $_output .= "[\$this->_tpl_vars['" . substr($_index, 2) . "']]"; + else + $_output .= "['" . substr($_index, 1) . "']"; + } else if (substr($_index,0,2) == '->') { + if(substr($_index,2,2) == '__') { + $this->_syntax_error('call to internal object members is not allowed', E_USER_ERROR, __FILE__, __LINE__); + } elseif($this->security && substr($_index, 2, 1) == '_') { + $this->_syntax_error('(secure) call to private object member is not allowed', E_USER_ERROR, __FILE__, __LINE__); + } elseif (substr($_index, 2, 1) == '$') { + if ($this->security) { + $this->_syntax_error('(secure) call to dynamic object member is not allowed', E_USER_ERROR, __FILE__, __LINE__); + } else { + $_output .= '->{(($_var=$this->_tpl_vars[\''.substr($_index,3).'\']) && substr($_var,0,2)!=\'__\') ? $_var : $this->trigger_error("cannot access property \\"$_var\\"")}'; + } + } else { + $_output .= $_index; + } + } elseif (substr($_index, 0, 1) == '(') { + $_index = $this->_parse_parenth_args($_index); + $_output .= $_index; + } else { + $_output .= $_index; + } + } + } + + return $_output; + } + + /** + * parse arguments in function call parenthesis + * + * @param string $parenth_args + * @return string + */ + function _parse_parenth_args($parenth_args) + { + preg_match_all('~' . $this->_param_regexp . '~',$parenth_args, $match); + $orig_vals = $match = $match[0]; + $this->_parse_vars_props($match); + $replace = array(); + for ($i = 0, $count = count($match); $i < $count; $i++) { + $replace[$orig_vals[$i]] = $match[$i]; + } + return strtr($parenth_args, $replace); + } + + /** + * parse configuration variable expression into PHP code + * + * @param string $conf_var_expr + */ + function _parse_conf_var($conf_var_expr) + { + $parts = explode('|', $conf_var_expr, 2); + $var_ref = $parts[0]; + $modifiers = isset($parts[1]) ? $parts[1] : ''; + + $var_name = substr($var_ref, 1, -1); + + $output = "\$this->_config[0]['vars']['$var_name']"; + + $this->_parse_modifiers($output, $modifiers); + + return $output; + } + + /** + * parse section property expression into PHP code + * + * @param string $section_prop_expr + * @return string + */ + function _parse_section_prop($section_prop_expr) + { + $parts = explode('|', $section_prop_expr, 2); + $var_ref = $parts[0]; + $modifiers = isset($parts[1]) ? $parts[1] : ''; + + preg_match('!%(\w+)\.(\w+)%!', $var_ref, $match); + $section_name = $match[1]; + $prop_name = $match[2]; + + $output = "\$this->_sections['$section_name']['$prop_name']"; + + $this->_parse_modifiers($output, $modifiers); + + return $output; + } + + + /** + * parse modifier chain into PHP code + * + * sets $output to parsed modified chain + * @param string $output + * @param string $modifier_string + */ + function _parse_modifiers(&$output, $modifier_string) + { + preg_match_all('~\|(@?\w+)((?>:(?:'. $this->_qstr_regexp . '|[^|]+))*)~', '|' . $modifier_string, $_match); + list(, $_modifiers, $modifier_arg_strings) = $_match; + + for ($_i = 0, $_for_max = count($_modifiers); $_i < $_for_max; $_i++) { + $_modifier_name = $_modifiers[$_i]; + + if($_modifier_name == 'smarty') { + // skip smarty modifier + continue; + } + + preg_match_all('~:(' . $this->_qstr_regexp . '|[^:]+)~', $modifier_arg_strings[$_i], $_match); + $_modifier_args = $_match[1]; + + if (substr($_modifier_name, 0, 1) == '@') { + $_map_array = false; + $_modifier_name = substr($_modifier_name, 1); + } else { + $_map_array = true; + } + + if (empty($this->_plugins['modifier'][$_modifier_name]) + && !$this->_get_plugin_filepath('modifier', $_modifier_name) + && function_exists($_modifier_name)) { + if ($this->security && !in_array($_modifier_name, $this->security_settings['MODIFIER_FUNCS'])) { + $this->_trigger_fatal_error("[plugin] (secure mode) modifier '$_modifier_name' is not allowed" , $this->_current_file, $this->_current_line_no, __FILE__, __LINE__); + } else { + $this->_plugins['modifier'][$_modifier_name] = array($_modifier_name, null, null, false); + } + } + $this->_add_plugin('modifier', $_modifier_name); + + $this->_parse_vars_props($_modifier_args); + + if($_modifier_name == 'default') { + // supress notifications of default modifier vars and args + if(substr($output, 0, 1) == '$') { + $output = '@' . $output; + } + if(isset($_modifier_args[0]) && substr($_modifier_args[0], 0, 1) == '$') { + $_modifier_args[0] = '@' . $_modifier_args[0]; + } + } + if (count($_modifier_args) > 0) + $_modifier_args = ', '.implode(', ', $_modifier_args); + else + $_modifier_args = ''; + + if ($_map_array) { + $output = "((is_array(\$_tmp=$output)) ? \$this->_run_mod_handler('$_modifier_name', true, \$_tmp$_modifier_args) : " . $this->_compile_plugin_call('modifier', $_modifier_name) . "(\$_tmp$_modifier_args))"; + + } else { + + $output = $this->_compile_plugin_call('modifier', $_modifier_name)."($output$_modifier_args)"; + + } + } + } + + + /** + * add plugin + * + * @param string $type + * @param string $name + * @param boolean? $delayed_loading + */ + function _add_plugin($type, $name, $delayed_loading = null) + { + if (!isset($this->_plugin_info[$type])) { + $this->_plugin_info[$type] = array(); + } + if (!isset($this->_plugin_info[$type][$name])) { + $this->_plugin_info[$type][$name] = array($this->_current_file, + $this->_current_line_no, + $delayed_loading); + } + } + + + /** + * Compiles references of type $smarty.foo + * + * @param string $indexes + * @return string + */ + function _compile_smarty_ref(&$indexes) + { + /* Extract the reference name. */ + $_ref = substr($indexes[0], 1); + foreach($indexes as $_index_no=>$_index) { + if (substr($_index, 0, 1) != '.' && $_index_no<2 || !preg_match('~^(\.|\[|->)~', $_index)) { + $this->_syntax_error('$smarty' . implode('', array_slice($indexes, 0, 2)) . ' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__); + } + } + + switch ($_ref) { + case 'now': + $compiled_ref = 'time()'; + $_max_index = 1; + break; + + case 'foreach': + array_shift($indexes); + $_var = $this->_parse_var_props(substr($indexes[0], 1)); + $_propname = substr($indexes[1], 1); + $_max_index = 1; + switch ($_propname) { + case 'index': + array_shift($indexes); + $compiled_ref = "(\$this->_foreach[$_var]['iteration']-1)"; + break; + + case 'first': + array_shift($indexes); + $compiled_ref = "(\$this->_foreach[$_var]['iteration'] <= 1)"; + break; + + case 'last': + array_shift($indexes); + $compiled_ref = "(\$this->_foreach[$_var]['iteration'] == \$this->_foreach[$_var]['total'])"; + break; + + case 'show': + array_shift($indexes); + $compiled_ref = "(\$this->_foreach[$_var]['total'] > 0)"; + break; + + default: + unset($_max_index); + $compiled_ref = "\$this->_foreach[$_var]"; + } + break; + + case 'section': + array_shift($indexes); + $_var = $this->_parse_var_props(substr($indexes[0], 1)); + $compiled_ref = "\$this->_sections[$_var]"; + break; + + case 'get': + $compiled_ref = ($this->request_use_auto_globals) ? '$_GET' : "\$GLOBALS['HTTP_GET_VARS']"; + break; + + case 'post': + $compiled_ref = ($this->request_use_auto_globals) ? '$_POST' : "\$GLOBALS['HTTP_POST_VARS']"; + break; + + case 'cookies': + $compiled_ref = ($this->request_use_auto_globals) ? '$_COOKIE' : "\$GLOBALS['HTTP_COOKIE_VARS']"; + break; + + case 'env': + $compiled_ref = ($this->request_use_auto_globals) ? '$_ENV' : "\$GLOBALS['HTTP_ENV_VARS']"; + break; + + case 'server': + $compiled_ref = ($this->request_use_auto_globals) ? '$_SERVER' : "\$GLOBALS['HTTP_SERVER_VARS']"; + break; + + case 'session': + $compiled_ref = ($this->request_use_auto_globals) ? '$_SESSION' : "\$GLOBALS['HTTP_SESSION_VARS']"; + break; + + /* + * These cases are handled either at run-time or elsewhere in the + * compiler. + */ + case 'request': + if ($this->request_use_auto_globals) { + $compiled_ref = '$_REQUEST'; + break; + } else { + $this->_init_smarty_vars = true; + } + return null; + + case 'capture': + return null; + + case 'template': + $compiled_ref = "'$this->_current_file'"; + $_max_index = 1; + break; + + case 'version': + $compiled_ref = "'$this->_version'"; + $_max_index = 1; + break; + + case 'const': + if ($this->security && !$this->security_settings['ALLOW_CONSTANTS']) { + $this->_syntax_error("(secure mode) constants not permitted", + E_USER_WARNING, __FILE__, __LINE__); + return; + } + array_shift($indexes); + if (preg_match('!^\.\w+$!', $indexes[0])) { + $compiled_ref = '@' . substr($indexes[0], 1); + } else { + $_val = $this->_parse_var_props(substr($indexes[0], 1)); + $compiled_ref = '@constant(' . $_val . ')'; + } + $_max_index = 1; + break; + + case 'config': + $compiled_ref = "\$this->_config[0]['vars']"; + $_max_index = 3; + break; + + case 'ldelim': + $compiled_ref = "'$this->left_delimiter'"; + break; + + case 'rdelim': + $compiled_ref = "'$this->right_delimiter'"; + break; + + default: + $this->_syntax_error('$smarty.' . $_ref . ' is an unknown reference', E_USER_ERROR, __FILE__, __LINE__); + break; + } + + if (isset($_max_index) && count($indexes) > $_max_index) { + $this->_syntax_error('$smarty' . implode('', $indexes) .' is an invalid reference', E_USER_ERROR, __FILE__, __LINE__); + } + + array_shift($indexes); + return $compiled_ref; + } + + /** + * compiles call to plugin of type $type with name $name + * returns a string containing the function-name or method call + * without the paramter-list that would have follow to make the + * call valid php-syntax + * + * @param string $type + * @param string $name + * @return string + */ + function _compile_plugin_call($type, $name) { + if (isset($this->_plugins[$type][$name])) { + /* plugin loaded */ + if (is_array($this->_plugins[$type][$name][0])) { + return ((is_object($this->_plugins[$type][$name][0][0])) ? + "\$this->_plugins['$type']['$name'][0][0]->" /* method callback */ + : (string)($this->_plugins[$type][$name][0][0]).'::' /* class callback */ + ). $this->_plugins[$type][$name][0][1]; + + } else { + /* function callback */ + return $this->_plugins[$type][$name][0]; + + } + } else { + /* plugin not loaded -> auto-loadable-plugin */ + return 'smarty_'.$type.'_'.$name; + + } + } + + /** + * load pre- and post-filters + */ + function _load_filters() + { + if (count($this->_plugins['prefilter']) > 0) { + foreach ($this->_plugins['prefilter'] as $filter_name => $prefilter) { + if ($prefilter === false) { + unset($this->_plugins['prefilter'][$filter_name]); + $_params = array('plugins' => array(array('prefilter', $filter_name, null, null, false))); + require_once(SMARTY_CORE_DIR . 'core.load_plugins.php'); + smarty_core_load_plugins($_params, $this); + } + } + } + if (count($this->_plugins['postfilter']) > 0) { + foreach ($this->_plugins['postfilter'] as $filter_name => $postfilter) { + if ($postfilter === false) { + unset($this->_plugins['postfilter'][$filter_name]); + $_params = array('plugins' => array(array('postfilter', $filter_name, null, null, false))); + require_once(SMARTY_CORE_DIR . 'core.load_plugins.php'); + smarty_core_load_plugins($_params, $this); + } + } + } + } + + + /** + * Quote subpattern references + * + * @param string $string + * @return string + */ + function _quote_replace($string) + { + return strtr($string, array('\\' => '\\\\', '$' => '\\$')); + } + + /** + * display Smarty syntax error + * + * @param string $error_msg + * @param integer $error_type + * @param string $file + * @param integer $line + */ + function _syntax_error($error_msg, $error_type = E_USER_ERROR, $file=null, $line=null) + { + $this->_trigger_fatal_error("syntax error: $error_msg", $this->_current_file, $this->_current_line_no, $file, $line, $error_type); + } + + + /** + * check if the compilation changes from cacheable to + * non-cacheable state with the beginning of the current + * plugin. return php-code to reflect the transition. + * @return string + */ + function _push_cacheable_state($type, $name) { + $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4]; + if ($_cacheable + || 0<$this->_cacheable_state++) return ''; + if (!isset($this->_cache_serial)) $this->_cache_serial = md5(uniqid('Smarty')); + $_ret = 'if ($this->caching && !$this->_cache_including) { echo \'{nocache:' + . $this->_cache_serial . '#' . $this->_nocache_count + . '}\'; };'; + return $_ret; + } + + + /** + * check if the compilation changes from non-cacheable to + * cacheable state with the end of the current plugin return + * php-code to reflect the transition. + * @return string + */ + function _pop_cacheable_state($type, $name) { + $_cacheable = !isset($this->_plugins[$type][$name]) || $this->_plugins[$type][$name][4]; + if ($_cacheable + || --$this->_cacheable_state>0) return ''; + return 'if ($this->caching && !$this->_cache_including) { echo \'{/nocache:' + . $this->_cache_serial . '#' . ($this->_nocache_count++) + . '}\'; };'; + } + + + /** + * push opening tag-name, file-name and line-number on the tag-stack + * @param string the opening tag's name + */ + function _push_tag($open_tag) + { + array_push($this->_tag_stack, array($open_tag, $this->_current_line_no)); + } + + /** + * pop closing tag-name + * raise an error if this stack-top doesn't match with the closing tag + * @param string the closing tag's name + * @return string the opening tag's name + */ + function _pop_tag($close_tag) + { + $message = ''; + if (count($this->_tag_stack)>0) { + list($_open_tag, $_line_no) = array_pop($this->_tag_stack); + if ($close_tag == $_open_tag) { + return $_open_tag; + } + if ($close_tag == 'if' && ($_open_tag == 'else' || $_open_tag == 'elseif' )) { + return $this->_pop_tag($close_tag); + } + if ($close_tag == 'section' && $_open_tag == 'sectionelse') { + $this->_pop_tag($close_tag); + return $_open_tag; + } + if ($close_tag == 'foreach' && $_open_tag == 'foreachelse') { + $this->_pop_tag($close_tag); + return $_open_tag; + } + if ($_open_tag == 'else' || $_open_tag == 'elseif') { + $_open_tag = 'if'; + } elseif ($_open_tag == 'sectionelse') { + $_open_tag = 'section'; + } elseif ($_open_tag == 'foreachelse') { + $_open_tag = 'foreach'; + } + $message = " expected {/$_open_tag} (opened line $_line_no)."; + } + $this->_syntax_error("mismatched tag {/$close_tag}.$message", + E_USER_ERROR, __FILE__, __LINE__); + } + +} + +/** + * compare to values by their string length + * + * @access private + * @param string $a + * @param string $b + * @return 0|-1|1 + */ +function _smarty_sort_length($a, $b) +{ + if($a == $b) + return 0; + + if(strlen($a) == strlen($b)) + return ($a > $b) ? -1 : 1; + + return (strlen($a) > strlen($b)) ? -1 : 1; +} + + +/* vim: set et: */ + +?> diff --git a/trunk/docs/include/smarty/debug.tpl b/trunk/docs/include/smarty/debug.tpl new file mode 100644 index 0000000..c05ef5d --- /dev/null +++ b/trunk/docs/include/smarty/debug.tpl @@ -0,0 +1,157 @@ +{* Smarty *} +{* debug.tpl, last updated version 2.1.0 *} +{assign_debug_info} +{capture assign=debug_output} + + + + Smarty Debug Console +{literal} + +{/literal} + + + +

Smarty Debug Console

+ +

included templates & config files (load time in seconds)

+ +
+{section name=templates loop=$_debug_tpls} + {section name=indent loop=$_debug_tpls[templates].depth}   {/section} + + {$_debug_tpls[templates].filename|escape:html} + {if isset($_debug_tpls[templates].exec_time)} + + ({$_debug_tpls[templates].exec_time|string_format:"%.5f"}) + {if %templates.index% eq 0}(total){/if} + + {/if} +
+{sectionelse} +

no templates included

+{/section} +
+ +

assigned template variables

+ + + {section name=vars loop=$_debug_keys} + + + + {sectionelse} + + {/section} +
{ldelim}${$_debug_keys[vars]|escape:'html'}{rdelim}{$_debug_vals[vars]|@debug_print_var}

no template variables assigned

+ +

assigned config file variables (outer template scope)

+ + + {section name=config_vars loop=$_debug_config_keys} + + + + {sectionelse} + + {/section} +
{ldelim}#{$_debug_config_keys[config_vars]|escape:'html'}#{rdelim}{$_debug_config_vals[config_vars]|@debug_print_var}

no config vars assigned

+ + +{/capture} +{if isset($_smarty_debug_output) and $_smarty_debug_output eq "html"} + {$debug_output} +{else} + +{/if} \ No newline at end of file diff --git a/trunk/docs/include/smarty/internals/core.assemble_plugin_filepath.php b/trunk/docs/include/smarty/internals/core.assemble_plugin_filepath.php new file mode 100644 index 0000000..690d3dd --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.assemble_plugin_filepath.php @@ -0,0 +1,67 @@ +plugins_dir as $_plugin_dir) { + + $_plugin_filepath = $_plugin_dir . DIRECTORY_SEPARATOR . $_plugin_filename; + + // see if path is relative + if (!preg_match("/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/", $_plugin_dir)) { + $_relative_paths[] = $_plugin_dir; + // relative path, see if it is in the SMARTY_DIR + if (@is_readable(SMARTY_DIR . $_plugin_filepath)) { + $_return = SMARTY_DIR . $_plugin_filepath; + break; + } + } + // try relative to cwd (or absolute) + if (@is_readable($_plugin_filepath)) { + $_return = $_plugin_filepath; + break; + } + } + + if($_return === false) { + // still not found, try PHP include_path + if(isset($_relative_paths)) { + foreach ((array)$_relative_paths as $_plugin_dir) { + + $_plugin_filepath = $_plugin_dir . DIRECTORY_SEPARATOR . $_plugin_filename; + + $_params = array('file_path' => $_plugin_filepath); + require_once(SMARTY_CORE_DIR . 'core.get_include_path.php'); + if(smarty_core_get_include_path($_params, $smarty)) { + $_return = $_params['new_file_path']; + break; + } + } + } + } + $_filepaths_cache[$_plugin_filename] = $_return; + return $_return; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.assign_smarty_interface.php b/trunk/docs/include/smarty/internals/core.assign_smarty_interface.php new file mode 100644 index 0000000..7e65a73 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.assign_smarty_interface.php @@ -0,0 +1,43 @@ + + * Name: assign_smarty_interface
+ * Purpose: assign the $smarty interface variable + * @param array Format: null + * @param Smarty + */ +function smarty_core_assign_smarty_interface($params, &$smarty) +{ + if (isset($smarty->_smarty_vars) && isset($smarty->_smarty_vars['request'])) { + return; + } + + $_globals_map = array('g' => 'HTTP_GET_VARS', + 'p' => 'HTTP_POST_VARS', + 'c' => 'HTTP_COOKIE_VARS', + 's' => 'HTTP_SERVER_VARS', + 'e' => 'HTTP_ENV_VARS'); + + $_smarty_vars_request = array(); + + foreach (preg_split('!!', strtolower($smarty->request_vars_order)) as $_c) { + if (isset($_globals_map[$_c])) { + $_smarty_vars_request = array_merge($_smarty_vars_request, $GLOBALS[$_globals_map[$_c]]); + } + } + $_smarty_vars_request = @array_merge($_smarty_vars_request, $GLOBALS['HTTP_SESSION_VARS']); + + $smarty->_smarty_vars['request'] = $_smarty_vars_request; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.create_dir_structure.php b/trunk/docs/include/smarty/internals/core.create_dir_structure.php new file mode 100644 index 0000000..3eecc49 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.create_dir_structure.php @@ -0,0 +1,79 @@ +_dir_perms) && !is_dir($_new_dir)) { + $smarty->trigger_error("problem creating directory '" . $_new_dir . "'"); + return false; + } + $_new_dir .= '/'; + } + } +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.display_debug_console.php b/trunk/docs/include/smarty/internals/core.display_debug_console.php new file mode 100644 index 0000000..1a80f39 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.display_debug_console.php @@ -0,0 +1,61 @@ + + * Name: display_debug_console
+ * Purpose: display the javascript debug console window + * @param array Format: null + * @param Smarty + */ +function smarty_core_display_debug_console($params, &$smarty) +{ + // we must force compile the debug template in case the environment + // changed between separate applications. + + if(empty($smarty->debug_tpl)) { + // set path to debug template from SMARTY_DIR + $smarty->debug_tpl = SMARTY_DIR . 'debug.tpl'; + if($smarty->security && is_file($smarty->debug_tpl)) { + $smarty->secure_dir[] = realpath($smarty->debug_tpl); + } + $smarty->debug_tpl = 'file:' . SMARTY_DIR . 'debug.tpl'; + } + + $_ldelim_orig = $smarty->left_delimiter; + $_rdelim_orig = $smarty->right_delimiter; + + $smarty->left_delimiter = '{'; + $smarty->right_delimiter = '}'; + + $_compile_id_orig = $smarty->_compile_id; + $smarty->_compile_id = null; + + $_compile_path = $smarty->_get_compile_path($smarty->debug_tpl); + if ($smarty->_compile_resource($smarty->debug_tpl, $_compile_path)) + { + ob_start(); + $smarty->_include($_compile_path); + $_results = ob_get_contents(); + ob_end_clean(); + } else { + $_results = ''; + } + + $smarty->_compile_id = $_compile_id_orig; + + $smarty->left_delimiter = $_ldelim_orig; + $smarty->right_delimiter = $_rdelim_orig; + + return $_results; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.get_include_path.php b/trunk/docs/include/smarty/internals/core.get_include_path.php new file mode 100644 index 0000000..4343241 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.get_include_path.php @@ -0,0 +1,44 @@ + diff --git a/trunk/docs/include/smarty/internals/core.get_microtime.php b/trunk/docs/include/smarty/internals/core.get_microtime.php new file mode 100644 index 0000000..f1a28e0 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.get_microtime.php @@ -0,0 +1,23 @@ + diff --git a/trunk/docs/include/smarty/internals/core.get_php_resource.php b/trunk/docs/include/smarty/internals/core.get_php_resource.php new file mode 100644 index 0000000..786d4e7 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.get_php_resource.php @@ -0,0 +1,80 @@ +trusted_dir; + $smarty->_parse_resource_name($params, $smarty); + + /* + * Find out if the resource exists. + */ + + if ($params['resource_type'] == 'file') { + $_readable = false; + if(file_exists($params['resource_name']) && is_readable($params['resource_name'])) { + $_readable = true; + } else { + // test for file in include_path + $_params = array('file_path' => $params['resource_name']); + require_once(SMARTY_CORE_DIR . 'core.get_include_path.php'); + if(smarty_core_get_include_path($_params, $smarty)) { + $_include_path = $_params['new_file_path']; + $_readable = true; + } + } + } else if ($params['resource_type'] != 'file') { + $_template_source = null; + $_readable = is_callable($smarty->_plugins['resource'][$params['resource_type']][0][0]) + && call_user_func_array($smarty->_plugins['resource'][$params['resource_type']][0][0], + array($params['resource_name'], &$_template_source, &$smarty)); + } + + /* + * Set the error function, depending on which class calls us. + */ + if (method_exists($smarty, '_syntax_error')) { + $_error_funcc = '_syntax_error'; + } else { + $_error_funcc = 'trigger_error'; + } + + if ($_readable) { + if ($smarty->security) { + require_once(SMARTY_CORE_DIR . 'core.is_trusted.php'); + if (!smarty_core_is_trusted($params, $smarty)) { + $smarty->$_error_funcc('(secure mode) ' . $params['resource_type'] . ':' . $params['resource_name'] . ' is not trusted'); + return false; + } + } + } else { + $smarty->$_error_funcc($params['resource_type'] . ':' . $params['resource_name'] . ' is not readable'); + return false; + } + + if ($params['resource_type'] == 'file') { + $params['php_resource'] = $params['resource_name']; + } else { + $params['php_resource'] = $_template_source; + } + return true; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.is_secure.php b/trunk/docs/include/smarty/internals/core.is_secure.php new file mode 100644 index 0000000..d54abd4 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.is_secure.php @@ -0,0 +1,59 @@ +security || $smarty->security_settings['INCLUDE_ANY']) { + return true; + } + + if ($params['resource_type'] == 'file') { + $_rp = realpath($params['resource_name']); + if (isset($params['resource_base_path'])) { + foreach ((array)$params['resource_base_path'] as $curr_dir) { + if ( ($_cd = realpath($curr_dir)) !== false && + strncmp($_rp, $_cd, strlen($_cd)) == 0 && + substr($_rp, strlen($_cd), 1) == DIRECTORY_SEPARATOR ) { + return true; + } + } + } + if (!empty($smarty->secure_dir)) { + foreach ((array)$smarty->secure_dir as $curr_dir) { + if ( ($_cd = realpath($curr_dir)) !== false) { + if($_cd == $_rp) { + return true; + } elseif (strncmp($_rp, $_cd, strlen($_cd)) == 0 && + substr($_rp, strlen($_cd), 1) == DIRECTORY_SEPARATOR) { + return true; + } + } + } + } + } else { + // resource is not on local file system + return call_user_func_array( + $smarty->_plugins['resource'][$params['resource_type']][0][2], + array($params['resource_name'], &$smarty)); + } + + return false; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.is_trusted.php b/trunk/docs/include/smarty/internals/core.is_trusted.php new file mode 100644 index 0000000..4299731 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.is_trusted.php @@ -0,0 +1,47 @@ +trusted_dir)) { + $_rp = realpath($params['resource_name']); + foreach ((array)$smarty->trusted_dir as $curr_dir) { + if (!empty($curr_dir) && is_readable ($curr_dir)) { + $_cd = realpath($curr_dir); + if (strncmp($_rp, $_cd, strlen($_cd)) == 0 + && substr($_rp, strlen($_cd), 1) == DIRECTORY_SEPARATOR ) { + $_smarty_trusted = true; + break; + } + } + } + } + + } else { + // resource is not on local file system + $_smarty_trusted = call_user_func_array($smarty->_plugins['resource'][$params['resource_type']][0][3], + array($params['resource_name'], $smarty)); + } + + return $_smarty_trusted; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.load_plugins.php b/trunk/docs/include/smarty/internals/core.load_plugins.php new file mode 100644 index 0000000..6db1dc5 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.load_plugins.php @@ -0,0 +1,125 @@ +_plugins[$_type][$_name]; + + /* + * We do not load plugin more than once for each instance of Smarty. + * The following code checks for that. The plugin can also be + * registered dynamically at runtime, in which case template file + * and line number will be unknown, so we fill them in. + * + * The final element of the info array is a flag that indicates + * whether the dynamically registered plugin function has been + * checked for existence yet or not. + */ + if (isset($_plugin)) { + if (empty($_plugin[3])) { + if (!is_callable($_plugin[0])) { + $smarty->_trigger_fatal_error("[plugin] $_type '$_name' is not implemented", $_tpl_file, $_tpl_line, __FILE__, __LINE__); + } else { + $_plugin[1] = $_tpl_file; + $_plugin[2] = $_tpl_line; + $_plugin[3] = true; + if (!isset($_plugin[4])) $_plugin[4] = true; /* cacheable */ + } + } + continue; + } else if ($_type == 'insert') { + /* + * For backwards compatibility, we check for insert functions in + * the symbol table before trying to load them as a plugin. + */ + $_plugin_func = 'insert_' . $_name; + if (function_exists($_plugin_func)) { + $_plugin = array($_plugin_func, $_tpl_file, $_tpl_line, true, false); + continue; + } + } + + $_plugin_file = $smarty->_get_plugin_filepath($_type, $_name); + + if (! $_found = ($_plugin_file != false)) { + $_message = "could not load plugin file '$_type.$_name.php'\n"; + } + + /* + * If plugin file is found, it -must- provide the properly named + * plugin function. In case it doesn't, simply output the error and + * do not fall back on any other method. + */ + if ($_found) { + include_once $_plugin_file; + + $_plugin_func = 'smarty_' . $_type . '_' . $_name; + if (!function_exists($_plugin_func)) { + $smarty->_trigger_fatal_error("[plugin] function $_plugin_func() not found in $_plugin_file", $_tpl_file, $_tpl_line, __FILE__, __LINE__); + continue; + } + } + /* + * In case of insert plugins, their code may be loaded later via + * 'script' attribute. + */ + else if ($_type == 'insert' && $_delayed_loading) { + $_plugin_func = 'smarty_' . $_type . '_' . $_name; + $_found = true; + } + + /* + * Plugin specific processing and error checking. + */ + if (!$_found) { + if ($_type == 'modifier') { + /* + * In case modifier falls back on using PHP functions + * directly, we only allow those specified in the security + * context. + */ + if ($smarty->security && !in_array($_name, $smarty->security_settings['MODIFIER_FUNCS'])) { + $_message = "(secure mode) modifier '$_name' is not allowed"; + } else { + if (!function_exists($_name)) { + $_message = "modifier '$_name' is not implemented"; + } else { + $_plugin_func = $_name; + $_found = true; + } + } + } else if ($_type == 'function') { + /* + * This is a catch-all situation. + */ + $_message = "unknown tag - '$_name'"; + } + } + + if ($_found) { + $smarty->_plugins[$_type][$_name] = array($_plugin_func, $_tpl_file, $_tpl_line, true, true); + } else { + // output error + $smarty->_trigger_fatal_error('[plugin] ' . $_message, $_tpl_file, $_tpl_line, __FILE__, __LINE__); + } + } +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.load_resource_plugin.php b/trunk/docs/include/smarty/internals/core.load_resource_plugin.php new file mode 100644 index 0000000..a7d37d1 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.load_resource_plugin.php @@ -0,0 +1,74 @@ +_plugins['resource'][$params['type']]; + if (isset($_plugin)) { + if (!$_plugin[1] && count($_plugin[0])) { + $_plugin[1] = true; + foreach ($_plugin[0] as $_plugin_func) { + if (!is_callable($_plugin_func)) { + $_plugin[1] = false; + break; + } + } + } + + if (!$_plugin[1]) { + $smarty->_trigger_fatal_error("[plugin] resource '" . $params['type'] . "' is not implemented", null, null, __FILE__, __LINE__); + } + + return; + } + + $_plugin_file = $smarty->_get_plugin_filepath('resource', $params['type']); + $_found = ($_plugin_file != false); + + if ($_found) { /* + * If the plugin file is found, it -must- provide the properly named + * plugin functions. + */ + include_once($_plugin_file); + + /* + * Locate functions that we require the plugin to provide. + */ + $_resource_ops = array('source', 'timestamp', 'secure', 'trusted'); + $_resource_funcs = array(); + foreach ($_resource_ops as $_op) { + $_plugin_func = 'smarty_resource_' . $params['type'] . '_' . $_op; + if (!function_exists($_plugin_func)) { + $smarty->_trigger_fatal_error("[plugin] function $_plugin_func() not found in $_plugin_file", null, null, __FILE__, __LINE__); + return; + } else { + $_resource_funcs[] = $_plugin_func; + } + } + + $smarty->_plugins['resource'][$params['type']] = array($_resource_funcs, true); + } +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.process_cached_inserts.php b/trunk/docs/include/smarty/internals/core.process_cached_inserts.php new file mode 100644 index 0000000..1d78edd --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.process_cached_inserts.php @@ -0,0 +1,71 @@ +_smarty_md5.'{insert_cache (.*)}'.$smarty->_smarty_md5.'!Uis', + $params['results'], $match); + list($cached_inserts, $insert_args) = $match; + + for ($i = 0, $for_max = count($cached_inserts); $i < $for_max; $i++) { + if ($smarty->debugging) { + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $debug_start_time = smarty_core_get_microtime($_params, $smarty); + } + + $args = unserialize($insert_args[$i]); + $name = $args['name']; + + if (isset($args['script'])) { + $_params = array('resource_name' => $smarty->_dequote($args['script'])); + require_once(SMARTY_CORE_DIR . 'core.get_php_resource.php'); + if(!smarty_core_get_php_resource($_params, $smarty)) { + return false; + } + $resource_type = $_params['resource_type']; + $php_resource = $_params['php_resource']; + + + if ($resource_type == 'file') { + $smarty->_include($php_resource, true); + } else { + $smarty->_eval($php_resource); + } + } + + $function_name = $smarty->_plugins['insert'][$name][0]; + if (empty($args['assign'])) { + $replace = $function_name($args, $smarty); + } else { + $smarty->assign($args['assign'], $function_name($args, $smarty)); + $replace = ''; + } + + $params['results'] = substr_replace($params['results'], $replace, strpos($params['results'], $cached_inserts[$i]), strlen($cached_inserts[$i])); + if ($smarty->debugging) { + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $smarty->_smarty_debug_info[] = array('type' => 'insert', + 'filename' => 'insert_'.$name, + 'depth' => $smarty->_inclusion_depth, + 'exec_time' => smarty_core_get_microtime($_params, $smarty) - $debug_start_time); + } + } + + return $params['results']; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.process_compiled_include.php b/trunk/docs/include/smarty/internals/core.process_compiled_include.php new file mode 100644 index 0000000..d539423 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.process_compiled_include.php @@ -0,0 +1,37 @@ +_cache_including; + $smarty->_cache_including = true; + + $_return = $params['results']; + + foreach ($smarty->_cache_info['cache_serials'] as $_include_file_path=>$_cache_serial) { + $smarty->_include($_include_file_path, true); + } + + foreach ($smarty->_cache_serials as $_include_file_path=>$_cache_serial) { + $_return = preg_replace_callback('!(\{nocache\:('.$_cache_serial.')#(\d+)\})!s', + array(&$smarty, '_process_compiled_include_callback'), + $_return); + } + $smarty->_cache_including = $_cache_including; + return $_return; +} + +?> diff --git a/trunk/docs/include/smarty/internals/core.read_cache_file.php b/trunk/docs/include/smarty/internals/core.read_cache_file.php new file mode 100644 index 0000000..c60e113 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.read_cache_file.php @@ -0,0 +1,101 @@ +force_compile) { + // force compile enabled, always regenerate + return false; + } + + if (isset($content_cache[$params['tpl_file'].','.$params['cache_id'].','.$params['compile_id']])) { + list($params['results'], $smarty->_cache_info) = $content_cache[$params['tpl_file'].','.$params['cache_id'].','.$params['compile_id']]; + return true; + } + + if (!empty($smarty->cache_handler_func)) { + // use cache_handler function + call_user_func_array($smarty->cache_handler_func, + array('read', &$smarty, &$params['results'], $params['tpl_file'], $params['cache_id'], $params['compile_id'], null)); + } else { + // use local cache file + $_auto_id = $smarty->_get_auto_id($params['cache_id'], $params['compile_id']); + $_cache_file = $smarty->_get_auto_filename($smarty->cache_dir, $params['tpl_file'], $_auto_id); + $params['results'] = $smarty->_read_file($_cache_file); + } + + if (empty($params['results'])) { + // nothing to parse (error?), regenerate cache + return false; + } + + $_contents = $params['results']; + $_info_start = strpos($_contents, "\n") + 1; + $_info_len = (int)substr($_contents, 0, $_info_start - 1); + $_cache_info = unserialize(substr($_contents, $_info_start, $_info_len)); + $params['results'] = substr($_contents, $_info_start + $_info_len); + + if ($smarty->caching == 2 && isset ($_cache_info['expires'])){ + // caching by expiration time + if ($_cache_info['expires'] > -1 && (time() > $_cache_info['expires'])) { + // cache expired, regenerate + return false; + } + } else { + // caching by lifetime + if ($smarty->cache_lifetime > -1 && (time() - $_cache_info['timestamp'] > $smarty->cache_lifetime)) { + // cache expired, regenerate + return false; + } + } + + if ($smarty->compile_check) { + $_params = array('get_source' => false, 'quiet'=>true); + foreach (array_keys($_cache_info['template']) as $_template_dep) { + $_params['resource_name'] = $_template_dep; + if (!$smarty->_fetch_resource_info($_params) || $_cache_info['timestamp'] < $_params['resource_timestamp']) { + // template file has changed, regenerate cache + return false; + } + } + + if (isset($_cache_info['config'])) { + $_params = array('resource_base_path' => $smarty->config_dir, 'get_source' => false, 'quiet'=>true); + foreach (array_keys($_cache_info['config']) as $_config_dep) { + $_params['resource_name'] = $_config_dep; + if (!$smarty->_fetch_resource_info($_params) || $_cache_info['timestamp'] < $_params['resource_timestamp']) { + // config file has changed, regenerate cache + return false; + } + } + } + } + + $content_cache[$params['tpl_file'].','.$params['cache_id'].','.$params['compile_id']] = array($params['results'], $_cache_info); + + $smarty->_cache_info = $_cache_info; + return true; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.rm_auto.php b/trunk/docs/include/smarty/internals/core.rm_auto.php new file mode 100644 index 0000000..b251f64 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.rm_auto.php @@ -0,0 +1,71 @@ + $params['auto_base'], + 'level' => 0, + 'exp_time' => $params['exp_time'] + ); + require_once(SMARTY_CORE_DIR . 'core.rmdir.php'); + $_res = smarty_core_rmdir($_params, $smarty); + } else { + $_tname = $smarty->_get_auto_filename($params['auto_base'], $params['auto_source'], $params['auto_id']); + + if(isset($params['auto_source'])) { + if (isset($params['extensions'])) { + $_res = false; + foreach ((array)$params['extensions'] as $_extension) + $_res |= $smarty->_unlink($_tname.$_extension, $params['exp_time']); + } else { + $_res = $smarty->_unlink($_tname, $params['exp_time']); + } + } elseif ($smarty->use_sub_dirs) { + $_params = array( + 'dirname' => $_tname, + 'level' => 1, + 'exp_time' => $params['exp_time'] + ); + require_once(SMARTY_CORE_DIR . 'core.rmdir.php'); + $_res = smarty_core_rmdir($_params, $smarty); + } else { + // remove matching file names + $_handle = opendir($params['auto_base']); + $_res = true; + while (false !== ($_filename = readdir($_handle))) { + if($_filename == '.' || $_filename == '..') { + continue; + } elseif (substr($params['auto_base'] . DIRECTORY_SEPARATOR . $_filename, 0, strlen($_tname)) == $_tname) { + $_res &= (bool)$smarty->_unlink($params['auto_base'] . DIRECTORY_SEPARATOR . $_filename, $params['exp_time']); + } + } + } + } + + return $_res; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.rmdir.php b/trunk/docs/include/smarty/internals/core.rmdir.php new file mode 100644 index 0000000..2166c44 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.rmdir.php @@ -0,0 +1,54 @@ + keep root) + * WARNING: no tests, it will try to remove what you tell it! + * + * @param string $dirname + * @param integer $level + * @param integer $exp_time + * @return boolean + */ + +// $dirname, $level = 1, $exp_time = null + +function smarty_core_rmdir($params, &$smarty) +{ + if(!isset($params['level'])) { $params['level'] = 1; } + if(!isset($params['exp_time'])) { $params['exp_time'] = null; } + + if($_handle = @opendir($params['dirname'])) { + + while (false !== ($_entry = readdir($_handle))) { + if ($_entry != '.' && $_entry != '..') { + if (@is_dir($params['dirname'] . DIRECTORY_SEPARATOR . $_entry)) { + $_params = array( + 'dirname' => $params['dirname'] . DIRECTORY_SEPARATOR . $_entry, + 'level' => $params['level'] + 1, + 'exp_time' => $params['exp_time'] + ); + smarty_core_rmdir($_params, $smarty); + } + else { + $smarty->_unlink($params['dirname'] . DIRECTORY_SEPARATOR . $_entry, $params['exp_time']); + } + } + } + closedir($_handle); + } + + if ($params['level']) { + return @rmdir($params['dirname']); + } + return (bool)$_handle; + +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.run_insert_handler.php b/trunk/docs/include/smarty/internals/core.run_insert_handler.php new file mode 100644 index 0000000..71c3845 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.run_insert_handler.php @@ -0,0 +1,71 @@ +debugging) { + $_params = array(); + $_debug_start_time = smarty_core_get_microtime($_params, $smarty); + } + + if ($smarty->caching) { + $_arg_string = serialize($params['args']); + $_name = $params['args']['name']; + if (!isset($smarty->_cache_info['insert_tags'][$_name])) { + $smarty->_cache_info['insert_tags'][$_name] = array('insert', + $_name, + $smarty->_plugins['insert'][$_name][1], + $smarty->_plugins['insert'][$_name][2], + !empty($params['args']['script']) ? true : false); + } + return $smarty->_smarty_md5."{insert_cache $_arg_string}".$smarty->_smarty_md5; + } else { + if (isset($params['args']['script'])) { + $_params = array('resource_name' => $smarty->_dequote($params['args']['script'])); + require_once(SMARTY_CORE_DIR . 'core.get_php_resource.php'); + if(!smarty_core_get_php_resource($_params, $smarty)) { + return false; + } + + if ($_params['resource_type'] == 'file') { + $smarty->_include($_params['php_resource'], true); + } else { + $smarty->_eval($_params['php_resource']); + } + unset($params['args']['script']); + } + + $_funcname = $smarty->_plugins['insert'][$params['args']['name']][0]; + $_content = $_funcname($params['args'], $smarty); + if ($smarty->debugging) { + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $smarty->_smarty_debug_info[] = array('type' => 'insert', + 'filename' => 'insert_'.$params['args']['name'], + 'depth' => $smarty->_inclusion_depth, + 'exec_time' => smarty_core_get_microtime($_params, $smarty) - $_debug_start_time); + } + + if (!empty($params['args']["assign"])) { + $smarty->assign($params['args']["assign"], $_content); + } else { + return $_content; + } + } +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.smarty_include_php.php b/trunk/docs/include/smarty/internals/core.smarty_include_php.php new file mode 100644 index 0000000..30c6e76 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.smarty_include_php.php @@ -0,0 +1,50 @@ + $params['smarty_file']); + require_once(SMARTY_CORE_DIR . 'core.get_php_resource.php'); + smarty_core_get_php_resource($_params, $smarty); + $_smarty_resource_type = $_params['resource_type']; + $_smarty_php_resource = $_params['php_resource']; + + if (!empty($params['smarty_assign'])) { + ob_start(); + if ($_smarty_resource_type == 'file') { + $smarty->_include($_smarty_php_resource, $params['smarty_once'], $params['smarty_include_vars']); + } else { + $smarty->_eval($_smarty_php_resource, $params['smarty_include_vars']); + } + $smarty->assign($params['smarty_assign'], ob_get_contents()); + ob_end_clean(); + } else { + if ($_smarty_resource_type == 'file') { + $smarty->_include($_smarty_php_resource, $params['smarty_once'], $params['smarty_include_vars']); + } else { + $smarty->_eval($_smarty_php_resource, $params['smarty_include_vars']); + } + } +} + + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.write_cache_file.php b/trunk/docs/include/smarty/internals/core.write_cache_file.php new file mode 100644 index 0000000..72f785b --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.write_cache_file.php @@ -0,0 +1,96 @@ +_cache_info['timestamp'] = time(); + if ($smarty->cache_lifetime > -1){ + // expiration set + $smarty->_cache_info['expires'] = $smarty->_cache_info['timestamp'] + $smarty->cache_lifetime; + } else { + // cache will never expire + $smarty->_cache_info['expires'] = -1; + } + + // collapse nocache.../nocache-tags + if (preg_match_all('!\{(/?)nocache\:[0-9a-f]{32}#\d+\}!', $params['results'], $match, PREG_PATTERN_ORDER)) { + // remove everything between every pair of outermost noache.../nocache-tags + // and replace it by a single nocache-tag + // this new nocache-tag will be replaced by dynamic contents in + // smarty_core_process_compiled_includes() on a cache-read + + $match_count = count($match[0]); + $results = preg_split('!(\{/?nocache\:[0-9a-f]{32}#\d+\})!', $params['results'], -1, PREG_SPLIT_DELIM_CAPTURE); + + $level = 0; + $j = 0; + for ($i=0, $results_count = count($results); $i < $results_count && $j < $match_count; $i++) { + if ($results[$i] == $match[0][$j]) { + // nocache tag + if ($match[1][$j]) { // closing tag + $level--; + unset($results[$i]); + } else { // opening tag + if ($level++ > 0) unset($results[$i]); + } + $j++; + } elseif ($level > 0) { + unset($results[$i]); + } + } + $params['results'] = implode('', $results); + } + $smarty->_cache_info['cache_serials'] = $smarty->_cache_serials; + + // prepend the cache header info into cache file + $_cache_info = serialize($smarty->_cache_info); + $params['results'] = strlen($_cache_info) . "\n" . $_cache_info . $params['results']; + + if (!empty($smarty->cache_handler_func)) { + // use cache_handler function + call_user_func_array($smarty->cache_handler_func, + array('write', &$smarty, &$params['results'], $params['tpl_file'], $params['cache_id'], $params['compile_id'], null)); + } else { + // use local cache file + + if(!@is_writable($smarty->cache_dir)) { + // cache_dir not writable, see if it exists + if(!@is_dir($smarty->cache_dir)) { + $smarty->trigger_error('the $cache_dir \'' . $smarty->cache_dir . '\' does not exist, or is not a directory.', E_USER_ERROR); + return false; + } + $smarty->trigger_error('unable to write to $cache_dir \'' . realpath($smarty->cache_dir) . '\'. Be sure $cache_dir is writable by the web server user.', E_USER_ERROR); + return false; + } + + $_auto_id = $smarty->_get_auto_id($params['cache_id'], $params['compile_id']); + $_cache_file = $smarty->_get_auto_filename($smarty->cache_dir, $params['tpl_file'], $_auto_id); + $_params = array('filename' => $_cache_file, 'contents' => $params['results'], 'create_dirs' => true); + require_once(SMARTY_CORE_DIR . 'core.write_file.php'); + smarty_core_write_file($_params, $smarty); + return true; + } +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.write_compiled_include.php b/trunk/docs/include/smarty/internals/core.write_compiled_include.php new file mode 100644 index 0000000..3a78094 --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.write_compiled_include.php @@ -0,0 +1,91 @@ +caching && \!\$this->_cache_including\) \{ echo \'\{nocache\:('.$params['cache_serial'].')#(\d+)\}\'; \};'; + $_tag_end = 'if \(\$this->caching && \!\$this->_cache_including\) \{ echo \'\{/nocache\:(\\2)#(\\3)\}\'; \};'; + + preg_match_all('!('.$_tag_start.'(.*)'.$_tag_end.')!Us', + $params['compiled_content'], $_match_source, PREG_SET_ORDER); + + // no nocache-parts found: done + if (count($_match_source)==0) return; + + // convert the matched php-code to functions + $_include_compiled = "_version.", created on ".strftime("%Y-%m-%d %H:%M:%S")."\n"; + $_include_compiled .= " compiled from " . strtr(urlencode($params['resource_name']), array('%2F'=>'/', '%3A'=>':')) . " */\n\n"; + + $_compile_path = $params['include_file_path']; + + $smarty->_cache_serials[$_compile_path] = $params['cache_serial']; + $_include_compiled .= "\$this->_cache_serials['".$_compile_path."'] = '".$params['cache_serial']."';\n\n?>"; + + $_include_compiled .= $params['plugins_code']; + $_include_compiled .= "= 5.0) ? '_smarty' : 'this'; + for ($_i = 0, $_for_max = count($_match_source); $_i < $_for_max; $_i++) { + $_match =& $_match_source[$_i]; + $source = $_match[4]; + if ($this_varname == '_smarty') { + /* rename $this to $_smarty in the sourcecode */ + $tokens = token_get_all('\n"; + + $_params = array('filename' => $_compile_path, + 'contents' => $_include_compiled, 'create_dirs' => true); + + require_once(SMARTY_CORE_DIR . 'core.write_file.php'); + smarty_core_write_file($_params, $smarty); + return true; +} + + +?> diff --git a/trunk/docs/include/smarty/internals/core.write_compiled_resource.php b/trunk/docs/include/smarty/internals/core.write_compiled_resource.php new file mode 100644 index 0000000..b902eff --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.write_compiled_resource.php @@ -0,0 +1,35 @@ +compile_dir)) { + // compile_dir not writable, see if it exists + if(!@is_dir($smarty->compile_dir)) { + $smarty->trigger_error('the $compile_dir \'' . $smarty->compile_dir . '\' does not exist, or is not a directory.', E_USER_ERROR); + return false; + } + $smarty->trigger_error('unable to write to $compile_dir \'' . realpath($smarty->compile_dir) . '\'. Be sure $compile_dir is writable by the web server user.', E_USER_ERROR); + return false; + } + + $_params = array('filename' => $params['compile_path'], 'contents' => $params['compiled_content'], 'create_dirs' => true); + require_once(SMARTY_CORE_DIR . 'core.write_file.php'); + smarty_core_write_file($_params, $smarty); + return true; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/internals/core.write_file.php b/trunk/docs/include/smarty/internals/core.write_file.php new file mode 100644 index 0000000..48a3b1a --- /dev/null +++ b/trunk/docs/include/smarty/internals/core.write_file.php @@ -0,0 +1,54 @@ + $_dirname); + require_once(SMARTY_CORE_DIR . 'core.create_dir_structure.php'); + smarty_core_create_dir_structure($_params, $smarty); + } + + // write to tmp file, then rename it to avoid file locking race condition + $_tmp_file = tempnam($_dirname, 'wrt'); + + if (!($fd = @fopen($_tmp_file, 'wb'))) { + $_tmp_file = $_dirname . DIRECTORY_SEPARATOR . uniqid('wrt'); + if (!($fd = @fopen($_tmp_file, 'wb'))) { + $smarty->trigger_error("problem writing temporary file '$_tmp_file'"); + return false; + } + } + + fwrite($fd, $params['contents']); + fclose($fd); + + if (PHP_OS == 'Windows' || !@rename($_tmp_file, $params['filename'])) { + // On platforms and filesystems that cannot overwrite with rename() + // delete the file before renaming it -- because windows always suffers + // this, it is short-circuited to avoid the initial rename() attempt + @unlink($params['filename']); + @rename($_tmp_file, $params['filename']); + } + @chmod($params['filename'], $smarty->_file_perms); + + return true; +} + +/* vim: set expandtab: */ + +?> \ No newline at end of file diff --git a/trunk/docs/include/smarty/plugins/block.textformat.php b/trunk/docs/include/smarty/plugins/block.textformat.php new file mode 100644 index 0000000..8cd010a --- /dev/null +++ b/trunk/docs/include/smarty/plugins/block.textformat.php @@ -0,0 +1,103 @@ + + * Name: textformat
+ * Purpose: format text a certain way with preset styles + * or custom wrap/indent settings
+ * @link http://smarty.php.net/manual/en/language.function.textformat.php {textformat} + * (Smarty online manual) + * @param array + *
+ * Params:   style: string (email)
+ *           indent: integer (0)
+ *           wrap: integer (80)
+ *           wrap_char string ("\n")
+ *           indent_char: string (" ")
+ *           wrap_boundary: boolean (true)
+ * 
+ * @author Monte Ohrt + * @param string contents of the block + * @param Smarty clever simulation of a method + * @return string string $content re-formatted + */ +function smarty_block_textformat($params, $content, &$smarty) +{ + if (is_null($content)) { + return; + } + + $style = null; + $indent = 0; + $indent_first = 0; + $indent_char = ' '; + $wrap = 80; + $wrap_char = "\n"; + $wrap_cut = false; + $assign = null; + + foreach ($params as $_key => $_val) { + switch ($_key) { + case 'style': + case 'indent_char': + case 'wrap_char': + case 'assign': + $$_key = (string)$_val; + break; + + case 'indent': + case 'indent_first': + case 'wrap': + $$_key = (int)$_val; + break; + + case 'wrap_cut': + $$_key = (bool)$_val; + break; + + default: + $smarty->trigger_error("textformat: unknown attribute '$_key'"); + } + } + + if ($style == 'email') { + $wrap = 72; + } + + // split into paragraphs + $_paragraphs = preg_split('![\r\n][\r\n]!',$content); + $_output = ''; + + for($_x = 0, $_y = count($_paragraphs); $_x < $_y; $_x++) { + if ($_paragraphs[$_x] == '') { + continue; + } + // convert mult. spaces & special chars to single space + $_paragraphs[$_x] = preg_replace(array('!\s+!','!(^\s+)|(\s+$)!'), array(' ',''), $_paragraphs[$_x]); + // indent first line + if($indent_first > 0) { + $_paragraphs[$_x] = str_repeat($indent_char, $indent_first) . $_paragraphs[$_x]; + } + // wordwrap sentences + $_paragraphs[$_x] = wordwrap($_paragraphs[$_x], $wrap - $indent, $wrap_char, $wrap_cut); + // indent lines + if($indent > 0) { + $_paragraphs[$_x] = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraphs[$_x]); + } + } + $_output = implode($wrap_char . $wrap_char, $_paragraphs); + + return $assign ? $smarty->assign($assign, $_output) : $_output; + +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/compiler.assign.php b/trunk/docs/include/smarty/plugins/compiler.assign.php new file mode 100644 index 0000000..be17298 --- /dev/null +++ b/trunk/docs/include/smarty/plugins/compiler.assign.php @@ -0,0 +1,40 @@ + + * Name: assign
+ * Purpose: assign a value to a template variable + * @link http://smarty.php.net/manual/en/language.custom.functions.php#LANGUAGE.FUNCTION.ASSIGN {assign} + * (Smarty online manual) + * @author Monte Ohrt (initial author) + * @auther messju mohr (conversion to compiler function) + * @param string containing var-attribute and value-attribute + * @param Smarty_Compiler + */ +function smarty_compiler_assign($tag_attrs, &$compiler) +{ + $_params = $compiler->_parse_attrs($tag_attrs); + + if (!isset($_params['var'])) { + $compiler->_syntax_error("assign: missing 'var' parameter", E_USER_WARNING); + return; + } + + if (!isset($_params['value'])) { + $compiler->_syntax_error("assign: missing 'value' parameter", E_USER_WARNING); + return; + } + + return "\$this->assign({$_params['var']}, {$_params['value']});"; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.assign_debug_info.php b/trunk/docs/include/smarty/plugins/function.assign_debug_info.php new file mode 100644 index 0000000..6540498 --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.assign_debug_info.php @@ -0,0 +1,40 @@ + + * Name: assign_debug_info
+ * Purpose: assign debug info to the template
+ * @author Monte Ohrt + * @param array unused in this plugin, this plugin uses {@link Smarty::$_config}, + * {@link Smarty::$_tpl_vars} and {@link Smarty::$_smarty_debug_info} + * @param Smarty + */ +function smarty_function_assign_debug_info($params, &$smarty) +{ + $assigned_vars = $smarty->_tpl_vars; + ksort($assigned_vars); + if (@is_array($smarty->_config[0])) { + $config_vars = $smarty->_config[0]; + ksort($config_vars); + $smarty->assign("_debug_config_keys", array_keys($config_vars)); + $smarty->assign("_debug_config_vals", array_values($config_vars)); + } + + $included_templates = $smarty->_smarty_debug_info; + + $smarty->assign("_debug_keys", array_keys($assigned_vars)); + $smarty->assign("_debug_vals", array_values($assigned_vars)); + + $smarty->assign("_debug_tpls", $included_templates); +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.config_load.php b/trunk/docs/include/smarty/plugins/function.config_load.php new file mode 100644 index 0000000..db89f63 --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.config_load.php @@ -0,0 +1,142 @@ + + * Name: config_load
+ * Purpose: load config file vars + * @link http://smarty.php.net/manual/en/language.function.config.load.php {config_load} + * (Smarty online manual) + * @author Monte Ohrt + * @author messju mohr (added use of resources) + * @param array Format: + *
+ * array('file' => required config file name,
+ *       'section' => optional config file section to load
+ *       'scope' => local/parent/global
+ *       'global' => overrides scope, setting to parent if true)
+ * 
+ * @param Smarty + */ +function smarty_function_config_load($params, &$smarty) +{ + if ($smarty->debugging) { + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $_debug_start_time = smarty_core_get_microtime($_params, $smarty); + } + + $_file = isset($params['file']) ? $smarty->_dequote($params['file']) : null; + $_section = isset($params['section']) ? $smarty->_dequote($params['section']) : null; + $_scope = isset($params['scope']) ? $smarty->_dequote($params['scope']) : 'global'; + $_global = isset($params['global']) ? $smarty->_dequote($params['global']) : false; + + if (!isset($_file) || strlen($_file) == 0) { + $smarty->trigger_error("missing 'file' attribute in config_load tag", E_USER_ERROR, __FILE__, __LINE__); + } + + if (isset($_scope)) { + if ($_scope != 'local' && + $_scope != 'parent' && + $_scope != 'global') { + $smarty->trigger_error("invalid 'scope' attribute value", E_USER_ERROR, __FILE__, __LINE__); + } + } else { + if ($_global) { + $_scope = 'parent'; + } else { + $_scope = 'local'; + } + } + + $_params = array('resource_name' => $_file, + 'resource_base_path' => $smarty->config_dir, + 'get_source' => false); + $smarty->_parse_resource_name($_params); + $_file_path = $_params['resource_type'] . ':' . $_params['resource_name']; + if (isset($_section)) + $_compile_file = $smarty->_get_compile_path($_file_path.'|'.$_section); + else + $_compile_file = $smarty->_get_compile_path($_file_path); + + if($smarty->force_compile || !file_exists($_compile_file)) { + $_compile = true; + } elseif ($smarty->compile_check) { + $_params = array('resource_name' => $_file, + 'resource_base_path' => $smarty->config_dir, + 'get_source' => false); + $_compile = $smarty->_fetch_resource_info($_params) && + $_params['resource_timestamp'] > filemtime($_compile_file); + } else { + $_compile = false; + } + + if($_compile) { + // compile config file + if(!is_object($smarty->_conf_obj)) { + require_once SMARTY_DIR . $smarty->config_class . '.class.php'; + $smarty->_conf_obj = new $smarty->config_class(); + $smarty->_conf_obj->overwrite = $smarty->config_overwrite; + $smarty->_conf_obj->booleanize = $smarty->config_booleanize; + $smarty->_conf_obj->read_hidden = $smarty->config_read_hidden; + $smarty->_conf_obj->fix_newlines = $smarty->config_fix_newlines; + } + + $_params = array('resource_name' => $_file, + 'resource_base_path' => $smarty->config_dir, + $_params['get_source'] = true); + if (!$smarty->_fetch_resource_info($_params)) { + return; + } + $smarty->_conf_obj->set_file_contents($_file, $_params['source_content']); + $_config_vars = array_merge($smarty->_conf_obj->get($_file), + $smarty->_conf_obj->get($_file, $_section)); + if(function_exists('var_export')) { + $_output = ''; + } else { + $_output = ''\\\'', '\\'=>'\\\\')) . '\'); ?>'; + } + $_params = (array('compile_path' => $_compile_file, 'compiled_content' => $_output, 'resource_timestamp' => $_params['resource_timestamp'])); + require_once(SMARTY_CORE_DIR . 'core.write_compiled_resource.php'); + smarty_core_write_compiled_resource($_params, $smarty); + } else { + include($_compile_file); + } + + if ($smarty->caching) { + $smarty->_cache_info['config'][$_file] = true; + } + + $smarty->_config[0]['vars'] = @array_merge($smarty->_config[0]['vars'], $_config_vars); + $smarty->_config[0]['files'][$_file] = true; + + if ($_scope == 'parent') { + $smarty->_config[1]['vars'] = @array_merge($smarty->_config[1]['vars'], $_config_vars); + $smarty->_config[1]['files'][$_file] = true; + } else if ($_scope == 'global') { + for ($i = 1, $for_max = count($smarty->_config); $i < $for_max; $i++) { + $smarty->_config[$i]['vars'] = @array_merge($smarty->_config[$i]['vars'], $_config_vars); + $smarty->_config[$i]['files'][$_file] = true; + } + } + + if ($smarty->debugging) { + $_params = array(); + require_once(SMARTY_CORE_DIR . 'core.get_microtime.php'); + $smarty->_smarty_debug_info[] = array('type' => 'config', + 'filename' => $_file.' ['.$_section.'] '.$_scope, + 'depth' => $smarty->_inclusion_depth, + 'exec_time' => smarty_core_get_microtime($_params, $smarty) - $_debug_start_time); + } + +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.counter.php b/trunk/docs/include/smarty/plugins/function.counter.php new file mode 100644 index 0000000..1f26db5 --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.counter.php @@ -0,0 +1,80 @@ + + * Name: counter
+ * Purpose: print out a counter value + * @author Monte Ohrt + * @link http://smarty.php.net/manual/en/language.function.counter.php {counter} + * (Smarty online manual) + * @param array parameters + * @param Smarty + * @return string|null + */ +function smarty_function_counter($params, &$smarty) +{ + static $counters = array(); + + $name = (isset($params['name'])) ? $params['name'] : 'default'; + if (!isset($counters[$name])) { + $counters[$name] = array( + 'start'=>1, + 'skip'=>1, + 'direction'=>'up', + 'count'=>1 + ); + } + $counter =& $counters[$name]; + + if (isset($params['start'])) { + $counter['start'] = $counter['count'] = (int)$params['start']; + } + + if (!empty($params['assign'])) { + $counter['assign'] = $params['assign']; + } + + if (isset($counter['assign'])) { + $smarty->assign($counter['assign'], $counter['count']); + } + + if (isset($params['print'])) { + $print = (bool)$params['print']; + } else { + $print = empty($counter['assign']); + } + + if ($print) { + $retval = $counter['count']; + } else { + $retval = null; + } + + if (isset($params['skip'])) { + $counter['skip'] = $params['skip']; + } + + if (isset($params['direction'])) { + $counter['direction'] = $params['direction']; + } + + if ($counter['direction'] == "down") + $counter['count'] -= $counter['skip']; + else + $counter['count'] += $counter['skip']; + + return $retval; + +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.cycle.php b/trunk/docs/include/smarty/plugins/function.cycle.php new file mode 100644 index 0000000..fe78bb8 --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.cycle.php @@ -0,0 +1,102 @@ + + * Name: cycle
+ * Date: May 3, 2002
+ * Purpose: cycle through given values
+ * Input: + * - name = name of cycle (optional) + * - values = comma separated list of values to cycle, + * or an array of values to cycle + * (this can be left out for subsequent calls) + * - reset = boolean - resets given var to true + * - print = boolean - print var or not. default is true + * - advance = boolean - whether or not to advance the cycle + * - delimiter = the value delimiter, default is "," + * - assign = boolean, assigns to template var instead of + * printed. + * + * Examples:
+ *
+ * {cycle values="#eeeeee,#d0d0d0d"}
+ * {cycle name=row values="one,two,three" reset=true}
+ * {cycle name=row}
+ * 
+ * @link http://smarty.php.net/manual/en/language.function.cycle.php {cycle} + * (Smarty online manual) + * @author Monte Ohrt + * @author credit to Mark Priatel + * @author credit to Gerard + * @author credit to Jason Sweat + * @version 1.3 + * @param array + * @param Smarty + * @return string|null + */ +function smarty_function_cycle($params, &$smarty) +{ + static $cycle_vars; + + $name = (empty($params['name'])) ? 'default' : $params['name']; + $print = (isset($params['print'])) ? (bool)$params['print'] : true; + $advance = (isset($params['advance'])) ? (bool)$params['advance'] : true; + $reset = (isset($params['reset'])) ? (bool)$params['reset'] : false; + + if (!in_array('values', array_keys($params))) { + if(!isset($cycle_vars[$name]['values'])) { + $smarty->trigger_error("cycle: missing 'values' parameter"); + return; + } + } else { + if(isset($cycle_vars[$name]['values']) + && $cycle_vars[$name]['values'] != $params['values'] ) { + $cycle_vars[$name]['index'] = 0; + } + $cycle_vars[$name]['values'] = $params['values']; + } + + $cycle_vars[$name]['delimiter'] = (isset($params['delimiter'])) ? $params['delimiter'] : ','; + + if(is_array($cycle_vars[$name]['values'])) { + $cycle_array = $cycle_vars[$name]['values']; + } else { + $cycle_array = explode($cycle_vars[$name]['delimiter'],$cycle_vars[$name]['values']); + } + + if(!isset($cycle_vars[$name]['index']) || $reset ) { + $cycle_vars[$name]['index'] = 0; + } + + if (isset($params['assign'])) { + $print = false; + $smarty->assign($params['assign'], $cycle_array[$cycle_vars[$name]['index']]); + } + + if($print) { + $retval = $cycle_array[$cycle_vars[$name]['index']]; + } else { + $retval = null; + } + + if($advance) { + if ( $cycle_vars[$name]['index'] >= count($cycle_array) -1 ) { + $cycle_vars[$name]['index'] = 0; + } else { + $cycle_vars[$name]['index']++; + } + } + + return $retval; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.debug.php b/trunk/docs/include/smarty/plugins/function.debug.php new file mode 100644 index 0000000..4345230 --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.debug.php @@ -0,0 +1,35 @@ + + * Name: debug
+ * Date: July 1, 2002
+ * Purpose: popup debug window + * @link http://smarty.php.net/manual/en/language.function.debug.php {debug} + * (Smarty online manual) + * @author Monte Ohrt + * @version 1.0 + * @param array + * @param Smarty + * @return string output from {@link Smarty::_generate_debug_output()} + */ +function smarty_function_debug($params, &$smarty) +{ + if (isset($params['output'])) { + $smarty->assign('_smarty_debug_output', $params['output']); + } + require_once(SMARTY_CORE_DIR . 'core.display_debug_console.php'); + return smarty_core_display_debug_console(null, $smarty); +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.eval.php b/trunk/docs/include/smarty/plugins/function.eval.php new file mode 100644 index 0000000..ff0472d --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.eval.php @@ -0,0 +1,49 @@ + + * Name: eval
+ * Purpose: evaluate a template variable as a template
+ * @link http://smarty.php.net/manual/en/language.function.eval.php {eval} + * (Smarty online manual) + * @author Monte Ohrt + * @param array + * @param Smarty + */ +function smarty_function_eval($params, &$smarty) +{ + + if (!isset($params['var'])) { + $smarty->trigger_error("eval: missing 'var' parameter"); + return; + } + + if($params['var'] == '') { + return; + } + + $smarty->_compile_source('evaluated template', $params['var'], $_var_compiled); + + ob_start(); + $smarty->_eval('?>' . $_var_compiled); + $_contents = ob_get_contents(); + ob_end_clean(); + + if (!empty($params['assign'])) { + $smarty->assign($params['assign'], $_contents); + } else { + return $_contents; + } +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.fetch.php b/trunk/docs/include/smarty/plugins/function.fetch.php new file mode 100644 index 0000000..81b1bfc --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.fetch.php @@ -0,0 +1,221 @@ + + * Name: fetch
+ * Purpose: fetch file, web or ftp data and display results + * @link http://smarty.php.net/manual/en/language.function.fetch.php {fetch} + * (Smarty online manual) + * @author Monte Ohrt + * @param array + * @param Smarty + * @return string|null if the assign parameter is passed, Smarty assigns the + * result to a template variable + */ +function smarty_function_fetch($params, &$smarty) +{ + if (empty($params['file'])) { + $smarty->_trigger_fatal_error("[plugin] parameter 'file' cannot be empty"); + return; + } + + $content = ''; + if ($smarty->security && !preg_match('!^(http|ftp)://!i', $params['file'])) { + $_params = array('resource_type' => 'file', 'resource_name' => $params['file']); + require_once(SMARTY_CORE_DIR . 'core.is_secure.php'); + if(!smarty_core_is_secure($_params, $smarty)) { + $smarty->_trigger_fatal_error('[plugin] (secure mode) fetch \'' . $params['file'] . '\' is not allowed'); + return; + } + + // fetch the file + if($fp = @fopen($params['file'],'r')) { + while(!feof($fp)) { + $content .= fgets ($fp,4096); + } + fclose($fp); + } else { + $smarty->_trigger_fatal_error('[plugin] fetch cannot read file \'' . $params['file'] . '\''); + return; + } + } else { + // not a local file + if(preg_match('!^http://!i',$params['file'])) { + // http fetch + if($uri_parts = parse_url($params['file'])) { + // set defaults + $host = $server_name = $uri_parts['host']; + $timeout = 30; + $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; + $agent = "Smarty Template Engine ".$smarty->_version; + $referer = ""; + $uri = !empty($uri_parts['path']) ? $uri_parts['path'] : '/'; + $uri .= !empty($uri_parts['query']) ? '?' . $uri_parts['query'] : ''; + $_is_proxy = false; + if(empty($uri_parts['port'])) { + $port = 80; + } else { + $port = $uri_parts['port']; + } + if(!empty($uri_parts['user'])) { + $user = $uri_parts['user']; + } + if(!empty($uri_parts['pass'])) { + $pass = $uri_parts['pass']; + } + // loop through parameters, setup headers + foreach($params as $param_key => $param_value) { + switch($param_key) { + case "file": + case "assign": + case "assign_headers": + break; + case "user": + if(!empty($param_value)) { + $user = $param_value; + } + break; + case "pass": + if(!empty($param_value)) { + $pass = $param_value; + } + break; + case "accept": + if(!empty($param_value)) { + $accept = $param_value; + } + break; + case "header": + if(!empty($param_value)) { + if(!preg_match('![\w\d-]+: .+!',$param_value)) { + $smarty->_trigger_fatal_error("[plugin] invalid header format '".$param_value."'"); + return; + } else { + $extra_headers[] = $param_value; + } + } + break; + case "proxy_host": + if(!empty($param_value)) { + $proxy_host = $param_value; + } + break; + case "proxy_port": + if(!preg_match('!\D!', $param_value)) { + $proxy_port = (int) $param_value; + } else { + $smarty->_trigger_fatal_error("[plugin] invalid value for attribute '".$param_key."'"); + return; + } + break; + case "agent": + if(!empty($param_value)) { + $agent = $param_value; + } + break; + case "referer": + if(!empty($param_value)) { + $referer = $param_value; + } + break; + case "timeout": + if(!preg_match('!\D!', $param_value)) { + $timeout = (int) $param_value; + } else { + $smarty->_trigger_fatal_error("[plugin] invalid value for attribute '".$param_key."'"); + return; + } + break; + default: + $smarty->_trigger_fatal_error("[plugin] unrecognized attribute '".$param_key."'"); + return; + } + } + if(!empty($proxy_host) && !empty($proxy_port)) { + $_is_proxy = true; + $fp = fsockopen($proxy_host,$proxy_port,$errno,$errstr,$timeout); + } else { + $fp = fsockopen($server_name,$port,$errno,$errstr,$timeout); + } + + if(!$fp) { + $smarty->_trigger_fatal_error("[plugin] unable to fetch: $errstr ($errno)"); + return; + } else { + if($_is_proxy) { + fputs($fp, 'GET ' . $params['file'] . " HTTP/1.0\r\n"); + } else { + fputs($fp, "GET $uri HTTP/1.0\r\n"); + } + if(!empty($host)) { + fputs($fp, "Host: $host\r\n"); + } + if(!empty($accept)) { + fputs($fp, "Accept: $accept\r\n"); + } + if(!empty($agent)) { + fputs($fp, "User-Agent: $agent\r\n"); + } + if(!empty($referer)) { + fputs($fp, "Referer: $referer\r\n"); + } + if(isset($extra_headers) && is_array($extra_headers)) { + foreach($extra_headers as $curr_header) { + fputs($fp, $curr_header."\r\n"); + } + } + if(!empty($user) && !empty($pass)) { + fputs($fp, "Authorization: BASIC ".base64_encode("$user:$pass")."\r\n"); + } + + fputs($fp, "\r\n"); + while(!feof($fp)) { + $content .= fgets($fp,4096); + } + fclose($fp); + $csplit = split("\r\n\r\n",$content,2); + + $content = $csplit[1]; + + if(!empty($params['assign_headers'])) { + $smarty->assign($params['assign_headers'],split("\r\n",$csplit[0])); + } + } + } else { + $smarty->_trigger_fatal_error("[plugin] unable to parse URL, check syntax"); + return; + } + } else { + // ftp fetch + if($fp = @fopen($params['file'],'r')) { + while(!feof($fp)) { + $content .= fgets ($fp,4096); + } + fclose($fp); + } else { + $smarty->_trigger_fatal_error('[plugin] fetch cannot read file \'' . $params['file'] .'\''); + return; + } + } + + } + + + if (!empty($params['assign'])) { + $smarty->assign($params['assign'],$content); + } else { + return $content; + } +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.html_checkboxes.php b/trunk/docs/include/smarty/plugins/function.html_checkboxes.php new file mode 100644 index 0000000..ed8ad7f --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.html_checkboxes.php @@ -0,0 +1,143 @@ + + * Type: function
+ * Name: html_checkboxes
+ * Date: 24.Feb.2003
+ * Purpose: Prints out a list of checkbox input types
+ * Input:
+ * - name (optional) - string default "checkbox" + * - values (required) - array + * - options (optional) - associative array + * - checked (optional) - array default not set + * - separator (optional) - ie
or   + * - output (optional) - the output next to each checkbox + * - assign (optional) - assign the output as an array to this variable + * Examples: + *
+ * {html_checkboxes values=$ids output=$names}
+ * {html_checkboxes values=$ids name='box' separator='
' output=$names} + * {html_checkboxes values=$ids checked=$checked separator='
' output=$names} + *
+ * @link http://smarty.php.net/manual/en/language.function.html.checkboxes.php {html_checkboxes} + * (Smarty online manual) + * @author Christopher Kvarme + * @author credits to Monte Ohrt + * @version 1.0 + * @param array + * @param Smarty + * @return string + * @uses smarty_function_escape_special_chars() + */ +function smarty_function_html_checkboxes($params, &$smarty) +{ + require_once $smarty->_get_plugin_filepath('shared','escape_special_chars'); + + $name = 'checkbox'; + $values = null; + $options = null; + $selected = null; + $separator = ''; + $labels = true; + $output = null; + + $extra = ''; + + foreach($params as $_key => $_val) { + switch($_key) { + case 'name': + case 'separator': + $$_key = $_val; + break; + + case 'labels': + $$_key = (bool)$_val; + break; + + case 'options': + $$_key = (array)$_val; + break; + + case 'values': + case 'output': + $$_key = array_values((array)$_val); + break; + + case 'checked': + case 'selected': + $selected = array_map('strval', array_values((array)$_val)); + break; + + case 'checkboxes': + $smarty->trigger_error('html_checkboxes: the use of the "checkboxes" attribute is deprecated, use "options" instead', E_USER_WARNING); + $options = (array)$_val; + break; + + case 'assign': + break; + + default: + if(!is_array($_val)) { + $extra .= ' '.$_key.'="'.smarty_function_escape_special_chars($_val).'"'; + } else { + $smarty->trigger_error("html_checkboxes: extra attribute '$_key' cannot be an array", E_USER_NOTICE); + } + break; + } + } + + if (!isset($options) && !isset($values)) + return ''; /* raise error here? */ + + settype($selected, 'array'); + $_html_result = array(); + + if (isset($options)) { + + foreach ($options as $_key=>$_val) + $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels); + + + } else { + foreach ($values as $_i=>$_key) { + $_val = isset($output[$_i]) ? $output[$_i] : ''; + $_html_result[] = smarty_function_html_checkboxes_output($name, $_key, $_val, $selected, $extra, $separator, $labels); + } + + } + + if(!empty($params['assign'])) { + $smarty->assign($params['assign'], $_html_result); + } else { + return implode("\n",$_html_result); + } + +} + +function smarty_function_html_checkboxes_output($name, $value, $output, $selected, $extra, $separator, $labels) { + $_output = ''; + if ($labels) $_output .= ''; + $_output .= $separator; + + return $_output; +} + +?> diff --git a/trunk/docs/include/smarty/plugins/function.html_image.php b/trunk/docs/include/smarty/plugins/function.html_image.php new file mode 100644 index 0000000..9abae72 --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.html_image.php @@ -0,0 +1,142 @@ + + * Name: html_image
+ * Date: Feb 24, 2003
+ * Purpose: format HTML tags for the image
+ * Input:
+ * - file = file (and path) of image (required) + * - height = image height (optional, default actual height) + * - width = image width (optional, default actual width) + * - basedir = base directory for absolute paths, default + * is environment variable DOCUMENT_ROOT + * - path_prefix = prefix for path output (optional, default empty) + * + * Examples: {html_image file="/images/masthead.gif"} + * Output: + * @link http://smarty.php.net/manual/en/language.function.html.image.php {html_image} + * (Smarty online manual) + * @author Monte Ohrt + * @author credits to Duda - wrote first image function + * in repository, helped with lots of functionality + * @version 1.0 + * @param array + * @param Smarty + * @return string + * @uses smarty_function_escape_special_chars() + */ +function smarty_function_html_image($params, &$smarty) +{ + require_once $smarty->_get_plugin_filepath('shared','escape_special_chars'); + + $alt = ''; + $file = ''; + $height = ''; + $width = ''; + $extra = ''; + $prefix = ''; + $suffix = ''; + $path_prefix = ''; + $server_vars = ($smarty->request_use_auto_globals) ? $_SERVER : $GLOBALS['HTTP_SERVER_VARS']; + $basedir = isset($server_vars['DOCUMENT_ROOT']) ? $server_vars['DOCUMENT_ROOT'] : ''; + foreach($params as $_key => $_val) { + switch($_key) { + case 'file': + case 'height': + case 'width': + case 'dpi': + case 'path_prefix': + case 'basedir': + $$_key = $_val; + break; + + case 'alt': + if(!is_array($_val)) { + $$_key = smarty_function_escape_special_chars($_val); + } else { + $smarty->trigger_error("html_image: extra attribute '$_key' cannot be an array", E_USER_NOTICE); + } + break; + + case 'link': + case 'href': + $prefix = ''; + $suffix = ''; + break; + + default: + if(!is_array($_val)) { + $extra .= ' '.$_key.'="'.smarty_function_escape_special_chars($_val).'"'; + } else { + $smarty->trigger_error("html_image: extra attribute '$_key' cannot be an array", E_USER_NOTICE); + } + break; + } + } + + if (empty($file)) { + $smarty->trigger_error("html_image: missing 'file' parameter", E_USER_NOTICE); + return; + } + + if (substr($file,0,1) == '/') { + $_image_path = $basedir . $file; + } else { + $_image_path = $file; + } + + if(!isset($params['width']) || !isset($params['height'])) { + if(!$_image_data = @getimagesize($_image_path)) { + if(!file_exists($_image_path)) { + $smarty->trigger_error("html_image: unable to find '$_image_path'", E_USER_NOTICE); + return; + } else if(!is_readable($_image_path)) { + $smarty->trigger_error("html_image: unable to read '$_image_path'", E_USER_NOTICE); + return; + } else { + $smarty->trigger_error("html_image: '$_image_path' is not a valid image file", E_USER_NOTICE); + return; + } + } + if ($smarty->security && + ($_params = array('resource_type' => 'file', 'resource_name' => $_image_path)) && + (require_once(SMARTY_CORE_DIR . 'core.is_secure.php')) && + (!smarty_core_is_secure($_params, $smarty)) ) { + $smarty->trigger_error("html_image: (secure) '$_image_path' not in secure directory", E_USER_NOTICE); + } + + if(!isset($params['width'])) { + $width = $_image_data[0]; + } + if(!isset($params['height'])) { + $height = $_image_data[1]; + } + + } + + if(isset($params['dpi'])) { + if(strstr($server_vars['HTTP_USER_AGENT'], 'Mac')) { + $dpi_default = 72; + } else { + $dpi_default = 96; + } + $_resize = $dpi_default/$params['dpi']; + $width = round($width * $_resize); + $height = round($height * $_resize); + } + + return $prefix . ''.$alt.'' . $suffix; +} + +/* vim: set expandtab: */ + +?> diff --git a/trunk/docs/include/smarty/plugins/function.html_options.php b/trunk/docs/include/smarty/plugins/function.html_options.php new file mode 100644 index 0000000..cebadde --- /dev/null +++ b/trunk/docs/include/smarty/plugins/function.html_options.php @@ -0,0 +1,122 @@ + + * Name: html_options
+ * Input:
+ * - name (optional) - string default "select" + * - values (required if no options supplied) - array + * - options (required if no values supplied) - associative array + * - selected (optional) - string default not set + * - output (required if not options supplied) - array + * Purpose: Prints the list of
+
+ +
+
+ {if $onloadscript !="" || $set_focus_control !=""} + + {/if} + + + + diff --git a/trunk/docs/templates/getinvolved.tpl b/trunk/docs/templates/getinvolved.tpl new file mode 100644 index 0000000..f8f1130 --- /dev/null +++ b/trunk/docs/templates/getinvolved.tpl @@ -0,0 +1,23 @@ +{include file="header.tpl"} + +

PlanningAlerts.com is run by volunteers. If you would like to get involved we'd love to here from you:

+ +

I am a programmer and want to add my local authority

+

+ You can help by writing a screen scraper for your local authority that was can import into plannignalerts.com. There are only 2 criteria for the screen scraper: +

+
    +
  1. That it can output data in the following format: http://www.planningalerts.com/lambeth.xml
  2. +
  3. That it can accept a query sting in the format day=X&month=Y&year=Z
  4. +
+

+ Other than that it's up to you. It can be in any language. You can host them yourself or we can host it for you. +

+ +

I work for a local authority and would like to make our data available

+

+ The most important thing you can do is publish your data in a simple format that is freely available on the internet. Something like this is good but we can work with most formats. Please get in touch if you would like to discuss how you can help. +

+ + +{include file="footer.tpl"} \ No newline at end of file diff --git a/trunk/docs/templates/header.tpl b/trunk/docs/templates/header.tpl new file mode 100644 index 0000000..03e4713 --- /dev/null +++ b/trunk/docs/templates/header.tpl @@ -0,0 +1,37 @@ + + + + + + PlanningAlerts.com | {$page_title} + + + + + + + + +
+ Skip navigation +
+
+ +
+
+
+

PlanningAlerts.combeta

+

Email alerts of planning applications near you

+ logo +
+
+
+ {$warnings} +
+ \ No newline at end of file diff --git a/trunk/docs/templates/index.tpl b/trunk/docs/templates/index.tpl new file mode 100644 index 0000000..ad43053 --- /dev/null +++ b/trunk/docs/templates/index.tpl @@ -0,0 +1,49 @@ +{include file="header.tpl"} +
+
+ +
+

This site is in Beta (test) mode, so local authorities near you may not be included in alerts.

+
    +
  • + + +
  • +
  • + + + e.g. SW9 8JX +
  • +
  • +

    3 Choose what size area would you like to receive alerts for

    +
    + +
    +
  • +
  • +
+
+
+

Recent site updates

+
    +
  • December 2006 beta launch
  • +
+
+ +{include file="footer.tpl"} \ No newline at end of file diff --git a/trunk/docs/templates/preview.tpl b/trunk/docs/templates/preview.tpl new file mode 100644 index 0000000..ade7ae4 --- /dev/null +++ b/trunk/docs/templates/preview.tpl @@ -0,0 +1,15 @@ + + + + + PlanningAlerts.com | Alert area preview + + + + + +
+ + diff --git a/trunk/docs/templates/rss.tpl b/trunk/docs/templates/rss.tpl new file mode 100644 index 0000000..ab63d4e --- /dev/null +++ b/trunk/docs/templates/rss.tpl @@ -0,0 +1,19 @@ + + + + Search Results + + + {foreach name="applications" from="$applications" item="application"} + + {$application->council_reference} + {$application->address} + {$application->lat} {$application->lon} + {$application->description} + {$application->info_url} + {$application->comment_url} + {$application->date_received} + + {/foreach} + + diff --git a/trunk/docs/templates/unsubscribe.tpl b/trunk/docs/templates/unsubscribe.tpl new file mode 100644 index 0000000..9814cce --- /dev/null +++ b/trunk/docs/templates/unsubscribe.tpl @@ -0,0 +1,12 @@ +{include file="header.tpl"} +
+

You have been unsubscribed

+

+ You will no longer receive email alerts for any planning applications we find near {$postcode|upper} (within approximately {$alert_area_size}m). +

+

+ If you have unsubscribed because you were getting too many emails, you could always create a new alert for a smaller area? +

+
+ +{include file="footer.tpl"} \ No newline at end of file diff --git a/trunk/docs/templates/xml.tpl b/trunk/docs/templates/xml.tpl new file mode 100644 index 0000000..a6b2b6b --- /dev/null +++ b/trunk/docs/templates/xml.tpl @@ -0,0 +1,23 @@ + + + + {$authority_name|escape:html} + + + {$authority_short_name|escape:html} + + + {foreach name="applications" from="$applications" item="application"} + + {$application->council_reference|escape:html} +
{$application->address|escape:html}
+ {$application->postcode|escape:html} + {$application->description|escape:html} + {$application->info_url|escape:html} + {$application->comment_url|escape:html} + {$application->date_received|escape:html} +
+ {/foreach} +
+ +
\ No newline at end of file diff --git a/trunk/docs/test.php b/trunk/docs/test.php new file mode 100644 index 0000000..09d09e7 --- /dev/null +++ b/trunk/docs/test.php @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/trunk/docs/unsubscribe.php b/trunk/docs/unsubscribe.php new file mode 100644 index 0000000..bc1e6fe --- /dev/null +++ b/trunk/docs/unsubscribe.php @@ -0,0 +1,69 @@ +setup(); + $this->bind(); + } + + //Setup + function setup (){ + + //Grab the user + if(isset($_GET['cid'])){ + $confirm_id = $_GET['cid']; + }else{ + header("HTTP/1.0 404 Not Found"); + exit; + } + + $user = new user(); + if ($user->load_from_confirm_id($confirm_id)){ + + //Grab the postcode and area + $this->postcode = $user->postcode; + $this->alert_area_size = alert_size_to_meters($user->alert_area_size); + + //delete the user + $user->delete(); + }else{ + header("HTTP/1.0 404 Not Found"); + exit; + } + + } + + //Bind + function bind() { + + $form_action = $_SERVER['PHP_SELF']; + + //smarty + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + + $smarty->assign("menu_item", "signup"); + $smarty->assign("page_title","Unsubscribed"); + $smarty->assign("postcode", $this->postcode); + $smarty->assign("alert_area_size", $this->alert_area_size); + + //Render + $smarty->display('unsubscribed.tpl'); + + } + +} + + + +?> \ No newline at end of file diff --git a/trunk/schema/planningalerts.sql b/trunk/schema/planningalerts.sql new file mode 100644 index 0000000..b2203f5 --- /dev/null +++ b/trunk/schema/planningalerts.sql @@ -0,0 +1,277 @@ +-- MySQL Administrator dump 1.4 +-- +-- ------------------------------------------------------ +-- Server version 5.0.24-standard + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; + + +-- +-- Create schema planning +-- + +CREATE DATABASE IF NOT EXISTS planning; +USE planning; +CREATE TABLE `planning`.`application` ( + `application_id` int(11) NOT NULL auto_increment, + `council_reference` varchar(50) NOT NULL, + `address` text NOT NULL, + `postcode` varchar(10) NOT NULL, + `description` text, + `info_url` varchar(150) default NULL, + `info_tinyurl` varchar(50) default NULL, + `comment_url` varchar(150) default NULL, + `comment_tinyurl` varchar(50) default NULL, + `authority_id` int(11) NOT NULL, + `x` int(11) NOT NULL, + `y` int(11) NOT NULL, + `date_scraped` timestamp NOT NULL default CURRENT_TIMESTAMP, + `date_recieved` date default NULL, + `map_url` varchar(150) default NULL, + PRIMARY KEY (`application_id`) +) ENGINE=MyISAM AUTO_INCREMENT=972 DEFAULT CHARSET=utf8; +INSERT INTO `planning`.`application` VALUES (817,'06/04276/FUL','143 Crownstone Road\nLondon\nSW2 1NB\n','SW2 1NB','Conversion of ground floor work area within existing live work unit to a two-bedroom self contained unit with the formation of a new side acess door and installation of new windows to the side elevation at ground floor level.','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAH9NNBOO7000','http://tinyurl.com/y7wxxc','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAH9NNBOO7000','http://tinyurl.com/tfmzw',1,531064,174763,'2007-01-07 10:42:42',NULL,'http://maps.google.com/maps?q=sw21nb&z=15'), + (818,'06/04262/FUL','32 Tooting Bec Gardens\nLondon\nSW16 1RB\n','SW16 1RB','Change of Use from Guest House ( Class C1) to Residential (Class C3) with conversion of property into six self contained flats comprising 1-one bed, 1-three bed and 4-two bed units involving the excavation of the basement with front, side and rear lightwells with railings, along with the removal of side door and replacement of rear window with double doors, retention of three forecourt car parking spaces.','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAH50QBOO7000','http://tinyurl.com/y7kyz5','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAH50QBOO7000','http://tinyurl.com/vp8os',1,529861,171793,'2007-01-07 10:42:47',NULL,'http://maps.google.com/maps?q=sw161rb&z=15'), + (819,'06/04281/LDCP','19 Cleaver Square\nLondon\nSE11 4DW\n','SE11 4DW','Application for a Certificate of Lawful Development (Proposed) in respect to the Installation of new window in basement pantry, replacement of existing staircase to basement, excavation of part of the retaining wall to provide a bin storage area at the front of the property at basement level, removal of window in utility room at basement level, underpinning existing utility room walls and brickwork to be cleaned, excavation of existing outhouse floor and area of rear garden to make level with existing basement floor, lowering of existing utility room door at basement level, form opening in existing dining room wall, erection of basement level single storey extension to enclose existing outhouse and area of rear garden, replacement of basement utility room roof covering with plasterboard ceiling, existing parapet to be retained and repointed, replacement of existing window on the rear elevation at first floor level, removal of MDF shutters on front and rear windows at first floor level, demolition of existing partition and new layout to bathroom, removal of window and lintel on the rear elevation at first floor level and replacement with a new frameless window, relocation of original door to storage in master bedroom, demolition of existing partition to storage in master bedroom, installation of new ensuite bathroom, and construction of partition to storage areas, installation of rear dormer and a rooflight and removal of existing rooflight, installation of new services and central heating to first and second floors, reconstruction of parts of the existing roof to match existing and removal of pipes on the rear elevation and installation of new pipes.\n','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAHCOEBON5000','http://tinyurl.com/y9cda7','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAHCOEBON5000','http://tinyurl.com/y2feqq',1,531396,178197,'2007-01-07 10:42:57',NULL,'http://maps.google.com/maps?q=se114dw&z=15'); +INSERT INTO `planning`.`application` VALUES (820,'06/03952/LB','19 Cleaver Square\nLondon\nSE11 4DW\n','SE11 4DW','Installation of new window in basement pantry, replacement of existing staircase to basement, excavation of part of the retaining wall to provide a bin storage area at the front of the property at basement level, removal of window in utility room at basement level, underpinning existing utility room walls and brickwork to be cleaned, excavation of existing outhouse floor and area of rear garden to make level with existing basement floor, lowering of existing utility room door at basement level, form opening in existing dining room wall, erection of basement level single storey extension to enclose existing outhouse and area of rear garden, replacement of basement utility room roof covering with plasterboard ceiling, existing parapet to be retained and repointed, replacement of existing window on the rear elevation at first floor level, removal of MDF shutters on front and rear windows at first floor level, demolition of existing partition and new layout to bathroom, removal of window and lintel on the rear elevation at first floor level and replacement with a new frameless window, relocation of original door to storage in master bedroom, demolition of existing partition to storage in master bedroom, installation of new ensuite bathroom, and construction of partition to storage areas, installation of rear dormer and a rooflight and removal of existing rooflight, installation of new services and central heating to first and second floors, reconstruction of parts of the existing roof to match existing and removal of pipes on the rear elevation and installation of new pipes.\n','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=J954EPBOQ8000','http://tinyurl.com/u5chk','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=J954EPBOQ8000','http://tinyurl.com/yxn3yo',1,531396,178197,'2007-01-07 10:43:06',NULL,'http://maps.google.com/maps?q=se114dw&z=15'), + (821,'06/03660/FUL','11 Greyhound Lane\nLondon\nSW16 5NP\n','SW16 5NP','Change of use of property from Retail (Use Class A1) to Take away (Use Class A5) along with installation of extract flue to rear elevation.','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=J81NJBBOL8000','http://tinyurl.com/vyzdl','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=J81NJBBOL8000','http://tinyurl.com/y53wet',1,529813,170531,'2007-01-07 10:43:17',NULL,'http://maps.google.com/maps?q=sw165np&z=15'); +INSERT INTO `planning`.`application` VALUES (822,'081803/FH/2007/S2','23 Dalston Drive\nDidsbury\nManchester\nM20 5LD\n','M20 5LD','Erection of first floor rear extension to form additional living accommodation','http://www.publicaccess.manchester.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8LQ6BC70000','http://tinyurl.com/t3o3p','http://www.publicaccess.manchester.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8LQ6BC70000','http://tinyurl.com/y3655x',2,385038,390770,'2007-01-07 10:44:06',NULL,'http://maps.google.com/maps?q=m205ld&z=15'), + (823,'DC/07/0010','7 Pound Lane\nUpper Beeding\nSteyning\nWest Sussex\nBN44 3JF','BN44 3JF','2-Storey side extension','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAW2ZIJA0000','http://tinyurl.com/yxucpg','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAW2ZIJA0000','http://tinyurl.com/y7wqqo',4,519641,110667,'2007-01-07 10:44:34',NULL,'http://maps.google.com/maps?q=bn443jf&z=15'), + (824,'DC/06/2965','Villa Adagio\nNewhouse Lane\nStorrington\nPulborough\nWest Sussex\nRH20 3HQ\n','RH20 3HQ','Single storey extension to kitchen and garage','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAGRH4IJK0000','http://tinyurl.com/yl8gx6','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAGRH4IJK0000','http://tinyurl.com/ycdn6t',4,511292,115229,'2007-01-07 10:44:42',NULL,'http://maps.google.com/maps?q=rh203hq&z=15'); +INSERT INTO `planning`.`application` VALUES (825,'DC/06/2910','Bradstock\nOak Lane\nBroadbridge Heath\nHorsham\nWest Sussex\nRH12 3NX','RH12 3NX','Erection of detached garage','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9WPN6IJK0000','http://tinyurl.com/yjg484','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9WPN6IJK0000','http://tinyurl.com/yfvdox',4,515083,131184,'2007-01-07 10:44:48',NULL,'http://maps.google.com/maps?q=rh123nx&z=15'), + (826,'07/00004/FUL','2 Fountain Cottages \nHigh Beech Lane\nLindfield\nHaywards Heath\nWest Sussex\nRH16 1XX','RH16 1XX','Two storey side extension and single storey rear extension','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JBA9I2KT32000','http://tinyurl.com/ybw3pc','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBA9I2KT32000','http://tinyurl.com/yfgr9g',5,533949,126863,'2007-01-07 10:45:17',NULL,'http://maps.google.com/maps?q=rh161xx&z=15'), + (827,'06/02733/FUL','61 Cuckfield Road\nHurstpierpoint\nHassocks\nWest Sussex\nBN6 9RR','BN6 9RR','Single storey front extension and existing main roof extended to form new gable roof. Pitched dormer window to rear elevation.','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JAB6MMKT32000','http://tinyurl.com/ymj8ao','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAB6MMKT32000','http://tinyurl.com/yb2utb',5,528009,116919,'2007-01-07 10:45:26',NULL,'http://maps.google.com/maps?q=bn69rr&z=15'); +INSERT INTO `planning`.`application` VALUES (828,'06/02182/FUL','12 The Oaks\nEast Grinstead\nWest Sussex\nRH19 3QJ','RH19 3QJ','Proposed first floor extension over garage and new rear conservatory','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=J70S1IKT32000','http://tinyurl.com/yekldf','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J70S1IKT32000','http://tinyurl.com/yln2en',5,540492,137920,'2007-01-07 10:45:37',NULL,'http://maps.google.com/maps?q=rh193qj&z=15'), + (829,'06/01111/FUL','89 Downhall Road\nRayleigh\nEssex\nSS6 9JX','SS6 9JX','Demolition of Existing Property and Creation of One Block Containing 7 No. Flats and 1 No. Detached Dwelling House. Vehicular Access to the Flats off Lakeside to a Car Park Court (10 Spaces) Vehicular Access to Dwelling House off Lakeside (2 Spaces)','http://62.173.68.168/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAVQSBNC96000','http://tinyurl.com/yevjg3','http://62.173.68.168/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAVQSBNC96000','http://tinyurl.com/ymyxhb',8,580625,191717,'2007-01-07 10:46:16',NULL,'http://maps.google.com/maps?q=ss69jx&z=15'), + (830,'06/01075/FUL','64 Louis Drive\nRayleigh\nEssex\nSS6 9DX\n','SS6 9DX','Hip to Gable Roof Extension to Create Rooms in the Roof Incorporating 2 No. Pitched Roofed Dormers and Rooflight to Front and Flat Roofed Dormer to Rear.','http://62.173.68.168/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JA3XVMNCS7000','http://tinyurl.com/ye2dk2','http://62.173.68.168/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JA3XVMNCS7000','http://tinyurl.com/yl4rla',8,579451,191475,'2007-01-07 10:46:21',NULL,'http://maps.google.com/maps?q=ss69dx&z=15'); +INSERT INTO `planning`.`application` VALUES (831,'07/00011/TPO','Slade House And Abell House\nSlade Hospital\nHorspath Driftway\nOxford\nOxfordshire\nOX3 7JH\n','OX3 7JH','Fell x 1 prunus and other tree works as specified in Nicholson Report of August 2006 at Slade and Abell House subject to Oxford City Council - Horspath Driftway, Eastern Bypass (No. 1) TPO 1994','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBARP2MF58000','http://tinyurl.com/yg9bql','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBARP2MF58000','http://tinyurl.com/ynbprt',9,455423,205276,'2007-01-07 10:48:37',NULL,'http://maps.google.com/maps?q=ox37jh&z=15'), + (832,'07/00010/CAT','7 Arthur Garrard Close\nOxford\nOxfordshire\nOX2 6EU','OX2 6EU','Prune eucalyptus tree to give 2.5 m clearance to adjacent buildings and crown lift by removal of x 5 lateral branches at 7 Arthur Garrard Close in the Walton Street Conservation Area','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAQLQMF58000','http://tinyurl.com/ygv7vu','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAQLQMF58000','http://tinyurl.com/yddheq',9,450778,207342,'2007-01-07 10:48:45',NULL,'http://maps.google.com/maps?q=ox26eu&z=15'), + (833,'07/00008/CAT','100 Temple Road\nOxford\nOxfordshire\nOX4 2EZ','OX4 2EZ','Prune x 2 mature apple trees at 100 Temple Road in the Temple Cowley Conservation Area','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAKENMF58000','http://tinyurl.com/ybl5mo','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAKENMF58000','http://tinyurl.com/yznjnw',9,454493,204487,'2007-01-07 10:48:49',NULL,'http://maps.google.com/maps?q=ox42ez&z=15'); +INSERT INTO `planning`.`application` VALUES (834,'07/00006/FUL','17 The Grates\nOxford\nOxfordshire\nOX4 3LJ','OX4 3LJ','Single and two storey rear extension, loft conversion with roof dormers to side and rear','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAE34MF24000','http://tinyurl.com/yaowqn','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAE34MF24000','http://tinyurl.com/yhrdrv',9,454172,203808,'2007-01-07 10:48:59',NULL,'http://maps.google.com/maps?q=ox43lj&z=15'), + (835,'07/00005/FUL','270 Iffley Road\nOxford\nOxfordshire\nOX4 4AA','OX4 4AA','Two storey side, and two and single storey rear extension with decking','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBACOBMF24000','http://tinyurl.com/ye9nar','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBACOBMF24000','http://tinyurl.com/ylc4c2',9,452835,204862,'2007-01-07 10:49:09',NULL,'http://maps.google.com/maps?q=ox44aa&z=15'), + (836,'07/00004/CT3','Regal Community Centre\nRidgefield Road\nOxford\nOxfordshire\nOX4 3BY\n','OX4 3BY','Erection of 1.2m fence and gates','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB90SYMF24000','http://tinyurl.com/yg222t','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB90SYMF24000','http://tinyurl.com/yn4g44',9,453513,205031,'2007-01-07 10:49:14',NULL,'http://maps.google.com/maps?q=ox43by&z=15'); +INSERT INTO `planning`.`application` VALUES (837,'07/00003/FUL','2 Maidcroft Road\nOxford\nOxfordshire\nOX4 3EN','OX4 3EN','Two storey rear and single storey side extension','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB938OMFWC000','http://tinyurl.com/yaadc6','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB938OMFWC000','http://tinyurl.com/yhcrde',9,453976,204328,'2007-01-07 10:49:35',NULL,'http://maps.google.com/maps?q=ox43en&z=15'), + (838,'07/00001/FUL','35 Southfield Road\nOxford\nOxfordshire\nOX4 1NX','OX4 1NX','Single storey rear extension','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB900VMFWC000','http://tinyurl.com/ykuhuz','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB900VMFWC000','http://tinyurl.com/yj565g',9,453336,205542,'2007-01-07 10:49:38',NULL,'http://maps.google.com/maps?q=ox41nx&z=15'), + (839,'06/02639/FUL','11 William Street\nOxford\nOxfordshire\nOX3 0ES\n','OX3 0ES','Single storey rear conservatory extension','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAOKV3MF24000','http://tinyurl.com/yfkflc','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAOKV3MF24000','http://tinyurl.com/ymmtmk',9,453002,207044,'2007-01-07 10:49:52',NULL,'http://maps.google.com/maps?q=ox30es&z=15'); +INSERT INTO `planning`.`application` VALUES (840,'07/00011/TPO','Slade House And Abell House\nSlade Hospital\nHorspath Driftway\nOxford\nOxfordshire\nOX3 7JH\n','OX3 7JH','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBARP2MF58000','http://tinyurl.com/ucbm9','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBARP2MF58000','http://tinyurl.com/y4epnh',11,455423,205276,'2007-01-07 10:51:09',NULL,'http://maps.google.com/maps?q=ox37jh&z=15'), + (841,'07/00010/CAT','7 Arthur Garrard Close\nOxford\nOxfordshire\nOX2 6EU','OX2 6EU','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAQLQMF58000','http://tinyurl.com/yxu26d','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAQLQMF58000','http://tinyurl.com/y7wg7l',11,450778,207342,'2007-01-07 10:51:14',NULL,'http://maps.google.com/maps?q=ox26eu&z=15'), + (842,'07/00008/CAT','100 Temple Road\nOxford\nOxfordshire\nOX4 2EZ','OX4 2EZ','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAKENMF58000','http://tinyurl.com/tg5q8','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAKENMF58000','http://tinyurl.com/yxxtac',11,454493,204487,'2007-01-07 10:51:19',NULL,'http://maps.google.com/maps?q=ox42ez&z=15'), + (843,'07/00006/FUL','17 The Grates\nOxford\nOxfordshire\nOX4 3LJ','OX4 3LJ','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAE34MF24000','http://tinyurl.com/y73abk','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAE34MF24000','http://tinyurl.com/v8hza',11,454172,203808,'2007-01-07 10:51:31',NULL,'http://maps.google.com/maps?q=ox43lj&z=15'); +INSERT INTO `planning`.`application` VALUES (844,'07/00005/FUL','270 Iffley Road\nOxford\nOxfordshire\nOX4 4AA','OX4 4AA','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBACOBMF24000','http://tinyurl.com/tyjbo','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBACOBMF24000','http://tinyurl.com/y34xcw',11,452835,204862,'2007-01-07 10:51:38',NULL,'http://maps.google.com/maps?q=ox44aa&z=15'), + (845,'07/00004/CT3','Regal Community Centre\nRidgefield Road\nOxford\nOxfordshire\nOX4 3BY\n','OX4 3BY','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB90SYMF24000','http://tinyurl.com/uqv3q','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB90SYMF24000','http://tinyurl.com/y4tc5y',11,453513,205031,'2007-01-07 10:51:41',NULL,'http://maps.google.com/maps?q=ox43by&z=15'), + (846,'07/00003/FUL','2 Maidcroft Road\nOxford\nOxfordshire\nOX4 3EN','OX4 3EN','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB938OMFWC000','http://tinyurl.com/y8e2m5','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB938OMFWC000','http://tinyurl.com/tup7p',11,453976,204328,'2007-01-07 10:51:48',NULL,'http://maps.google.com/maps?q=ox43en&z=15'), + (847,'07/00001/FUL','35 Southfield Road\nOxford\nOxfordshire\nOX4 1NX','OX4 1NX','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB900VMFWC000','http://tinyurl.com/sgbzb','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB900VMFWC000','http://tinyurl.com/wy22f',11,453336,205542,'2007-01-07 10:52:25',NULL,'http://maps.google.com/maps?q=ox41nx&z=15'); +INSERT INTO `planning`.`application` VALUES (848,'06/02639/FUL','11 William Street\nOxford\nOxfordshire\nOX3 0ES\n','OX3 0ES','','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAOKV3MF24000','http://tinyurl.com/u8nad','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAOKV3MF24000','http://tinyurl.com/y4b4bl',11,453002,207044,'2007-01-07 10:52:33',NULL,'http://maps.google.com/maps?q=ox30es&z=15'), + (849,'07/00003/FUL','43 Norman Road\nDartford\nKent\nDA1 1LE','DA1 1LE','Installation of a wind turbine at roof level on rear elevation together with provision of 2 solar panels on front roof elevation','http://publicaccess.dartford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAD15BQL0000','http://tinyurl.com/ykglyp','http://publicaccess.dartford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAD15BQL0000','http://tinyurl.com/ybls8f',18,554186,173134,'2007-01-07 10:53:41',NULL,'http://maps.google.com/maps?q=da11le&z=15'), + (850,'06/01396/FUL','11 Lonsdale Crescent\nDarenth\nKent\nDA2 6LQ','DA2 6LQ','Erection of a rear conservatory','http://publicaccess.dartford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAGVIEBQG3000','http://tinyurl.com/yzo99n','http://publicaccess.dartford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAGVIEBQG3000','http://tinyurl.com/yf6zpj',18,556434,173084,'2007-01-07 10:53:45',NULL,'http://maps.google.com/maps?q=da26lq&z=15'); +INSERT INTO `planning`.`application` VALUES (851,'1/0006/2007/AGR','East Wonford Farm\nMilton Damerel\nHolsworthy\nEX22 7DP\n','EX22 7DP','Erection of small building for water storage tank','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8XL1QKP1000','http://tinyurl.com/y9m6pl','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8XL1QKP1000','http://tinyurl.com/ygokqt',19,237960,109626,'2007-01-07 10:54:18',NULL,'http://maps.google.com/maps?q=ex227dp&z=15'), + (852,'1/0001/2007/AGR','Mambury Farm\nEast Putford\nHolsworthy\nEX22 7UH','EX22 7UH','3 bay extension to existing cubicle house for dairy cows','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8W8AQKP1000','http://tinyurl.com/yk9bbx','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8W8AQKP1000','http://tinyurl.com/ybezhn',19,237735,116099,'2007-01-07 10:54:21',NULL,'http://maps.google.com/maps?q=ex227uh&z=15'), + (853,'1/2583/2006/FUL','45 North Street\nNortham\nBideford\nEX39 1DH','EX39 1DH','Demolition of garage, erection of single storey extension to the side and rear, erection of first floor shower room over kitchen','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9UTSFQKP1000','http://tinyurl.com/ymyn32','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9UTSFQKP1000','http://tinyurl.com/yazclm',19,245081,129132,'2007-01-07 10:54:29',NULL,'http://maps.google.com/maps?q=ex391dh&z=15'); +INSERT INTO `planning`.`application` VALUES (854,'1/2386/2006/FUL','Rowan Park Farm\nAshwater\nBeaworthy\nEX21 5DJ','EX21 5DJ','Free range poultry unit for laying hens','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J8DDLNQKP1000','http://tinyurl.com/ye335q','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J8DDLNQKP1000','http://tinyurl.com/yl5h7y',19,239571,99497,'2007-01-07 10:54:32',NULL,'http://maps.google.com/maps?q=ex215dj&z=15'), + (855,'07/0008/FULL','45 Rectory Road\nWorthing\nWest Sussex\nBN14 7PE','BN14 7PE','Single story extension to north elevation and roof extensions including half end gables to south and north elevations, three dormers to the west elevation and a cottage dormer to the east elevation.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8Q4QRZB0000','http://tinyurl.com/yzdb72','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8Q4QRZB0000','http://tinyurl.com/yeskmv',21,513353,104132,'2007-01-07 10:55:50',NULL,'http://maps.google.com/maps?q=bn147pe&z=15'), + (856,'07/0004/FULL','39 Trent Road\nWorthing\nWest Sussex\nBN12 4EJ','BN12 4EJ','Single storey front extension.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8P21RZB0000','http://tinyurl.com/yaeo9q','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8P21RZB0000','http://tinyurl.com/yhh5by',21,512296,102855,'2007-01-07 10:55:54',NULL,'http://maps.google.com/maps?q=bn124ej&z=15'); +INSERT INTO `planning`.`application` VALUES (857,'06/1326/CLE','11 Gordon Road\nWorthing\nWest Sussex\nBN11 1DB','BN11 1DB','Application for a Certificate of Lawfulness for an existing rear extension and conservatory.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9HL77RZB0000','http://tinyurl.com/ydwequ','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9HL77RZB0000','http://tinyurl.com/ylyss5',21,514858,103144,'2007-01-07 10:55:57',NULL,'http://maps.google.com/maps?q=bn111db&z=15'), + (858,'06/1324/FULL','52-54 Tarring Road\nWorthing\nWest Sussex\nBN11 4ET','BN11 4ET','Construction of a block of 3 flats and a cottage at the rear of 52 - 54 Tarring Road (in conjunction with approved use of existing property as 2 flats). (WB/05/1112/ARM refers).','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9E1D6RZB0000','http://tinyurl.com/yfoqzw','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9E1D6RZB0000','http://tinyurl.com/ymr7k7',21,514227,103288,'2007-01-07 10:56:12',NULL,'http://maps.google.com/maps?q=bn114et&z=15'), + (859,'06/0641/LBC','Courtlands\nParklands Avenue\nWorthing\nWest Sussex\nBN12 4NQ','BN12 4NQ','Application for Listed Building Consent for a two storey extension to north elevation with single storey link to existing office building, additional car parking and landscaping.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J0HQO4RZB0000','http://tinyurl.com/ydhscd','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J0HQO4RZB0000','http://tinyurl.com/ybpgkb',21,0,0,'2007-01-07 10:56:16',NULL,'http://maps.google.com/maps?q=bn124nq&z=15'); +INSERT INTO `planning`.`application` VALUES (860,'06/0640/FULL','Courtlands\nParklands Avenue\nWorthing\nWest Sussex\nBN12 4NQ','BN12 4NQ','Two storey extension to north elevation with single storey link to existing office building, additional car parking and landscaping.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J0HQJMRZB0000','http://tinyurl.com/yfa74f','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J0HQJMRZB0000','http://tinyurl.com/ymcl5n',21,0,0,'2007-01-07 10:56:23',NULL,'http://maps.google.com/maps?q=bn124nq&z=15'), + (861,'07/00020/PLF','5 Farndale Road\nBridlington\nEast Riding Of Yorkshire\nYO16 6GT\n','YO16 6GT','Erection of conservatory to rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8WXIBJAH000','http://tinyurl.com/yg6dxg','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8WXIBJAH000','http://tinyurl.com/y9ps4h',22,518437,469322,'2007-01-07 10:57:05',NULL,'http://maps.google.com/maps?q=yo166gt&z=15'), + (862,'07/00017/PLF','Caracassa\nMain Road\nCamerton\nEast Riding Of Yorkshire\nHU12 9NG\n','HU12 9NG','Erection of conservatory to rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8VARBJAH000','http://tinyurl.com/ykcwmt','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8VARBJAH000','http://tinyurl.com/ybz6tj',22,521308,426261,'2007-01-07 10:57:09',NULL,'http://maps.google.com/maps?q=hu129ng&z=15'); +INSERT INTO `planning`.`application` VALUES (863,'07/00011/PLF','9 Hornsea Burton Road\nHornsea\nEast Riding Of Yorkshire\nHU18 1TP\n','HU18 1TP','Erection of single storey extension and detached garage at rear and construction of dormer window in roof at rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8QJIBJ0I000','http://tinyurl.com/yzkkur','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8QJIBJ0I000','http://tinyurl.com/yf2udn',22,520626,447211,'2007-01-07 10:57:15',NULL,'http://maps.google.com/maps?q=hu181tp&z=15'), + (864,'07/00009/PLF','7 Craikewells\nFlamborough\nEast Riding Of Yorkshire\nYO15 1QH\n','YO15 1QH','Erection of single storey extension to rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8PEUBJAH000','http://tinyurl.com/ye6ohm','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8PEUBJAH000','http://tinyurl.com/yl95zu',22,522628,470908,'2007-01-07 10:57:20',NULL,'http://maps.google.com/maps?q=yo151qh&z=15'), + (865,'07/00008/PLF','75 Butterfly Meadows\nMolescroft\nEast Riding Of Yorkshire\nHU17 9GB\n','HU17 9GB','Erection of a two storey extension to side and alterations to vehicular access (Re submission 06/06916/PLF)','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8PENBJ0I000','http://tinyurl.com/ycecpk','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8PENBJ0I000','http://tinyurl.com/yjgqqs',22,503235,440957,'2007-01-07 10:57:24',NULL,'http://maps.google.com/maps?q=hu179gb&z=15'); +INSERT INTO `planning`.`application` VALUES (866,'07/00007/PLF','4 Standage Road\nThorngumbald\nEast Riding Of Yorkshire\nHU12 9PH','HU12 9PH','Erection of single storey extension to rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8OLOBJAH000','http://tinyurl.com/ynyhaw','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8OLOBJAH000','http://tinyurl.com/ya7ejy',22,520733,426150,'2007-01-07 10:57:28',NULL,'http://maps.google.com/maps?q=hu129ph&z=15'), + (867,'06/10035/PLF','18 Highfield\nWithernsea\nEast Riding Of Yorkshire\nHU19 2LL\n','HU19 2LL','Erection of conservatory to the rear, retention of metal fencing and gates to front and side','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JAH6R6BJDI000','http://tinyurl.com/yh9sk9','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JAH6R6BJDI000','http://tinyurl.com/ykrj4d',22,534277,427224,'2007-01-07 10:57:34',NULL,'http://maps.google.com/maps?q=hu192ll&z=15'), + (868,'06/10025/PLF','73 All Hallows Road\nWalkington\nEast Riding Of Yorkshire\nHU17 8SH','HU17 8SH','Erection of garden shed/workshop at the rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JAGXYEBJDI000','http://tinyurl.com/ybwqb3','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JAGXYEBJDI000','http://tinyurl.com/yj27cb',22,499962,436956,'2007-01-07 10:57:40',NULL,'http://maps.google.com/maps?q=hu178sh&z=15'); +INSERT INTO `planning`.`application` VALUES (869,'06/09981/PLF','The Paddock\n35 Northfield Road\nWetwang\nEast Riding Of Yorkshire\nYO25 9XY\n','YO25 9XY','Erection of dormer windows at front and rear and pitched roof over garage','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JAB9YXBJDI000','http://tinyurl.com/ymjutf','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JAB9YXBJDI000','http://tinyurl.com/yz6yga',22,493048,459140,'2007-01-07 10:57:45',NULL,'http://maps.google.com/maps?q=yo259xy&z=15'), + (870,'06/09882/PLF','27 Southgate\nHutton Cranswick\nEast Riding Of Yorkshire\nYO25 9QX','YO25 9QX','Erection of single storey extension at rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JA7HY3BJDI000','http://tinyurl.com/yjvd8c','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JA7HY3BJDI000','http://tinyurl.com/ygdmo8',22,502566,451965,'2007-01-07 10:57:52',NULL,'http://maps.google.com/maps?q=yo259qx&z=15'), + (871,'06/04276/FUL','143 Crownstone Road\nLondon\nSW2 1NB\n','SW2 1NB','Conversion of ground floor work area within existing live work unit to a two-bedroom self contained unit with the formation of a new side acess door and installation of new windows to the side elevation at ground floor level.','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAH9NNBOO7000','http://tinyurl.com/y7wxxc','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAH9NNBOO7000','http://tinyurl.com/tfmzw',1,531064,174763,'2007-01-07 16:59:55',NULL,'http://maps.google.com/maps?q=sw21nb&z=15'); +INSERT INTO `planning`.`application` VALUES (872,'06/04262/FUL','32 Tooting Bec Gardens\nLondon\nSW16 1RB\n','SW16 1RB','Change of Use from Guest House ( Class C1) to Residential (Class C3) with conversion of property into six self contained flats comprising 1-one bed, 1-three bed and 4-two bed units involving the excavation of the basement with front, side and rear lightwells with railings, along with the removal of side door and replacement of rear window with double doors, retention of three forecourt car parking spaces.','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAH50QBOO7000','http://tinyurl.com/y7kyz5','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAH50QBOO7000','http://tinyurl.com/vp8os',1,529861,171793,'2007-01-07 17:00:06',NULL,'http://maps.google.com/maps?q=sw161rb&z=15'), + (873,'06/04281/LDCP','19 Cleaver Square\nLondon\nSE11 4DW\n','SE11 4DW','Application for a Certificate of Lawful Development (Proposed) in respect to the Installation of new window in basement pantry, replacement of existing staircase to basement, excavation of part of the retaining wall to provide a bin storage area at the front of the property at basement level, removal of window in utility room at basement level, underpinning existing utility room walls and brickwork to be cleaned, excavation of existing outhouse floor and area of rear garden to make level with existing basement floor, lowering of existing utility room door at basement level, form opening in existing dining room wall, erection of basement level single storey extension to enclose existing outhouse and area of rear garden, replacement of basement utility room roof covering with plasterboard ceiling, existing parapet to be retained and repointed, replacement of existing window on the rear elevation at first floor level, removal of MDF shutters on front and rear windows at first floor level, demolition of existing partition and new layout to bathroom, removal of window and lintel on the rear elevation at first floor level and replacement with a new frameless window, relocation of original door to storage in master bedroom, demolition of existing partition to storage in master bedroom, installation of new ensuite bathroom, and construction of partition to storage areas, installation of rear dormer and a rooflight and removal of existing rooflight, installation of new services and central heating to first and second floors, reconstruction of parts of the existing roof to match existing and removal of pipes on the rear elevation and installation of new pipes.\n','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAHCOEBON5000','http://tinyurl.com/y9cda7','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAHCOEBON5000','http://tinyurl.com/y2feqq',1,531396,178197,'2007-01-07 17:00:13',NULL,'http://maps.google.com/maps?q=se114dw&z=15'), + (874,'06/03952/LB','19 Cleaver Square\nLondon\nSE11 4DW\n','SE11 4DW','Installation of new window in basement pantry, replacement of existing staircase to basement, excavation of part of the retaining wall to provide a bin storage area at the front of the property at basement level, removal of window in utility room at basement level, underpinning existing utility room walls and brickwork to be cleaned, excavation of existing outhouse floor and area of rear garden to make level with existing basement floor, lowering of existing utility room door at basement level, form opening in existing dining room wall, erection of basement level single storey extension to enclose existing outhouse and area of rear garden, replacement of basement utility room roof covering with plasterboard ceiling, existing parapet to be retained and repointed, replacement of existing window on the rear elevation at first floor level, removal of MDF shutters on front and rear windows at first floor level, demolition of existing partition and new layout to bathroom, removal of window and lintel on the rear elevation at first floor level and replacement with a new frameless window, relocation of original door to storage in master bedroom, demolition of existing partition to storage in master bedroom, installation of new ensuite bathroom, and construction of partition to storage areas, installation of rear dormer and a rooflight and removal of existing rooflight, installation of new services and central heating to first and second floors, reconstruction of parts of the existing roof to match existing and removal of pipes on the rear elevation and installation of new pipes.\n','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=J954EPBOQ8000','http://tinyurl.com/u5chk','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=J954EPBOQ8000','http://tinyurl.com/yxn3yo',1,531396,178197,'2007-01-07 17:00:18',NULL,'http://maps.google.com/maps?q=se114dw&z=15'); +INSERT INTO `planning`.`application` VALUES (875,'06/03660/FUL','11 Greyhound Lane\nLondon\nSW16 5NP\n','SW16 5NP','Change of use of property from Retail (Use Class A1) to Take away (Use Class A5) along with installation of extract flue to rear elevation.','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=J81NJBBOL8000','http://tinyurl.com/vyzdl','http://planning.lambeth.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=J81NJBBOL8000','http://tinyurl.com/y53wet',1,529813,170531,'2007-01-07 17:00:25',NULL,'http://maps.google.com/maps?q=sw165np&z=15'), + (876,'081803/FH/2007/S2','23 Dalston Drive\nDidsbury\nManchester\nM20 5LD\n','M20 5LD','Erection of first floor rear extension to form additional living accommodation','http://www.publicaccess.manchester.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8LQ6BC70000','http://tinyurl.com/t3o3p','http://www.publicaccess.manchester.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8LQ6BC70000','http://tinyurl.com/y3655x',2,385038,390770,'2007-01-07 17:01:17',NULL,'http://maps.google.com/maps?q=m205ld&z=15'), + (877,'DC/07/0010','7 Pound Lane\nUpper Beeding\nSteyning\nWest Sussex\nBN44 3JF','BN44 3JF','2-Storey side extension','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAW2ZIJA0000','http://tinyurl.com/yxucpg','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAW2ZIJA0000','http://tinyurl.com/y7wqqo',4,519641,110667,'2007-01-07 17:01:42',NULL,'http://maps.google.com/maps?q=bn443jf&z=15'); +INSERT INTO `planning`.`application` VALUES (878,'DC/06/2965','Villa Adagio\nNewhouse Lane\nStorrington\nPulborough\nWest Sussex\nRH20 3HQ\n','RH20 3HQ','Single storey extension to kitchen and garage','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAGRH4IJK0000','http://tinyurl.com/yl8gx6','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAGRH4IJK0000','http://tinyurl.com/ycdn6t',4,511292,115229,'2007-01-07 17:01:48',NULL,'http://maps.google.com/maps?q=rh203hq&z=15'), + (879,'DC/06/2910','Bradstock\nOak Lane\nBroadbridge Heath\nHorsham\nWest Sussex\nRH12 3NX','RH12 3NX','Erection of detached garage','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9WPN6IJK0000','http://tinyurl.com/yjg484','http://publicaccess.horsham.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9WPN6IJK0000','http://tinyurl.com/yfvdox',4,515083,131184,'2007-01-07 17:01:53',NULL,'http://maps.google.com/maps?q=rh123nx&z=15'), + (880,'07/00004/FUL','2 Fountain Cottages \nHigh Beech Lane\nLindfield\nHaywards Heath\nWest Sussex\nRH16 1XX','RH16 1XX','Two storey side extension and single storey rear extension','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JBA9I2KT32000','http://tinyurl.com/ybw3pc','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBA9I2KT32000','http://tinyurl.com/yfgr9g',5,533949,126863,'2007-01-07 17:02:16',NULL,'http://maps.google.com/maps?q=rh161xx&z=15'); +INSERT INTO `planning`.`application` VALUES (881,'06/02733/FUL','61 Cuckfield Road\nHurstpierpoint\nHassocks\nWest Sussex\nBN6 9RR','BN6 9RR','Single storey front extension and existing main roof extended to form new gable roof. Pitched dormer window to rear elevation.','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JAB6MMKT32000','http://tinyurl.com/ymj8ao','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAB6MMKT32000','http://tinyurl.com/yb2utb',5,528009,116919,'2007-01-07 17:02:29',NULL,'http://maps.google.com/maps?q=bn69rr&z=15'), + (882,'06/02182/FUL','12 The Oaks\nEast Grinstead\nWest Sussex\nRH19 3QJ','RH19 3QJ','Proposed first floor extension over garage and new rear conservatory','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=J70S1IKT32000','http://tinyurl.com/yekldf','http://dc.midsussex.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J70S1IKT32000','http://tinyurl.com/yln2en',5,540492,137920,'2007-01-07 17:02:33',NULL,'http://maps.google.com/maps?q=rh193qj&z=15'), + (883,'06/01111/FUL','89 Downhall Road\nRayleigh\nEssex\nSS6 9JX','SS6 9JX','Demolition of Existing Property and Creation of One Block Containing 7 No. Flats and 1 No. Detached Dwelling House. Vehicular Access to the Flats off Lakeside to a Car Park Court (10 Spaces) Vehicular Access to Dwelling House off Lakeside (2 Spaces)','http://62.173.68.168/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAVQSBNC96000','http://tinyurl.com/yevjg3','http://62.173.68.168/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAVQSBNC96000','http://tinyurl.com/ymyxhb',8,580625,191717,'2007-01-07 17:02:56',NULL,'http://maps.google.com/maps?q=ss69jx&z=15'); +INSERT INTO `planning`.`application` VALUES (884,'06/01075/FUL','64 Louis Drive\nRayleigh\nEssex\nSS6 9DX\n','SS6 9DX','Hip to Gable Roof Extension to Create Rooms in the Roof Incorporating 2 No. Pitched Roofed Dormers and Rooflight to Front and Flat Roofed Dormer to Rear.','http://62.173.68.168/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JA3XVMNCS7000','http://tinyurl.com/ye2dk2','http://62.173.68.168/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JA3XVMNCS7000','http://tinyurl.com/yl4rla',8,579451,191475,'2007-01-07 17:03:02',NULL,'http://maps.google.com/maps?q=ss69dx&z=15'), + (885,'07/00011/TPO','Slade House And Abell House\nSlade Hospital\nHorspath Driftway\nOxford\nOxfordshire\nOX3 7JH\n','OX3 7JH','Fell x 1 prunus and other tree works as specified in Nicholson Report of August 2006 at Slade and Abell House subject to Oxford City Council - Horspath Driftway, Eastern Bypass (No. 1) TPO 1994','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBARP2MF58000','http://tinyurl.com/yg9bql','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBARP2MF58000','http://tinyurl.com/ynbprt',9,455423,205276,'2007-01-07 17:05:04',NULL,'http://maps.google.com/maps?q=ox37jh&z=15'), + (886,'07/00010/CAT','7 Arthur Garrard Close\nOxford\nOxfordshire\nOX2 6EU','OX2 6EU','Prune eucalyptus tree to give 2.5 m clearance to adjacent buildings and crown lift by removal of x 5 lateral branches at 7 Arthur Garrard Close in the Walton Street Conservation Area','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAQLQMF58000','http://tinyurl.com/ygv7vu','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAQLQMF58000','http://tinyurl.com/yddheq',9,450778,207342,'2007-01-07 17:05:09',NULL,'http://maps.google.com/maps?q=ox26eu&z=15'); +INSERT INTO `planning`.`application` VALUES (887,'07/00008/CAT','100 Temple Road\nOxford\nOxfordshire\nOX4 2EZ','OX4 2EZ','Prune x 2 mature apple trees at 100 Temple Road in the Temple Cowley Conservation Area','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAKENMF58000','http://tinyurl.com/ybl5mo','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAKENMF58000','http://tinyurl.com/yznjnw',9,454493,204487,'2007-01-07 17:05:17',NULL,'http://maps.google.com/maps?q=ox42ez&z=15'), + (888,'07/00006/FUL','17 The Grates\nOxford\nOxfordshire\nOX4 3LJ','OX4 3LJ','Single and two storey rear extension, loft conversion with roof dormers to side and rear','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAE34MF24000','http://tinyurl.com/yaowqn','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAE34MF24000','http://tinyurl.com/yhrdrv',9,454172,203808,'2007-01-07 17:05:26',NULL,'http://maps.google.com/maps?q=ox43lj&z=15'), + (889,'07/00005/FUL','270 Iffley Road\nOxford\nOxfordshire\nOX4 4AA','OX4 4AA','Two storey side, and two and single storey rear extension with decking','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBACOBMF24000','http://tinyurl.com/ye9nar','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBACOBMF24000','http://tinyurl.com/ylc4c2',9,452835,204862,'2007-01-07 17:05:32',NULL,'http://maps.google.com/maps?q=ox44aa&z=15'); +INSERT INTO `planning`.`application` VALUES (890,'07/00004/CT3','Regal Community Centre\nRidgefield Road\nOxford\nOxfordshire\nOX4 3BY\n','OX4 3BY','Erection of 1.2m fence and gates','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB90SYMF24000','http://tinyurl.com/yg222t','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB90SYMF24000','http://tinyurl.com/yn4g44',9,453513,205031,'2007-01-07 17:05:35',NULL,'http://maps.google.com/maps?q=ox43by&z=15'), + (891,'07/00003/FUL','2 Maidcroft Road\nOxford\nOxfordshire\nOX4 3EN','OX4 3EN','Two storey rear and single storey side extension','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB938OMFWC000','http://tinyurl.com/yaadc6','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB938OMFWC000','http://tinyurl.com/yhcrde',9,453976,204328,'2007-01-07 17:05:42',NULL,'http://maps.google.com/maps?q=ox43en&z=15'), + (892,'07/00001/FUL','35 Southfield Road\nOxford\nOxfordshire\nOX4 1NX','OX4 1NX','Single storey rear extension','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB900VMFWC000','http://tinyurl.com/ykuhuz','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB900VMFWC000','http://tinyurl.com/yj565g',9,453336,205542,'2007-01-07 17:05:50',NULL,'http://maps.google.com/maps?q=ox41nx&z=15'); +INSERT INTO `planning`.`application` VALUES (893,'06/02639/FUL','11 William Street\nOxford\nOxfordshire\nOX3 0ES\n','OX3 0ES','Single storey rear conservatory extension','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAOKV3MF24000','http://tinyurl.com/yfkflc','http://uniformpublicaccess.oxford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAOKV3MF24000','http://tinyurl.com/ymmtmk',9,453002,207044,'2007-01-07 17:05:57',NULL,'http://maps.google.com/maps?q=ox30es&z=15'), + (894,'S/07/0003','The Grove\nDrove Road\nSwindon\nWilts\nSN1 3AG','SN1 3AG','Erection of a single storey extension to restaurant, new porch and lobby with ramp access and new patio area with jumbrellas.','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8KHGPTV4000','http://tinyurl.com/t344c','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8KHGPTV4000','http://tinyurl.com/y35z5k',11,415722,184171,'2007-01-07 17:06:25',NULL,'http://maps.google.com/maps?q=sn13ag&z=15'), + (895,'S/06/3022','28 Tithe Barn Crescent\nSwindon\nWiltshire\nSN1 4JX','SN1 4JX','Installation of wind turbine onto gable wall of dwelling.','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9UMKHPT97000','http://tinyurl.com/y4utum','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9UMKHPT97000','http://tinyurl.com/y8fkeq',11,414038,183451,'2007-01-07 17:06:32',NULL,'http://maps.google.com/maps?q=sn14jx&z=15'); +INSERT INTO `planning`.`application` VALUES (896,'07/54009/FUL','Land Adjacent To Metroplex Business Park\nBroadway\nSalford\nM5 2UE\n','M5 2UE','Erection of a four storey office building together with associated car parking and alteration to existing vehicular access','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JBAHYLNP40000','http://tinyurl.com/y6e4qy','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JBAHYLNP40000','http://tinyurl.com/ujbwo',13,379666,397678,'2007-01-07 17:08:04',NULL,'http://maps.google.com/maps?q=m52ue&z=15'), + (897,'07/54007/ADV','Pavement Outside 66 Langley Road\nPendlebury\nSwinton\nM27 2SS\n','M27 2SS','Display of one 48 sheet advertisement display panel with associated works','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JB8UYGNP40000','http://tinyurl.com/y86gz3','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8UYGNP40000','http://tinyurl.com/y3pk4v',13,0,0,'2007-01-07 17:08:13',NULL,'http://maps.google.com/maps?q=m272ss&z=15'), + (898,'07/54006/FUL','383 Lower Broughton Road\nSalford\nM7 2HR\n','M7 2HR','Demolition of existing dwelling and erection of a terrace of four dwellings together with associated car parking and construction of new vehicular access','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JB8T72NP40000','http://tinyurl.com/yx7tkr','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8T72NP40000','http://tinyurl.com/y7aam2',13,382147,400416,'2007-01-07 17:08:16',NULL,'http://maps.google.com/maps?q=m72hr&z=15'); +INSERT INTO `planning`.`application` VALUES (899,'06/54005/FUL','13 The Polygon\nEccles\nM30 0DS\n','M30 0DS','Erection of external staircase, porch canopy and landing in front of platform lift','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JAMP39NPN1000','http://tinyurl.com/vfhsp','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JAMP39NPN1000','http://tinyurl.com/y5hvux',13,378004,398967,'2007-01-07 17:08:21',NULL,'http://maps.google.com/maps?q=m300ds&z=15'), + (900,'006/53885/ADV','Lidl\n2 Woodrow Way\nIrlam\nMANCHESTER\nM44 6NN\n','M44 6NN','Display of a 48 sheet advertisement hoarding','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=J9JTNYNPB0000','http://tinyurl.com/sjngu','http://publicaccess.salford.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=J9JTNYNPB0000','http://tinyurl.com/y2m4z5',13,371874,393708,'2007-01-07 17:08:29',NULL,'http://maps.google.com/maps?q=m446nn&z=15'), + (901,'06/01548/FUL','1 Boston Avenue\nReading\nBerkshire\nRG1 6JU','RG1 6JU','Two storey side and single storey rear extension plus rear conservatory','http://planning.reading.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAH6U3MSX1000','http://tinyurl.com/ty9ve','http://planning.reading.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAH6U3MSX1000','http://tinyurl.com/y33nwm',26,470950,172511,'2007-01-07 17:09:05',NULL,'http://maps.google.com/maps?q=rg16ju&z=15'); +INSERT INTO `planning`.`application` VALUES (902,'06/01531/ADV','6B Church Street\nReading\nBerkshire\nRG1 2SB','RG1 2SB','Non-illuminated hanging sign','http://planning.reading.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JA9HYZMSO3000','http://tinyurl.com/uqlng','http://planning.reading.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JA9HYZMSO3000','http://tinyurl.com/y4t2oo',26,471728,172978,'2007-01-07 17:09:10',NULL,'http://maps.google.com/maps?q=rg12sb&z=15'), + (903,'07/05006/FUL','55 Georges Hill\nWidmer End\nBuckinghamshire\nHP15 6BH\n','HP15 6BH','Insertion of front window to replace garage door in connection with garage conversion to living accommodation','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JB911MSCP9000','http://tinyurl.com/y7hkcr','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB911MSCP9000','http://tinyurl.com/vmrjh',15,488806,196890,'2007-01-07 17:09:55',NULL,'http://maps.google.com/maps?q=hp156bh&z=15'), + (904,'07/05005/FUL','7 Terry Road\nHigh Wycombe\nBuckinghamshire\nHP13 6QJ\n','HP13 6QJ','Erection of two storey side/single storey rear extension, attached double garage, creation of new access and greenhouse to rear (alternative scheme to PP 06/06134/FUL and PP 06/07154/FUL)','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JB90NHSCZC000','http://tinyurl.com/y27z3l','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB90NHSCZC000','http://tinyurl.com/sg25y',15,487363,193761,'2007-01-07 17:10:00',NULL,'http://maps.google.com/maps?q=hp136qj&z=15'); +INSERT INTO `planning`.`application` VALUES (905,'07/05004/LBC','Flint Cottage\nFrieth Hill\nFrieth\nBuckinghamshire\nRG9 6PL\n','RG9 6PL','Demolition of existing single storey extension and construction of new single storey side extension and associated minor internal and external works (alternative scheme to PP 06/0512/LBC)','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8UG3SCZC000','http://tinyurl.com/wxpm5','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8UG3SCZC000','http://tinyurl.com/y636nd',15,479706,190271,'2007-01-07 17:10:06',NULL,'http://maps.google.com/maps?q=rg96pl&z=15'), + (906,'07/05003/FUL','Flint Cottage\nFrieth Hill\nFrieth\nBuckinghamshire\nRG9 6PL\n','RG9 6PL','Demolition of existing single storey extension and construction of new single storey side extension (part retrospective) and alterations to provide walled terrace (alternative scheme to PP 06/05016/FUL)','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8U2RSCZC000','http://tinyurl.com/u8du3','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8U2RSCZC000','http://tinyurl.com/y4arvb',15,479706,190271,'2007-01-07 17:10:12',NULL,'http://maps.google.com/maps?q=rg96pl&z=15'), + (907,'07/05001/FUL','West Wycombe Shooting Ground\nPark Lane\nLane End\nBuckinghamshire\nHP14 3ED\n','HP14 3ED','Erection of B8 (storage) building for use in connection with existing shooting ground','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8KOXSCZC000','http://tinyurl.com/y7szff','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8KOXSCZC000','http://tinyurl.com/tc8y2',15,481125,192249,'2007-01-07 17:10:16',NULL,'http://maps.google.com/maps?q=hp143ed&z=15'); +INSERT INTO `planning`.`application` VALUES (908,'07/05000/FUL','West Wycombe Shooting Ground\nPark Lane\nLane End\nBuckinghamshire\nHP14 3ED\n','HP14 3ED','Erection of two storey and single storey extensions to shooting lodge (alternative scheme to PP 00/07852/FUL extended under PP 05/06831/FUL)','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8K8CSCZC000','http://tinyurl.com/yymaa8','http://planningpa.wycombe.gov.uk/PublicAccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8K8CSCZC000','http://tinyurl.com/y8oobg',15,481125,192249,'2007-01-07 17:10:31',NULL,'http://maps.google.com/maps?q=hp143ed&z=15'), + (909,'07/00003/FUL','43 Norman Road\nDartford\nKent\nDA1 1LE','DA1 1LE','Installation of a wind turbine at roof level on rear elevation together with provision of 2 solar panels on front roof elevation','http://publicaccess.dartford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAD15BQL0000','http://tinyurl.com/ykglyp','http://publicaccess.dartford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAD15BQL0000','http://tinyurl.com/ybls8f',18,554186,173134,'2007-01-07 17:11:47',NULL,'http://maps.google.com/maps?q=da11le&z=15'), + (910,'06/01396/FUL','11 Lonsdale Crescent\nDarenth\nKent\nDA2 6LQ','DA2 6LQ','Erection of a rear conservatory','http://publicaccess.dartford.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAGVIEBQG3000','http://tinyurl.com/yzo99n','http://publicaccess.dartford.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAGVIEBQG3000','http://tinyurl.com/yf6zpj',18,556434,173084,'2007-01-07 17:12:02',NULL,'http://maps.google.com/maps?q=da26lq&z=15'); +INSERT INTO `planning`.`application` VALUES (911,'1/0006/2007/AGR','East Wonford Farm\nMilton Damerel\nHolsworthy\nEX22 7DP\n','EX22 7DP','Erection of small building for water storage tank','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8XL1QKP1000','http://tinyurl.com/y9m6pl','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8XL1QKP1000','http://tinyurl.com/ygokqt',19,237960,109626,'2007-01-07 17:12:35',NULL,'http://maps.google.com/maps?q=ex227dp&z=15'), + (912,'1/0001/2007/AGR','Mambury Farm\nEast Putford\nHolsworthy\nEX22 7UH','EX22 7UH','3 bay extension to existing cubicle house for dairy cows','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8W8AQKP1000','http://tinyurl.com/yk9bbx','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8W8AQKP1000','http://tinyurl.com/ybezhn',19,237735,116099,'2007-01-07 17:12:41',NULL,'http://maps.google.com/maps?q=ex227uh&z=15'), + (913,'1/2583/2006/FUL','45 North Street\nNortham\nBideford\nEX39 1DH','EX39 1DH','Demolition of garage, erection of single storey extension to the side and rear, erection of first floor shower room over kitchen','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9UTSFQKP1000','http://tinyurl.com/ymyn32','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9UTSFQKP1000','http://tinyurl.com/yazclm',19,245081,129132,'2007-01-07 17:12:49',NULL,'http://maps.google.com/maps?q=ex391dh&z=15'); +INSERT INTO `planning`.`application` VALUES (914,'1/2386/2006/FUL','Rowan Park Farm\nAshwater\nBeaworthy\nEX21 5DJ','EX21 5DJ','Free range poultry unit for laying hens','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J8DDLNQKP1000','http://tinyurl.com/ye335q','http://www.torridge.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J8DDLNQKP1000','http://tinyurl.com/yl5h7y',19,239571,99497,'2007-01-07 17:12:55',NULL,'http://maps.google.com/maps?q=ex215dj&z=15'), + (915,'06/07675/FU','35 Manston Gardens\nCross Gates\nLeeds\nLS15 8EY','LS15 8EY','Single storey side extension','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAZRPWJB2G000','http://tinyurl.com/y7oxxk','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAZRPWJB2G000','http://tinyurl.com/t7mj7',20,436605,435053,'2007-01-07 17:13:47',NULL,'http://maps.google.com/maps?q=ls158ey&z=15'), + (916,'06/07556/FU','15 Syke Lane\nScarcroft\nLeeds\nLS14 3BH\n','LS14 3BH','Part two storey part single storey front and side extension, two bay windows with balconies to front, and detached swimming pool to rear','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAIXB0JB1N000','http://tinyurl.com/wpd3b','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAIXB0JB1N000','http://tinyurl.com/vxyb9',20,436349,441867,'2007-01-07 17:13:54',NULL,'http://maps.google.com/maps?q=ls143bh&z=15'); +INSERT INTO `planning`.`application` VALUES (917,'06/07513/FU','White Swan Hotel\nCross Hills\nKippax\nLeeds\nLS25 7JP','LS25 7JP','Alterations including new 1.8m high gates, free standing awning and retractable entrance canopy to public house','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAH4HQJBZF000','http://tinyurl.com/y52fch','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAH4HQJBZF000','http://tinyurl.com/yyhosd',20,441560,430222,'2007-01-07 17:13:58',NULL,'http://maps.google.com/maps?q=ls257jp&z=15'), + (918,'06/07480/FU','The Hollies\nWoodlands Drive\nRawdon\nBradford\nBD10 0NX\n','BD10 0NX','Two storey side extension with juliet balcony to front','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JABJLCJBZF000','http://tinyurl.com/y6d7n2','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JABJLCJBZF000','http://tinyurl.com/uzetp',20,419668,438420,'2007-01-07 17:14:09',NULL,'http://maps.google.com/maps?q=bd100nx&z=15'), + (919,'06/07338/FU','267 Dewsbury Road\nHunslet\nLeeds\nLS11 5HN\n','LS11 5HN','Change of use of vacant shop (A1 Class) to estate agents (A2 Class)','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9YM4QJBZF000','http://tinyurl.com/y4ksvx','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9YM4QJBZF000','http://tinyurl.com/yy35dt',20,430012,430930,'2007-01-07 17:14:18',NULL,'http://maps.google.com/maps?q=ls115hn&z=15'); +INSERT INTO `planning`.`application` VALUES (920,'06/06812/FU','Aran House\nClaypit Lane\nLedsham\nLS25 5LP','LS25 5LP','New pitched roofs to all existing dormers and new open porch to front','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J8S1HKJBZF000','http://tinyurl.com/w3gv2','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J8S1HKJBZF000','http://tinyurl.com/y65uwa',20,445482,429735,'2007-01-07 17:14:48',NULL,'http://maps.google.com/maps?q=ls255lp&z=15'), + (921,'06/07049/FU','746 Whitehall Road\nLower Wortley\nLeeds\nLS12 5HP\n','LS12 5HP','Installation of wind turbine','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9FRBFJB1N000','http://tinyurl.com/tew9w','http://planningapplications.leeds.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9FRBFJB1N000','http://tinyurl.com/y3hdb7',20,424063,430411,'2007-01-07 17:15:00',NULL,'http://maps.google.com/maps?q=ls125hp&z=15'), + (922,'07/0008/FULL','45 Rectory Road\nWorthing\nWest Sussex\nBN14 7PE','BN14 7PE','Single story extension to north elevation and roof extensions including half end gables to south and north elevations, three dormers to the west elevation and a cottage dormer to the east elevation.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8Q4QRZB0000','http://tinyurl.com/yzdb72','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8Q4QRZB0000','http://tinyurl.com/yeskmv',21,513353,104132,'2007-01-07 17:16:28',NULL,'http://maps.google.com/maps?q=bn147pe&z=15'); +INSERT INTO `planning`.`application` VALUES (923,'07/0004/FULL','39 Trent Road\nWorthing\nWest Sussex\nBN12 4EJ','BN12 4EJ','Single storey front extension.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8P21RZB0000','http://tinyurl.com/yaeo9q','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8P21RZB0000','http://tinyurl.com/yhh5by',21,512296,102855,'2007-01-07 17:16:36',NULL,'http://maps.google.com/maps?q=bn124ej&z=15'), + (924,'06/1326/CLE','11 Gordon Road\nWorthing\nWest Sussex\nBN11 1DB','BN11 1DB','Application for a Certificate of Lawfulness for an existing rear extension and conservatory.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9HL77RZB0000','http://tinyurl.com/ydwequ','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9HL77RZB0000','http://tinyurl.com/ylyss5',21,514858,103144,'2007-01-07 17:16:41',NULL,'http://maps.google.com/maps?q=bn111db&z=15'), + (925,'06/1324/FULL','52-54 Tarring Road\nWorthing\nWest Sussex\nBN11 4ET','BN11 4ET','Construction of a block of 3 flats and a cottage at the rear of 52 - 54 Tarring Road (in conjunction with approved use of existing property as 2 flats). (WB/05/1112/ARM refers).','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9E1D6RZB0000','http://tinyurl.com/yfoqzw','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9E1D6RZB0000','http://tinyurl.com/ymr7k7',21,514227,103288,'2007-01-07 17:16:46',NULL,'http://maps.google.com/maps?q=bn114et&z=15'); +INSERT INTO `planning`.`application` VALUES (926,'06/0641/LBC','Courtlands\nParklands Avenue\nWorthing\nWest Sussex\nBN12 4NQ','BN12 4NQ','Application for Listed Building Consent for a two storey extension to north elevation with single storey link to existing office building, additional car parking and landscaping.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J0HQO4RZB0000','http://tinyurl.com/ydhscd','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J0HQO4RZB0000','http://tinyurl.com/ybpgkb',21,0,0,'2007-01-07 17:16:51',NULL,'http://maps.google.com/maps?q=bn124nq&z=15'), + (927,'06/0640/FULL','Courtlands\nParklands Avenue\nWorthing\nWest Sussex\nBN12 4NQ','BN12 4NQ','Two storey extension to north elevation with single storey link to existing office building, additional car parking and landscaping.','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J0HQJMRZB0000','http://tinyurl.com/yfa74f','http://planning.worthing.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J0HQJMRZB0000','http://tinyurl.com/ymcl5n',21,0,0,'2007-01-07 17:16:53',NULL,'http://maps.google.com/maps?q=bn124nq&z=15'), + (928,'07/00020/PLF','5 Farndale Road\nBridlington\nEast Riding Of Yorkshire\nYO16 6GT\n','YO16 6GT','Erection of conservatory to rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8WXIBJAH000','http://tinyurl.com/yg6dxg','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8WXIBJAH000','http://tinyurl.com/y9ps4h',22,518437,469322,'2007-01-07 17:19:11',NULL,'http://maps.google.com/maps?q=yo166gt&z=15'); +INSERT INTO `planning`.`application` VALUES (929,'07/00017/PLF','Caracassa\nMain Road\nCamerton\nEast Riding Of Yorkshire\nHU12 9NG\n','HU12 9NG','Erection of conservatory to rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8VARBJAH000','http://tinyurl.com/ykcwmt','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8VARBJAH000','http://tinyurl.com/ybz6tj',22,521308,426261,'2007-01-07 17:19:15',NULL,'http://maps.google.com/maps?q=hu129ng&z=15'), + (930,'07/00011/PLF','9 Hornsea Burton Road\nHornsea\nEast Riding Of Yorkshire\nHU18 1TP\n','HU18 1TP','Erection of single storey extension and detached garage at rear and construction of dormer window in roof at rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8QJIBJ0I000','http://tinyurl.com/yzkkur','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8QJIBJ0I000','http://tinyurl.com/yf2udn',22,520626,447211,'2007-01-07 17:19:22',NULL,'http://maps.google.com/maps?q=hu181tp&z=15'), + (931,'07/00009/PLF','7 Craikewells\nFlamborough\nEast Riding Of Yorkshire\nYO15 1QH\n','YO15 1QH','Erection of single storey extension to rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8PEUBJAH000','http://tinyurl.com/ye6ohm','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8PEUBJAH000','http://tinyurl.com/yl95zu',22,522628,470908,'2007-01-07 17:19:27',NULL,'http://maps.google.com/maps?q=yo151qh&z=15'); +INSERT INTO `planning`.`application` VALUES (932,'07/00008/PLF','75 Butterfly Meadows\nMolescroft\nEast Riding Of Yorkshire\nHU17 9GB\n','HU17 9GB','Erection of a two storey extension to side and alterations to vehicular access (Re submission 06/06916/PLF)','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8PENBJ0I000','http://tinyurl.com/ycecpk','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8PENBJ0I000','http://tinyurl.com/yjgqqs',22,503235,440957,'2007-01-07 17:19:43',NULL,'http://maps.google.com/maps?q=hu179gb&z=15'), + (933,'07/00007/PLF','4 Standage Road\nThorngumbald\nEast Riding Of Yorkshire\nHU12 9PH','HU12 9PH','Erection of single storey extension to rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JB8OLOBJAH000','http://tinyurl.com/ynyhaw','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JB8OLOBJAH000','http://tinyurl.com/ya7ejy',22,520733,426150,'2007-01-07 17:19:48',NULL,'http://maps.google.com/maps?q=hu129ph&z=15'), + (934,'06/10035/PLF','18 Highfield\nWithernsea\nEast Riding Of Yorkshire\nHU19 2LL\n','HU19 2LL','Erection of conservatory to the rear, retention of metal fencing and gates to front and side','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JAH6R6BJDI000','http://tinyurl.com/yh9sk9','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JAH6R6BJDI000','http://tinyurl.com/ykrj4d',22,534277,427224,'2007-01-07 17:19:55',NULL,'http://maps.google.com/maps?q=hu192ll&z=15'); +INSERT INTO `planning`.`application` VALUES (935,'06/10025/PLF','73 All Hallows Road\nWalkington\nEast Riding Of Yorkshire\nHU17 8SH','HU17 8SH','Erection of garden shed/workshop at the rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JAGXYEBJDI000','http://tinyurl.com/ybwqb3','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JAGXYEBJDI000','http://tinyurl.com/yj27cb',22,499962,436956,'2007-01-07 17:20:07',NULL,'http://maps.google.com/maps?q=hu178sh&z=15'), + (936,'06/09981/PLF','The Paddock\n35 Northfield Road\nWetwang\nEast Riding Of Yorkshire\nYO25 9XY\n','YO25 9XY','Erection of dormer windows at front and rear and pitched roof over garage','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JAB9YXBJDI000','http://tinyurl.com/ymjutf','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JAB9YXBJDI000','http://tinyurl.com/yz6yga',22,493048,459140,'2007-01-07 17:20:11',NULL,'http://maps.google.com/maps?q=yo259xy&z=15'), + (937,'06/09882/PLF','27 Southgate\nHutton Cranswick\nEast Riding Of Yorkshire\nYO25 9QX','YO25 9QX','Erection of single storey extension at rear','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_detailview.aspx?caseno=JA7HY3BJDI000','http://tinyurl.com/yjvd8c','http://www.eastriding.gov.uk/PublicAccess731c/dc/DcApplication/application_comments_entryform.aspx?caseno=JA7HY3BJDI000','http://tinyurl.com/ygdmo8',22,502566,451965,'2007-01-07 17:20:19',NULL,'http://maps.google.com/maps?q=yo259qx&z=15'); +INSERT INTO `planning`.`application` VALUES (938,'07/07604/FUL','The Wood\nStallington Road\nMeir Heath\nStaffordshire\nST11 9QW\n','ST11 9QW','Reconfiguration of canopy/veranda.','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8PFAPSJ0000','http://tinyurl.com/skkjt','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8PFAPSJ0000','http://tinyurl.com/y2nyl4',23,393679,338809,'2007-01-07 17:21:09',NULL,'http://maps.google.com/maps?q=st119qw&z=15'), + (939,'07/07603/SCG','Alleynes High School\nOulton Road\nStone\nStaffordshire\nST15 8DR','ST15 8DR','Provision of volumetric double classroom unit incorporating a laboratory, classroom, storage, preparatory room, lobby and cloakroom','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8P4VPSJ0000','http://tinyurl.com/ucwbv','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8P4VPSJ0000','http://tinyurl.com/y4fdd6',23,390511,334842,'2007-01-07 17:21:12',NULL,'http://maps.google.com/maps?q=st158dr&z=15'), + (940,'07/07601/FUL','20 Bertelin Road\nStafford\nST16 3JJ','ST16 3JJ','Rear conservatory','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8OXIPSJ0000','http://tinyurl.com/yxumt2','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8OXIPSJ0000','http://tinyurl.com/y8x3ua',23,392984,325029,'2007-01-07 17:21:15',NULL,'http://maps.google.com/maps?q=st163jj&z=15'); +INSERT INTO `planning`.`application` VALUES (941,'07/07600/COU','75 Lichfield Road\nStafford\nStaffordshire\nST17 4LW','ST17 4LW','Change of use from veterinary surgery to 1 no ground floor flat','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8OP9PSJ0000','http://tinyurl.com/yxyhxy','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8OP9PSJ0000','http://tinyurl.com/y5bj97',23,393149,322403,'2007-01-07 17:21:23',NULL,'http://maps.google.com/maps?q=st174lw&z=15'), + (942,'07/07598/FUL','23 Corporation Street\nStafford\nStaffordshire\nST16 3LZ','ST16 3LZ','Ground and first floor extensions to rear of flats','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8O2NPSC3000','http://tinyurl.com/y8t9qb','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8O2NPSC3000','http://tinyurl.com/t27yd',23,392341,324045,'2007-01-07 17:21:36',NULL,'http://maps.google.com/maps?q=st163lz&z=15'), + (943,'07/07596/LBC','Cherry Trees\nHaughton\nStafford\nST18 9EX\n','ST18 9EX','Conservatory to rear','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8NGEPSA3000','http://tinyurl.com/urzqf','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8NGEPSA3000','http://tinyurl.com/y4twrn',23,386648,320372,'2007-01-07 17:21:41',NULL,'http://maps.google.com/maps?q=st189ex&z=15'); +INSERT INTO `planning`.`application` VALUES (944,'07/07595/FUL','Cherry Trees\nHaughton\nStafford\nST18 9EX\n','ST18 9EX','Conservatory to rear','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8NG9PSA3000','http://tinyurl.com/y4xqvm','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8NG9PSA3000','http://tinyurl.com/yxg3ez',23,386648,320372,'2007-01-07 17:21:49',NULL,'http://maps.google.com/maps?q=st189ex&z=15'), + (945,'07/07594/FUL','Woodside\nSpot Acre\nHilderstone\nStaffordshire\nST15 8RN','ST15 8RN','2 no proposed conservatories and addition of first floor window on side elevation','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8NL3PSJ0000','http://tinyurl.com/y7zhfq','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8NL3PSJ0000','http://tinyurl.com/y5q5no',23,394179,337198,'2007-01-07 17:21:54',NULL,'http://maps.google.com/maps?q=st158rn&z=15'), + (946,'07/07593/FUL','2 York Avenue\nFulford\nStaffordshire\nST11 9SB','ST11 9SB','First floor extension over existing garage and utility room extension to rear of garage','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8MYGPSJ0000','http://tinyurl.com/y2jd98','http://www3.staffordbc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8MYGPSJ0000','http://tinyurl.com/wymp4',23,395398,338060,'2007-01-07 17:21:59',NULL,'http://maps.google.com/maps?q=st119sb&z=15'); +INSERT INTO `planning`.`application` VALUES (947,'S/07/0003','The Grove\nDrove Road\nSwindon\nWilts\nSN1 3AG','SN1 3AG','Erection of a single storey extension to restaurant, new porch and lobby with ramp access and new patio area with jumbrellas.','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8KHGPTV4000','http://tinyurl.com/t344c','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8KHGPTV4000','http://tinyurl.com/y35z5k',32,415722,184171,'2007-01-07 17:23:23',NULL,'http://maps.google.com/maps?q=sn13ag&z=15'), + (948,'S/06/3022','28 Tithe Barn Crescent\nSwindon\nWiltshire\nSN1 4JX','SN1 4JX','Installation of wind turbine onto gable wall of dwelling.','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9UMKHPT97000','http://tinyurl.com/y4utum','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9UMKHPT97000','http://tinyurl.com/y8fkeq',32,414038,183451,'2007-01-07 17:23:32',NULL,'http://maps.google.com/maps?q=sn14jx&z=15'), + (949,'S/07/0003','The Grove\nDrove Road\nSwindon\nWilts\nSN1 3AG','SN1 3AG','Erection of a single storey extension to restaurant, new porch and lobby with ramp access and new patio area with jumbrellas.','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8KHGPTV4000','http://tinyurl.com/t344c','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8KHGPTV4000','http://tinyurl.com/y35z5k',33,415722,184171,'2007-01-07 17:23:58',NULL,'http://maps.google.com/maps?q=sn13ag&z=15'); +INSERT INTO `planning`.`application` VALUES (950,'S/06/3022','28 Tithe Barn Crescent\nSwindon\nWiltshire\nSN1 4JX','SN1 4JX','Installation of wind turbine onto gable wall of dwelling.','http://194.73.99.13/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9UMKHPT97000','http://tinyurl.com/y4utum','http://194.73.99.13/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9UMKHPT97000','http://tinyurl.com/y8fkeq',33,414038,183451,'2007-01-07 17:24:02',NULL,'http://maps.google.com/maps?q=sn14jx&z=15'), + (951,'07/00004/ENFORC','Greensands\nReading Road\nEast Hendred\nWantage\nOxon\nOX12 8JE\n','OX12 8JE','Enforcement authorised to: (a) include the removal from site of the unauthorised independent residential units and caravans (b) to cease the unauthorised occupation/residential use of the land.','http://planning.whitehorsedc.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8N7DBY7A000','http://tinyurl.com/vcqjs','http://planning.whitehorsedc.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8N7DBY7A000','http://tinyurl.com/yyuh3w',34,446069,189319,'2007-01-07 17:24:20',NULL,'http://maps.google.com/maps?q=ox128je&z=15'), + (952,'07/00005/FUL','113 Wisbech Road\nLittleport\nEly\nCambridgeshire\nCB6 1JJ\n','CB6 1JJ','Single storey rear extension.','http://pa.eastcambs.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBAQDCGGK0000','http://tinyurl.com/soaca','http://pa.eastcambs.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBAQDCGGK0000','http://tinyurl.com/y2qodz',35,555970,287311,'2007-01-07 17:26:04',NULL,'http://maps.google.com/maps?q=cb61jj&z=15'); +INSERT INTO `planning`.`application` VALUES (953,'07/00003/LBC','Dundas House\n17 High Street\nSutton\nEly\nCambridgeshire\nCB6 2RB\n','CB6 2RB','Extension to form orangery and enlargement of existing kitchen window (Part Demolition)','http://pa.eastcambs.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8XGXGG32000','http://tinyurl.com/y6beum','http://pa.eastcambs.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8XGXGG32000','http://tinyurl.com/ugm4c',35,544659,278898,'2007-01-07 17:26:09',NULL,'http://maps.google.com/maps?q=cb62rb&z=15'), + (954,'07/00002/FUL','Dundas House\n17 High Street\nSutton\nEly\nCambridgeshire\nCB6 2RB\n','CB6 2RB','Extension to form ground floor orangery increase window size in kitchen','http://pa.eastcambs.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8V34GG32000','http://tinyurl.com/y3mu9j','http://pa.eastcambs.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8V34GG32000','http://tinyurl.com/yx56pf',35,544659,278898,'2007-01-07 17:26:27',NULL,'http://maps.google.com/maps?q=cb62rb&z=15'), + (955,'07/00014/FUL','Sherenden\nCurtisden Green Lane\nGoudhurst\nCranbrook\nKent\nTN17 1LJ\n','TN17 1LJ','Construction of new one and two storey extensions','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBE71HBWS0000','http://tinyurl.com/sdc9m','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBE71HBWS0000','http://tinyurl.com/y2fqau',37,574502,140719,'2007-01-07 17:28:23',NULL,'http://maps.google.com/maps?q=tn171lj&z=15'); +INSERT INTO `planning`.`application` VALUES (956,'07/00012/FUL','Clayhill Cottage\nClayhill\nGoudhurst\nCranbrook\nKent\nTN17 1BE\n','TN17 1BE','Alterations and extensions to garage and sun room','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBCS00BWS0000','http://tinyurl.com/vv2qq','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBCS00BWS0000','http://tinyurl.com/y6xgsy',37,572070,137576,'2007-01-07 17:28:27',NULL,'http://maps.google.com/maps?q=tn171be&z=15'), + (957,'07/00004/LBC','Clayhill Cottage\nClayhill\nGoudhurst\nCranbrook\nKent\nTN17 1BE\n','TN17 1BE','Listed building consent - alterations and extensions to garage and sun room','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JBE4BPBWS0000','http://tinyurl.com/yxnezs','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JBE4BPBWS0000','http://tinyurl.com/y7psk3',37,572070,137576,'2007-01-07 17:28:35',NULL,'http://maps.google.com/maps?q=tn171be&z=15'), + (958,'07/00003/SUB','Highlands House Nursing Home\n10 - 12 Calverley Park Gardens\nRoyal Tunbridge Wells\nKent\nTN1 2JN\n','TN1 2JN','Condition 7 of TW/05/01139/FUL - Site Investigation Report and Closure Letter Report','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8YFVBWNE000','http://tinyurl.com/y3bw6v','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8YFVBWNE000','http://tinyurl.com/wr8mr',37,559065,139460,'2007-01-07 17:28:41',NULL,'http://maps.google.com/maps?q=tn12jn&z=15'); +INSERT INTO `planning`.`application` VALUES (959,'07/00002/SUB','Mithrandir\nBedgebury Road\nGoudhurst\nCranbrook\nKent\nTN17 2QX\n','TN17 2QX','Condition 5 of TW/06/01259 - Repair schedule in respect of timber frame','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8LVUBWNE000','http://tinyurl.com/y6tmo2','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8LVUBWNE000','http://tinyurl.com/vytup',37,572085,136455,'2007-01-07 17:28:45',NULL,'http://maps.google.com/maps?q=tn172qx&z=15'), + (960,'07/00001/SUB','Mithrandir\nBedgebury Road\nGoudhurst\nCranbrook\nKent\nTN17 2QX\n','TN17 2QX','Condition 2 of TW/06/01259 - Details of materials to be used externally','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8L8IBWNE000','http://tinyurl.com/yy5yxc','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8L8IBWNE000','http://tinyurl.com/y87fyk',37,572085,136455,'2007-01-07 17:28:59',NULL,'http://maps.google.com/maps?q=tn172qx&z=15'), + (961,'06/03743/FUL','13 Dudley Road\nRoyal Tunbridge Wells\nKent\nTN1 1LE\n','TN1 1LE','Re-roof bathroom and conversion of garden shed into sunroom','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAKU5ZBWYD000','http://tinyurl.com/y3qzlf','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAKU5ZBWYD000','http://tinyurl.com/yx8s4b',37,558346,139579,'2007-01-07 17:29:03',NULL,'http://maps.google.com/maps?q=tn11le&z=15'); +INSERT INTO `planning`.`application` VALUES (962,'06/03737/FUL','47 Chestnut Avenue\nSouthborough\nRoyal Tunbridge Wells\nKent\nTN4 0BT\n','TN4 0BT','Erection of conservatory to rear','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAKISMBWT9000','http://tinyurl.com/y5zudh','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAKISMBWT9000','http://tinyurl.com/y2y6td',37,558710,141698,'2007-01-07 17:29:10',NULL,'http://maps.google.com/maps?q=tn40bt&z=15'), + (963,'06/03739/FUL','Flat 1\n27 Grosvenor Park\nRoyal Tunbridge Wells\nKent\nTN1 2BB\n','TN1 2BB','Replace existing kitchen window to French doors and replace side door with double-glazed window','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAKORKBWT7000','http://tinyurl.com/y2n3ym','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAKORKBWT7000','http://tinyurl.com/w5chz',37,558428,140035,'2007-01-07 17:29:20',NULL,'http://maps.google.com/maps?q=tn12bb&z=15'), + (964,'06/03714/FUL','Lower Standen Granary\nStanden Street\nBenenden\nCranbrook\nKent\nTN17 4LA\n','TN17 4LA','Change of use during term time to a nursery for 3-5 year olds','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JABKLCBWYD000','http://tinyurl.com/y8tb6t','http://secure.tunbridgewells.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JABKLCBWYD000','http://tinyurl.com/t28ev',37,581057,130325,'2007-01-07 17:30:06',NULL,'http://maps.google.com/maps?q=tn174la&z=15'); +INSERT INTO `planning`.`application` VALUES (965,'06/14114/FULM','Woodman Service Station And \nFormer Public Conveniences\nWestwood Lane\nSidcup\nKent\nDA15 9PT\n','DA15 9PT','Erection of a four/five storey residential building comprising 14 x 1 bedroom and 10 x 2 bedroom apartments with access from Lyndon Road, associated parking and amenity space.','http://publicaccess.bexley.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAKLE8BED1000','http://tinyurl.com/t7nwp','http://publicaccess.bexley.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAKLE8BED1000','http://tinyurl.com/y3a5yx',38,546236,174625,'2007-01-07 17:31:03',NULL,'http://maps.google.com/maps?q=da159pt&z=15'), + (966,'06/14094/FUL','7 Harold Avenue\nBelvedere\nKent\nDA17 5NN\n','DA17 5NN','Single storey extension to front and porch.','http://publicaccess.bexley.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JAJ3ZCBEW4000','http://tinyurl.com/y85ksm','http://publicaccess.bexley.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JAJ3ZCBEW4000','http://tinyurl.com/tmae9',38,548736,178090,'2007-01-07 17:31:42',NULL,'http://maps.google.com/maps?q=da175nn&z=15'), + (967,'06/13593/FUL','49 Little Heath Road\nBexleyheath\nKent\nDA7 5HE\n','DA7 5HE','Single storey rear extension.','http://publicaccess.bexley.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JA9COFBED1000','http://tinyurl.com/y3oofh','http://publicaccess.bexley.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JA9COFBED1000','http://tinyurl.com/yx7xvd',38,548908,177052,'2007-01-07 17:31:46',NULL,'http://maps.google.com/maps?q=da75he&z=15'); +INSERT INTO `planning`.`application` VALUES (968,'06/13300/FUL','49 Ladbrooke Crescent\nSidcup\nKent\nDA14 4RU\n','DA14 4RU','Use of land for access and formation of vehicular access.','http://publicaccess.bexley.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J9YPRYBEW4000','http://tinyurl.com/yjlva2','http://publicaccess.bexley.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J9YPRYBEW4000','http://tinyurl.com/yg47pv',38,547760,172061,'2007-01-07 17:31:51',NULL,'http://maps.google.com/maps?q=da144ru&z=15'), + (969,'07/00011/COU','69 Lichfield Road\nEast Ham\nLondon\nE6 3LQ','E6 3LQ','Proposed change of use from residential dwelling into an all day Daycare and Nursery.','http://pacaps.newham.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=JB8XQLJYG7000','http://tinyurl.com/yl7xbc','http://pacaps.newham.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=JB8XQLJYG7000','http://tinyurl.com/ycc7z2',40,541779,182610,'2007-01-07 17:32:26',NULL,'http://maps.google.com/maps?q=e63lq&z=15'), + (970,'06/01961/FUL','2 Albert Square\nStratford\nLondon\nE15 1HH','E15 1HH','Two storey side and rear extensions and single storey rear extension.','http://pacaps.newham.gov.uk/publicaccess/tdc/DcApplication/application_detailview.aspx?caseno=J80A2LJYS1000','http://tinyurl.com/yftv26','http://pacaps.newham.gov.uk/publicaccess/tdc/DcApplication/application_comments_entryform.aspx?caseno=J80A2LJYS1000','http://tinyurl.com/ymwc3e',40,539402,185172,'2007-01-07 17:32:30',NULL,'http://maps.google.com/maps?q=e151hh&z=15'); +INSERT INTO `planning`.`application` VALUES (971,'07/00001/CM','The Grange\n88 Southgate\nEckington\nSheffield\nS21 4FT','S21 4FT','Proposed 10 space car park (Conservation Area)','http://planapps-online.ne-derbyshire.gov.uk/publicaccess/dc/DcApplication/application_detailview.aspx?caseno=JBACCWLIY1000','http://tinyurl.com/ybqwom','http://planapps-online.ne-derbyshire.gov.uk/publicaccess/dc/DcApplication/application_comments_entryform.aspx?caseno=JBACCWLIY1000','http://tinyurl.com/yfbn8q',47,443059,379251,'2007-01-07 17:34:22',NULL,'http://maps.google.com/maps?q=s214ft&z=15'); +CREATE TABLE `planning`.`authority` ( + `authority_id` int(11) NOT NULL auto_increment, + `full_name` varchar(200) NOT NULL, + `short_name` varchar(100) NOT NULL, + `planning_email` varchar(100) NOT NULL, + `feed_url` varchar(255) default NULL, + `external` tinyint(1) default NULL, + `disabled` tinyint(1) default NULL, + `notes` text, + PRIMARY KEY (`authority_id`) +) ENGINE=MyISAM AUTO_INCREMENT=52 DEFAULT CHARSET=utf8; +INSERT INTO `planning`.`authority` VALUES (1,'London Borough of Lambeth','Lambeth','planning@lambeth.gov.uk','/scrapers/lambeth.php?day={day}&month={month}&year={year}',0,0,NULL), + (2,'City of Manchester','Manchester','','/scrapers/manchester.php?day={day}&month={month}&year={year}',0,0,NULL), + (4,'Horsham Ditrict Council','Horsham','planning@horsham.gov.uk','/scrapers/horsham.php?day={day}&month={month}&year={year}',0,0,NULL), + (5,'Mid Sussex District Council','Mid Sussex','unknown@unknown.com','/scrapers/midsussex.php?day={day}&month={month}&year={year}',0,0,NULL), + (6,'Doncaster Metropolitan Borough Council','Doncaster','unknown@unknown.com','/scrapers/doncaster.php?day={day}&month={month}&year={year}',0,1,NULL), + (7,'London Borough of Bromley','Bromley','planning@bromley.gov.uk','/scrapers/doncaster.php?day={day}&month={month}&year={year}',0,1,'public access is bust'), + (8,'Rochford District Council','Rochford','planning.applications@rochford.gov.uk','/scrapers/rochford.php?day={day}&month={month}&year={year}',0,0,NULL), + (9,'Oxford City Council','Oxford','planning@oxford.gov.uk','/scrapers/oxford.php?day={day}&month={month}&year={year}',0,0,NULL); +INSERT INTO `planning`.`authority` VALUES (11,'Swindon Borough Council','Swindon','sbcdc@swindon.gov.uk','/scrapers/swindon.php?day={day}&month={month}&year={year}',0,0,NULL), + (13,'Salford City Council','Salford','planning.contact@salford.gov.uk','/scrapers/salford.php?day={day}&month={month}&year={year}',0,0,''), + (14,'Norwich City Council','Norwich','devcontrol@norwich.gov.uk','/scrapers/norwich.php?day={day}&month={month}&year={year}',0,0,''), + (26,'Reading Borough Council','Reading','unknown@unknown.com','/scrapers/reading.php?day={day}&month={month}&year={year}',0,0,NULL), + (15,'Wycombe District Council','Wycombe','planning@wycombe.gov.uk ','/scrapers/wycombe.php?day={day}&month={month}&year={year}',0,0,''), + (16,'Bedford Borough Council','Bedford','unknown@unknown.com','/scrapers/bedford.php?day={day}&month={month}&year={year}',0,0,NULL), + (17,'Three Rivers District Council','Three Rivers','unknown@unknown.com','/scrapers/threerivers.php?day={day}&month={month}&year={year}',0,0,NULL), + (18,'Dartford Borough Council','Dartford','unknown@unknown.com','/scrapers/dartford.php?day={day}&month={month}&year={year}',0,0,NULL); +INSERT INTO `planning`.`authority` VALUES (19,'Torridge District Council','Torridge','unknown@unknown.com','/scrapers/torridge.php?day={day}&month={month}&year={year}',0,0,NULL), + (20,'Leeds City Council','Leeds','planning@leeds.gov.uk','/scrapers/leeds.php?day={day}&month={month}&year={year}',0,0,NULL), + (21,'Worthing Borough Council','Worthing','planning@worthing.gov.uk','/scrapers/worthing.php?day={day}&month={month}&year={year}',0,0,NULL), + (22,'East Riding of Yorkshire Council','East Riding','unknown@unknown.com','/scrapers/eastriding.php?day={day}&month={month}&year={year}',0,0,NULL), + (23,'Stafford Borough Council','Stafford','planning@staffordbc.gov.uk.','/scrapers/stafford.php?day={day}&month={month}&year={year}',0,0,NULL), + (24,'Alnwick District Council','Alnwick','unknown@unknown.com','/scrapers/alnwick.php?day={day}&month={month}&year={year}',0,1,'Uses custom system'), + (27,'Chester-le-Street District Council','Chester-le-Street','planning@chester-le-street.gov.uk','/scrapers/chester-le-street.php?day={day}&month={month}&year={year}',0,0,NULL), + (28,'Staffordshire Moorlands District Council','Moorlands','unknown@unknown.com','/scrapers/moorlands.php?day={day}&month={month}&year={year}',0,0,NULL); +INSERT INTO `planning`.`authority` VALUES (29,'Argyll and Bute Council','Argyll and Bute','unknown@unknown.com','/scrapers/argyllandbute.php?day={day}&month={month}&year={year}',0,0,NULL), + (30,'Sandwell Metropolitan Borough','Sandwell','unknown@unknown.com','/scrapers/sandwell.php?day={day}&month={month}&year={year}',0,0,NULL), + (31,'Gloucester City Council','Gloucester','unknown@unknown.com','/scrapers/gloucester.php?day={day}&month={month}&year={year}',0,0,NULL), + (32,'Swindon Borough Council','Swindon','sbcdc@swindon.gov.uk','/scrapers/swindon.php?day={day}&month={month}&year={year}',0,0,NULL), + (33,'Wakefield Metropolitan District Council ','Wakefield','devcontrol@wakefield.gov.uk','/scrapers/swindon.php?day={day}&month={month}&year={year}',0,0,NULL), + (34,'Vale of White Horse District Council','Vale of White Horse','planning.dc@whitehorsedc.gov.uk','/scrapers/valeofthewhitehorse.php?day={day}&month={month}&year={year}',0,0,NULL), + (35,'East Cambridgeshire District Council','East Cambs','plservices@eastcambs.gov.uk','/scrapers/eastcambs.php?day={day}&month={month}&year={year}',0,0,NULL), + (36,'Scottish Borders Council','Scottish Borders','unknown@unknown.com','/scrapers/scottishborders.php?day={day}&month={month}&year={year}',0,0,NULL); +INSERT INTO `planning`.`authority` VALUES (37,'Tunbridge Wells Borough Council','Tunbridge Wells','planningcomments@tunbridgewells.gov.uk','/scrapers/tunbridgewells.php?day={day}&month={month}&year={year}',0,0,NULL), + (38,'London Borough of Bexley','Bexley','unknown@unknown.com','/scrapers/bexley.php?day={day}&month={month}&year={year}',0,0,NULL), + (39,'West Wiltshire District Council','West Wiltshire','unknown@unknown.com','/scrapers/westwiltshire.php?day={day}&month={month}&year={year}',0,0,NULL), + (40,'London Borough of Newham','Newham','development.control@newham.gov.uk','/scrapers/newham.php?day={day}&month={month}&year={year}',0,0,NULL), + (41,'Bradford Metropolitan District Council','Bradford','unknown@unknown.com','/scrapers/bradford.php?day={day}&month={month}&year={year}',0,0,NULL), + (42,'Richmondshire District Council','Richmondshire','unknown@unknown.com','/scrapers/richmondshire.php?day={day}&month={month}&year={year}',0,0,NULL), + (45,'Corby Borough Council','Corby','unknown@unknown.com','/scrapers/corby.php?day={day}&month={month}&year={year}',0,0,NULL), + (48,'Balby District Council','Balby','unknown@unknown.com','/scrapers/balby.php?day={day}&month={month}&year={year}',0,0,NULL); +INSERT INTO `planning`.`authority` VALUES (47,'North East Derbyshire District Council.','North East Derbyshire','unknown@unknown.com','/scrapers/northeastderbyshire.php?day={day}&month={month}&year={year}',0,0,NULL), + (49,'Staffordshire Moorlands District Council','Staffordshire Moorlands','unknown@unknown.com','/scrapers/staffordshiremoorlands.php?day={day}&month={month}&year={year}',0,0,NULL), + (50,'Kingston upon Hull City Council','Kingston upon Hull','unknown@unknown.com','/scrapers/hull.php?day={day}&month={month}&year={year}',0,0,NULL), + (51,'Tendring District Council','Tendring','unknown@unknown.com','/scrapers/tendring.php?day={day}&month={month}&year={year}',NULL,NULL,NULL); +CREATE TABLE `planning`.`user` ( + `user_id` int(11) NOT NULL auto_increment, + `email` varchar(120) NOT NULL, + `postcode` varchar(10) NOT NULL, + `digest_mode` tinyint(1) NOT NULL default '0', + `last_sent` datetime default NULL, + `bottom_left_x` int(11) default NULL, + `bottom_left_y` int(11) default NULL, + `top_right_x` int(11) default NULL, + `top_right_y` int(11) default NULL, + `confirm_id` varchar(20) default NULL, + `confirmed` tinyint(1) default NULL, + `alert_area_size` varchar(1) default NULL, + PRIMARY KEY (`user_id`) +) ENGINE=MyISAM AUTO_INCREMENT=53 DEFAULT CHARSET=utf8; +INSERT INTO `planning`.`user` VALUES (51,'richard@memespring.co.uk','sw98jx',0,'2007-01-07 10:16:08',530045,174349,532145,176449,'d8a8495e75bece822921',1,'l'), + (52,'richard@memespring.co.uk','sw98jx',0,'2007-01-06 11:04:37',530045,174349,532145,176449,'c4a175ffa33297125bf9',0,'l'); + + + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; diff --git a/trunk/tools/application_mailer.php b/trunk/tools/application_mailer.php new file mode 100644 index 0000000..381d4f3 --- /dev/null +++ b/trunk/tools/application_mailer.php @@ -0,0 +1,117 @@ +run(); + + //class + class application_mailer { + + //Properties + var $log = array(); + + //Run + function run (){ + + $db = DB::connect(DB_CONNECTION_STRING); + + //Grab all the users + $sql = "select user_id, email, postcode, bottom_left_x, bottom_left_y, top_right_x, top_right_y, alert_area_size, confirm_id + from user + where confirmed = 1 and last_sent < " . $db->quote(mysql_date(time() - (20 * 60 * 60))); + + $this->store_log("Grabbing users"); + + $user_results = $db->getAll($sql); + + $this->store_log("Found " . sizeof($user_results) . " who havnt been checked since " . mysql_date(time() - (20 * 60 * 60))); + + if(sizeof($user_results) > 0){ + + //Loop though users + for ($i=0; $i < sizeof($user_results); $i++){ + + $this->store_log("Processing user " . $i); + + //Find applications for that user + $sql = "select council_reference, address, + postcode, description, info_tinyurl, + comment_tinyurl, map_url, full_name + from application + inner join authority on application.authority_id = authority.authority_id + where date_scraped > " . $db->quote(mysql_date(time() - (24 * 60 * 60))) . + " and (application.x > " . $user_results[$i][3] . " and application.x < " . $user_results[$i][5] . ") + and (application.y > " . $user_results[$i][4] . " and application.y < " . $user_results[$i][6] . ")"; + + $application_results = $db->getAll($sql); + + //Send email if we have any + if(sizeof($application_results) > 0){ + + $this->store_log("Found " . sizeof($application_results) . "applications for this user. w00t!"); + + //Setup applications array (bit pikey this) + $applications = array(); + for ($ii=0; $ii < sizeof($application_results); $ii++){ + + $application = new application(); + $application->council_reference = $application_results[$ii][0]; + $application->address = $application_results[$ii][1]; + $application->postcode = $application_results[$ii][2]; + $application->description= $application_results[$ii][3]; + $application->info_tinyurl= $application_results[$ii][4]; + $application->comment_tinyurl= $application_results[$ii][5]; + $application->map_url = $application_results[$ii][6]; + $application->authority_name = $application_results[$ii][7]; + + array_push($applications, $application); + } + + //Smarty template + $smarty = new Smarty; + $smarty->force_compile = true; + $smarty->compile_dir = SMARTY_COMPILE_DIRECTORY; + $smarty->assign("applications", $applications); + $smarty->assign("base_url", BASE_URL); + $smarty->assign("confirm_id", $user_results[$i][8]); + + //Get the email text + $email_text = $smarty->fetch(SMARTY_TEMPLATE_DIRECTORY . 'alert_email_text.tpl'); + + //Send the email + if($email_text !=""){ + send_text_email($user_results[$i][1], EMAIL_FROM_NAME, EMAIL_FROM_ADDRESS, "Planning applications near " . $user_results[$i][2], $email_text); + $this->store_log("sent applications to " . $user_results[$i][1]); + }else{ + $this->store_log("BLANK EMAIL TEXT !!! EMAIL NOT SENT"); + } + + //Update last sent + $sql = "update user set last_sent = " . $db->quote(mysql_date(time())) . " where user_id = " . $user_results[$i][0]; + $db->query($sql); + $this->store_log("Updating last checked date/time"); + } + + } + + } + + //Send the log + send_text_email(LOG_EMAIL, "mailer@" . DOMAIN, "mailer@" . DOMAIN, "Planning mailer log", print_r($this->log, true)); + + $this->store_log("Debug email sent to " . LOG_EMAIL); + + } + + function store_log($text){ + array_push($this->log, $text); + print $text . "\n\n"; + } + + } + +?> \ No newline at end of file diff --git a/trunk/tools/application_parser.php b/trunk/tools/application_parser.php new file mode 100644 index 0000000..e462860 --- /dev/null +++ b/trunk/tools/application_parser.php @@ -0,0 +1,171 @@ +date = getdate(strtotime("-" . SCRAPE_DELAY . " days")); + $application_parser->run(); + + //class + class application_parser{ + + //Properties + var $date; + var $log = array(); + var $sleep_interval = 5; //how long to wait between scraping each feed + + //Constructor + function application_parser (){ + + //set default date + $this->date = getdate(); + + } + + //Run + function run(){ + + $db = DB::connect(DB_CONNECTION_STRING); + $sql = "Select authority_id, feed_url, external, disabled, short_name from authority where disabled <> 1"; + $results = $db->getAll($sql); + + if (sizeof($results) == 0){ + //throw new exception("You need to put some authorities to scrape in the database"); + } + + //log + $this->store_log("Scraping " . sizeof($results) . "authorities"); + + //Parse & save each feed + foreach($results as $result){ + + //reset the timeout + set_time_limit(0); + + $authority_id = $result[0]; + $external = $result[2]; + $disabled = $result[3]; + if($external != true){ + $feed_url = BASE_URL . $feed_url = $result[1]; + }else{ + $feed_url = $result[1]; + } + + //replace date wild cards + $feed_url = str_replace("{day}",$this->date['mday'], $feed_url); + $feed_url = str_replace("{month}",$this->date['mon'], $feed_url); + $feed_url = str_replace("{year}",$this->date['year'], $feed_url); + + //log + $this->store_log("Scraping authority " . $result[4] . " from " . $feed_url); + + //if it isnt disabled parse it + if ($disabled == false){ + $applications = $this->parse_applications($feed_url, $authority_id); + + //log + $this->store_log("Found " . sizeof($applications) . " applications for " . $result[4]); + + //save applications (probably shouldent be saved individually, but sod it for the moment) + foreach ($applications as $application){ + if(!$application->exists()){ + $application->save(); + $this->store_log("Saving application" . $application->council_reference); + }else{ + $this->store_log("Application already exists in database" . $application->council_reference); + } + } + + } + + //wait for a bit so we dont blow anyone's server (mainly tinyurl) + sleep($this->sleep_interval); + + } + + //Email log + send_text_email(LOG_EMAIL, "parser@" . DOMAIN, "parser@" . DOMAIN, "Planning parser log", print_r($this->log, true)); + $this->store_log("Debug email sent to " . LOG_EMAIL); + + } + + //Turn xml into application objects + function parse_applications($feed_url, $authority_id){ + + $return_applications = array(); + + //reset warnings + + //Grab the XML + $xml = ""; + try{ + $xml = safe_scrape_page($feed_url); + }catch (exception $e){ + array_push($this->log, "ERROR: problem occured when grabbing feed: " . $feed_url . " ---->>>" . $e); + } + + if ($xml == false){ + $this->store_log("ERROR: empty feed feed: " . $feed_url); + } + + //Turn the xml into an object + $parsed_applications = simplexml_load_string($xml); + + //Loop through the applications, add tinyurl / google maps etc and add to array + if(sizeof($parsed_applications) >0){ + foreach($parsed_applications->applications->application as $parsed_application){ + + $application = new application(); + + //Grab basic data from the xml + $application->authority_id = $authority_id; + $application->council_reference = $parsed_application->council_reference; + $application->date_received = $parsed_application->date_received; + $application->address = $parsed_application->address; + $application->postcode = $parsed_application->postcode; + $application->description = $parsed_application->description; + $application->info_url = $parsed_application->info_url; + $application->comment_url = $parsed_application->comment_url; + $application->date_scraped = mysql_date(time()); + + //Make the urls + $info_tiny_url = tiny_url($application->info_url); + if ($info_tiny_url == ""){ + $this->store_log("ERROR: Created blank info tiny url"); + } + $comment_tiny_url = tiny_url($application->comment_url); + if ($comment_tiny_url == ""){ + $this->store_log("ERROR: Created blank comment tiny url"); + } + + $application->info_tinyurl =$info_tiny_url; + $application->comment_tinyurl = $comment_tiny_url; + $application->map_url = googlemap_url_from_postcode($application->postcode); + + //Workout the XY location from postcode + $xy = postcode_to_location($application->postcode); + $application->x = $xy[0]; + $application->y = $xy[1]; + + //Add to array + array_push($return_applications, $application); + + } + } + return $return_applications; + + } + + function store_log($text){ + array_push($this->log, $text); + print $text . "\n\n"; + } + + + +} + +?> \ No newline at end of file diff --git a/trunk/tools/tools_ini.php b/trunk/tools/tools_ini.php new file mode 100644 index 0000000..9ff1b81 --- /dev/null +++ b/trunk/tools/tools_ini.php @@ -0,0 +1,9 @@ + \ No newline at end of file