diff options
144 files changed, 24331 insertions, 33 deletions
diff --git a/close.html b/close.html new file mode 100644 index 0000000..dca0447 --- /dev/null +++ b/close.html @@ -0,0 +1,10 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <title>Closing</title> + </head> + <body onLoad="document.forms['close'].submit();"> + <form name="close" action="./close.html" method="get" /></form> + </body> +</html>
\ No newline at end of file diff --git a/css/readability-print.css b/css/readability-print.css index 83aa757..a4c8cb3 100755 --- a/css/readability-print.css +++ b/css/readability-print.css @@ -1,5 +1,7 @@ -
#readInner {
width: 100% !important;
font-size: 12pt;
+}
+#readTools {
+ display: none;
}
\ No newline at end of file diff --git a/css/readability.css b/css/readability.css index 15292d0..f7e8f41 100755 --- a/css/readability.css +++ b/css/readability.css @@ -3,38 +3,31 @@ /*
#readOverlay {
- background-image: none;
-
-
- background: #eee;
+ background-image: none;
+ background: #eee;
font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif;
}
*/
-
#readOverlay {
display: block;
position: absolute;
top: 0;
left: 0;
- z-index: 250000;
width: 100%;
}
-
#readInner {
text-align: left;
- line-height: 1.4em;
+ line-height: 1.4em;
margin: 1em auto;
max-width: 800px;
-
}
#readInner a {
color: blue;
- text-decoration: underline;
+ text-decoration: underline;
}
-
#readInner * {
- margin-bottom: 16px;
+ margin-bottom: 16px;
border: none;
background: none;
}
@@ -43,20 +36,19 @@ margin-right: 12px;
margin-bottom: 12px;
}
-
#readInner h1 {
display: block;
width: 100%;
border-bottom: 1px solid #333;
font-size: 1.2em;
}
-#readInner blockquote {
+#readInner blockquote {
margin-left: 3em;
margin-right: 3em;
}
#readFooter {
display: block;
- border-top: 1px solid #333;
+ border-top: 1px solid #333;
text-align: center;
}
div.footer-right {
@@ -67,17 +59,36 @@ div.footer-right { margin-top: 18px
}
span.version {
- display: none;
+ display: none;
}
+/* Article Tools */
+#readTools {
+ width: 34px;
+ height: 150px;
+ position: fixed;
+ z-index: 100;
+ top: 10px;
+ left: 10px;
+}
+#readTools a {
+ margin-bottom: 8px;
+ display: block;
+}
+#readTools a img {
+ border: none;
+}
/* ---------------- USER-CONFIGURABLE STYLING --------------------- */
+
/* ---------------- USER-CONFIGURABLE STYLING --------------------- */
+
/* Size options */
+
.size-small {
font-size: 12px;
}
.size-medium {
- font-size: 18px;
+ font-size: 18px;
}
.size-large {
font-size: 26px;
@@ -85,14 +96,13 @@ span.version { .size-x-large {
font-size: 34px;
}
-
/* Style options */
+
.style-novel {
font-family:"Palatino Linotype", "Book Antiqua", Palatino, serif;
background: #F4F3DB;
color: #222;
}
-
.style-ebook {
font-family:Arial, Helvetica, sans-serif;
background: #eee;
@@ -102,24 +112,22 @@ span.version { font-family: "Arial Black", Gadget, sans-serif;
font-weight: normal;
}
-
.style-newspaper {
font-family:"Times New Roman", Times, serif;
- background: #F7F7F7;
+ background: #FFF;
color: #222;
}
.style-newspaper h1 {
text-transform:capitalize;
font-family: Georgia, "Times New Roman", Times, serif;
}
-
.style-terminal {
font-family: "Lucida Console", Monaco, monospace;
background: #1D4E2C;
color: #C6FFC6;
}
-
/* Margin Options */
+
.margin-x-wide {
width: 35%;
}
@@ -135,6 +143,7 @@ span.version { /* ---------------- USER-CONFIGURABLE STYLING --------------------- */
/* ------ DEBUG ------- */
+
.bug-green {
background: #BBF9B0;
border: 4px solid green;
@@ -143,8 +152,24 @@ span.version { background: red;
}
.bug-yellow {
- background: #FFFF8E;
+ background: #FFFF8E;
}
.bug-blue {
- background: #BFDFFF;
+ background: #BFDFFF;
}
+
+/* ---------------- EMAIL POP UP --------------------- */
+
+#email-container{
+ position: fixed;
+ top: 60px;
+ left: 50%;
+ margin: 0 0 0 -240px;
+ padding: 0;
+ width: 480px;
+ height: 460px;
+ border: solid 3px #666;
+ background-color: #fff;
+ z-index: 100 !important;
+ overflow: hidden;
+}
\ No newline at end of file diff --git a/email.php b/email.php new file mode 100644 index 0000000..a83b97a --- /dev/null +++ b/email.php @@ -0,0 +1,427 @@ +<?php + set_include_path( get_include_path() . PATH_SEPARATOR . 'lib'); + session_start(); + + require_once 'Zend/Filter.php'; + require_once 'Zend/Filter/StripTags.php'; + require_once 'Zend/Filter/StringTrim.php'; + + $filters = new Zend_Filter(); + $filters->addFilter(new Zend_Filter_StripTags()) + ->addFilter(new Zend_Filter_StringTrim()); + + $pageTitle = array_key_exists('pageTitle', $_GET) ? $filters->filter($_GET['pageTitle']) : ''; + $pageUrl = array_key_exists('pageUrl', $_GET) ? $filters->filter($_GET['pageUrl']) : ''; + $page = 'form'; + $errors = array(); + + if('post' == strtolower($_SERVER['REQUEST_METHOD'])) + { + // someone sent over an invalid + if(!Readability::hasValidParams()) + { + Readability::logMessage("ERROR:Someone tried to send a request with an invalid set of parameters."); + die(); + } + + require_once 'Zend/Validate/EmailAddress.php'; + + //FILTER DATA + + $from = $filters->filter($_POST['from']); + $to = $filters->filter($_POST['to']); + $to = array_map('trim', split(',', $to)); + $note = $filters->filter($_POST['note']); + $key = $filters->filter($_POST['key']); + + if(!Readability::validateSecureKey($key)) + { + $errors[] = 'key'; + Readability::logMessage("ERROR:Someone tried to send an email with an invalid key."); + } + + // VALIDATE DATA + + $emailValidator = new Zend_Validate_EmailAddress(); + + if(!$emailValidator->isValid($_POST['from'])) + { + $errors[] = 'from'; + } + + if(count($to) == 0) + { + $errors[] = 'to'; + } + else + { + foreach($to as $toAddress) + { + if(!$emailValidator->isValid($toAddress)) + { + $errors[] = 'to'; + break; + } + } + } + + // NO ERRORS SEND EMAIL + if(count($errors) == 0) + { + // store the from address so it's saved for future use + setcookie("from", $from, time()+3600*24*7*4, "/"); + + require_once 'Zend/Mail.php'; + require_once 'Zend/Mail/Transport/Smtp.php'; + + $mailer = new Zend_Mail_Transport_Smtp('smtp.googlemail.com', Array( + 'auth' => 'login', + 'username' => 'readability@arc90.com', + 'password' => 'arc90inc', + 'ssl' => 'ssl', + 'port' => 465, + )); + $mailer->EOL = "\r\n"; // gmail is fussy about this + Zend_Mail::setDefaultTransport($mailer); + + $body = '<html><head>'; + $body = '</head>'; + $body = '<body>'; + $body .= '<div style="font-size: 15px;">'; + $body .= '<p>This page was sent to you by: '.$from.'</p>'; + $body .= '<p>Message from sender: </p><p><blockquote>'.stripslashes($note).'</blockquote></p>'; + $body .= '<p>Just click this link: <a href="'.$pageUrl.'">'.$pageTitle.'</a></p>'; + $body .= '<hr />'; + $body .= '<p style="font-size: 90%;">Sent from <a href="http://lab.arc90.com/experiments/readability/">Readability</a> | An <a href="http://www.arc90.com">Arc90</a> lab experiment<p>'; + $body .= '</div>'; + $body .= '</body></html>'; + + $mail = new Zend_Mail(); + $mail->setBodyHtml($body); + $mail->setFrom($from); + + foreach($to as $toAddress) + { + $mail->addTo($toAddress); + } + + $mail->setSubject("Arc90 Readability: {$pageTitle}"); + + try + { + if(!$mail->send()) + { + Readability::logMessage("ERROR:There was an error sending the email. [to:".implode(', ', $to).", from:{$from}, notes:{$note}, pageUrl: {$pageUrl}, pageTitle: {$pageTitle}]"); + } + else + { + $page = 'complete'; + } + } + catch(Exception $e) + { + Readability::logMessage("ERROR:There was an exception sending the email. [to:".implode(', ', $to).", from:{$from}, notes:{$note}, pageUrl: {$pageUrl}, pageTitle: {$pageTitle}]"); + Readability::logMessage("ERROR:".$e->getMessage()); + } + + //header('location: close.html'); + } + } // end of: if method == POST + + elseif('get' == strtolower($_SERVER['REQUEST_METHOD'])) + { + $_SESSION['secureKey'] = Readability::generateSecureKey(); + } + + class Readability + { + public static function isError($field, $errors) + { + if(in_array($field, $errors)) + { + return TRUE; + } + return FALSE; + } + + public static function getErrorClass($field, $errors) + { + if(in_array($field, $errors)) + { + return 'class = "error"'; + } + return ''; + } + + public static function getParam($param) + { + if(isset($_POST) && array_key_exists($param, $_POST)) + { + return $_POST[$param]; + } + elseif(isset($_COOKIE) && array_key_exists($param, $_COOKIE)) + { + return $_COOKIE[$param]; + } + return ''; + } + + public static function logMessage($message) + { + $logFile = dirname(__FILE__) . '/log.txt'; + + $handle = @fopen($logFile, 'a'); + if(is_resource($handle)) + { + $message = date('Y-m-d G:i:s') . ' :: ' . $message . "\n"; + fwrite($handle, $message); + fclose($handle); + } + } + + public static function generateSecureKey($length = 8) + { + $sucureKey = ""; + $possible = "012*3456)789b(cdfg#hjkmn@pqrs!tvwx[yz"; + + for($x=0; $x < $length; $x++) + { + $char = substr($possible, mt_rand(0, strlen($possible)-1), 1); + + if (!strstr($sucureKey, $char)) + { + $sucureKey .= $char; + } + } + return $sucureKey; + } + + /** + * this adds a small (very small) level of security + * + * @param string $testKey + * @return void + * @author David Hauenstein + */ + public static function validateSecureKey($testKey) + { + if(!array_key_exists('secureKey', $_SESSION)) + { + $_SESSION['secureKey'] = self::generateSecureKey(); + return false; + } + else + { + if($testKey != $_SESSION['secureKey']) + { + return false; + } + } + return true; + } + + public static function emailAsLinks($addresses) + { + $toReturn = ''; + foreach($addresses as $address) + { + $toReturn .= '<a href="mailto:'.$address.'">' . $address . '</a>, '; + } + return substr($toReturn, 0, strlen($toReturn)-2); + } + + public static function hasValidParams() + { + $requiredParams = array('from', 'to', 'note', 'key'); + $sentParams = array_keys($_POST); + foreach($requiredParams as $required) + { + if(!in_array($required, $sentParams)) + { + return false; + } + } + return true; + } + } +?> +<?= '<?' ?>xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>Readability</title> + <script type="text/javascript" charset="utf-8"> + window.onload = function(){ + document.getElementById('cancel-email').onclick = function(){ + window.location = 'http://davehauenstein.com/readability/close.html'; + return false; + }; + document.getElementById('send-email').onclick = function(){ + document.getElementById('send-email-form').submit(); + return false; + }; + }; + <?php if($page == "complete"){ ?> + timer = setTimeout(function(){ + window.location = 'close.html'; + }, 3000); + <?php } ?> + </script> + <style type="text/css" media="screen"> + *{ + margin: 0; + } + #email-container{ + font-size: 14px; + margin: 0; + padding: 0; + width: 480px; + height: 450px; + font-family: times, serif; + background-color: #fff; + } + h2{ + margin: 0 0 10px; + background: url(http://lab.arc90.com/experiments/readability/images/email-head.gif) #e2e3e4 no-repeat 15px center; + text-indent: -99999px; + height: 40px; + } + form{ + padding-right: 20px; + } + label{ + font-size: 20px; + padding-right: 10px; + display: block; + float: left; + width: 100px; + text-align: right; + } + input, + textarea{ + padding: 5px; + width: 330px; + font-family: times, serif; + font-size: 14px; + border: solid 1px #999; + } + input.error{ + border: solid 2px #c33; + } + p.error{ + color: #c33; + font-size: 14px; + } + .helper{ + font-size: 12px; + margin-top: 3px; + color: #666; + } + .details{ + font-style: italic; + font-size: 15px; + } + .helper, + .details{ + margin-left: 110px; /* add label width + label padding-right */ + } + .section{ + margin-top: 15px; + clear: both; + } + #note{ + <?php if(count($errors) > 0){ ?> + height: 100px; + <?php } else { ?> + height: 140px; + <?php } ?> + } + #send-email, + #cancel-email{ + padding: 2px 2px; + font-family: times, serif; + background-color: #e7e8e9; + font-size: 17px; + border: solid 2px #666; + cursor: pointer; + } + #send-email{ + margin-left: 180px; + font-weight: bold; + } + #cancel-email{ + margin-left: 10px; + } + .logo{ + position: absolute; + left: 10px; + bottom: 10px; + } + #complete{ + padding: 20px; + } + #complete p{ + margin: 0 0 10px; + font-size: 16px; + } + </style> + </head> + <body> + <div id="email-container"> + <h2>Email Page</h2> + + <?php if($page == 'form'){ ?> + <form action="" method="post" accept-charset="utf-8" id="send-email-form"> + <div class="section"> + <label for="from">From :</label> + <input type="text" name="from" id="from" value="<?php echo Readability::getParam('from') ?>" <?php echo Readability::getErrorClass('from', $errors); ?> /> + <?php if(Readability::isError('from', $errors)){ ?> + <p class="helper error"> + This field should be a valid email address. + </p> + <?php } ?> + </div> + <div class="section"> + <label for="to">To :</label> + <input type="text" name="to" id="to" value="<?php echo Readability::getParam('to') ?>" <?php echo Readability::getErrorClass('to', $errors); ?> /> + <?php if(Readability::isError('to', $errors)){ ?> + <p class="helper error"> + Please ensure that all addresses are valid email adderesses. + </p> + <?php } ?> + <p class="helper"> + Seperate multiple recipients with commas. + </p> + </div> + <div class="section"> + <label>Sending :</label> + <p class="details"> + <?= $pageTitle ?> + </p> + </div> + <div class="section"> + <label for="note">Note :</label> + <textarea name="note" id="note" rows="8" cols="40"><?php echo Readability::getParam('note') ?></textarea> + </div> + <div class="section"> + <button id="send-email">Email Page</button> + <button id="cancel-email">Cancel</button> + </div> + <img src="http://lab.arc90.com/experiments/readability/images/email-readability.gif" alt="Readability" class="logo" /> + <input type="hidden" name="pageUrl" value="<?= $pageUrl; ?>" id="pageUrl" /> + <input type="hidden" name="pageTitle" value="<?= $pageTitle; ?>" id="pageTitle" /> + <input type="hidden" name="key" value="<?= $_SESSION['secureKey']; ?>" id="key" /> + </form> + <?php }else if($page == "complete"){ ?> + <div id="complete"> + <p> + Thanks for using Readability! + </p> + <p> + A link to this page has been sent to <?php echo Readability::emailAsLinks($to) ?> + </p> + </div> + <?php } ?> + </div> + </body> +</html>
\ No newline at end of file diff --git a/images/email-head.gif b/images/email-head.gif Binary files differnew file mode 100644 index 0000000..1f08afc --- /dev/null +++ b/images/email-head.gif diff --git a/images/email-readability.gif b/images/email-readability.gif Binary files differnew file mode 100644 index 0000000..f8465ab --- /dev/null +++ b/images/email-readability.gif diff --git a/images/read-email.png b/images/read-email.png Binary files differnew file mode 100644 index 0000000..0bc865b --- /dev/null +++ b/images/read-email.png diff --git a/images/read-print.png b/images/read-print.png Binary files differnew file mode 100644 index 0000000..8790be3 --- /dev/null +++ b/images/read-print.png diff --git a/images/read-refresh.png b/images/read-refresh.png Binary files differnew file mode 100644 index 0000000..320d3f5 --- /dev/null +++ b/images/read-refresh.png diff --git a/images/twitter.png b/images/twitter.png Binary files differnew file mode 100644 index 0000000..06a8485 --- /dev/null +++ b/images/twitter.png diff --git a/js/readability.js b/js/readability.js index 934ffb8..97cd2bb 100755 --- a/js/readability.js +++ b/js/readability.js @@ -1,9 +1,12 @@ -var readabilityVersion = "0.3";
+var readabilityVersion = "0.4";
+var emailSrc = 'http://lab.arc90.com/experiments/readability/email.php';
+var iframeLoads = 0;
(function(){
var objOverlay = document.createElement("div");
var objinnerDiv = document.createElement("div");
-
+ var articleTools = document.createElement("DIV");
+
objOverlay.id = "readOverlay";
objinnerDiv.id = "readInner";
@@ -12,7 +15,20 @@ var readabilityVersion = "0.3"; objOverlay.className = readStyle;
objinnerDiv.className = readMargin + " " + readSize;
+ // Set up tools widget
+
+ // NOTE THE IMAGE URL'S HERE !!!!!!!!!!!!!!!!!
+ // NOTE THE IMAGE URL'S HERE !!!!!!!!!!!!!!!!!
+ // NOTE THE IMAGE URL'S HERE !!!!!!!!!!!!!!!!!
+ articleTools.id = "readTools";
+ articleTools.innerHTML = "\
+ <a href='#' onclick='return window.location.reload()'><img src='http://lab.arc90.com/experiments/readability/images/read-refresh.png' title='Reload original page'></a>\
+ <a href='#' onclick='javascript:window.print();'><img src='http://lab.arc90.com/experiments/readability/images/read-print.png' title='Print page'></a>\
+ <a href='#' onclick='emailBox(); return false;'><img src='http://lab.arc90.com/experiments/readability/images/read-email.png' title='Email page'></a>\
+ ";
+
objinnerDiv.appendChild(grabArticle()); // Get the article and place it inside the inner Div
+ objOverlay.appendChild(articleTools);
objOverlay.appendChild(objinnerDiv); // Insert the inner div into the overlay
// For totally hosed HTML, add body node that can't be found because of bad HTML or something.
@@ -113,25 +129,24 @@ function grabArticle() { topDiv = clean(topDiv, "h2");
topDiv = clean(topDiv, "iframe");
+
// Add the footer and contents:
articleFooter.id = "readFooter";
articleFooter.innerHTML = "\
<a href='http://www.arc90.com'><img src='http://lab.arc90.com/experiments/readability/images/footer.png'></a>\
<div class='footer-right' >\
- <a href='#' onclick='return window.location.reload()'>Reload Page »</a>\
<span class='version'>Readability version " + readabilityVersion + "</span>\
</div>\
";
-
+
articleContent.appendChild(topDiv);
articleContent.appendChild(articleFooter);
-
+
return articleContent;
}
// Get the inner text of a node - cross browser compatibly.
-function getInnerText(e)
-{
+function getInnerText(e) {
if (navigator.appName == "Microsoft Internet Explorer")
return e.innerText;
else
@@ -210,3 +225,30 @@ function clean(e, tags, minWords) { return e;
}
+function emailBox() {
+ var emailContainer = document.getElementById('email-container');
+ if(null != emailContainer)
+ {
+ return;
+ }
+
+ var emailContainer = document.createElement('div');
+ emailContainer.setAttribute('id', 'email-container');
+ emailContainer.innerHTML = '<iframe src="'+emailSrc + '?pageUrl='+escape(window.location)+'&pageTitle='+escape(document.title)+'" scrolling="no" onload="removeFrame()" style="width:480px; height: 460px; border: 0;"></iframe>';
+
+ document.body.appendChild(emailContainer);
+}
+
+function removeFrame()
+{
+ ++iframeLoads;
+ if(iframeLoads >= 6)
+ {
+ var emailContainer = document.getElementById('email-container');
+ if(null != emailContainer) {
+ emailContainer.parentNode.removeChild(emailContainer);
+ }
+ // reset the count
+ iframeLoads = 0;
+ }
+}
\ No newline at end of file diff --git a/lib/Zend/Exception.php b/lib/Zend/Exception.php new file mode 100644 index 0000000..599d8a0 --- /dev/null +++ b/lib/Zend/Exception.php @@ -0,0 +1,30 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + + +/** + * @category Zend + * @package Zend + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Exception extends Exception +{} + diff --git a/lib/Zend/Filter.php b/lib/Zend/Filter.php new file mode 100644 index 0000000..23bec21 --- /dev/null +++ b/lib/Zend/Filter.php @@ -0,0 +1,115 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Filter.php 8434 2008-02-27 19:15:13Z darby $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter implements Zend_Filter_Interface +{ + /** + * Filter chain + * + * @var array + */ + protected $_filters = array(); + + /** + * Adds a filter to the end of the chain + * + * @param Zend_Filter_Interface $filter + * @return Zend_Filter Provides a fluent interface + */ + public function addFilter(Zend_Filter_Interface $filter) + { + $this->_filters[] = $filter; + return $this; + } + + /** + * Returns $value filtered through each filter in the chain + * + * Filters are run in the order in which they were added to the chain (FIFO) + * + * @param mixed $value + * @return mixed + */ + public function filter($value) + { + $valueFiltered = $value; + foreach ($this->_filters as $filter) { + $valueFiltered = $filter->filter($valueFiltered); + } + return $valueFiltered; + } + + /** + * Returns a value filtered through a specified filter class, without requiring separate + * instantiation of the filter object. + * + * The first argument of this method is a data input value, that you would have filtered. + * The second argument is a string, which corresponds to the basename of the filter class, + * relative to the Zend_Filter namespace. This method automatically loads the class, + * creates an instance, and applies the filter() method to the data input. You can also pass + * an array of constructor arguments, if they are needed for the filter class. + * + * @param mixed $value + * @param string $classBaseName + * @param array $args OPTIONAL + * @param array|string $namespaces OPTIONAL + * @return mixed + * @throws Zend_Filter_Exception + */ + public static function get($value, $classBaseName, array $args = array(), $namespaces = array()) + { + require_once 'Zend/Loader.php'; + $namespaces = array_merge(array('Zend_Filter'), (array) $namespaces); + foreach ($namespaces as $namespace) { + $className = $namespace . '_' . ucfirst($classBaseName); + try { + @Zend_Loader::loadClass($className); + } catch (Zend_Exception $ze) { + continue; + } + $class = new ReflectionClass($className); + if ($class->implementsInterface('Zend_Filter_Interface')) { + if ($class->hasMethod('__construct')) { + $object = $class->newInstanceArgs($args); + } else { + $object = $class->newInstance(); + } + return $object->filter($value); + } + } + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Filter class not found from basename '$classBaseName'"); + } +} diff --git a/lib/Zend/Filter/Alnum.php b/lib/Zend/Filter/Alnum.php new file mode 100644 index 0000000..a50cddd --- /dev/null +++ b/lib/Zend/Filter/Alnum.php @@ -0,0 +1,115 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Alnum.php 12751 2008-11-21 18:30:48Z yoshida@zend.co.jp $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; +/** + * @see Zend_Locale + */ +require_once 'Zend/Locale.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Alnum implements Zend_Filter_Interface +{ + /** + * Whether to allow white space characters; off by default + * + * @var boolean + */ + public $allowWhiteSpace; + + /** + * Is PCRE is compiled with UTF-8 and Unicode support + * + * @var mixed + **/ + protected static $_unicodeEnabled; + + /** + * Locale in browser. + * + * @var Zend_Locale object + */ + protected $_locale; + + /** + * The Alphabet means english alphabet. + * + * @var boolean + */ + protected static $_meansEnglishAlphabet; + + /** + * Sets default option values for this instance + * + * @param boolean $allowWhiteSpace + * @return void + */ + public function __construct($allowWhiteSpace = false) + { + $this->allowWhiteSpace = (boolean) $allowWhiteSpace; + if (null === self::$_unicodeEnabled) { + self::$_unicodeEnabled = (@preg_match('/\pL/u', 'a')) ? true : false; + } + + if (null === self::$_meansEnglishAlphabet) { + $this->_locale = new Zend_Locale('auto'); + self::$_meansEnglishAlphabet = in_array($this->_locale->getLanguage(), + array('ja', 'ko', 'zh') + ); + } + + } + + /** + * Defined by Zend_Filter_Interface + * + * Returns the string $value, removing all but alphabetic and digit characters + * + * @param string $value + * @return string + */ + public function filter($value) + { + $whiteSpace = $this->allowWhiteSpace ? '\s' : ''; + if (!self::$_unicodeEnabled) { + // POSIX named classes are not supported, use alternative a-zA-Z0-9 match + $pattern = '/[^a-zA-Z0-9' . $whiteSpace . ']/'; + } else if (self::$_meansEnglishAlphabet) { + //The Alphabet means english alphabet. + $pattern = '/[^a-zA-Z0-9' . $whiteSpace . ']/u'; + } else { + //The Alphabet means each language's alphabet. + $pattern = '/[^\p{L}\p{N}' . $whiteSpace . ']/u'; + } + + return preg_replace($pattern, '', (string) $value); + } +} diff --git a/lib/Zend/Filter/Alpha.php b/lib/Zend/Filter/Alpha.php new file mode 100644 index 0000000..adfaafe --- /dev/null +++ b/lib/Zend/Filter/Alpha.php @@ -0,0 +1,115 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Alpha.php 12751 2008-11-21 18:30:48Z yoshida@zend.co.jp $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; +/** + * @see Zend_Locale + */ +require_once 'Zend/Locale.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Alpha implements Zend_Filter_Interface +{ + /** + * Whether to allow white space characters; off by default + * + * @var boolean + */ + public $allowWhiteSpace; + + /** + * Is PCRE is compiled with UTF-8 and Unicode support + * + * @var mixed + **/ + protected static $_unicodeEnabled; + + /** + * Locale in browser. + * + * @var Zend_Locale object + */ + protected $_locale; + + /** + * The Alphabet means english alphabet. + * + * @var boolean + */ + protected static $_meansEnglishAlphabet; + + /** + * Sets default option values for this instance + * + * @param boolean $allowWhiteSpace + * @return void + */ + public function __construct($allowWhiteSpace = false) + { + $this->allowWhiteSpace = (boolean) $allowWhiteSpace; + if (null === self::$_unicodeEnabled) { + self::$_unicodeEnabled = (@preg_match('/\pL/u', 'a')) ? true : false; + } + + if (null === self::$_meansEnglishAlphabet) { + $this->_locale = new Zend_Locale('auto'); + self::$_meansEnglishAlphabet = in_array($this->_locale->getLanguage(), + array('ja', 'ko', 'zh') + ); + } + + } + + /** + * Defined by Zend_Filter_Interface + * + * Returns the string $value, removing all but alphabetic characters + * + * @param string $value + * @return string + */ + public function filter($value) + { + $whiteSpace = $this->allowWhiteSpace ? '\s' : ''; + if (!self::$_unicodeEnabled) { + // POSIX named classes are not supported, use alternative a-zA-Z match + $pattern = '/[^a-zA-Z' . $whiteSpace . ']/'; + } else if (self::$_meansEnglishAlphabet) { + //The Alphabet means english alphabet. + $pattern = '/[^a-zA-Z' . $whiteSpace . ']/u'; + } else { + //The Alphabet means each language's alphabet. + $pattern = '/[^\p{L}' . $whiteSpace . ']/u'; + } + + return preg_replace($pattern, '', (string) $value); + } +} diff --git a/lib/Zend/Filter/BaseName.php b/lib/Zend/Filter/BaseName.php new file mode 100644 index 0000000..a0652ab --- /dev/null +++ b/lib/Zend/Filter/BaseName.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: BaseName.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_BaseName implements Zend_Filter_Interface +{ + /** + * Defined by Zend_Filter_Interface + * + * Returns basename($value) + * + * @param string $value + * @return string + */ + public function filter($value) + { + return basename((string) $value); + } +} diff --git a/lib/Zend/Filter/Digits.php b/lib/Zend/Filter/Digits.php new file mode 100644 index 0000000..e062e1a --- /dev/null +++ b/lib/Zend/Filter/Digits.php @@ -0,0 +1,82 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Digits.php 8731 2008-03-10 15:08:03Z darby $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Digits implements Zend_Filter_Interface +{ + /** + * Is PCRE is compiled with UTF-8 and Unicode support + * + * @var mixed + **/ + protected static $_unicodeEnabled; + + /** + * Class constructor + * + * Checks if PCRE is compiled with UTF-8 and Unicode support + * + * @return void + */ + public function __construct() + { + if (null === self::$_unicodeEnabled) { + self::$_unicodeEnabled = (@preg_match('/\pL/u', 'a')) ? true : false; + } + } + + /** + * Defined by Zend_Filter_Interface + * + * Returns the string $value, removing all but digit characters + * + * @param string $value + * @return string + */ + public function filter($value) + { + if (!self::$_unicodeEnabled) { + // POSIX named classes are not supported, use alternative 0-9 match + $pattern = '/[^0-9]/'; + } else if (extension_loaded('mbstring')) { + // Filter for the value with mbstring + $pattern = '/[^[:digit:]]/'; + } else { + // Filter for the value without mbstring + $pattern = '/[\p{^N}]/'; + } + + return preg_replace($pattern, '', (string) $value); + } +} diff --git a/lib/Zend/Filter/Dir.php b/lib/Zend/Filter/Dir.php new file mode 100644 index 0000000..a08534e --- /dev/null +++ b/lib/Zend/Filter/Dir.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Dir.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Dir implements Zend_Filter_Interface +{ + /** + * Defined by Zend_Filter_Interface + * + * Returns dirname($value) + * + * @param string $value + * @return string + */ + public function filter($value) + { + return dirname((string) $value); + } +} diff --git a/lib/Zend/Filter/Exception.php b/lib/Zend/Filter/Exception.php new file mode 100644 index 0000000..3142c81 --- /dev/null +++ b/lib/Zend/Filter/Exception.php @@ -0,0 +1,37 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Exception.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Exception + */ +require_once 'Zend/Exception.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Exception extends Zend_Exception +{} diff --git a/lib/Zend/Filter/File/LowerCase.php b/lib/Zend/Filter/File/LowerCase.php new file mode 100644 index 0000000..98abab2 --- /dev/null +++ b/lib/Zend/Filter/File/LowerCase.php @@ -0,0 +1,84 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Filter_StringToLower + */ +require_once 'Zend/Filter/StringToLower.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_File_LowerCase extends Zend_Filter_StringToLower +{ + /** + * Adds options to the filter at initiation + * + * @param string $options + */ + public function __construct($options = null) + { + if (!empty($options)) { + $this->setEncoding($options); + } + } + + /** + * Defined by Zend_Filter_Interface + * + * Does a lowercase on the content of the given file + * + * @param string $value Full path of file to change + * @return string The given $value + * @throws Zend_Filter_Exception + */ + public function filter($value) + { + if (!file_exists($value)) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("File '$value' not found"); + } + + if (!is_writable($value)) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("File '$value' is not writable"); + } + + $content = file_get_contents($value); + if (!$content) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Problem while reading file '$value'"); + } + + $content = parent::filter($content); + $result = file_put_contents($value, $content); + + if (!$result) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Problem while writing file '$value'"); + } + + return $value; + } +} diff --git a/lib/Zend/Filter/File/Rename.php b/lib/Zend/Filter/File/Rename.php new file mode 100644 index 0000000..718443f --- /dev/null +++ b/lib/Zend/Filter/File/Rename.php @@ -0,0 +1,282 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_File_Rename implements Zend_Filter_Interface +{ + /** + * Internal array of array(source, target, overwrite) + */ + protected $_files = array(); + + /** + * Class constructor + * + * Options argument may be either a string, a Zend_Config object, or an array. + * If an array or Zend_Config object, it accepts the following keys: + * 'source' => Source filename or directory which will be renamed + * 'target' => Target filename or directory, the new name of the sourcefile + * 'overwrite' => Shall existing files be overwritten ? + * + * @param string|array $options Target file or directory to be renamed + * @param string $target Source filename or directory (deprecated) + * @param bool $overwrite Should existing files be overwritten (deprecated) + * @return void + */ + public function __construct($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (is_string($options)) { + $options = array('target' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception('Invalid options argument provided to filter'); + } + + if (1 < func_num_args()) { + trigger_error('Support for multiple arguments is deprecated in favor of a single options array', E_USER_NOTICE); + $argv = func_get_args(); + array_shift($argv); + $source = array_shift($argv); + $overwrite = false; + if (!empty($argv)) { + $overwrite = array_shift($argv); + } + $options['source'] = $source; + $options['overwrite'] = $overwrite; + } + + $this->setFile($options); + } + + /** + * Returns the files to rename and their new name and location + * + * @return array + */ + public function getFile() + { + return $this->_files; + } + + /** + * Sets a new file or directory as target, deleting existing ones + * + * Array accepts the following keys: + * 'source' => Source filename or directory which will be renamed + * 'target' => Target filename or directory, the new name of the sourcefile + * 'overwrite' => Shall existing files be overwritten ? + * + * @param string|array $options Old file or directory to be rewritten + * @return Zend_Filter_File_Rename + */ + public function setFile($options) + { + $this->_files = array(); + $this->addFile($options); + + return $this; + } + + /** + * Adds a new file or directory as target to the existing ones + * + * Array accepts the following keys: + * 'source' => Source filename or directory which will be renamed + * 'target' => Target filename or directory, the new name of the sourcefile + * 'overwrite' => Shall existing files be overwritten ? + * + * @param string|array $options Old file or directory to be rewritten + * @return Zend_Filter_File_Rename + */ + public function addFile($options) + { + if (is_string($options)) { + $options = array('target' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception ('Invalid options to rename filter provided'); + } + + $this->_convertOptions($options); + + return $this; + } + + /** + * Defined by Zend_Filter_Interface + * + * Renames the file $value to the new name set before + * Returns the file $value, removing all but digit characters + * + * @param string $value Full path of file to change + * @return string The new filename which has been set, or false when there were errors + */ + public function filter($value) + { + $file = $this->_getFileName($value); + if ($file['source'] == $file['target']) { + return $value; + } + + if (!file_exists($file['source'])) { + return $value; + } + + if (($file['overwrite'] == true) and (file_exists($file['target']))) { + unlink($file['target']); + } + + if (file_exists($file['target'])) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception(sprintf("File '%s' could not be renamed. It already exists.", $value)); + } + + $result = rename($file['source'], $file['target']); + + if ($result === true) { + return $file['target']; + } + + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception(sprintf("File '%s' could not be renamed. An error occured while processing the file.", $value)); + } + + /** + * Internal method for creating the file array + * Supports single and nested arrays + * + * @param array $options + * @return array + */ + protected function _convertOptions($options) { + $files = array(); + foreach ($options as $key => $value) { + if (is_array($value)) { + $this->_convertOptions($value); + continue; + } + + switch ($key) { + case "source": + $files['source'] = (string) $value; + break; + + case 'target' : + $files['target'] = (string) $value; + break; + + case 'overwrite' : + $files['overwrite'] = (boolean) $value; + break; + + default: + break; + } + } + + if (empty($files)) { + return $this; + } + + if (empty($files['source'])) { + $files['source'] = '*'; + } + + if (empty($files['target'])) { + $files['target'] = '*'; + } + + if (empty($files['overwrite'])) { + $files['overwrite'] = false; + } + + $found = false; + foreach ($this->_files as $key => $value) { + if ($value['source'] == $files['source']) { + $this->_files[$key] = $files; + $found = true; + } + } + + if (!$found) { + $count = count($this->_files); + $this->_files[$count] = $files; + } + + return $this; + } + + /** + * Internal method to resolve the requested source + * and return all other related parameters + * + * @param string $file Filename to get the informations for + * @return array + */ + protected function _getFileName($file) + { + $rename = array(); + foreach ($this->_files as $value) { + if ($value['source'] == '*') { + if (!isset($rename['source'])) { + $rename = $value; + $rename['source'] = $file; + } + } + + if ($value['source'] == $file) { + $rename = $value; + } + } + + if (!isset($rename['source'])) { + return $file; + } + + if (!isset($rename['target']) or ($rename['target'] == '*')) { + $rename['target'] = $rename['source']; + } + + if (is_dir($rename['target'])) { + $name = basename($rename['source']); + $last = $rename['target'][strlen($rename['target']) - 1]; + if (($last != '/') and ($last != '\\')) { + $rename['target'] .= DIRECTORY_SEPARATOR; + } + + $rename['target'] .= $name; + } + + return $rename; + } +} diff --git a/lib/Zend/Filter/File/UpperCase.php b/lib/Zend/Filter/File/UpperCase.php new file mode 100644 index 0000000..9501e9e --- /dev/null +++ b/lib/Zend/Filter/File/UpperCase.php @@ -0,0 +1,84 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Filter_StringToUpper + */ +require_once 'Zend/Filter/StringToUpper.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_File_UpperCase extends Zend_Filter_StringToUpper +{ + /** + * Adds options to the filter at initiation + * + * @param string $options + */ + public function __construct($options = null) + { + if (!empty($options)) { + $this->setEncoding($options); + } + } + + /** + * Defined by Zend_Filter_Interface + * + * Does a lowercase on the content of the given file + * + * @param string $value Full path of file to change + * @return string The given $value + * @throws Zend_Filter_Exception + */ + public function filter($value) + { + if (!file_exists($value)) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("File '$value' not found"); + } + + if (!is_writable($value)) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("File '$value' is not writable"); + } + + $content = file_get_contents($value); + if (!$content) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Problem while reading file '$value'"); + } + + $content = parent::filter($content); + $result = file_put_contents($value, $content); + + if (!$result) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Problem while writing file '$value'"); + } + + return $value; + } +} diff --git a/lib/Zend/Filter/HtmlEntities.php b/lib/Zend/Filter/HtmlEntities.php new file mode 100644 index 0000000..e9887a2 --- /dev/null +++ b/lib/Zend/Filter/HtmlEntities.php @@ -0,0 +1,122 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: HtmlEntities.php 11783 2008-10-09 17:38:54Z andries $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_HtmlEntities implements Zend_Filter_Interface +{ + /** + * Corresponds to second htmlentities() argument + * + * @var integer + */ + protected $_quoteStyle; + + /** + * Corresponds to third htmlentities() argument + * + * @var string + */ + protected $_charSet; + + /** + * Sets filter options + * + * @param integer $quoteStyle + * @param string $charSet + * @return void + */ + public function __construct($quoteStyle = ENT_COMPAT, $charSet = 'ISO-8859-1') + { + $this->_quoteStyle = $quoteStyle; + $this->_charSet = $charSet; + } + + /** + * Returns the quoteStyle option + * + * @return integer + */ + public function getQuoteStyle() + { + return $this->_quoteStyle; + } + + /** + * Sets the quoteStyle option + * + * @param integer $quoteStyle + * @return Zend_Filter_HtmlEntities Provides a fluent interface + */ + public function setQuoteStyle($quoteStyle) + { + $this->_quoteStyle = $quoteStyle; + return $this; + } + + /** + * Returns the charSet option + * + * @return string + */ + public function getCharSet() + { + return $this->_charSet; + } + + /** + * Sets the charSet option + * + * @param string $charSet + * @return Zend_Filter_HtmlEntities Provides a fluent interface + */ + public function setCharSet($charSet) + { + $this->_charSet = $charSet; + return $this; + } + + /** + * Defined by Zend_Filter_Interface + * + * Returns the string $value, converting characters to their corresponding HTML entity + * equivalents where they exist + * + * @param string $value + * @return string + */ + public function filter($value) + { + return htmlentities((string) $value, $this->_quoteStyle, $this->_charSet); + } +} diff --git a/lib/Zend/Filter/Inflector.php b/lib/Zend/Filter/Inflector.php new file mode 100644 index 0000000..4e7e493 --- /dev/null +++ b/lib/Zend/Filter/Inflector.php @@ -0,0 +1,497 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Inflector.php 12501 2008-11-10 16:28:31Z matthew $ + */ + +/** + * @see Zend_Filter + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter.php'; + +/** + * @see Zend_Loader_PluginLoader + */ +require_once 'Zend/Loader/PluginLoader.php'; + +/** + * Filter chain for string inflection + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Inflector implements Zend_Filter_Interface +{ + /** + * @var Zend_Loader_PluginLoader_Interface + */ + protected $_pluginLoader = null; + + /** + * @var string + */ + protected $_target = null; + + /** + * @var bool + */ + protected $_throwTargetExceptionsOn = true; + + /** + * @var string + */ + protected $_targetReplacementIdentifier = ':'; + + /** + * @var array + */ + protected $_rules = array(); + + /** + * Constructor + * + * @param string $target + * @param array $rules + */ + public function __construct($target = null, Array $rules = array(), $throwTargetExceptionsOn = null, $targetReplacementIdentifer = null) + { + if ($target instanceof Zend_Config) { + $this->setConfig($target); + } else { + if ((null !== $target) && is_string($target)) { + $this->setTarget($target); + } + + if (null !== $rules) { + $this->addRules($rules); + } + + if ($throwTargetExceptionsOn !== null) { + $this->setThrowTargetExceptionsOn($throwTargetExceptionsOn); + } + + if ($targetReplacementIdentifer != '') { + $this->setTargetReplacementIdentifier($targetReplacementIdentifer); + } + } + } + + /** + * Retreive PluginLoader + * + * @return Zend_Loader_PluginLoader_Interface + */ + public function getPluginLoader() + { + if (!$this->_pluginLoader instanceof Zend_Loader_PluginLoader_Interface) { + $this->_pluginLoader = new Zend_Loader_PluginLoader(array('Zend_Filter_' => 'Zend/Filter/'), __CLASS__); + } + + return $this->_pluginLoader; + } + + /** + * Set PluginLoader + * + * @param Zend_Loader_PluginLoader_Interface $pluginLoader + * @return Zend_Filter_Inflector + */ + public function setPluginLoader(Zend_Loader_PluginLoader_Interface $pluginLoader) + { + $this->_pluginLoader = $pluginLoader; + return $this; + } + + /** + * Use Zend_Config object to set object state + * + * @param Zend_Config $config + * @return Zend_Filter_Inflector + */ + public function setConfig(Zend_Config $config) + { + foreach ($config as $key => $value) { + switch ($key) { + case 'target': + $this->setTarget($value); + break; + case 'filterPrefixPath': + if (is_scalar($value)) { + break; + } + $paths = $value->toArray(); + foreach ($paths as $prefix => $path) { + $this->addFilterPrefixPath($prefix, $path); + } + break; + case 'throwTargetExceptionsOn': + $this->setThrowTargetExceptionsOn($value); + break; + case 'targetReplacementIdentifier': + $this->setTargetReplacementIdentifier($value); + break; + case 'rules': + $this->addRules($value->toArray()); + break; + default: + break; + } + } + return $this; + } + + /** + * Convienence method to add prefix and path to PluginLoader + * + * @param string $prefix + * @param string $path + * @return Zend_Filter_Inflector + */ + public function addFilterPrefixPath($prefix, $path) + { + $this->getPluginLoader()->addPrefixPath($prefix, $path); + return $this; + } + + /** + * Set Whether or not the inflector should throw an exception when a replacement + * identifier is still found within an inflected target. + * + * @param bool $throwTargetExceptions + * @return Zend_Filter_Inflector + */ + public function setThrowTargetExceptionsOn($throwTargetExceptionsOn) + { + $this->_throwTargetExceptionsOn = ($throwTargetExceptionsOn == true) ? true : false; + return $this; + } + + /** + * Will exceptions be thrown? + * + * @return bool + */ + public function isThrowTargetExceptionsOn() + { + return $this->_throwTargetExceptionsOn; + } + + /** + * Set the Target Replacement Identifier, by default ':' + * + * @param string $targetReplacementIdentifier + * @return Zend_Filter_Inflector + */ + public function setTargetReplacementIdentifier($targetReplacementIdentifier) + { + $this->_targetReplacementIdentifier = (string) $targetReplacementIdentifier; + return $this; + } + + /** + * Get Target Replacement Identifier + * + * @return string + */ + public function getTargetReplacementIdentifier() + { + return $this->_targetReplacementIdentifier; + } + + /** + * Set a Target + * ex: 'scripts/:controller/:action.:suffix' + * + * @param string + * @return Zend_Filter_Inflector + */ + public function setTarget($target) + { + $this->_target = (string) $target; + return $this; + } + + /** + * Retrieve target + * + * @return string + */ + public function getTarget() + { + return $this->_target; + } + + /** + * Set Target Reference + * + * @param reference $target + * @return Zend_Filter_Inflector + */ + public function setTargetReference(&$target) + { + $this->_target =& $target; + return $this; + } + + /** + * SetRules() is the same as calling addRules() with the exception that it + * clears the rules before adding them. + * + * @param array $rules + * @return Zend_Filter_Inflector + */ + public function setRules(Array $rules) + { + $this->clearRules(); + $this->addRules($rules); + return $this; + } + + /** + * AddRules(): multi-call to setting filter rules. + * + * If prefixed with a ":" (colon), a filter rule will be added. If not + * prefixed, a static replacement will be added. + * + * ex: + * array( + * ':controller' => array('CamelCaseToUnderscore','StringToLower'), + * ':action' => array('CamelCaseToUnderscore','StringToLower'), + * 'suffix' => 'phtml' + * ); + * + * @param array + * @return Zend_Filter_Inflector + */ + public function addRules(Array $rules) + { + $keys = array_keys($rules); + foreach ($keys as $spec) { + if ($spec[0] == ':') { + $this->addFilterRule($spec, $rules[$spec]); + } else { + $this->setStaticRule($spec, $rules[$spec]); + } + } + + return $this; + } + + /** + * Get rules + * + * By default, returns all rules. If a $spec is provided, will return those + * rules if found, false otherwise. + * + * @param string $spec + * @return array|false + */ + public function getRules($spec = null) + { + if (null !== $spec) { + $spec = $this->_normalizeSpec($spec); + if (isset($this->_rules[$spec])) { + return $this->_rules[$spec]; + } + return false; + } + + return $this->_rules; + } + + /** + * getRule() returns a rule set by setFilterRule(), a numeric index must be provided + * + * @param string $spec + * @param int $index + * @return Zend_Filter_Interface|false + */ + public function getRule($spec, $index) + { + $spec = $this->_normalizeSpec($spec); + if (isset($this->_rules[$spec]) && is_array($this->_rules[$spec])) { + if (isset($this->_rules[$spec][$index])) { + return $this->_rules[$spec][$index]; + } + } + return false; + } + + /** + * ClearRules() clears the rules currently in the inflector + * + * @return Zend_Filter_Inflector + */ + public function clearRules() + { + $this->_rules = array(); + return $this; + } + + /** + * Set a filtering rule for a spec. $ruleSet can be a string, Filter object + * or an array of strings or filter objects. + * + * @param string $spec + * @param array|string|Zend_Filter_Interface $ruleSet + * @return Zend_Filter_Inflector + */ + public function setFilterRule($spec, $ruleSet) + { + $spec = $this->_normalizeSpec($spec); + $this->_rules[$spec] = array(); + return $this->addFilterRule($spec, $ruleSet); + } + + /** + * Add a filter rule for a spec + * + * @param mixed $spec + * @param mixed $ruleSet + * @return void + */ + public function addFilterRule($spec, $ruleSet) + { + $spec = $this->_normalizeSpec($spec); + if (!isset($this->_rules[$spec])) { + $this->_rules[$spec] = array(); + } + if (!is_array($ruleSet)) { + $ruleSet = array($ruleSet); + } + foreach ($ruleSet as $rule) { + $this->_rules[$spec][] = $this->_getRule($rule); + } + return $this; + } + + /** + * Set a static rule for a spec. This is a single string value + * + * @param string $name + * @param string $value + * @return Zend_Filter_Inflector + */ + public function setStaticRule($name, $value) + { + $name = $this->_normalizeSpec($name); + $this->_rules[$name] = (string) $value; + return $this; + } + + /** + * Set Static Rule Reference. + * + * This allows a consuming class to pass a property or variable + * in to be referenced when its time to build the output string from the + * target. + * + * @param string $name + * @param mixed $reference + * @return Zend_Filter_Inflector + */ + public function setStaticRuleReference($name, &$reference) + { + $name = $this->_normalizeSpec($name); + $this->_rules[$name] =& $reference; + return $this; + } + + /** + * Inflect + * + * @param string|array $source + * @return string + */ + public function filter($source) + { + // clean source + foreach ( (array) $source as $sourceName => $sourceValue) { + $source[ltrim($sourceName, ':')] = $sourceValue; + } + + $pregQuotedTargetReplacementIdentifier = preg_quote($this->_targetReplacementIdentifier, '#'); + $processedParts = array(); + + foreach ($this->_rules as $ruleName => $ruleValue) { + if (isset($source[$ruleName])) { + if (is_string($ruleValue)) { + // overriding the set rule + $processedParts['#'.$pregQuotedTargetReplacementIdentifier.$ruleName.'#'] = str_replace('\\', '\\\\', $source[$ruleName]); + } elseif (is_array($ruleValue)) { + $processedPart = $source[$ruleName]; + foreach ($ruleValue as $ruleFilter) { + $processedPart = $ruleFilter->filter($processedPart); + } + $processedParts['#'.$pregQuotedTargetReplacementIdentifier.$ruleName.'#'] = str_replace('\\', '\\\\', $processedPart); + } + } elseif (is_string($ruleValue)) { + $processedParts['#'.$pregQuotedTargetReplacementIdentifier.$ruleName.'#'] = str_replace('\\', '\\\\', $ruleValue); + } + } + + // all of the values of processedParts would have been str_replace('\\', '\\\\', ..)'d to disable preg_replace backreferences + $inflectedTarget = preg_replace(array_keys($processedParts), array_values($processedParts), $this->_target); + + if ($this->_throwTargetExceptionsOn && (preg_match('#(?='.$pregQuotedTargetReplacementIdentifier.'[A-Za-z]{1})#', $inflectedTarget) == true)) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception('A replacement identifier ' . $this->_targetReplacementIdentifier . ' was found inside the inflected target, perhaps a rule was not satisfied with a target source? Unsatisfied inflected target: ' . $inflectedTarget); + } + + return $inflectedTarget; + } + + /** + * Normalize spec string + * + * @param string $spec + * @return string + */ + protected function _normalizeSpec($spec) + { + return ltrim((string) $spec, ':&'); + } + + /** + * Resolve named filters and convert them to filter objects. + * + * @param string $rule + * @return Zend_Filter_Interface + */ + protected function _getRule($rule) + { + if ($rule instanceof Zend_Filter_Interface) { + return $rule; + } + + $rule = (string) $rule; + + $className = $this->getPluginLoader()->load($rule); + $ruleObject = new $className(); + if (!$ruleObject instanceof Zend_Filter_Interface) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception('No class named ' . $rule . ' implementing Zend_Filter_Interface could be found'); + } + + return $ruleObject; + } + +} diff --git a/lib/Zend/Filter/Input.php b/lib/Zend/Filter/Input.php new file mode 100644 index 0000000..a6782a3 --- /dev/null +++ b/lib/Zend/Filter/Input.php @@ -0,0 +1,926 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Input.php 8856 2008-03-16 11:28:48Z thomas $ + */ + +/** + * @see Zend_Loader + */ +require_once 'Zend/Loader.php'; + +/** + * @see Zend_Filter + */ +require_once 'Zend/Filter.php'; + +/** + * @see Zend_Validate + */ +require_once 'Zend/Validate.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Input +{ + + const ALLOW_EMPTY = 'allowEmpty'; + const BREAK_CHAIN = 'breakChainOnFailure'; + const DEFAULT_VALUE = 'default'; + const MESSAGES = 'messages'; + const ESCAPE_FILTER = 'escapeFilter'; + const FIELDS = 'fields'; + const FILTER = 'filter'; + const FILTER_CHAIN = 'filterChain'; + const MISSING_MESSAGE = 'missingMessage'; + const INPUT_NAMESPACE = 'inputNamespace'; + const NOT_EMPTY_MESSAGE = 'notEmptyMessage'; + const PRESENCE = 'presence'; + const PRESENCE_OPTIONAL = 'optional'; + const PRESENCE_REQUIRED = 'required'; + const RULE = 'rule'; + const RULE_WILDCARD = '*'; + const VALIDATE = 'validate'; + const VALIDATOR = 'validator'; + const VALIDATOR_CHAIN = 'validatorChain'; + const VALIDATOR_CHAIN_COUNT = 'validatorChainCount'; + + /** + * @var array Input data, before processing. + */ + protected $_data = array(); + + /** + * @var array Association of rules to filters. + */ + protected $_filterRules = array(); + + /** + * @var array Association of rules to validators. + */ + protected $_validatorRules = array(); + + /** + * @var array After processing data, this contains mapping of valid fields + * to field values. + */ + protected $_validFields = array(); + + /** + * @var array After processing data, this contains mapping of validation + * rules that did not pass validation to the array of messages returned + * by the validator chain. + */ + protected $_invalidMessages = array(); + + /** + * @var array After processing data, this contains mapping of validation + * rules that did not pass validation to the array of error identifiers + * returned by the validator chain. + */ + protected $_invalidErrors = array(); + + /** + * @var array After processing data, this contains mapping of validation + * rules in which some fields were missing to the array of messages + * indicating which fields were missing. + */ + protected $_missingFields = array(); + + /** + * @var array After processing, this contains a copy of $_data elements + * that were not mentioned in any validation rule. + */ + protected $_unknownFields = array(); + + /** + * @var Zend_Filter_Interface The filter object that is run on values + * returned by the getEscaped() method. + */ + protected $_defaultEscapeFilter = null; + + /** + * Plugin loaders + * @var array + */ + protected $_loaders = array(); + + /** + * @var array Default values to use when processing filters and validators. + */ + protected $_defaults = array( + self::ALLOW_EMPTY => false, + self::BREAK_CHAIN => false, + self::ESCAPE_FILTER => 'HtmlEntities', + self::MISSING_MESSAGE => "Field '%field%' is required by rule '%rule%', but the field is missing", + self::NOT_EMPTY_MESSAGE => "You must give a non-empty value for field '%field%'", + self::PRESENCE => self::PRESENCE_OPTIONAL + ); + + /** + * @var boolean Set to False initially, this is set to True after the + * input data have been processed. Reset to False in setData() method. + */ + protected $_processed = false; + + /** + * @param array $filterRules + * @param array $validatorRules + * @param array $data OPTIONAL + * @param array $options OPTIONAL + */ + public function __construct($filterRules, $validatorRules, array $data = null, array $options = null) + { + if ($options) { + $this->setOptions($options); + } + + $this->_filterRules = (array) $filterRules; + $this->_validatorRules = (array) $validatorRules; + + if ($data) { + $this->setData($data); + } + } + + /** + * @param mixed $namespaces + * @return Zend_Filter_Input + * @deprecated since 1.5.0RC1 - use addFilterPrefixPath() or addValidatorPrefixPath instead. + */ + public function addNamespace($namespaces) + { + if (!is_array($namespaces)) { + $namespaces = array($namespaces); + } + + foreach ($namespaces as $namespace) { + $prefix = $namespace; + $path = str_replace('_', DIRECTORY_SEPARATOR, $prefix); + $this->addFilterPrefixPath($prefix, $path); + $this->addValidatorPrefixPath($prefix, $path); + } + + return $this; + } + + /** + * Add prefix path for all elements + * + * @param string $prefix + * @param string $path + * @return Zend_Filter_Input + */ + public function addFilterPrefixPath($prefix, $path) + { + $this->getPluginLoader(self::FILTER)->addPrefixPath($prefix, $path); + + return $this; + } + + /** + * Add prefix path for all elements + * + * @param string $prefix + * @param string $path + * @return Zend_Filter_Input + */ + public function addValidatorPrefixPath($prefix, $path) + { + $this->getPluginLoader(self::VALIDATE)->addPrefixPath($prefix, $path); + + return $this; + } + + /** + * Set plugin loaders for use with decorators and elements + * + * @param Zend_Loader_PluginLoader_Interface $loader + * @param string $type 'filter' or 'validate' + * @return Zend_Filter_Input + * @throws Zend_Filter_Exception on invalid type + */ + public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type) + { + $type = strtolower($type); + switch ($type) { + case self::FILTER: + case self::VALIDATE: + $this->_loaders[$type] = $loader; + return $this; + default: + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type)); + } + + return $this; + } + + /** + * Retrieve plugin loader for given type + * + * $type may be one of: + * - filter + * - validator + * + * If a plugin loader does not exist for the given type, defaults are + * created. + * + * @param string $type 'filter' or 'validate' + * @return Zend_Loader_PluginLoader_Interface + * @throws Zend_Filter_Exception on invalid type + */ + public function getPluginLoader($type) + { + $type = strtolower($type); + if (!isset($this->_loaders[$type])) { + switch ($type) { + case self::FILTER: + $prefixSegment = 'Zend_Filter_'; + $pathSegment = 'Zend/Filter/'; + break; + case self::VALIDATE: + $prefixSegment = 'Zend_Validate_'; + $pathSegment = 'Zend/Validate/'; + break; + default: + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type)); + } + + require_once 'Zend/Loader/PluginLoader.php'; + $this->_loaders[$type] = new Zend_Loader_PluginLoader( + array($prefixSegment => $pathSegment) + ); + } + + return $this->_loaders[$type]; + } + + /** + * @return array + */ + public function getMessages() + { + $this->_process(); + return array_merge($this->_invalidMessages, $this->_missingFields); + } + + /** + * @return array + */ + public function getErrors() + { + $this->_process(); + return $this->_invalidErrors; + } + + /** + * @return array + */ + public function getInvalid() + { + $this->_process(); + return $this->_invalidMessages; + } + + /** + * @return array + */ + public function getMissing() + { + $this->_process(); + return $this->_missingFields; + } + + /** + * @return array + */ + public function getUnknown() + { + $this->_process(); + return $this->_unknownFields; + } + + /** + * @param string $fieldName OPTIONAL + * @return mixed + */ + public function getEscaped($fieldName = null) + { + $this->_process(); + $this->_getDefaultEscapeFilter(); + + if ($fieldName === null) { + return $this->_escapeRecursive($this->_validFields); + } + if (array_key_exists($fieldName, $this->_validFields)) { + return $this->_escapeRecursive($this->_validFields[$fieldName]); + } + return null; + } + + /** + * @param mixed $value + * @return mixed + */ + protected function _escapeRecursive($data) + { + if (!is_array($data)) { + return $this->_getDefaultEscapeFilter()->filter($data); + } + foreach ($data as &$element) { + $element = $this->_escapeRecursive($element); + } + return $data; + } + + /** + * @param string $fieldName OPTIONAL + * @return mixed + */ + public function getUnescaped($fieldName = null) + { + $this->_process(); + if ($fieldName === null) { + return $this->_validFields; + } + if (array_key_exists($fieldName, $this->_validFields)) { + return $this->_validFields[$fieldName]; + } + return null; + } + + /** + * @param string $fieldName + * @return mixed + */ + public function __get($fieldName) + { + return $this->getEscaped($fieldName); + } + + /** + * @return boolean + */ + public function hasInvalid() + { + $this->_process(); + return !(empty($this->_invalidMessages)); + } + + /** + * @return boolean + */ + public function hasMissing() + { + $this->_process(); + return !(empty($this->_missingFields)); + } + + /** + * @return boolean + */ + public function hasUnknown() + { + $this->_process(); + return !(empty($this->_unknownFields)); + } + + /** + * @return boolean + */ + public function hasValid() + { + $this->_process(); + return !(empty($this->_validFields)); + } + + /** + * @param string $fieldName + * @return boolean + */ + public function isValid($fieldName = null) + { + $this->_process(); + if ($fieldName === null) { + return !($this->hasMissing() || $this->hasInvalid()); + } + return array_key_exists($fieldName, $this->_validFields); + } + + /** + * @param string $fieldName + * @return boolean + */ + public function __isset($fieldName) + { + $this->_process(); + return isset($this->_validFields[$fieldName]); + } + + /** + * @return Zend_Filter_Input + * @throws Zend_Filter_Exception + */ + public function process() + { + $this->_process(); + if ($this->hasInvalid()) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Input has invalid fields"); + } + if ($this->hasMissing()) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Input has missing fields"); + } + + return $this; + } + + /** + * @param array $data + * @return Zend_Filter_Input + */ + public function setData(array $data) + { + $this->_data = $data; + + /** + * Reset to initial state + */ + $this->_validFields = array(); + $this->_invalidMessages = array(); + $this->_invalidErrors = array(); + $this->_missingFields = array(); + $this->_unknownFields = array(); + + $this->_processed = false; + + return $this; + } + + /** + * @param mixed $escapeFilter + * @return Zend_Filter_Interface + */ + public function setDefaultEscapeFilter($escapeFilter) + { + if (is_string($escapeFilter) || is_array($escapeFilter)) { + $escapeFilter = $this->_getFilter($escapeFilter); + } + if (!$escapeFilter instanceof Zend_Filter_Interface) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception('Escape filter specified does not implement Zend_Filter_Interface'); + } + $this->_defaultEscapeFilter = $escapeFilter; + return $escapeFilter; + } + + /** + * @param array $options + * @return Zend_Filter_Input + * @throws Zend_Filter_Exception if an unknown option is given + */ + public function setOptions(array $options) + { + foreach ($options as $option => $value) { + switch ($option) { + case self::ESCAPE_FILTER: + $this->setDefaultEscapeFilter($value); + break; + case self::INPUT_NAMESPACE: + $this->addNamespace($value); + break; + case self::ALLOW_EMPTY: + case self::BREAK_CHAIN: + case self::MISSING_MESSAGE: + case self::NOT_EMPTY_MESSAGE: + case self::PRESENCE: + $this->_defaults[$option] = $value; + break; + default: + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Unknown option '$option'"); + break; + } + } + + return $this; + } + + /* + * Protected methods + */ + + /** + * @return void + */ + protected function _filter() + { + foreach ($this->_filterRules as $ruleName => &$filterRule) { + /** + * Make sure we have an array representing this filter chain. + * Don't typecast to (array) because it might be a Zend_Filter object + */ + if (!is_array($filterRule)) { + $filterRule = array($filterRule); + } + + /** + * Filters are indexed by integer, metacommands are indexed by string. + * Pick out the filters. + */ + $filterList = array(); + foreach ($filterRule as $key => $value) { + if (is_int($key)) { + $filterList[] = $value; + } + } + + /** + * Use defaults for filter metacommands. + */ + $filterRule[self::RULE] = $ruleName; + if (!isset($filterRule[self::FIELDS])) { + $filterRule[self::FIELDS] = $ruleName; + } + + /** + * Load all the filter classes and add them to the chain. + */ + if (!isset($filterRule[self::FILTER_CHAIN])) { + $filterRule[self::FILTER_CHAIN] = new Zend_Filter(); + foreach ($filterList as $filter) { + if (is_string($filter) || is_array($filter)) { + $filter = $this->_getFilter($filter); + } + $filterRule[self::FILTER_CHAIN]->addFilter($filter); + } + } + + /** + * If the ruleName is the special wildcard rule, + * then apply the filter chain to all input data. + * Else just process the field named by the rule. + */ + if ($ruleName == self::RULE_WILDCARD) { + foreach (array_keys($this->_data) as $field) { + $this->_filterRule(array_merge($filterRule, array(self::FIELDS => $field))); + } + } else { + $this->_filterRule($filterRule); + } + } + } + + /** + * @param array $filterRule + * @return void + */ + protected function _filterRule(array $filterRule) + { + $field = $filterRule[self::FIELDS]; + if (!array_key_exists($field, $this->_data)) { + return; + } + if (is_array($this->_data[$field])) { + foreach ($this->_data[$field] as $key => $value) { + $this->_data[$field][$key] = $filterRule[self::FILTER_CHAIN]->filter($value); + } + } else { + $this->_data[$field] = + $filterRule[self::FILTER_CHAIN]->filter($this->_data[$field]); + } + } + + /** + * @return Zend_Filter_Interface + */ + protected function _getDefaultEscapeFilter() + { + if ($this->_defaultEscapeFilter !== null) { + return $this->_defaultEscapeFilter; + } + return $this->setDefaultEscapeFilter($this->_defaults[self::ESCAPE_FILTER]); + } + + /** + * @param string $rule + * @param string $field + * @return string + */ + protected function _getMissingMessage($rule, $field) + { + $message = $this->_defaults[self::MISSING_MESSAGE]; + $message = str_replace('%rule%', $rule, $message); + $message = str_replace('%field%', $field, $message); + return $message; + } + + /** + * @return string + */ + protected function _getNotEmptyMessage($rule, $field) + { + $message = $this->_defaults[self::NOT_EMPTY_MESSAGE]; + $message = str_replace('%rule%', $rule, $message); + $message = str_replace('%field%', $field, $message); + return $message; + } + + /** + * @return void + */ + protected function _process() + { + if ($this->_processed === false) { + $this->_filter(); + $this->_validate(); + $this->_processed = true; + } + } + + /** + * @return void + */ + protected function _validate() + { + /** + * Special case: if there are no validators, treat all fields as valid. + */ + if (!$this->_validatorRules) { + $this->_validFields = $this->_data; + $this->_data = array(); + return; + } + + foreach ($this->_validatorRules as $ruleName => &$validatorRule) { + /** + * Make sure we have an array representing this validator chain. + * Don't typecast to (array) because it might be a Zend_Validate object + */ + if (!is_array($validatorRule)) { + $validatorRule = array($validatorRule); + } + + /** + * Validators are indexed by integer, metacommands are indexed by string. + * Pick out the validators. + */ + $validatorList = array(); + foreach ($validatorRule as $key => $value) { + if (is_int($key)) { + $validatorList[] = $value; + } + } + + /** + * Use defaults for validation metacommands. + */ + $validatorRule[self::RULE] = $ruleName; + if (!isset($validatorRule[self::FIELDS])) { + $validatorRule[self::FIELDS] = $ruleName; + } + if (!isset($validatorRule[self::BREAK_CHAIN])) { + $validatorRule[self::BREAK_CHAIN] = $this->_defaults[self::BREAK_CHAIN]; + } + if (!isset($validatorRule[self::PRESENCE])) { + $validatorRule[self::PRESENCE] = $this->_defaults[self::PRESENCE]; + } + if (!isset($validatorRule[self::ALLOW_EMPTY])) { + $validatorRule[self::ALLOW_EMPTY] = $this->_defaults[self::ALLOW_EMPTY]; + } + if (!isset($validatorRule[self::MESSAGES])) { + $validatorRule[self::MESSAGES] = array(); + } else if (!is_array($validatorRule[self::MESSAGES])) { + $validatorRule[self::MESSAGES] = array($validatorRule[self::MESSAGES]); + } else if (!array_intersect_key($validatorList, $validatorRule[self::MESSAGES])) { + $validatorRule[self::MESSAGES] = array($validatorRule[self::MESSAGES]); + } + + /** + * Load all the validator classes and add them to the chain. + */ + if (!isset($validatorRule[self::VALIDATOR_CHAIN])) { + $validatorRule[self::VALIDATOR_CHAIN] = new Zend_Validate(); + $i = 0; + foreach ($validatorList as $validator) { + + if (is_string($validator) || is_array($validator)) { + $validator = $this->_getValidator($validator); + } + if (isset($validatorRule[self::MESSAGES][$i])) { + $value = $validatorRule[self::MESSAGES][$i]; + if (is_array($value)) { + $validator->setMessages($value); + } else { + $validator->setMessage($value); + } + } + + $validatorRule[self::VALIDATOR_CHAIN]->addValidator($validator, $validatorRule[self::BREAK_CHAIN]); + ++$i; + } + $validatorRule[self::VALIDATOR_CHAIN_COUNT] = $i; + } + + /** + * If the ruleName is the special wildcard rule, + * then apply the validator chain to all input data. + * Else just process the field named by the rule. + */ + if ($ruleName == self::RULE_WILDCARD) { + foreach (array_keys($this->_data) as $field) { + $this->_validateRule(array_merge($validatorRule, array(self::FIELDS => $field))); + } + } else { + $this->_validateRule($validatorRule); + } + } + + /** + * Unset fields in $_data that have been added to other arrays. + * We have to wait until all rules have been processed because + * a given field may be referenced by multiple rules. + */ + foreach (array_merge(array_keys($this->_missingFields), array_keys($this->_invalidMessages)) as $rule) { + foreach ((array) $this->_validatorRules[$rule][self::FIELDS] as $field) { + unset($this->_data[$field]); + } + } + foreach ($this->_validFields as $field => $value) { + unset($this->_data[$field]); + } + + /** + * Anything left over in $_data is an unknown field. + */ + $this->_unknownFields = $this->_data; + } + + /** + * @param array $validatorRule + * @return void + */ + protected function _validateRule(array $validatorRule) + { + /** + * Get one or more data values from input, and check for missing fields. + * Apply defaults if fields are missing. + */ + $data = array(); + foreach ((array) $validatorRule[self::FIELDS] as $field) { + if (array_key_exists($field, $this->_data)) { + $data[$field] = $this->_data[$field]; + } else + if (array_key_exists(self::DEFAULT_VALUE, $validatorRule)) { + if (is_array($validatorRule[self::DEFAULT_VALUE])) { + $key = array_search($field, (array) $validatorRule[self::FIELDS]); + if (array_key_exists($key, $validatorRule[self::DEFAULT_VALUE])) { + $data[$field] = $validatorRule[self::DEFAULT_VALUE][$key]; + } + } else { + $data[$field] = $validatorRule[self::DEFAULT_VALUE]; + } + } else + if ($validatorRule[self::PRESENCE] == self::PRESENCE_REQUIRED) { + $this->_missingFields[$validatorRule[self::RULE]][] = + $this->_getMissingMessage($validatorRule[self::RULE], $field); + } + } + + /** + * If any required fields are missing, break the loop. + */ + if (isset($this->_missingFields[$validatorRule[self::RULE]]) && count($this->_missingFields[$validatorRule[self::RULE]]) > 0) { + return; + } + + /** + * Evaluate the inputs against the validator chain. + */ + if (count((array) $validatorRule[self::FIELDS]) > 1) { + if (!$validatorRule[self::VALIDATOR_CHAIN]->isValid($data)) { + $this->_invalidMessages[$validatorRule[self::RULE]] = $validatorRule[self::VALIDATOR_CHAIN]->getMessages(); + $this->_invalidErrors[$validatorRule[self::RULE]] = $validatorRule[self::VALIDATOR_CHAIN]->getErrors(); + return; + } + } else { + $failed = false; + foreach ($data as $fieldKey => $field) { + if (!is_array($field)) { + $field = array($field); + } + foreach ($field as $value) { + if (empty($value)) { + if ($validatorRule[self::ALLOW_EMPTY] == true) { + continue; + } + if ($validatorRule[self::VALIDATOR_CHAIN_COUNT] == 0) { + $notEmptyValidator = $this->_getValidator('NotEmpty'); + $notEmptyValidator->setMessage($this->_getNotEmptyMessage($validatorRule[self::RULE], $fieldKey)); + $validatorRule[self::VALIDATOR_CHAIN]->addValidator($notEmptyValidator); + } + } + if (!$validatorRule[self::VALIDATOR_CHAIN]->isValid($value)) { + $this->_invalidMessages[$validatorRule[self::RULE]] = + $validatorRule[self::VALIDATOR_CHAIN]->getMessages(); + $this->_invalidErrors[$validatorRule[self::RULE]] = + $validatorRule[self::VALIDATOR_CHAIN]->getErrors(); + unset($this->_validFields[$fieldKey]); + $failed = true; + if ($validatorRule[self::BREAK_CHAIN]) { + return; + } + } + } + } + if ($failed) { + return; + } + } + + /** + * If we got this far, the inputs for this rule pass validation. + */ + foreach ((array) $validatorRule[self::FIELDS] as $field) { + if (array_key_exists($field, $data)) { + $this->_validFields[$field] = $data[$field]; + } + } + } + + /** + * @param mixed $classBaseName + * @return Zend_Filter_Interface + */ + protected function _getFilter($classBaseName) + { + return $this->_getFilterOrValidator(self::FILTER, $classBaseName); + } + + /** + * @param mixed $classBaseName + * @return Zend_Validate_Interface + */ + protected function _getValidator($classBaseName) + { + return $this->_getFilterOrValidator(self::VALIDATE, $classBaseName); + } + + /** + * @param string $type + * @param mixed $classBaseName + * @return Zend_Filter_Interface|Zend_Validate_Interface + * @throws Zend_Filter_Exception + */ + protected function _getFilterOrValidator($type, $classBaseName) + { + $args = array(); + + if (is_array($classBaseName)) { + $args = $classBaseName; + $classBaseName = array_shift($args); + } + + $interfaceName = 'Zend_' . ucfirst($type) . '_Interface'; + $className = $this->getPluginLoader($type)->load(ucfirst($classBaseName)); + + $class = new ReflectionClass($className); + + if (!$class->implementsInterface($interfaceName)) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception("Class based on basename '$classBaseName' must implement the '$interfaceName' interface"); + } + + if ($class->hasMethod('__construct')) { + $object = $class->newInstanceArgs($args); + } else { + $object = $class->newInstance(); + } + + return $object; + } + +} diff --git a/lib/Zend/Filter/Int.php b/lib/Zend/Filter/Int.php new file mode 100644 index 0000000..39c3d6d --- /dev/null +++ b/lib/Zend/Filter/Int.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Int.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Int implements Zend_Filter_Interface +{ + /** + * Defined by Zend_Filter_Interface + * + * Returns (int) $value + * + * @param string $value + * @return integer + */ + public function filter($value) + { + return (int) ((string) $value); + } +} diff --git a/lib/Zend/Filter/Interface.php b/lib/Zend/Filter/Interface.php new file mode 100644 index 0000000..61a3531 --- /dev/null +++ b/lib/Zend/Filter/Interface.php @@ -0,0 +1,40 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Interface.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +interface Zend_Filter_Interface +{ + /** + * Returns the result of filtering $value + * + * @param mixed $value + * @throws Zend_Filter_Exception If filtering $value is impossible + * @return mixed + */ + public function filter($value); +} diff --git a/lib/Zend/Filter/PregReplace.php b/lib/Zend/Filter/PregReplace.php new file mode 100644 index 0000000..7e6ab27 --- /dev/null +++ b/lib/Zend/Filter/PregReplace.php @@ -0,0 +1,156 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: PregReplace.php 8064 2008-02-16 10:58:39Z thomas $ + */ + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_PregReplace implements Zend_Filter_Interface +{ + /** + * Pattern to match + * @var mixed + */ + protected $_matchPattern = null; + + /** + * Replacement pattern + * @var mixed + */ + protected $_replacement = ''; + + /** + * Is unicode enabled? + * + * @var bool + */ + static protected $_unicodeSupportEnabled = null; + + /** + * Is Unicode Support Enabled Utility function + * + * @return bool + */ + static public function isUnicodeSupportEnabled() + { + if (self::$_unicodeSupportEnabled === null) { + self::_determineUnicodeSupport(); + } + + return self::$_unicodeSupportEnabled; + } + + /** + * Method to cache the regex needed to determine if unicode support is available + * + * @return bool + */ + static protected function _determineUnicodeSupport() + { + self::$_unicodeSupportEnabled = (@preg_match('/\pL/u', 'a')) ? true : false; + } + + /** + * Constructor + * + * @param string $match + * @param string $replace + * @return void + */ + public function __construct($matchPattern = null, $replacement = null) + { + if ($matchPattern) { + $this->setMatchPattern($matchPattern); + } + + if ($replacement) { + $this->setReplacement($replacement); + } + } + + /** + * Set the match pattern for the regex being called within filter() + * + * @param mixed $match - same as the first argument of preg_replace + * @return Zend_Filter_PregReplace + */ + public function setMatchPattern($match) + { + $this->_matchPattern = $match; + return $this; + } + + /** + * Get currently set match pattern + * + * @return string + */ + public function getMatchPattern() + { + return $this->_matchPattern; + } + + /** + * Set the Replacement pattern/string for the preg_replace called in filter + * + * @param mixed $replacement - same as the second argument of preg_replace + * @return Zend_Filter_PregReplace + */ + public function setReplacement($replacement) + { + $this->_replacement = $replacement; + return $this; + } + + /** + * Get currently set replacement value + * + * @return string + */ + public function getReplacement() + { + return $this->_replacement; + } + + /** + * Perform regexp replacement as filter + * + * @param string $value + * @return string + */ + public function filter($value) + { + if ($this->_matchPattern == null) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception(get_class($this) . ' does not have a valid MatchPattern set.'); + } + + return preg_replace($this->_matchPattern, $this->_replacement, $value); + } + +} diff --git a/lib/Zend/Filter/RealPath.php b/lib/Zend/Filter/RealPath.php new file mode 100644 index 0000000..f94de32 --- /dev/null +++ b/lib/Zend/Filter/RealPath.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: RealPath.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_RealPath implements Zend_Filter_Interface +{ + /** + * Defined by Zend_Filter_Interface + * + * Returns realpath($value) + * + * @param string $value + * @return string + */ + public function filter($value) + { + return realpath((string) $value); + } +} diff --git a/lib/Zend/Filter/StringToLower.php b/lib/Zend/Filter/StringToLower.php new file mode 100644 index 0000000..acd91f7 --- /dev/null +++ b/lib/Zend/Filter/StringToLower.php @@ -0,0 +1,76 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: StringToLower.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_StringToLower implements Zend_Filter_Interface +{ + /** + * Encoding for the input string + * + * @var string + */ + protected $_encoding = null; + + /** + * Set the input encoding for the given string + * + * @param string $encoding + * @throws Zend_Filter_Exception + */ + public function setEncoding($encoding = null) + { + if (!function_exists('mb_strtolower')) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception('mbstring is required for this feature'); + } + $this->_encoding = $encoding; + } + + /** + * Defined by Zend_Filter_Interface + * + * Returns the string $value, converting characters to lowercase as necessary + * + * @param string $value + * @return string + */ + public function filter($value) + { + if ($this->_encoding) { + return mb_strtolower((string) $value, $this->_encoding); + } + + return strtolower((string) $value); + } +} diff --git a/lib/Zend/Filter/StringToUpper.php b/lib/Zend/Filter/StringToUpper.php new file mode 100644 index 0000000..bd68aae --- /dev/null +++ b/lib/Zend/Filter/StringToUpper.php @@ -0,0 +1,76 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: StringToUpper.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_StringToUpper implements Zend_Filter_Interface +{ + /** + * Encoding for the input string + * + * @var string + */ + protected $_encoding = null; + + /** + * Set the input encoding for the given string + * + * @param string $encoding + * @throws Zend_Filter_Exception + */ + public function setEncoding($encoding = null) + { + if (!function_exists('mb_strtoupper')) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception('mbstring is required for this feature'); + } + $this->_encoding = $encoding; + } + + /** + * Defined by Zend_Filter_Interface + * + * Returns the string $value, converting characters to uppercase as necessary + * + * @param string $value + * @return string + */ + public function filter($value) + { + if ($this->_encoding) { + return mb_strtoupper((string) $value, $this->_encoding); + } + + return strtoupper((string) $value); + } +} diff --git a/lib/Zend/Filter/StringTrim.php b/lib/Zend/Filter/StringTrim.php new file mode 100644 index 0000000..af22900 --- /dev/null +++ b/lib/Zend/Filter/StringTrim.php @@ -0,0 +1,97 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: StringTrim.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_StringTrim implements Zend_Filter_Interface +{ + /** + * List of characters provided to the trim() function + * + * If this is null, then trim() is called with no specific character list, + * and its default behavior will be invoked, trimming whitespace. + * + * @var string|null + */ + protected $_charList; + + /** + * Sets filter options + * + * @param string $charList + * @return void + */ + public function __construct($charList = null) + { + $this->_charList = $charList; + } + + /** + * Returns the charList option + * + * @return string|null + */ + public function getCharList() + { + return $this->_charList; + } + + /** + * Sets the charList option + * + * @param string|null $charList + * @return Zend_Filter_StringTrim Provides a fluent interface + */ + public function setCharList($charList) + { + $this->_charList = $charList; + return $this; + } + + /** + * Defined by Zend_Filter_Interface + * + * Returns the string $value with characters stripped from the beginning and end + * + * @param string $value + * @return string + */ + public function filter($value) + { + if (null === $this->_charList) { + return trim((string) $value); + } else { + return trim((string) $value, $this->_charList); + } + } +} diff --git a/lib/Zend/Filter/StripNewlines.php b/lib/Zend/Filter/StripNewlines.php new file mode 100644 index 0000000..b2dd042 --- /dev/null +++ b/lib/Zend/Filter/StripNewlines.php @@ -0,0 +1,48 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_StripNewlines implements Zend_Filter_Interface +{ + + /** + * Defined by Zend_Filter_Interface + * + * Returns $value without newline control characters + * + * @param string $value + * @return string + */ + public function filter ($value) + { + return str_replace(array("\n", "\r"), '', $value); + } +} diff --git a/lib/Zend/Filter/StripTags.php b/lib/Zend/Filter/StripTags.php new file mode 100644 index 0000000..4bd907c --- /dev/null +++ b/lib/Zend/Filter/StripTags.php @@ -0,0 +1,294 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: StripTags.php 14273 2009-03-10 20:00:32Z matthew $ + */ + + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Interface.php'; + + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_StripTags implements Zend_Filter_Interface +{ + /** + * Unique ID prefix used for allowing comments + */ + const UNIQUE_ID_PREFIX = '__Zend_Filter_StripTags__'; + + /** + * Whether comments are allowed + * + * If false (the default), then comments are removed from the input string. + * + * @var boolean + */ + public $commentsAllowed; + + /** + * Array of allowed tags and allowed attributes for each allowed tag + * + * Tags are stored in the array keys, and the array values are themselves + * arrays of the attributes allowed for the corresponding tag. + * + * @var array + */ + protected $_tagsAllowed = array(); + + /** + * Array of allowed attributes for all allowed tags + * + * Attributes stored here are allowed for all of the allowed tags. + * + * @var array + */ + protected $_attributesAllowed = array(); + + /** + * Sets the filter options + * + * @param array|string $tagsAllowed + * @param array|string $attributesAllowed + * @param boolean $allowComments + * @return void + */ + public function __construct($tagsAllowed = null, $attributesAllowed = null, $commentsAllowed = false) + { + $this->setTagsAllowed($tagsAllowed); + $this->setAttributesAllowed($attributesAllowed); + $this->commentsAllowed = (boolean) $commentsAllowed; + } + + /** + * Returns the tagsAllowed option + * + * @return array + */ + public function getTagsAllowed() + { + return $this->_tagsAllowed; + } + + /** + * Sets the tagsAllowed option + * + * @param array|string $tagsAllowed + * @return Zend_Filter_StripTags Provides a fluent interface + */ + public function setTagsAllowed($tagsAllowed) + { + if (!is_array($tagsAllowed)) { + $tagsAllowed = array($tagsAllowed); + } + + foreach ($tagsAllowed as $index => $element) { + // If the tag was provided without attributes + if (is_int($index) && is_string($element)) { + // Canonicalize the tag name + $tagName = strtolower($element); + // Store the tag as allowed with no attributes + $this->_tagsAllowed[$tagName] = array(); + } + // Otherwise, if a tag was provided with attributes + else if (is_string($index) && (is_array($element) || is_string($element))) { + // Canonicalize the tag name + $tagName = strtolower($index); + // Canonicalize the attributes + if (is_string($element)) { + $element = array($element); + } + // Store the tag as allowed with the provided attributes + $this->_tagsAllowed[$tagName] = array(); + foreach ($element as $attribute) { + if (is_string($attribute)) { + // Canonicalize the attribute name + $attributeName = strtolower($attribute); + $this->_tagsAllowed[$tagName][$attributeName] = null; + } + } + } + } + + return $this; + } + + /** + * Returns the attributesAllowed option + * + * @return array + */ + public function getAttributesAllowed() + { + return $this->_attributesAllowed; + } + + /** + * Sets the attributesAllowed option + * + * @param array|string $attributesAllowed + * @return Zend_Filter_StripTags Provides a fluent interface + */ + public function setAttributesAllowed($attributesAllowed) + { + if (!is_array($attributesAllowed)) { + $attributesAllowed = array($attributesAllowed); + } + + // Store each attribute as allowed + foreach ($attributesAllowed as $attribute) { + if (is_string($attribute)) { + // Canonicalize the attribute name + $attributeName = strtolower($attribute); + $this->_attributesAllowed[$attributeName] = null; + } + } + + return $this; + } + + /** + * Defined by Zend_Filter_Interface + * + * @todo improve docblock descriptions + * + * @param string $value + * @return string + */ + public function filter($value) + { + $valueCopy = (string) $value; + + // If comments are allowed, then replace them with unique identifiers + if ($this->commentsAllowed) { + preg_match_all('/<\!--.*?--\s*>/s' , (string) $valueCopy, $matches); + $comments = array_unique($matches[0]); + foreach ($comments as $k => $v) { + $valueCopy = str_replace($v, self::UNIQUE_ID_PREFIX . $k, $valueCopy); + } + } + + // Initialize accumulator for filtered data + $dataFiltered = ''; + // Parse the input data iteratively as regular pre-tag text followed by a + // tag; either may be empty strings + preg_match_all('/([^<]*)(<?[^>]*>?)/', (string) $valueCopy, $matches); + // Iterate over each set of matches + foreach ($matches[1] as $index => $preTag) { + // If the pre-tag text is non-empty, strip any ">" characters from it + if (strlen($preTag)) { + $preTag = str_replace('>', '', $preTag); + } + // If a tag exists in this match, then filter the tag + $tag = $matches[2][$index]; + if (strlen($tag)) { + $tagFiltered = $this->_filterTag($tag); + } else { + $tagFiltered = ''; + } + // Add the filtered pre-tag text and filtered tag to the data buffer + $dataFiltered .= $preTag . $tagFiltered; + } + + // If comments are allowed, then replace the unique identifiers with the corresponding comments + if ($this->commentsAllowed) { + foreach ($comments as $k => $v) { + $dataFiltered = str_replace(self::UNIQUE_ID_PREFIX . $k, $v, $dataFiltered); + } + } + + // Return the filtered data + return $dataFiltered; + } + + /** + * Filters a single tag against the current option settings + * + * @param string $tag + * @return string + */ + protected function _filterTag($tag) + { + // Parse the tag into: + // 1. a starting delimiter (mandatory) + // 2. a tag name (if available) + // 3. a string of attributes (if available) + // 4. an ending delimiter (if available) + $isMatch = preg_match('~(</?)(\w*)((/(?!>)|[^/>])*)(/?>)~', $tag, $matches); + + // If the tag does not match, then strip the tag entirely + if (!$isMatch) { + return ''; + } + + // Save the matches to more meaningfully named variables + $tagStart = $matches[1]; + $tagName = strtolower($matches[2]); + $tagAttributes = $matches[3]; + $tagEnd = $matches[5]; + + // If the tag is not an allowed tag, then remove the tag entirely + if (!isset($this->_tagsAllowed[$tagName])) { + return ''; + } + + // Trim the attribute string of whitespace at the ends + $tagAttributes = trim($tagAttributes); + + // If there are non-whitespace characters in the attribute string + if (strlen($tagAttributes)) { + // Parse iteratively for well-formed attributes + preg_match_all('/(\w+)\s*=\s*(?:(")(.*?)"|(\')(.*?)\')/s', $tagAttributes, $matches); + + // Initialize valid attribute accumulator + $tagAttributes = ''; + + // Iterate over each matched attribute + foreach ($matches[1] as $index => $attributeName) { + $attributeName = strtolower($attributeName); + $attributeDelimiter = $matches[2][$index]; + $attributeValue = $matches[3][$index]; + + // If the attribute is not allowed, then remove it entirely + if (!array_key_exists($attributeName, $this->_tagsAllowed[$tagName]) + && !array_key_exists($attributeName, $this->_attributesAllowed)) { + continue; + } + // Add the attribute to the accumulator + $tagAttributes .= " $attributeName=" . $attributeDelimiter + . $attributeValue . $attributeDelimiter; + } + } + + // Reconstruct tags ending with "/>" as backwards-compatible XHTML tag + if (strpos($tagEnd, '/') !== false) { + $tagEnd = " $tagEnd"; + } + + // Return the filtered tag + return $tagStart . $tagName . $tagAttributes . $tagEnd; + } +} diff --git a/lib/Zend/Filter/Word/CamelCaseToDash.php b/lib/Zend/Filter/Word/CamelCaseToDash.php new file mode 100644 index 0000000..c4717f2 --- /dev/null +++ b/lib/Zend/Filter/Word/CamelCaseToDash.php @@ -0,0 +1,44 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToDash.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Word/CamelCaseToSeparator.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_CamelCaseToDash extends Zend_Filter_Word_CamelCaseToSeparator +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct('-'); + } +} diff --git a/lib/Zend/Filter/Word/CamelCaseToSeparator.php b/lib/Zend/Filter/Word/CamelCaseToSeparator.php new file mode 100644 index 0000000..5dfa590 --- /dev/null +++ b/lib/Zend/Filter/Word/CamelCaseToSeparator.php @@ -0,0 +1,49 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToSeparator.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_PregReplace + */ +require_once 'Zend/Filter/Word/Separator/Abstract.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_CamelCaseToSeparator extends Zend_Filter_Word_Separator_Abstract +{ + + public function filter($value) + { + if (self::isUnicodeSupportEnabled()) { + parent::setMatchPattern(array('#(?<=(?:\p{Lu}))(\p{Lu}\p{Ll})#','#(?<=(?:\p{Ll}))(\p{Lu})#')); + parent::setReplacement(array($this->_separator . '\1', $this->_separator . '\1')); + } else { + parent::setMatchPattern(array('#(?<=(?:[A-Z]))([A-Z]+)([A-Z][A-z])#', '#(?<=(?:[a-z]))([A-Z])#')); + parent::setReplacement(array('\1' . $this->_separator . '\2', $this->_separator . '\1')); + } + + return parent::filter($value); + } + +} diff --git a/lib/Zend/Filter/Word/CamelCaseToUnderscore.php b/lib/Zend/Filter/Word/CamelCaseToUnderscore.php new file mode 100644 index 0000000..659dfb4 --- /dev/null +++ b/lib/Zend/Filter/Word/CamelCaseToUnderscore.php @@ -0,0 +1,44 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToUnderscore.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_CamelCaseToSeparator + */ +require_once 'Zend/Filter/Word/CamelCaseToSeparator.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_CamelCaseToUnderscore extends Zend_Filter_Word_CamelCaseToSeparator +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct('_'); + } +} diff --git a/lib/Zend/Filter/Word/DashToCamelCase.php b/lib/Zend/Filter/Word/DashToCamelCase.php new file mode 100644 index 0000000..a1ab8e7 --- /dev/null +++ b/lib/Zend/Filter/Word/DashToCamelCase.php @@ -0,0 +1,44 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToDash.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Word/SeparatorToCamelCase.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_DashToCamelCase extends Zend_Filter_Word_SeparatorToCamelCase +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct('-'); + } +} diff --git a/lib/Zend/Filter/Word/DashToSeparator.php b/lib/Zend/Filter/Word/DashToSeparator.php new file mode 100644 index 0000000..884ba1a --- /dev/null +++ b/lib/Zend/Filter/Word/DashToSeparator.php @@ -0,0 +1,42 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToSeparator.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_PregReplace + */ +require_once 'Zend/Filter/Word/Separator/Abstract.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_DashToSeparator extends Zend_Filter_Word_Separator_Abstract +{ + + public function filter($value) + { + $this->setMatchPattern('#-#'); + $this->setReplacement($this->_separator); + return parent::filter($value); + } +} diff --git a/lib/Zend/Filter/Word/DashToUnderscore.php b/lib/Zend/Filter/Word/DashToUnderscore.php new file mode 100644 index 0000000..404598a --- /dev/null +++ b/lib/Zend/Filter/Word/DashToUnderscore.php @@ -0,0 +1,45 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToSeparator.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_PregReplace + */ +require_once 'Zend/Filter/Word/SeparatorToSeparator.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_DashToUnderscore extends Zend_Filter_Word_SeparatorToSeparator +{ + /** + * Constructor + * + * @param string $separator Space by default + * @return void + */ + public function __construct() + { + parent::__construct('-', '_'); + } +}
\ No newline at end of file diff --git a/lib/Zend/Filter/Word/Separator/Abstract.php b/lib/Zend/Filter/Word/Separator/Abstract.php new file mode 100644 index 0000000..b33b96b --- /dev/null +++ b/lib/Zend/Filter/Word/Separator/Abstract.php @@ -0,0 +1,76 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToUnderscore.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_PregReplace + */ +require_once 'Zend/Filter/PregReplace.php'; + +/** + * @category Zend + * @package Zend_Filter + * @uses Zend_Filter_PregReplace + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +abstract class Zend_Filter_Word_Separator_Abstract extends Zend_Filter_PregReplace +{ + + protected $_separator = null; + + /** + * Constructor + * + * @param string $separator Space by default + * @return void + */ + public function __construct($separator = ' ') + { + $this->setSeparator($separator); + } + + /** + * Sets a new seperator + * + * @param string $separator Seperator + * @return $this + */ + public function setSeparator($separator) + { + if ($separator == null) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception('"' . $separator . '" is not a valid separator.'); + } + $this->_separator = $separator; + return $this; + } + + /** + * Returns the actual set seperator + * + * @return string + */ + public function getSeparator() + { + return $this->_separator; + } + +}
\ No newline at end of file diff --git a/lib/Zend/Filter/Word/SeparatorToCamelCase.php b/lib/Zend/Filter/Word/SeparatorToCamelCase.php new file mode 100644 index 0000000..853ec1b --- /dev/null +++ b/lib/Zend/Filter/Word/SeparatorToCamelCase.php @@ -0,0 +1,52 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToSeparator.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_PregReplace + */ +require_once 'Zend/Filter/Word/Separator/Abstract.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_SeparatorToCamelCase extends Zend_Filter_Word_Separator_Abstract +{ + + public function filter($value) + { + // a unicode safe way of converting characters to \x00\x00 notation + $pregQuotedSeparator = preg_quote($this->_separator, '#'); + + if (self::isUnicodeSupportEnabled()) { + parent::setMatchPattern(array('#('.$pregQuotedSeparator.')(\p{L}{1})#e','#(^\p{Ll}{1})#e')); + parent::setReplacement(array("strtoupper('\\2')","strtoupper('\\1')")); + } else { + parent::setMatchPattern(array('#('.$pregQuotedSeparator.')([A-Z]{1})#e','#(^[a-z]{1})#e')); + parent::setReplacement(array("strtoupper('\\2')","strtoupper('\\1')")); + } + + return parent::filter($value); + } + +} diff --git a/lib/Zend/Filter/Word/SeparatorToDash.php b/lib/Zend/Filter/Word/SeparatorToDash.php new file mode 100644 index 0000000..d5eab06 --- /dev/null +++ b/lib/Zend/Filter/Word/SeparatorToDash.php @@ -0,0 +1,46 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToUnderscore.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_SeperatorToSeparator + */ +require_once 'Zend/Filter/Word/SeparatorToSeparator.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_SeparatorToDash extends Zend_Filter_Word_SeparatorToSeparator +{ + /** + * Constructor + * + * @param string $searchSeparator Seperator to search for change + * @return void + */ + public function __construct($searchSeparator = ' ') + { + parent::__construct($searchSeparator, '-'); + } + +}
\ No newline at end of file diff --git a/lib/Zend/Filter/Word/SeparatorToSeparator.php b/lib/Zend/Filter/Word/SeparatorToSeparator.php new file mode 100644 index 0000000..e70aaf3 --- /dev/null +++ b/lib/Zend/Filter/Word/SeparatorToSeparator.php @@ -0,0 +1,129 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToUnderscore.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_PregReplace + */ +require_once 'Zend/Filter/PregReplace.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_SeparatorToSeparator extends Zend_Filter_PregReplace +{ + + protected $_searchSeparator = null; + protected $_replacementSeparator = null; + + /** + * Constructor + * + * @param string $searchSeparator Seperator to search for + * @param string $replacementSeperator Seperator to replace with + * @return void + */ + public function __construct($searchSeparator = ' ', $replacementSeparator = '-') + { + $this->setSearchSeparator($searchSeparator); + $this->setReplacementSeparator($replacementSeparator); + } + + /** + * Sets a new seperator to search for + * + * @param string $separator Seperator to search for + * @return $this + */ + public function setSearchSeparator($separator) + { + $this->_searchSeparator = $separator; + return $this; + } + + /** + * Returns the actual set seperator to search for + * + * @return string + */ + public function getSearchSeparator() + { + return $this->_searchSeparator; + } + + /** + * Sets a new seperator which replaces the searched one + * + * @param string $separator Seperator which replaces the searched one + * @return $this + */ + public function setReplacementSeparator($separator) + { + $this->_replacementSeparator = $separator; + return $this; + } + + /** + * Returns the actual set seperator which replaces the searched one + * + * @return string + */ + public function getReplacementSeparator() + { + return $this->_replacementSeparator; + } + + /** + * Defined by Zend_Filter_Interface + * + * Returns the string $value, replacing the searched seperators with the defined ones + * + * @param string $value + * @return string + */ + public function filter($value) + { + return $this->_separatorToSeparatorFilter($value); + } + + /** + * Do the real work, replaces the seperator to search for with the replacement seperator + * + * Returns the replaced string + * + * @param string $value + * @return string + */ + protected function _separatorToSeparatorFilter($value) + { + if ($this->_searchSeparator == null) { + require_once 'Zend/Filter/Exception.php'; + throw new Zend_Filter_Exception('You must provide a search separator for this filter to work.'); + } + + $this->setMatchPattern('#' . preg_quote($this->_searchSeparator, '#') . '#'); + $this->setReplacement($this->_replacementSeparator); + return parent::filter($value); + } + +}
\ No newline at end of file diff --git a/lib/Zend/Filter/Word/UnderscoreToCamelCase.php b/lib/Zend/Filter/Word/UnderscoreToCamelCase.php new file mode 100644 index 0000000..aabbba9 --- /dev/null +++ b/lib/Zend/Filter/Word/UnderscoreToCamelCase.php @@ -0,0 +1,44 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToDash.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_Interface + */ +require_once 'Zend/Filter/Word/SeparatorToCamelCase.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_UnderscoreToCamelCase extends Zend_Filter_Word_SeparatorToCamelCase +{ + /** + * Constructor + * + * @return void + */ + public function __construct() + { + parent::__construct('_'); + } +} diff --git a/lib/Zend/Filter/Word/UnderscoreToDash.php b/lib/Zend/Filter/Word/UnderscoreToDash.php new file mode 100644 index 0000000..43326c4 --- /dev/null +++ b/lib/Zend/Filter/Word/UnderscoreToDash.php @@ -0,0 +1,45 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToSeparator.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Filter_PregReplace + */ +require_once 'Zend/Filter/Word/SeparatorToSeparator.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_UnderscoreToDash extends Zend_Filter_Word_SeparatorToSeparator +{ + /** + * Constructor + * + * @param string $separator Space by default + * @return void + */ + public function __construct() + { + parent::__construct('_', '-'); + } +}
\ No newline at end of file diff --git a/lib/Zend/Filter/Word/UnderscoreToSeparator.php b/lib/Zend/Filter/Word/UnderscoreToSeparator.php new file mode 100644 index 0000000..3fba924 --- /dev/null +++ b/lib/Zend/Filter/Word/UnderscoreToSeparator.php @@ -0,0 +1,45 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: UnderscoreToPathSeparator.php 6793 2007-11-09 18:10:06Z matthew $ + */ + +/** + * @see Zend_Filter_PregReplace + */ +require_once 'Zend/Filter/Word/SeparatorToSeparator.php'; + +/** + * @category Zend + * @package Zend_Filter + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Filter_Word_UnderscoreToSeparator extends Zend_Filter_Word_SeparatorToSeparator +{ + /** + * Constructor + * + * @param string $separator Space by default + * @return void + */ + public function __construct($replacementSeparator = ' ') + { + parent::__construct('_', $replacementSeparator); + } +} diff --git a/lib/Zend/Loader.php b/lib/Zend/Loader.php new file mode 100644 index 0000000..3d962a5 --- /dev/null +++ b/lib/Zend/Loader.php @@ -0,0 +1,263 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Loader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Loader.php 12507 2008-11-10 16:29:09Z matthew $ + */ + +/** + * Static methods for loading classes and files. + * + * @category Zend + * @package Zend_Loader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Loader +{ + /** + * Loads a class from a PHP file. The filename must be formatted + * as "$class.php". + * + * If $dirs is a string or an array, it will search the directories + * in the order supplied, and attempt to load the first matching file. + * + * If $dirs is null, it will split the class name at underscores to + * generate a path hierarchy (e.g., "Zend_Example_Class" will map + * to "Zend/Example/Class.php"). + * + * If the file was not found in the $dirs, or if no $dirs were specified, + * it will attempt to load it from PHP's include_path. + * + * @param string $class - The full class name of a Zend component. + * @param string|array $dirs - OPTIONAL Either a path or an array of paths + * to search. + * @return void + * @throws Zend_Exception + */ + public static function loadClass($class, $dirs = null) + { + if (class_exists($class, false) || interface_exists($class, false)) { + return; + } + + if ((null !== $dirs) && !is_string($dirs) && !is_array($dirs)) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception('Directory argument must be a string or an array'); + } + + // autodiscover the path from the class name + $file = str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; + if (!empty($dirs)) { + // use the autodiscovered path + $dirPath = dirname($file); + if (is_string($dirs)) { + $dirs = explode(PATH_SEPARATOR, $dirs); + } + foreach ($dirs as $key => $dir) { + if ($dir == '.') { + $dirs[$key] = $dirPath; + } else { + $dir = rtrim($dir, '\\/'); + $dirs[$key] = $dir . DIRECTORY_SEPARATOR . $dirPath; + } + } + $file = basename($file); + self::loadFile($file, $dirs, true); + } else { + self::_securityCheck($file); + include_once $file; + } + + if (!class_exists($class, false) && !interface_exists($class, false)) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception("File \"$file\" does not exist or class \"$class\" was not found in the file"); + } + } + + /** + * Loads a PHP file. This is a wrapper for PHP's include() function. + * + * $filename must be the complete filename, including any + * extension such as ".php". Note that a security check is performed that + * does not permit extended characters in the filename. This method is + * intended for loading Zend Framework files. + * + * If $dirs is a string or an array, it will search the directories + * in the order supplied, and attempt to load the first matching file. + * + * If the file was not found in the $dirs, or if no $dirs were specified, + * it will attempt to load it from PHP's include_path. + * + * If $once is TRUE, it will use include_once() instead of include(). + * + * @param string $filename + * @param string|array $dirs - OPTIONAL either a path or array of paths + * to search. + * @param boolean $once + * @return boolean + * @throws Zend_Exception + */ + public static function loadFile($filename, $dirs = null, $once = false) + { + self::_securityCheck($filename); + + /** + * Search in provided directories, as well as include_path + */ + $incPath = false; + if (!empty($dirs) && (is_array($dirs) || is_string($dirs))) { + if (is_array($dirs)) { + $dirs = implode(PATH_SEPARATOR, $dirs); + } + $incPath = get_include_path(); + set_include_path($dirs . PATH_SEPARATOR . $incPath); + } + + /** + * Try finding for the plain filename in the include_path. + */ + if ($once) { + include_once $filename; + } else { + include $filename; + } + + /** + * If searching in directories, reset include_path + */ + if ($incPath) { + set_include_path($incPath); + } + + return true; + } + + /** + * Returns TRUE if the $filename is readable, or FALSE otherwise. + * This function uses the PHP include_path, where PHP's is_readable() + * does not. + * + * Note from ZF-2900: + * If you use custom error handler, please check whether return value + * from error_reporting() is zero or not. + * At mark of fopen() can not suppress warning if the handler is used. + * + * @param string $filename + * @return boolean + */ + public static function isReadable($filename) + { + if (!$fh = @fopen($filename, 'r', true)) { + return false; + } + @fclose($fh); + return true; + } + + /** + * spl_autoload() suitable implementation for supporting class autoloading. + * + * Attach to spl_autoload() using the following: + * <code> + * spl_autoload_register(array('Zend_Loader', 'autoload')); + * </code> + * + * @param string $class + * @return string|false Class name on success; false on failure + */ + public static function autoload($class) + { + try { + self::loadClass($class); + return $class; + } catch (Exception $e) { + return false; + } + } + + /** + * Register {@link autoload()} with spl_autoload() + * + * @param string $class (optional) + * @param boolean $enabled (optional) + * @return void + * @throws Zend_Exception if spl_autoload() is not found + * or if the specified class does not have an autoload() method. + */ + public static function registerAutoload($class = 'Zend_Loader', $enabled = true) + { + if (!function_exists('spl_autoload_register')) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception('spl_autoload does not exist in this PHP installation'); + } + + self::loadClass($class); + $methods = get_class_methods($class); + if (!in_array('autoload', (array) $methods)) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception("The class \"$class\" does not have an autoload() method"); + } + + if ($enabled === true) { + spl_autoload_register(array($class, 'autoload')); + } else { + spl_autoload_unregister(array($class, 'autoload')); + } + } + + /** + * Ensure that filename does not contain exploits + * + * @param string $filename + * @return void + * @throws Zend_Exception + */ + protected static function _securityCheck($filename) + { + /** + * Security check + */ + if (preg_match('/[^a-z0-9\\/\\\\_.-]/i', $filename)) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception('Security check: Illegal character in filename'); + } + } + + /** + * Attempt to include() the file. + * + * include() is not prefixed with the @ operator because if + * the file is loaded and contains a parse error, execution + * will halt silently and this is difficult to debug. + * + * Always set display_errors = Off on production servers! + * + * @param string $filespec + * @param boolean $once + * @return boolean + * @deprecated Since 1.5.0; use loadFile() instead + */ + protected static function _includeFile($filespec, $once = false) + { + if ($once) { + return include_once $filespec; + } else { + return include $filespec ; + } + } +} diff --git a/lib/Zend/Loader/Exception.php b/lib/Zend/Loader/Exception.php new file mode 100644 index 0000000..2c0e1e4 --- /dev/null +++ b/lib/Zend/Loader/Exception.php @@ -0,0 +1,35 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Loader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: CamelCaseToUnderscore.php 6779 2007-11-08 15:10:41Z matthew $ + */ + +/** + * @see Zend_Exception + */ +require_once 'Zend/Exception.php'; + +/** + * @category Zend + * @package Zend_Loader + * @uses Zend_Exception + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Loader_Exception extends Zend_Exception +{}
\ No newline at end of file diff --git a/lib/Zend/Loader/PluginLoader.php b/lib/Zend/Loader/PluginLoader.php new file mode 100644 index 0000000..c1bd989 --- /dev/null +++ b/lib/Zend/Loader/PluginLoader.php @@ -0,0 +1,464 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Loader + * @subpackage PluginLoader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +/** Zend_Loader_PluginLoader_Interface */ +require_once 'Zend/Loader/PluginLoader/Interface.php'; + +/** Zend_Loader */ +require_once 'Zend/Loader.php'; + +/** + * Generic plugin class loader + * + * @category Zend + * @package Zend_Loader + * @subpackage PluginLoader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Loader_PluginLoader implements Zend_Loader_PluginLoader_Interface +{ + /** + * Class map cache file + * @var string + */ + protected static $_includeFileCache; + + /** + * Instance loaded plugin paths + * + * @var array + */ + protected $_loadedPluginPaths = array(); + + /** + * Instance loaded plugins + * + * @var array + */ + protected $_loadedPlugins = array(); + + /** + * Instance registry property + * + * @var array + */ + protected $_prefixToPaths = array(); + + /** + * Statically loaded plugin path mappings + * + * @var array + */ + protected static $_staticLoadedPluginPaths = array(); + + /** + * Statically loaded plugins + * + * @var array + */ + protected static $_staticLoadedPlugins = array(); + + /** + * Static registry property + * + * @var array + */ + protected static $_staticPrefixToPaths = array(); + + /** + * Whether to use a statically named registry for loading plugins + * + * @var string|null + */ + protected $_useStaticRegistry = null; + + /** + * Constructor + * + * @param array $prefixToPaths + * @param string $staticRegistryName OPTIONAL + */ + public function __construct(Array $prefixToPaths = array(), $staticRegistryName = null) + { + if (is_string($staticRegistryName) && !empty($staticRegistryName)) { + $this->_useStaticRegistry = $staticRegistryName; + if(!isset(self::$_staticPrefixToPaths[$staticRegistryName])) { + self::$_staticPrefixToPaths[$staticRegistryName] = array(); + } + if(!isset(self::$_staticLoadedPlugins[$staticRegistryName])) { + self::$_staticLoadedPlugins[$staticRegistryName] = array(); + } + } + + foreach ($prefixToPaths as $prefix => $path) { + $this->addPrefixPath($prefix, $path); + } + } + + /** + * Format prefix for internal use + * + * @param string $prefix + * @return string + */ + protected function _formatPrefix($prefix) + { + return rtrim($prefix, '_') . '_'; + } + + /** + * Add prefixed paths to the registry of paths + * + * @param string $prefix + * @param string $path + * @return Zend_Loader_PluginLoader + */ + public function addPrefixPath($prefix, $path) + { + if (!is_string($prefix) || !is_string($path)) { + require_once 'Zend/Loader/PluginLoader/Exception.php'; + throw new Zend_Loader_PluginLoader_Exception('Zend_Loader_PluginLoader::addPrefixPath() method only takes strings for prefix and path.'); + } + + $prefix = $this->_formatPrefix($prefix); + $path = rtrim($path, '/\\') . '/'; + + if ($this->_useStaticRegistry) { + self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix][] = $path; + } else { + $this->_prefixToPaths[$prefix][] = $path; + } + return $this; + } + + /** + * Get path stack + * + * @param string $prefix + * @return false|array False if prefix does not exist, array otherwise + */ + public function getPaths($prefix = null) + { + if ((null !== $prefix) && is_string($prefix)) { + $prefix = $this->_formatPrefix($prefix); + if ($this->_useStaticRegistry) { + if (isset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix])) { + return self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix]; + } + + return false; + } + + if (isset($this->_prefixToPaths[$prefix])) { + return $this->_prefixToPaths[$prefix]; + } + + return false; + } + + if ($this->_useStaticRegistry) { + return self::$_staticPrefixToPaths[$this->_useStaticRegistry]; + } + + return $this->_prefixToPaths; + } + + /** + * Clear path stack + * + * @param string $prefix + * @return bool False only if $prefix does not exist + */ + public function clearPaths($prefix = null) + { + if ((null !== $prefix) && is_string($prefix)) { + $prefix = $this->_formatPrefix($prefix); + if ($this->_useStaticRegistry) { + if (isset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix])) { + unset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix]); + return true; + } + + return false; + } + + if (isset($this->_prefixToPaths[$prefix])) { + unset($this->_prefixToPaths[$prefix]); + return true; + } + + return false; + } + + if ($this->_useStaticRegistry) { + self::$_staticPrefixToPaths[$this->_useStaticRegistry] = array(); + } else { + $this->_prefixToPaths = array(); + } + + return true; + } + + /** + * Remove a prefix (or prefixed-path) from the registry + * + * @param string $prefix + * @param string $path OPTIONAL + * @return Zend_Loader_PluginLoader + */ + public function removePrefixPath($prefix, $path = null) + { + $prefix = $this->_formatPrefix($prefix); + if ($this->_useStaticRegistry) { + $registry =& self::$_staticPrefixToPaths[$this->_useStaticRegistry]; + } else { + $registry =& $this->_prefixToPaths; + } + + if (!isset($registry[$prefix])) { + require_once 'Zend/Loader/PluginLoader/Exception.php'; + throw new Zend_Loader_PluginLoader_Exception('Prefix ' . $prefix . ' was not found in the PluginLoader.'); + } + + if ($path != null) { + $pos = array_search($path, $registry[$prefix]); + if ($pos === null) { + require_once 'Zend/Loader/PluginLoader/Exception.php'; + throw new Zend_Loader_PluginLoader_Exception('Prefix ' . $prefix . ' / Path ' . $path . ' was not found in the PluginLoader.'); + } + unset($registry[$prefix][$pos]); + } else { + unset($registry[$prefix]); + } + + return $this; + } + + /** + * Normalize plugin name + * + * @param string $name + * @return string + */ + protected function _formatName($name) + { + return ucfirst((string) $name); + } + + /** + * Whether or not a Plugin by a specific name is loaded + * + * @param string $name + * @return Zend_Loader_PluginLoader + */ + public function isLoaded($name) + { + $name = $this->_formatName($name); + if ($this->_useStaticRegistry) { + return isset(self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name]); + } + + return isset($this->_loadedPlugins[$name]); + } + + /** + * Return full class name for a named plugin + * + * @param string $name + * @return string|false False if class not found, class name otherwise + */ + public function getClassName($name) + { + $name = $this->_formatName($name); + if ($this->_useStaticRegistry + && isset(self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name]) + ) { + return self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name]; + } elseif (isset($this->_loadedPlugins[$name])) { + return $this->_loadedPlugins[$name]; + } + + return false; + } + + /** + * Get path to plugin class + * + * @param mixed $name + * @return string|false False if not found + */ + public function getClassPath($name) + { + $name = $this->_formatName($name); + if ($this->_useStaticRegistry + && !empty(self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name]) + ) { + return self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name]; + } elseif (!empty($this->_loadedPluginPaths[$name])) { + return $this->_loadedPluginPaths[$name]; + } + + if ($this->isLoaded($name)) { + $class = $this->getClassName($name); + $r = new ReflectionClass($class); + $path = $r->getFileName(); + if ($this->_useStaticRegistry) { + self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name] = $path; + } else { + $this->_loadedPluginPaths[$name] = $path; + } + return $path; + } + + return false; + } + + /** + * Load a plugin via the name provided + * + * @param string $name + * @return string Class name of loaded class + * @throws Zend_Loader_Exception if class not found + */ + public function load($name) + { + $name = $this->_formatName($name); + if ($this->isLoaded($name)) { + return $this->getClassName($name); + } + + if ($this->_useStaticRegistry) { + $registry = self::$_staticPrefixToPaths[$this->_useStaticRegistry]; + } else { + $registry = $this->_prefixToPaths; + } + + $registry = array_reverse($registry, true); + $found = false; + $classFile = str_replace('_', DIRECTORY_SEPARATOR, $name) . '.php'; + $incFile = self::getIncludeFileCache(); + foreach ($registry as $prefix => $paths) { + $className = $prefix . $name; + + if (class_exists($className, false)) { + $found = true; + break; + } + + $paths = array_reverse($paths, true); + + foreach ($paths as $path) { + $loadFile = $path . $classFile; + if (Zend_Loader::isReadable($loadFile)) { + include_once $loadFile; + if (class_exists($className, false)) { + if (null !== $incFile) { + self::_appendIncFile($loadFile); + } + $found = true; + break 2; + } + } + } + } + + if (!$found) { + $message = "Plugin by name '$name' was not found in the registry; used paths:"; + foreach ($registry as $prefix => $paths) { + $message .= "\n$prefix: " . implode(PATH_SEPARATOR, $paths); + } + require_once 'Zend/Loader/PluginLoader/Exception.php'; + throw new Zend_Loader_PluginLoader_Exception($message); + } + + if ($this->_useStaticRegistry) { + self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name] = $className; + self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name] = (isset($loadFile) ? $loadFile : ''); + } else { + $this->_loadedPlugins[$name] = $className; + $this->_loadedPluginPaths[$name] = (isset($loadFile) ? $loadFile : ''); + } + return $className; + } + + /** + * Set path to class file cache + * + * Specify a path to a file that will add include_once statements for each + * plugin class loaded. This is an opt-in feature for performance purposes. + * + * @param string $file + * @return void + * @throws Zend_Loader_PluginLoader_Exception if file is not writeable or path does not exist + */ + public static function setIncludeFileCache($file) + { + if (null === $file) { + self::$_includeFileCache = null; + return; + } + + if (!file_exists($file) && !file_exists(dirname($file))) { + require_once 'Zend/Loader/PluginLoader/Exception.php'; + throw new Zend_Loader_PluginLoader_Exception('Specified file does not exist and/or directory does not exist (' . $file . ')'); + } + if (file_exists($file) && !is_writable($file)) { + require_once 'Zend/Loader/PluginLoader/Exception.php'; + throw new Zend_Loader_PluginLoader_Exception('Specified file is not writeable (' . $file . ')'); + } + if (!file_exists($file) && file_exists(dirname($file)) && !is_writable(dirname($file))) { + require_once 'Zend/Loader/PluginLoader/Exception.php'; + throw new Zend_Loader_PluginLoader_Exception('Specified file is not writeable (' . $file . ')'); + } + + self::$_includeFileCache = $file; + } + + /** + * Retrieve class file cache path + * + * @return string|null + */ + public static function getIncludeFileCache() + { + return self::$_includeFileCache; + } + + /** + * Append an include_once statement to the class file cache + * + * @param string $incFile + * @return void + */ + protected static function _appendIncFile($incFile) + { + if (!file_exists(self::$_includeFileCache)) { + $file = '<?php'; + } else { + $file = file_get_contents(self::$_includeFileCache); + } + if (!strstr($file, $incFile)) { + $file .= "\ninclude_once '$incFile';"; + file_put_contents(self::$_includeFileCache, $file); + } + } +} diff --git a/lib/Zend/Loader/PluginLoader/Exception.php b/lib/Zend/Loader/PluginLoader/Exception.php new file mode 100644 index 0000000..60b53a4 --- /dev/null +++ b/lib/Zend/Loader/PluginLoader/Exception.php @@ -0,0 +1,38 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Loader + * @subpackage PluginLoader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +/** + * @see Zend_Loader_Exception + */ +require_once 'Zend/Loader/Exception.php'; + +/** + * Plugin class loader exceptions + * + * @category Zend + * @package Zend_Loader + * @subpackage PluginLoader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Loader_PluginLoader_Exception extends Zend_Loader_Exception +{ +} diff --git a/lib/Zend/Loader/PluginLoader/Interface.php b/lib/Zend/Loader/PluginLoader/Interface.php new file mode 100644 index 0000000..50f34aa --- /dev/null +++ b/lib/Zend/Loader/PluginLoader/Interface.php @@ -0,0 +1,74 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Loader + * @subpackage PluginLoader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +/** + * Plugin class loader interface + * + * @category Zend + * @package Zend_Loader + * @subpackage PluginLoader + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +interface Zend_Loader_PluginLoader_Interface +{ + /** + * Add prefixed paths to the registry of paths + * + * @param string $prefix + * @param string $path + * @return Zend_Loader_PluginLoader + */ + public function addPrefixPath($prefix, $path); + + /** + * Remove a prefix (or prefixed-path) from the registry + * + * @param string $prefix + * @param string $path OPTIONAL + * @return Zend_Loader_PluginLoader + */ + public function removePrefixPath($prefix, $path = null); + + /** + * Whether or not a Helper by a specific name + * + * @param string $name + * @return Zend_Loader_PluginLoader + */ + public function isLoaded($name); + + /** + * Return full class name for a named helper + * + * @param string $name + * @return string + */ + public function getClassName($name); + + /** + * Load a helper via the name provided + * + * @param string $name + * @return string + */ + public function load($name); +} diff --git a/lib/Zend/Mail.php b/lib/Zend/Mail.php new file mode 100644 index 0000000..5364f22 --- /dev/null +++ b/lib/Zend/Mail.php @@ -0,0 +1,1042 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Mail.php 14071 2009-02-12 22:22:43Z beberlei $ + */ + + +/** + * @see Zend_Mail_Transport_Abstract + */ +require_once 'Zend/Mail/Transport/Abstract.php'; + +/** + * @see Zend_Mime + */ +require_once 'Zend/Mime.php'; + +/** + * @see Zend_Mime_Message + */ +require_once 'Zend/Mime/Message.php'; + +/** + * @see Zend_Mime_Part + */ +require_once 'Zend/Mime/Part.php'; + + +/** + * Class for sending an email. + * + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail extends Zend_Mime_Message +{ + /**#@+ + * @access protected + */ + + /** + * @var Zend_Mail_Transport_Abstract + * @static + */ + protected static $_defaultTransport = null; + + /** + * Mail character set + * @var string + */ + protected $_charset = null; + + /** + * Mail headers + * @var array + */ + protected $_headers = array(); + + /** + * Encoding of Mail headers + * @var string + */ + protected $_headerEncoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE; + + /** + * From: address + * @var string + */ + protected $_from = null; + + /** + * To: addresses + * @var array + */ + protected $_to = array(); + + /** + * Array of all recipients + * @var array + */ + protected $_recipients = array(); + + /** + * Return-Path header + * @var string + */ + protected $_returnPath = null; + + /** + * Subject: header + * @var string + */ + protected $_subject = null; + + /** + * Date: header + * @var string + */ + protected $_date = null; + + /** + * Message-ID: header + * @var string + */ + protected $_messageId = null; + + /** + * text/plain MIME part + * @var false|Zend_Mime_Part + */ + protected $_bodyText = false; + + /** + * text/html MIME part + * @var false|Zend_Mime_Part + */ + protected $_bodyHtml = false; + + /** + * MIME boundary string + * @var string + */ + protected $_mimeBoundary = null; + + /** + * Content type of the message + * @var string + */ + protected $_type = null; + + /**#@-*/ + + /** + * Flag: whether or not email has attachments + * @var boolean + */ + public $hasAttachments = false; + + + /** + * Sets the default mail transport for all following uses of + * Zend_Mail::send(); + * + * @todo Allow passing a string to indicate the transport to load + * @todo Allow passing in optional options for the transport to load + * @param Zend_Mail_Transport_Abstract $transport + */ + public static function setDefaultTransport(Zend_Mail_Transport_Abstract $transport) + { + self::$_defaultTransport = $transport; + } + + /** + * Public constructor + * + * @param string $charset + */ + public function __construct($charset = 'iso-8859-1') + { + $this->_charset = $charset; + } + + /** + * Return charset string + * + * @return string + */ + public function getCharset() + { + return $this->_charset; + } + + /** + * Set content type + * + * Should only be used for manually setting multipart content types. + * + * @param string $type Content type + * @return Zend_Mail Implements fluent interface + * @throws Zend_Mail_Exception for types not supported by Zend_Mime + */ + public function setType($type) + { + $allowed = array( + Zend_Mime::MULTIPART_ALTERNATIVE, + Zend_Mime::MULTIPART_MIXED, + Zend_Mime::MULTIPART_RELATED, + ); + if (!in_array($type, $allowed)) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('Invalid content type "' . $type . '"'); + } + + $this->_type = $type; + return $this; + } + + /** + * Get content type of the message + * + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * Set an arbitrary mime boundary for the message + * + * If not set, Zend_Mime will generate one. + * + * @param string $boundary + * @return Zend_Mail Provides fluent interface + */ + public function setMimeBoundary($boundary) + { + $this->_mimeBoundary = $boundary; + + return $this; + } + + /** + * Return the boundary string used for the message + * + * @return string + */ + public function getMimeBoundary() + { + return $this->_mimeBoundary; + } + + /** + * Return encoding of mail headers + * + * @deprecated use {@link getHeaderEncoding()} instead + * @return <type> + */ + public function getEncodingOfHeaders() + { + return $this->getHeaderEncoding(); + } + + /** + * Return the encoding of mail headers + * + * Either Zend_Mime::ENCODING_QUOTEDPRINTABLE or Zend_Mime::ENCODING_BASE64 + * + * @return string + */ + public function getHeaderEncoding() + { + return $this->_headerEncoding; + } + + /** + * Set the encoding of mail headers + * + * @deprecated Use {@link setHeaderEncoding()} instead. + * @param string $encoding + * @return Zend_Mail + */ + public function setEncodingOfHeaders($encoding) + { + return $this->setHeaderEncoding($encoding); + } + + /** + * Set the encoding of mail headers + * + * @param string $encoding Zend_Mime::ENCODING_QUOTEDPRINTABLE or Zend_Mime::ENCODING_BASE64 + * @return Zend_Mail Provides fluent interface + */ + public function setHeaderEncoding($encoding) + { + $allowed = array( + Zend_Mime::ENCODING_BASE64, + Zend_Mime::ENCODING_QUOTEDPRINTABLE + ); + if (!in_array($encoding, $allowed)) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('Invalid encoding "' . $encoding . '"'); + } + $this->_headerEncoding = $encoding; + + return $this; + } + + /** + * Sets the text body for the message. + * + * @param string $txt + * @param string $charset + * @param string $encoding + * @return Zend_Mail Provides fluent interface + */ + public function setBodyText($txt, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE) + { + if ($charset === null) { + $charset = $this->_charset; + } + + $mp = new Zend_Mime_Part($txt); + $mp->encoding = $encoding; + $mp->type = Zend_Mime::TYPE_TEXT; + $mp->disposition = Zend_Mime::DISPOSITION_INLINE; + $mp->charset = $charset; + + $this->_bodyText = $mp; + + return $this; + } + + /** + * Return text body Zend_Mime_Part or string + * + * @param bool textOnly Whether to return just the body text content or the MIME part; defaults to false, the MIME part + * @return false|Zend_Mime_Part|string + */ + public function getBodyText($textOnly = false) + { + if ($textOnly && $this->_bodyText) { + $body = $this->_bodyText; + return $body->getContent(); + } + + return $this->_bodyText; + } + + /** + * Sets the HTML body for the message + * + * @param string $html + * @param string $charset + * @param string $encoding + * @return Zend_Mail Provides fluent interface + */ + public function setBodyHtml($html, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE) + { + if ($charset === null) { + $charset = $this->_charset; + } + + $mp = new Zend_Mime_Part($html); + $mp->encoding = $encoding; + $mp->type = Zend_Mime::TYPE_HTML; + $mp->disposition = Zend_Mime::DISPOSITION_INLINE; + $mp->charset = $charset; + + $this->_bodyHtml = $mp; + + return $this; + } + + /** + * Return Zend_Mime_Part representing body HTML + * + * @param bool $htmlOnly Whether to return the body HTML only, or the MIME part; defaults to false, the MIME part + * @return false|Zend_Mime_Part|string + */ + public function getBodyHtml($htmlOnly = false) + { + if ($htmlOnly && $this->_bodyHtml) { + $body = $this->_bodyHtml; + return $body->getContent(); + } + + return $this->_bodyHtml; + } + + /** + * Adds an existing attachment to the mail message + * + * @param Zend_Mime_Part $attachment + * @return Zend_Mail Provides fluent interface + */ + public function addAttachment(Zend_Mime_Part $attachment) + { + $this->addPart($attachment); + $this->hasAttachments = true; + + return $this; + } + + /** + * Creates a Zend_Mime_Part attachment + * + * Attachment is automatically added to the mail object after creation. The + * attachment object is returned to allow for further manipulation. + * + * @param string $body + * @param string $mimeType + * @param string $disposition + * @param string $encoding + * @param string $filename OPTIONAL A filename for the attachment + * @return Zend_Mime_Part Newly created Zend_Mime_Part object (to allow + * advanced settings) + */ + public function createAttachment($body, + $mimeType = Zend_Mime::TYPE_OCTETSTREAM, + $disposition = Zend_Mime::DISPOSITION_ATTACHMENT, + $encoding = Zend_Mime::ENCODING_BASE64, + $filename = null) + { + + $mp = new Zend_Mime_Part($body); + $mp->encoding = $encoding; + $mp->type = $mimeType; + $mp->disposition = $disposition; + $mp->filename = $filename; + + $this->addAttachment($mp); + + return $mp; + } + + /** + * Return a count of message parts + * + * @return integer + */ + public function getPartCount() + { + return count($this->_parts); + } + + /** + * Encode header fields + * + * Encodes header content according to RFC1522 if it contains non-printable + * characters. + * + * @param string $value + * @return string + */ + protected function _encodeHeader($value) + { + if (Zend_Mime::isPrintable($value) === false) { + if ($this->getHeaderEncoding() === Zend_Mime::ENCODING_QUOTEDPRINTABLE) { + $value = Zend_Mime::encodeQuotedPrintableHeader($value, $this->getCharset(), Zend_Mime::LINELENGTH, Zend_Mime::LINEEND); + } else { + $value = Zend_Mime::encodeBase64Header($value, $this->getCharset(), Zend_Mime::LINELENGTH, Zend_Mime::LINEEND); + } + } + + return $value; + } + + /** + * Add a header to the message + * + * Adds a header to this message. If append is true and the header already + * exists, raises a flag indicating that the header should be appended. + * + * @param string $headerName + * @param string $value + * @param bool $append + */ + protected function _storeHeader($headerName, $value, $append = false) + { + if (isset($this->_headers[$headerName])) { + $this->_headers[$headerName][] = $value; + } else { + $this->_headers[$headerName] = array($value); + } + + if ($append) { + $this->_headers[$headerName]['append'] = true; + } + + } + + /** + * Clear header from the message + * + * @param string $headerName + */ + protected function _clearHeader($headerName) + { + if (isset($this->_headers[$headerName])){ + unset($this->_headers[$headerName]); + } + } + + /** + * Helper function for adding a recipient and the corresponding header + * + * @param string $headerName + * @param string $email + * @param string $name + */ + protected function _addRecipientAndHeader($headerName, $email, $name) + { + $email = $this->_filterEmail($email); + $name = $this->_filterName($name); + // prevent duplicates + $this->_recipients[$email] = 1; + $this->_storeHeader($headerName, $this->_formatAddress($email, $name), true); + } + + /** + * Adds To-header and recipient + * + * @param string $email + * @param string $name + * @return Zend_Mail Provides fluent interface + */ + public function addTo($email, $name='') + { + $this->_addRecipientAndHeader('To', $email, $name); + $this->_to[] = $email; + return $this; + } + + /** + * Adds Cc-header and recipient + * + * @param string $email + * @param string $name + * @return Zend_Mail Provides fluent interface + */ + public function addCc($email, $name='') + { + $this->_addRecipientAndHeader('Cc', $email, $name); + return $this; + } + + /** + * Adds Bcc recipient + * + * @param string $email + * @return Zend_Mail Provides fluent interface + */ + public function addBcc($email) + { + $this->_addRecipientAndHeader('Bcc', $email, ''); + return $this; + } + + /** + * Return list of recipient email addresses + * + * @return array (of strings) + */ + public function getRecipients() + { + return array_keys($this->_recipients); + } + + /** + * Clears list of recipient email addresses + * + * @return Zend_Mail Provides fluent interface + */ + public function clearRecipients() + { + $this->_recipients = array(); + $this->_to = array(); + + $this->_clearHeader('To'); + $this->_clearHeader('Cc'); + $this->_clearHeader('Bcc'); + + return $this; + } + + /** + * Sets From-header and sender of the message + * + * @param string $email + * @param string $name + * @return Zend_Mail Provides fluent interface + * @throws Zend_Mail_Exception if called subsequent times + */ + public function setFrom($email, $name = null) + { + if ($this->_from === null) { + $email = $this->_filterEmail($email); + $name = $this->_filterName($name); + $this->_from = $email; + $this->_storeHeader('From', $this->_formatAddress($email, $name), true); + } else { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('From Header set twice'); + } + return $this; + } + + /** + * Returns the sender of the mail + * + * @return string + */ + public function getFrom() + { + return $this->_from; + } + + /** + * Clears the sender from the mail + * + * @return Zend_Mail Provides fluent interface + */ + public function clearFrom() + { + $this->_from = null; + $this->_clearHeader('From'); + + return $this; + } + + /** + * Sets the Return-Path header of the message + * + * @param string $email + * @return Zend_Mail Provides fluent interface + * @throws Zend_Mail_Exception if set multiple times + */ + public function setReturnPath($email) + { + if ($this->_returnPath === null) { + $email = $this->_filterEmail($email); + $this->_returnPath = $email; + $this->_storeHeader('Return-Path', $email, false); + } else { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('Return-Path Header set twice'); + } + return $this; + } + + /** + * Returns the current Return-Path address of the message + * + * If no Return-Path header is set, returns the value of {@link $_from}. + * + * @return string + */ + public function getReturnPath() + { + if (null !== $this->_returnPath) { + return $this->_returnPath; + } + + return $this->_from; + } + + /** + * Clears the current Return-Path address from the message + * + * @return Zend_Mail Provides fluent interface + */ + public function clearReturnPath() + { + $this->_returnPath = null; + $this->_clearHeader('Return-Path'); + + return $this; + } + + /** + * Sets the subject of the message + * + * @param string $subject + * @return Zend_Mail Provides fluent interface + * @throws Zend_Mail_Exception + */ + public function setSubject($subject) + { + if ($this->_subject === null) { + $subject = $this->_filterOther($subject); + $this->_subject = $this->_encodeHeader($subject); + $this->_storeHeader('Subject', $this->_subject); + } else { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('Subject set twice'); + } + return $this; + } + + /** + * Returns the encoded subject of the message + * + * @return string + */ + public function getSubject() + { + return $this->_subject; + } + + /** + * Clears the encoded subject from the message + * + * @return Zend_Mail Provides fluent interface + */ + public function clearSubject() + { + $this->_subject = null; + $this->_clearHeader('Subject'); + + return $this; + } + + /** + * Sets Date-header + * + * @param string $date + * @return Zend_Mail Provides fluent interface + * @throws Zend_Mail_Exception if called subsequent times + */ + public function setDate($date = null) + { + if ($this->_date === null) { + if ($date === null) { + $date = date('r'); + } else if (is_int($date)) { + $date = date('r', $date); + } else if (is_string($date)) { + $date = strtotime($date); + if ($date === false || $date < 0) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('String representations of Date Header must be ' . + 'strtotime()-compatible'); + } + $date = date('r', $date); + } else if ($date instanceof Zend_Date) { + $date = $date->get(Zend_Date::RFC_2822); + } else { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception(__METHOD__ . ' only accepts UNIX timestamps, Zend_Date objects, ' . + ' and strtotime()-compatible strings'); + } + $this->_date = $date; + $this->_storeHeader('Date', $date); + } else { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('Date Header set twice'); + } + return $this; + } + + /** + * Returns the formatted date of the message + * + * @return string + */ + public function getDate() + { + return $this->_date; + } + + /** + * Clears the formatted date from the message + * + * @return Zend_Mail Provides fluent interface + */ + public function clearDate() + { + $this->_date = null; + $this->_clearHeader('Date'); + + return $this; + } + + /** + * Sets the Message-ID of the message + * + * @param boolean|string $id + * true :Auto + * false :No set + * null :No set + * string:Sets string + * @return Zend_Mail Provides fluent interface + * @throws Zend_Mail_Exception + */ + public function setMessageId($id = true) + { + if ($id === null || $id === false) { + return $this; + } elseif ($id === true) { + $id = $this->createMessageId(); + } + + if ($this->_messageId === null) { + $id = $this->_filterOther($id); + $this->_messageId = $id; + $this->_storeHeader('Message-Id', $this->_messageId); + } else { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('Message-ID set twice'); + } + + return $this; + } + + /** + * Returns the Message-ID of the message + * + * @return string + */ + public function getMessageId() + { + return $this->_messageId; + } + + + /** + * Clears the Message-ID from the message + * + * @return Zend_Mail Provides fluent interface + */ + public function clearMessageId() + { + $this->_messageId = null; + $this->_clearHeader('Message-Id'); + + return $this; + } + + /** + * Creates the Message-ID + * + * @return string + */ + public function createMessageId() { + + $time = time(); + + if ($this->_from !== null) { + $user = $this->_from; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $user = $_SERVER['REMOTE_ADDR']; + } else { + $user = getmypid(); + } + + $rand = mt_rand(); + + if ($this->_recipients !== array()) { + $recipient = array_rand($this->_recipients); + } else { + $recipient = 'unknown'; + } + + if (isset($_SERVER["SERVER_NAME"])) { + $hostName = $_SERVER["SERVER_NAME"]; + } else { + $hostName = php_uname('n'); + } + + return sha1($time . $user . $rand . $recipient) . '@' . $hostName; + } + + /** + * Add a custom header to the message + * + * @param string $name + * @param string $value + * @param boolean $append + * @return Zend_Mail Provides fluent interface + * @throws Zend_Mail_Exception on attempts to create standard headers + */ + public function addHeader($name, $value, $append = false) + { + $prohibit = array('to', 'cc', 'bcc', 'from', 'subject', + 'return-path', 'date', 'message-id', + ); + if (in_array(strtolower($name), $prohibit)) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('Cannot set standard header from addHeader()'); + } + + $value = $this->_filterOther($value); + $value = $this->_encodeHeader($value); + $this->_storeHeader($name, $value, $append); + + return $this; + } + + /** + * Return mail headers + * + * @return void + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Sends this email using the given transport or a previously + * set DefaultTransport or the internal mail function if no + * default transport had been set. + * + * @param Zend_Mail_Transport_Abstract $transport + * @return Zend_Mail Provides fluent interface + */ + public function send($transport = null) + { + if ($transport === null) { + if (! self::$_defaultTransport instanceof Zend_Mail_Transport_Abstract) { + require_once 'Zend/Mail/Transport/Sendmail.php'; + $transport = new Zend_Mail_Transport_Sendmail(); + } else { + $transport = self::$_defaultTransport; + } + } + + if ($this->_date === null) { + $this->setDate(); + } + + $transport->send($this); + + return $this; + } + + /** + * Filter of email data + * + * @param string $email + * @return string + */ + protected function _filterEmail($email) + { + $rule = array("\r" => '', + "\n" => '', + "\t" => '', + '"' => '', + ',' => '', + '<' => '', + '>' => '', + ); + + return strtr($email, $rule); + } + + /** + * Filter of name data + * + * @param string $name + * @return string + */ + protected function _filterName($name) + { + $rule = array("\r" => '', + "\n" => '', + "\t" => '', + '"' => "'", + '<' => '[', + '>' => ']', + ); + + return trim(strtr($name, $rule)); + } + + /** + * Filter of other data + * + * @param string $data + * @return string + */ + protected function _filterOther($data) + { + $rule = array("\r" => '', + "\n" => '', + "\t" => '', + ); + + return strtr($data, $rule); + } + + /** + * Formats e-mail address + * + * @param string $email + * @param string $name + * @return string + */ + protected function _formatAddress($email, $name) + { + if ($name === '' || $name === null || $name === $email) { + return $email; + } else { + $encodedName = $this->_encodeHeader($name); + if ($encodedName === $name && strpos($name, ',') !== false) { + $format = '"%s" <%s>'; + } else { + $format = '%s <%s>'; + } + return sprintf($format, $encodedName, $email); + } + } + +} diff --git a/lib/Zend/Mail/Exception.php b/lib/Zend/Mail/Exception.php new file mode 100644 index 0000000..bab758e --- /dev/null +++ b/lib/Zend/Mail/Exception.php @@ -0,0 +1,37 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Exception.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Exception + */ +require_once 'Zend/Exception.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Exception extends Zend_Exception +{} + diff --git a/lib/Zend/Mail/Message.php b/lib/Zend/Mail/Message.php new file mode 100644 index 0000000..787df98 --- /dev/null +++ b/lib/Zend/Mail/Message.php @@ -0,0 +1,112 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Message.php 9099 2008-03-30 19:35:47Z thomas $ + */ + + +/** + * Zend_Mail_Part + */ +require_once 'Zend/Mail/Part.php'; + +/** + * Zend_Mail_Message_Interface + */ +require_once 'Zend/Mail/Message/Interface.php'; + +/** + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Message extends Zend_Mail_Part implements Zend_Mail_Message_Interface +{ + /** + * flags for this message + * @var array + */ + protected $_flags = array(); + + /** + * Public constructor + * + * In addition to the parameters of Zend_Mail_Part::__construct() this constructor supports: + * - file filename or file handle of a file with raw message content + * - flags array with flags for message, keys are ignored, use constants defined in Zend_Mail_Storage + * + * @param string $rawMessage full message with or without headers + * @throws Zend_Mail_Exception + */ + public function __construct(array $params) + { + if (isset($params['file'])) { + if (!is_resource($params['file'])) { + $params['raw'] = @file_get_contents($params['file']); + if ($params['raw'] === false) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('could not open file'); + } + } else { + $params['raw'] = stream_get_contents($params['file']); + } + } + + if (!empty($params['flags'])) { + // set key and value to the same value for easy lookup + $this->_flags = array_combine($params['flags'], $params['flags']); + } + + parent::__construct($params); + } + + /** + * return toplines as found after headers + * + * @return string toplines + */ + public function getTopLines() + { + return $this->_topLines; + } + + /** + * check if flag is set + * + * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage + * @return bool true if set, otherwise false + */ + public function hasFlag($flag) + { + return isset($this->_flags[$flag]); + } + + /** + * get all set flags + * + * @return array array with flags, key and value are the same for easy lookup + */ + public function getFlags() + { + return $this->_flags; + } +} diff --git a/lib/Zend/Mail/Message/File.php b/lib/Zend/Mail/Message/File.php new file mode 100644 index 0000000..5a93449 --- /dev/null +++ b/lib/Zend/Mail/Message/File.php @@ -0,0 +1,96 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Message.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * Zend_Mail_Part + */ +require_once 'Zend/Mail/Part/File.php'; + +/** + * Zend_Mail_Message_Interface + */ +require_once 'Zend/Mail/Message/Interface.php'; + +/** + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Message_File extends Zend_Mail_Part_File implements Zend_Mail_Message_Interface +{ + /** + * flags for this message + * @var array + */ + protected $_flags = array(); + + /** + * Public constructor + * + * In addition to the parameters of Zend_Mail_Part::__construct() this constructor supports: + * - flags array with flags for message, keys are ignored, use constants defined in Zend_Mail_Storage + * + * @param string $rawMessage full message with or without headers + * @throws Zend_Mail_Exception + */ + public function __construct(array $params) + { + if (!empty($params['flags'])) { + // set key and value to the same value for easy lookup + $this->_flags = array_combine($params['flags'], $params['flags']); + } + + parent::__construct($params); + } + + /** + * return toplines as found after headers + * + * @return string toplines + */ + public function getTopLines() + { + return $this->_topLines; + } + + /** + * check if flag is set + * + * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage + * @return bool true if set, otherwise false + */ + public function hasFlag($flag) + { + return isset($this->_flags[$flag]); + } + + /** + * get all set flags + * + * @return array array with flags, key and value are the same for easy lookup + */ + public function getFlags() + { + return $this->_flags; + } +} diff --git a/lib/Zend/Mail/Message/Interface.php b/lib/Zend/Mail/Message/Interface.php new file mode 100644 index 0000000..92acd49 --- /dev/null +++ b/lib/Zend/Mail/Message/Interface.php @@ -0,0 +1,55 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Interface.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +interface Zend_Mail_Message_Interface +{ + /** + * return toplines as found after headers + * + * @return string toplines + */ + public function getTopLines(); + + /** + * check if flag is set + * + * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage + * @return bool true if set, otherwise false + */ + public function hasFlag($flag); + + /** + * get all set flags + * + * @return array array with flags, key and value are the same for easy lookup + */ + public function getFlags(); +}
\ No newline at end of file diff --git a/lib/Zend/Mail/Part.php b/lib/Zend/Mail/Part.php new file mode 100644 index 0000000..c2f6bef --- /dev/null +++ b/lib/Zend/Mail/Part.php @@ -0,0 +1,489 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Part.php 14073 2009-02-12 22:30:40Z beberlei $ + */ + + +/** + * @see Zend_Mime_Decode + */ +require_once 'Zend/Mime/Decode.php'; + +/** + * @see Zend_Mail_Part_Interface + */ +require_once 'Zend/Mail/Part/Interface.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Part implements RecursiveIterator, Zend_Mail_Part_Interface +{ + /** + * headers of part as array + * @var null|array + */ + protected $_headers; + + /** + * raw part body + * @var null|string + */ + protected $_content; + + /** + * toplines as fetched with headers + * @var string + */ + protected $_topLines = ''; + + /** + * parts of multipart message + * @var array + */ + protected $_parts = array(); + + /** + * count of parts of a multipart message + * @var null|int + */ + protected $_countParts; + + /** + * current position of iterator + * @var int + */ + protected $_iterationPos = 1; + + /** + * mail handler, if late fetch is active + * @var null|Zend_Mail_Storage_Abstract + */ + protected $_mail; + + /** + * message number for mail handler + * @var int + */ + protected $_messageNum = 0; + + /** + * Public constructor + * + * Zend_Mail_Part supports different sources for content. The possible params are: + * - handler a instance of Zend_Mail_Storage_Abstract for late fetch + * - id number of message for handler + * - raw raw content with header and body as string + * - headers headers as array (name => value) or string, if a content part is found it's used as toplines + * - noToplines ignore content found after headers in param 'headers' + * - content content as string + * + * @param array $params full message with or without headers + * @throws Zend_Mail_Exception + */ + public function __construct(array $params) + { + if (isset($params['handler'])) { + if (!$params['handler'] instanceof Zend_Mail_Storage_Abstract) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('handler is not a valid mail handler'); + } + if (!isset($params['id'])) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('need a message id with a handler'); + } + + $this->_mail = $params['handler']; + $this->_messageNum = $params['id']; + } + + if (isset($params['raw'])) { + Zend_Mime_Decode::splitMessage($params['raw'], $this->_headers, $this->_content); + } else if (isset($params['headers'])) { + if (is_array($params['headers'])) { + $this->_headers = $params['headers']; + } else { + if (!empty($params['noToplines'])) { + Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $null); + } else { + Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $this->_topLines); + } + } + if (isset($params['content'])) { + $this->_content = $params['content']; + } + } + } + + /** + * Check if part is a multipart message + * + * @return bool if part is multipart + */ + public function isMultipart() + { + try { + return stripos($this->contentType, 'multipart/') === 0; + } catch(Zend_Mail_Exception $e) { + return false; + } + } + + + /** + * Body of part + * + * If part is multipart the raw content of this part with all sub parts is returned + * + * @return string body + * @throws Zend_Mail_Exception + */ + public function getContent() + { + if ($this->_content !== null) { + return $this->_content; + } + + if ($this->_mail) { + return $this->_mail->getRawContent($this->_messageNum); + } else { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('no content'); + } + } + + /** + * Return size of part + * + * Quite simple implemented currently (not decoding). Handle with care. + * + * @return int size + */ + public function getSize() { + return strlen($this->getContent()); + } + + + /** + * Cache content and split in parts if multipart + * + * @return null + * @throws Zend_Mail_Exception + */ + protected function _cacheContent() + { + // caching content if we can't fetch parts + if ($this->_content === null && $this->_mail) { + $this->_content = $this->_mail->getRawContent($this->_messageNum); + } + + if (!$this->isMultipart()) { + return; + } + + // split content in parts + $boundary = $this->getHeaderField('content-type', 'boundary'); + if (!$boundary) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('no boundary found in content type to split message'); + } + $parts = Zend_Mime_Decode::splitMessageStruct($this->_content, $boundary); + if ($parts === null) { + return; + } + $counter = 1; + foreach ($parts as $part) { + $this->_parts[$counter++] = new self(array('headers' => $part['header'], 'content' => $part['body'])); + } + } + + /** + * Get part of multipart message + * + * @param int $num number of part starting with 1 for first part + * @return Zend_Mail_Part wanted part + * @throws Zend_Mail_Exception + */ + public function getPart($num) + { + if (isset($this->_parts[$num])) { + return $this->_parts[$num]; + } + + if (!$this->_mail && $this->_content === null) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('part not found'); + } + + if ($this->_mail && $this->_mail->hasFetchPart) { + // TODO: fetch part + // return + } + + $this->_cacheContent(); + + if (!isset($this->_parts[$num])) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('part not found'); + } + + return $this->_parts[$num]; + } + + /** + * Count parts of a multipart part + * + * @return int number of sub-parts + */ + public function countParts() + { + if ($this->_countParts) { + return $this->_countParts; + } + + $this->_countParts = count($this->_parts); + if ($this->_countParts) { + return $this->_countParts; + } + + if ($this->_mail && $this->_mail->hasFetchPart) { + // TODO: fetch part + // return + } + + $this->_cacheContent(); + + $this->_countParts = count($this->_parts); + return $this->_countParts; + } + + + /** + * Get all headers + * + * The returned headers are as saved internally. All names are lowercased. The value is a string or an array + * if a header with the same name occurs more than once. + * + * @return array headers as array(name => value) + */ + public function getHeaders() + { + if ($this->_headers === null) { + if (!$this->_mail) { + $this->_headers = array(); + } else { + $part = $this->_mail->getRawHeader($this->_messageNum); + Zend_Mime_Decode::splitMessage($part, $this->_headers, $null); + } + } + + return $this->_headers; + } + + /** + * Get a header in specificed format + * + * Internally headers that occur more than once are saved as array, all other as string. If $format + * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim). + * + * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes + * @param string $format change type of return value to 'string' or 'array' + * @return string|array value of header in wanted or internal format + * @throws Zend_Mail_Exception + */ + public function getHeader($name, $format = null) + { + if ($this->_headers === null) { + $this->getHeaders(); + } + + $lowerName = strtolower($name); + + if (!isset($this->_headers[$lowerName])) { + $lowerName = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $name)); + if (!isset($this->_headers[$lowerName])) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception("no Header with Name $name found"); + } + } + $name = $lowerName; + + $header = $this->_headers[$name]; + + switch ($format) { + case 'string': + if (is_array($header)) { + $header = implode(Zend_Mime::LINEEND, $header); + } + break; + case 'array': + $header = (array)$header; + default: + // do nothing + } + + return $header; + } + + /** + * Get a specific field from a header like content type or all fields as array + * + * If the header occurs more than once, only the value from the first header + * is returned. + * + * Throws a Zend_Mail_Exception if the requested header does not exist. If + * the specific header field does not exist, returns null. + * + * @param string $name name of header, like in getHeader() + * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned + * @param string $firstName key name for the first part + * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) + * @throws Zend_Exception, Zend_Mail_Exception + */ + public function getHeaderField($name, $wantedPart = 0, $firstName = 0) { + return Zend_Mime_Decode::splitHeaderField(current($this->getHeader($name, 'array')), $wantedPart, $firstName); + } + + + /** + * Getter for mail headers - name is matched in lowercase + * + * This getter is short for Zend_Mail_Part::getHeader($name, 'string') + * + * @see Zend_Mail_Part::getHeader() + * + * @param string $name header name + * @return string value of header + * @throws Zend_Mail_Exception + */ + public function __get($name) + { + return $this->getHeader($name, 'string'); + } + + /** + * magic method to get content of part + * + * @return string content + */ + public function __toString() + { + return $this->getContent(); + } + + /** + * implements RecursiveIterator::hasChildren() + * + * @return bool current element has children/is multipart + */ + public function hasChildren() + { + $current = $this->current(); + return $current && $current instanceof Zend_Mail_Part && $current->isMultipart(); + } + + /** + * implements RecursiveIterator::getChildren() + * + * @return Zend_Mail_Part same as self::current() + */ + public function getChildren() + { + return $this->current(); + } + + /** + * implements Iterator::valid() + * + * @return bool check if there's a current element + */ + public function valid() + { + if ($this->_countParts === null) { + $this->countParts(); + } + return $this->_iterationPos && $this->_iterationPos <= $this->_countParts; + } + + /** + * implements Iterator::next() + * + * @return null + */ + public function next() + { + ++$this->_iterationPos; + } + + /** + * implements Iterator::key() + * + * @return string key/number of current part + */ + public function key() + { + return $this->_iterationPos; + } + + /** + * implements Iterator::current() + * + * @return Zend_Mail_Part current part + */ + public function current() + { + return $this->getPart($this->_iterationPos); + } + + /** + * implements Iterator::rewind() + * + * @return null + */ + public function rewind() + { + $this->countParts(); + $this->_iterationPos = 1; + } +} diff --git a/lib/Zend/Mail/Part/File.php b/lib/Zend/Mail/Part/File.php new file mode 100644 index 0000000..6ff5147 --- /dev/null +++ b/lib/Zend/Mail/Part/File.php @@ -0,0 +1,198 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Part.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Mime_Decode + */ +require_once 'Zend/Mime/Decode.php'; + +/** + * @see Zend_Mail_Part + */ +require_once 'Zend/Mail/Part.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Part_File extends Zend_Mail_Part +{ + protected $_contentPos = array(); + protected $_partPos = array(); + protected $_fh; + + /** + * Public constructor + * + * This handler supports the following params: + * - file filename or open file handler with message content (required) + * - startPos start position of message or part in file (default: current position) + * - endPos end position of message or part in file (default: end of file) + * + * @param array $params full message with or without headers + * @throws Zend_Mail_Exception + */ + public function __construct(array $params) + { + if (empty($params['file'])) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('no file given in params'); + } + + if (!is_resource($params['file'])) { + $this->_fh = fopen($params['file'], 'r'); + } else { + $this->_fh = $params['file']; + } + if (!$this->_fh) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('could not open file'); + } + if (isset($params['startPos'])) { + fseek($this->_fh, $params['startPos']); + } + $header = ''; + $endPos = isset($params['endPos']) ? $params['endPos'] : null; + while (($endPos === null || ftell($this->_fh) < $endPos) && trim($line = fgets($this->_fh))) { + $header .= $line; + } + + Zend_Mime_Decode::splitMessage($header, $this->_headers, $null); + + $this->_contentPos[0] = ftell($this->_fh); + if ($endPos !== null) { + $this->_contentPos[1] = $endPos; + } else { + fseek($this->_fh, 0, SEEK_END); + $this->_contentPos[1] = ftell($this->_fh); + } + if (!$this->isMultipart()) { + return; + } + + $boundary = $this->getHeaderField('content-type', 'boundary'); + if (!$boundary) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('no boundary found in content type to split message'); + } + + $part = array(); + $pos = $this->_contentPos[0]; + fseek($this->_fh, $pos); + while (!feof($this->_fh) && ($endPos === null || $pos < $endPos)) { + $line = fgets($this->_fh); + if ($line === false) { + if (feof($this->_fh)) { + break; + } + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('error reading file'); + } + + $lastPos = $pos; + $pos = ftell($this->_fh); + $line = trim($line); + + if ($line == '--' . $boundary) { + if ($part) { + // not first part + $part[1] = $lastPos; + $this->_partPos[] = $part; + } + $part = array($pos); + } else if ($line == '--' . $boundary . '--') { + $part[1] = $lastPos; + $this->_partPos[] = $part; + break; + } + } + $this->_countParts = count($this->_partPos); + + } + + + /** + * Body of part + * + * If part is multipart the raw content of this part with all sub parts is returned + * + * @return string body + * @throws Zend_Mail_Exception + */ + public function getContent($stream = null) + { + fseek($this->_fh, $this->_contentPos[0]); + if ($stream !== null) { + return stream_copy_to_stream($this->_fh, $stream, $this->_contentPos[1] - $this->_contentPos[0]); + } + $length = $this->_contentPos[1] - $this->_contentPos[0]; + return $length < 1 ? '' : fread($this->_fh, $length); + } + + /** + * Return size of part + * + * Quite simple implemented currently (not decoding). Handle with care. + * + * @return int size + */ + public function getSize() { + return $this->_contentPos[1] - $this->_contentPos[0]; + } + + /** + * Get part of multipart message + * + * @param int $num number of part starting with 1 for first part + * @return Zend_Mail_Part wanted part + * @throws Zend_Mail_Exception + */ + public function getPart($num) + { + --$num; + if (!isset($this->_partPos[$num])) { + /** + * @see Zend_Mail_Exception + */ + require_once 'Zend/Mail/Exception.php'; + throw new Zend_Mail_Exception('part not found'); + } + + return new self(array('file' => $this->_fh, 'startPos' => $this->_partPos[$num][0], + 'endPos' => $this->_partPos[$num][1])); + } +} diff --git a/lib/Zend/Mail/Part/Interface.php b/lib/Zend/Mail/Part/Interface.php new file mode 100644 index 0000000..d1a22f1 --- /dev/null +++ b/lib/Zend/Mail/Part/Interface.php @@ -0,0 +1,136 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Interface.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +interface Zend_Mail_Part_Interface extends RecursiveIterator +{ + /** + * Check if part is a multipart message + * + * @return bool if part is multipart + */ + public function isMultipart(); + + + /** + * Body of part + * + * If part is multipart the raw content of this part with all sub parts is returned + * + * @return string body + * @throws Zend_Mail_Exception + */ + public function getContent(); + + /** + * Return size of part + * + * @return int size + */ + public function getSize(); + + /** + * Get part of multipart message + * + * @param int $num number of part starting with 1 for first part + * @return Zend_Mail_Part wanted part + * @throws Zend_Mail_Exception + */ + public function getPart($num); + + /** + * Count parts of a multipart part + * + * @return int number of sub-parts + */ + public function countParts(); + + + /** + * Get all headers + * + * The returned headers are as saved internally. All names are lowercased. The value is a string or an array + * if a header with the same name occurs more than once. + * + * @return array headers as array(name => value) + */ + public function getHeaders(); + + /** + * Get a header in specificed format + * + * Internally headers that occur more than once are saved as array, all other as string. If $format + * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim). + * + * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes + * @param string $format change type of return value to 'string' or 'array' + * @return string|array value of header in wanted or internal format + * @throws Zend_Mail_Exception + */ + public function getHeader($name, $format = null); + + /** + * Get a specific field from a header like content type or all fields as array + * + * If the header occurs more than once, only the value from the first header + * is returned. + * + * Throws a Zend_Mail_Exception if the requested header does not exist. If + * the specific header field does not exist, returns null. + * + * @param string $name name of header, like in getHeader() + * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned + * @param string $firstName key name for the first part + * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) + * @throws Zend_Exception, Zend_Mail_Exception + */ + public function getHeaderField($name, $wantedPart = 0, $firstName = 0); + + + /** + * Getter for mail headers - name is matched in lowercase + * + * This getter is short for Zend_Mail_Part::getHeader($name, 'string') + * + * @see Zend_Mail_Part::getHeader() + * + * @param string $name header name + * @return string value of header + * @throws Zend_Mail_Exception + */ + public function __get($name); + + /** + * magic method to get content of part + * + * @return string content + */ + public function __toString(); +}
\ No newline at end of file diff --git a/lib/Zend/Mail/Protocol/Abstract.php b/lib/Zend/Mail/Protocol/Abstract.php new file mode 100644 index 0000000..cfb1f42 --- /dev/null +++ b/lib/Zend/Mail/Protocol/Abstract.php @@ -0,0 +1,385 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Abstract.php 12395 2008-11-07 23:58:38Z nico $ + */ + + +/** + * @see Zend_Validate + */ +require_once 'Zend/Validate.php'; + + +/** + * @see Zend_Validate_Hostname + */ +require_once 'Zend/Validate/Hostname.php'; + + +/** + * Zend_Mail_Protocol_Abstract + * + * Provides low-level methods for concrete adapters to communicate with a remote mail server and track requests and responses. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Abstract.php 12395 2008-11-07 23:58:38Z nico $ + * @todo Implement proxy settings + */ +abstract class Zend_Mail_Protocol_Abstract +{ + /** + * Mail default EOL string + */ + const EOL = "\r\n"; + + + /** + * Default timeout in seconds for initiating session + */ + const TIMEOUT_CONNECTION = 30; + + + /** + * Hostname or IP address of remote server + * @var string + */ + protected $_host; + + + /** + * Port number of connection + * @var integer + */ + protected $_port; + + + /** + * Instance of Zend_Validate to check hostnames + * @var Zend_Validate + */ + protected $_validHost; + + + /** + * Socket connection resource + * @var resource + */ + protected $_socket; + + + /** + * Last request sent to server + * @var string + */ + protected $_request; + + + /** + * Array of server responses to last request + * @var array + */ + protected $_response; + + + /** + * String template for parsing server responses using sscanf (default: 3 digit code and response string) + * @var resource + */ + protected $_template = '%d%s'; + + + /** + * Log of mail requests and server responses for a session + * @var string + */ + private $_log; + + + /** + * Constructor. + * + * @param string $host OPTIONAL Hostname of remote connection (default: 127.0.0.1) + * @param integer $port OPTIONAL Port number (default: null) + * @throws Zend_Mail_Protocol_Exception + * @return void + */ + public function __construct($host = '127.0.0.1', $port = null) + { + $this->_validHost = new Zend_Validate(); + $this->_validHost->addValidator(new Zend_Validate_Hostname(Zend_Validate_Hostname::ALLOW_ALL)); + + if (!$this->_validHost->isValid($host)) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception(join(', ', $this->_validHost->getMessages())); + } + + $this->_host = $host; + $this->_port = $port; + } + + + /** + * Class destructor to cleanup open resources + * + * @return void + */ + public function __destruct() + { + $this->_disconnect(); + } + + + /** + * Create a connection to the remote host + * + * Concrete adapters for this class will implement their own unique connect scripts, using the _connect() method to create the socket resource. + */ + abstract public function connect(); + + + /** + * Retrieve the last client request + * + * @return string + */ + public function getRequest() + { + return $this->_request; + } + + + /** + * Retrieve the last server response + * + * @return array + */ + public function getResponse() + { + return $this->_response; + } + + + /** + * Retrieve the transaction log + * + * @return string + */ + public function getLog() + { + return $this->_log; + } + + + /** + * Reset the transaction log + * + * @return void + */ + public function resetLog() + { + $this->_log = ''; + } + + + /** + * Connect to the server using the supplied transport and target + * + * An example $remote string may be 'tcp://mail.example.com:25' or 'ssh://hostname.com:2222' + * + * @param string $remote Remote + * @throws Zend_Mail_Protocol_Exception + * @return boolean + */ + protected function _connect($remote) + { + $errorNum = 0; + $errorStr = ''; + + // open connection + $this->_socket = @stream_socket_client($remote, $errorNum, $errorStr, self::TIMEOUT_CONNECTION); + + if ($this->_socket === false) { + if ($errorNum == 0) { + $errorStr = 'Could not open socket'; + } + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception($errorStr); + } + + if (($result = stream_set_timeout($this->_socket, self::TIMEOUT_CONNECTION)) === false) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('Could not set stream timeout'); + } + + return $result; + } + + + /** + * Disconnect from remote host and free resource + * + * @return void + */ + protected function _disconnect() + { + if (is_resource($this->_socket)) { + fclose($this->_socket); + } + } + + + /** + * Send the given request followed by a LINEEND to the server. + * + * @param string $request + * @throws Zend_Mail_Protocol_Exception + * @return integer|boolean Number of bytes written to remote host + */ + protected function _send($request) + { + if (!is_resource($this->_socket)) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('No connection has been established to ' . $this->_host); + } + + $this->_request = $request; + + $result = fwrite($this->_socket, $request . self::EOL); + + // Save request to internal log + $this->_log .= $request . self::EOL; + + if ($result === false) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('Could not send request to ' . $this->_host); + } + + return $result; + } + + + /** + * Get a line from the stream. + * + * @var integer $timeout Per-request timeout value if applicable + * @throws Zend_Mail_Protocol_Exception + * @return string + */ + protected function _receive($timeout = null) + { + if (!is_resource($this->_socket)) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('No connection has been established to ' . $this->_host); + } + + // Adapters may wish to supply per-commend timeouts according to appropriate RFC + if ($timeout !== null) { + stream_set_timeout($this->_socket, $timeout); + } + + // Retrieve response + $reponse = fgets($this->_socket, 1024); + + // Save request to internal log + $this->_log .= $reponse; + + // Check meta data to ensure connection is still valid + $info = stream_get_meta_data($this->_socket); + + if (!empty($info['timed_out'])) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception($this->_host . ' has timed out'); + } + + if ($reponse === false) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('Could not read from ' . $this->_host); + } + + return $reponse; + } + + + /** + * Parse server response for successful codes + * + * Read the response from the stream and check for expected return code. + * Throws a Zend_Mail_Protocol_Exception if an unexpected code is returned. + * + * @param string|array $code One or more codes that indicate a successful response + * @throws Zend_Mail_Protocol_Exception + * @return string Last line of response string + */ + protected function _expect($code, $timeout = null) + { + $this->_response = array(); + $cmd = ''; + $msg = ''; + + if (!is_array($code)) { + $code = array($code); + } + + do { + $this->_response[] = $result = $this->_receive($timeout); + sscanf($result, $this->_template, $cmd, $msg); + + if ($cmd === null || !in_array($cmd, $code)) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception($result); + } + + } while (strpos($msg, '-') === 0); // The '-' message prefix indicates an information string instead of a response string. + + return $msg; + } +} diff --git a/lib/Zend/Mail/Protocol/Exception.php b/lib/Zend/Mail/Protocol/Exception.php new file mode 100644 index 0000000..c3add11 --- /dev/null +++ b/lib/Zend/Mail/Protocol/Exception.php @@ -0,0 +1,39 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Exception.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Exception + */ +require_once 'Zend/Mail/Exception.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Protocol_Exception extends Zend_Mail_Exception +{} + diff --git a/lib/Zend/Mail/Protocol/Imap.php b/lib/Zend/Mail/Protocol/Imap.php new file mode 100644 index 0000000..eec0ff3 --- /dev/null +++ b/lib/Zend/Mail/Protocol/Imap.php @@ -0,0 +1,837 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Imap.php 12539 2008-11-11 02:47:17Z yoshida@zend.co.jp $ + */ + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Protocol_Imap +{ + /** + * Default timeout in seconds for initiating session + */ + const TIMEOUT_CONNECTION = 30; + + /** + * socket to imap server + * @var resource|null + */ + protected $_socket; + + /** + * counter for request tag + * @var int + */ + protected $_tagCount = 0; + + /** + * Public constructor + * + * @param string $host hostname of IP address of IMAP server, if given connect() is called + * @param int|null $port port of IMAP server, null for default (143 or 993 for ssl) + * @param bool $ssl use ssl? 'SSL', 'TLS' or false + * @throws Zend_Mail_Protocol_Exception + */ + function __construct($host = '', $port = null, $ssl = false) + { + if ($host) { + $this->connect($host, $port, $ssl); + } + } + + /** + * Public destructor + */ + public function __destruct() + { + $this->logout(); + } + + /** + * Open connection to POP3 server + * + * @param string $host hostname of IP address of POP3 server + * @param int|null $port of IMAP server, default is 143 (993 for ssl) + * @param string|bool $ssl use 'SSL', 'TLS' or false + * @return string welcome message + * @throws Zend_Mail_Protocol_Exception + */ + public function connect($host, $port = null, $ssl = false) + { + if ($ssl == 'SSL') { + $host = 'ssl://' . $host; + } + + if ($port === null) { + $port = $ssl === 'SSL' ? 993 : 143; + } + + $errno = 0; + $errstr = ''; + $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION); + if (!$this->_socket) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('cannot connect to host : ' . $errno . ' : ' . $errstr); + } + + if (!$this->_assumedNextLine('* OK')) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('host doesn\'t allow connection'); + } + + if ($ssl === 'TLS') { + $result = $this->requestAndResponse('STARTTLS'); + $result = $result && stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + if (!$result) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('cannot enable TLS'); + } + } + } + + /** + * get the next line from socket with error checking, but nothing else + * + * @return string next line + * @throws Zend_Mail_Protocol_Exception + */ + protected function _nextLine() + { + $line = @fgets($this->_socket); + if ($line === false) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('cannot read - connection closed?'); + } + + return $line; + } + + /** + * get next line and assume it starts with $start. some requests give a simple + * feedback so we can quickly check if we can go on. + * + * @param string $start the first bytes we assume to be in the next line + * @return bool line starts with $start + * @throws Zend_Mail_Protocol_Exception + */ + protected function _assumedNextLine($start) + { + $line = $this->_nextLine(); + return strpos($line, $start) === 0; + } + + /** + * get next line and split the tag. that's the normal case for a response line + * + * @param string $tag tag of line is returned by reference + * @return string next line + * @throws Zend_Mail_Protocol_Exception + */ + protected function _nextTaggedLine(&$tag) + { + $line = $this->_nextLine(); + + // seperate tag from line + list($tag, $line) = explode(' ', $line, 2); + + return $line; + } + + /** + * split a given line in tokens. a token is literal of any form or a list + * + * @param string $line line to decode + * @return array tokens, literals are returned as string, lists as array + * @throws Zend_Mail_Protocol_Exception + */ + protected function _decodeLine($line) + { + $tokens = array(); + $stack = array(); + + /* + We start to decode the response here. The unterstood tokens are: + literal + "literal" or also "lit\\er\"al" + {bytes}<NL>literal + (literals*) + All tokens are returned in an array. Literals in braces (the last unterstood + token in the list) are returned as an array of tokens. I.e. the following response: + "foo" baz {3}<NL>bar ("f\\\"oo" bar) + would be returned as: + array('foo', 'baz', 'bar', array('f\\\"oo', 'bar')); + + // TODO: add handling of '[' and ']' to parser for easier handling of response text + */ + // replace any trailling <NL> including spaces with a single space + $line = rtrim($line) . ' '; + while (($pos = strpos($line, ' ')) !== false) { + $token = substr($line, 0, $pos); + while ($token[0] == '(') { + array_push($stack, $tokens); + $tokens = array(); + $token = substr($token, 1); + } + if ($token[0] == '"') { + if (preg_match('%^"((.|\\\\|\\")*?)" *%', $line, $matches)) { + $tokens[] = $matches[1]; + $line = substr($line, strlen($matches[0])); + continue; + } + } + if ($token[0] == '{') { + $endPos = strpos($token, '}'); + $chars = substr($token, 1, $endPos - 1); + if (is_numeric($chars)) { + $token = ''; + while (strlen($token) < $chars) { + $token .= $this->_nextLine(); + } + $line = ''; + if (strlen($token) > $chars) { + $line = substr($token, $chars); + $token = substr($token, 0, $chars); + } else { + $line .= $this->_nextLine(); + } + $tokens[] = $token; + $line = trim($line) . ' '; + continue; + } + } + if ($stack && $token[strlen($token) - 1] == ')') { + // closing braces are not seperated by spaces, so we need to count them + $braces = strlen($token); + $token = rtrim($token, ')'); + // only count braces if more than one + $braces -= strlen($token) + 1; + // only add if token had more than just closing braces + if ($token) { + $tokens[] = $token; + } + $token = $tokens; + $tokens = array_pop($stack); + // special handline if more than one closing brace + while ($braces-- > 0) { + $tokens[] = $token; + $token = $tokens; + $tokens = array_pop($stack); + } + } + $tokens[] = $token; + $line = substr($line, $pos + 1); + } + + // maybe the server forgot to send some closing braces + while ($stack) { + $child = $tokens; + $tokens = array_pop($stack); + $tokens[] = $child; + } + + return $tokens; + } + + /** + * read a response "line" (could also be more than one real line if response has {..}<NL>) + * and do a simple decode + * + * @param array|string $tokens decoded tokens are returned by reference, if $dontParse + * is true the unparsed line is returned here + * @param string $wantedTag check for this tag for response code. Default '*' is + * continuation tag. + * @param bool $dontParse if true only the unparsed line is returned $tokens + * @return bool if returned tag matches wanted tag + * @throws Zend_Mail_Protocol_Exception + */ + public function readLine(&$tokens = array(), $wantedTag = '*', $dontParse = false) + { + $line = $this->_nextTaggedLine($tag); + if (!$dontParse) { + $tokens = $this->_decodeLine($line); + } else { + $tokens = $line; + } + + // if tag is wanted tag we might be at the end of a multiline response + return $tag == $wantedTag; + } + + /** + * read all lines of response until given tag is found (last line of response) + * + * @param string $tag the tag of your request + * @param string|array $filter you can filter the response so you get only the + * given response lines + * @param bool $dontParse if true every line is returned unparsed instead of + * the decoded tokens + * @return null|bool|array tokens if success, false if error, null if bad request + * @throws Zend_Mail_Protocol_Exception + */ + public function readResponse($tag, $dontParse = false) + { + $lines = array(); + while (!$this->readLine($tokens, $tag, $dontParse)) { + $lines[] = $tokens; + } + + if ($dontParse) { + // last to chars are still needed for response code + $tokens = array(substr($tokens, 0, 2)); + } + // last line has response code + if ($tokens[0] == 'OK') { + return $lines ? $lines : true; + } else if ($tokens[0] == 'NO'){ + return false; + } + return null; + } + + /** + * send a request + * + * @param string $command your request command + * @param array $tokens additional parameters to command, use escapeString() to prepare + * @param string $tag provide a tag otherwise an autogenerated is returned + * @return null + * @throws Zend_Mail_Protocol_Exception + */ + public function sendRequest($command, $tokens = array(), &$tag = null) + { + if (!$tag) { + ++$this->_tagCount; + $tag = 'TAG' . $this->_tagCount; + } + + $line = $tag . ' ' . $command; + + foreach ($tokens as $token) { + if (is_array($token)) { + if (@fputs($this->_socket, $line . ' ' . $token[0] . "\r\n") === false) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?'); + } + if (!$this->_assumedNextLine('+ ')) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('cannot send literal string'); + } + $line = $token[1]; + } else { + $line .= ' ' . $token; + } + } + + if (@fputs($this->_socket, $line . "\r\n") === false) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?'); + } + } + + /** + * send a request and get response at once + * + * @param string $command command as in sendRequest() + * @param array $tokens parameters as in sendRequest() + * @param bool $dontParse if true unparsed lines are returned instead of tokens + * @return mixed response as in readResponse() + * @throws Zend_Mail_Protocol_Exception + */ + public function requestAndResponse($command, $tokens = array(), $dontParse = false) + { + $this->sendRequest($command, $tokens, $tag); + $response = $this->readResponse($tag, $dontParse); + + return $response; + } + + /** + * escape one or more literals i.e. for sendRequest + * + * @param string|array $string the literal/-s + * @return string|array escape literals, literals with newline ar returned + * as array('{size}', 'string'); + */ + public function escapeString($string) + { + if (func_num_args() < 2) { + if (strpos($string, "\n") !== false) { + return array('{' . strlen($string) . '}', $string); + } else { + return '"' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $string) . '"'; + } + } + $result = array(); + foreach (func_get_args() as $string) { + $result[] = $this->escapeString($string); + } + return $result; + } + + /** + * escape a list with literals or lists + * + * @param array $list list with literals or lists as PHP array + * @return string escaped list for imap + */ + public function escapeList($list) + { + $result = array(); + foreach ($list as $k => $v) { + if (!is_array($v)) { +// $result[] = $this->escapeString($v); + $result[] = $v; + continue; + } + $result[] = $this->escapeList($v); + } + return '(' . implode(' ', $result) . ')'; + } + + /** + * Login to IMAP server. + * + * @param string $user username + * @param string $password password + * @return bool success + * @throws Zend_Mail_Protocol_Exception + */ + public function login($user, $password) + { + return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true); + } + + /** + * logout of imap server + * + * @return bool success + */ + public function logout() + { + $result = false; + if ($this->_socket) { + try { + $result = $this->requestAndResponse('LOGOUT', array(), true); + } catch (Zend_Mail_Protocol_Exception $e) { + // ignoring exception + } + fclose($this->_socket); + $this->_socket = null; + } + return $result; + } + + + /** + * Get capabilities from IMAP server + * + * @return array list of capabilities + * @throws Zend_Mail_Protocol_Exception + */ + public function capability() + { + $response = $this->requestAndResponse('CAPABILITY'); + + if (!$response) { + return $response; + } + + $capabilities = array(); + foreach ($response as $line) { + $capabilities = array_merge($capabilities, $line); + } + return $capabilities; + } + + /** + * Examine and select have the same response. The common code for both + * is in this method + * + * @param string $command can be 'EXAMINE' or 'SELECT' and this is used as command + * @param string $box which folder to change to or examine + * @return bool|array false if error, array with returned information + * otherwise (flags, exists, recent, uidvalidity) + * @throws Zend_Mail_Protocol_Exception + */ + public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX') + { + $this->sendRequest($command, array($this->escapeString($box)), $tag); + + $result = array(); + while (!$this->readLine($tokens, $tag)) { + if ($tokens[0] == 'FLAGS') { + array_shift($tokens); + $result['flags'] = $tokens; + continue; + } + switch ($tokens[1]) { + case 'EXISTS': + case 'RECENT': + $result[strtolower($tokens[1])] = $tokens[0]; + break; + case '[UIDVALIDITY': + $result['uidvalidity'] = (int)$tokens[2]; + break; + default: + // ignore + } + } + + if ($tokens[0] != 'OK') { + return false; + } + return $result; + } + + /** + * change folder + * + * @param string $box change to this folder + * @return bool|array see examineOrselect() + * @throws Zend_Mail_Protocol_Exception + */ + public function select($box = 'INBOX') + { + return $this->examineOrSelect('SELECT', $box); + } + + /** + * examine folder + * + * @param string $box examine this folder + * @return bool|array see examineOrselect() + * @throws Zend_Mail_Protocol_Exception + */ + public function examine($box = 'INBOX') + { + return $this->examineOrSelect('EXAMINE', $box); + } + + /** + * fetch one or more items of one or more messages + * + * @param string|array $items items to fetch from message(s) as string (if only one item) + * or array of strings + * @param int $from message for items or start message if $to !== null + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message avaible + * @return string|array if only one item of one message is fetched it's returned as string + * if items of one message are fetched it's returned as (name => value) + * if one items of messages are fetched it's returned as (msgno => value) + * if items of messages are fetchted it's returned as (msgno => (name => value)) + * @throws Zend_Mail_Protocol_Exception + */ + public function fetch($items, $from, $to = null) + { + if (is_array($from)) { + $set = implode(',', $from); + } else if ($to === null) { + $set = (int)$from; + } else if ($to === INF) { + $set = (int)$from . ':*'; + } else { + $set = (int)$from . ':' . (int)$to; + } + + $items = (array)$items; + $itemList = $this->escapeList($items); + + $this->sendRequest('FETCH', array($set, $itemList), $tag); + + $result = array(); + while (!$this->readLine($tokens, $tag)) { + // ignore other responses + if ($tokens[1] != 'FETCH') { + continue; + } + // ignore other messages + if ($to === null && !is_array($from) && $tokens[0] != $from) { + continue; + } + // if we only want one item we return that one directly + if (count($items) == 1) { + if ($tokens[2][0] == $items[0]) { + $data = $tokens[2][1]; + } else { + // maybe the server send an other field we didn't wanted + $count = count($tokens[2]); + // we start with 2, because 0 was already checked + for ($i = 2; $i < $count; $i += 2) { + if ($tokens[2][$i] != $items[0]) { + continue; + } + $data = $tokens[2][$i + 1]; + break; + } + } + } else { + $data = array(); + while (key($tokens[2]) !== null) { + $data[current($tokens[2])] = next($tokens[2]); + next($tokens[2]); + } + } + // if we want only one message we can ignore everything else and just return + if ($to === null && !is_array($from) && $tokens[0] == $from) { + // we still need to read all lines + while (!$this->readLine($tokens, $tag)); + return $data; + } + $result[$tokens[0]] = $data; + } + + if ($to === null && !is_array($from)) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('the single id was not found in response'); + } + + return $result; + } + + /** + * get mailbox list + * + * this method can't be named after the IMAP command 'LIST', as list is a reserved keyword + * + * @param string $reference mailbox reference for list + * @param string $mailbox mailbox name match with wildcards + * @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..)) + * @throws Zend_Mail_Protocol_Exception + */ + public function listMailbox($reference = '', $mailbox = '*') + { + $result = array(); + $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox)); + if (!$list || $list === true) { + return $result; + } + + foreach ($list as $item) { + if (count($item) != 4 || $item[0] != 'LIST') { + continue; + } + $result[$item[3]] = array('delim' => $item[2], 'flags' => $item[1]); + } + + return $result; + } + + /** + * set flags + * + * @param array $flags flags to set, add or remove - see $mode + * @param int $from message for items or start message if $to !== null + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message avaible + * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given + * @param bool $silent if false the return values are the new flags for the wanted messages + * @return bool|array new flags if $silent is false, else true or false depending on success + * @throws Zend_Mail_Protocol_Exception + */ + public function store(array $flags, $from, $to = null, $mode = null, $silent = true) + { + $item = 'FLAGS'; + if ($mode == '+' || $mode == '-') { + $item = $mode . $item; + } + if ($silent) { + $item .= '.SILENT'; + } + + $flags = $this->escapeList($flags); + $set = (int)$from; + if ($to != null) { + $set .= ':' . ($to == INF ? '*' : (int)$to); + } + + $result = $this->requestAndResponse('STORE', array($set, $item, $flags), $silent); + + if ($silent) { + return $result ? true : false; + } + + $tokens = $result; + $result = array(); + foreach ($tokens as $token) { + if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') { + continue; + } + $result[$token[0]] = $token[2][1]; + } + + return $result; + } + + /** + * append a new message to given folder + * + * @param string $folder name of target folder + * @param string $message full message content + * @param array $flags flags for new message + * @param string $date date for new message + * @return bool success + * @throws Zend_Mail_Protocol_Exception + */ + public function append($folder, $message, $flags = null, $date = null) + { + $tokens = array(); + $tokens[] = $this->escapeString($folder); + if ($flags !== null) { + $tokens[] = $this->escapeList($flags); + } + if ($date !== null) { + $tokens[] = $this->escapeString($date); + } + $tokens[] = $this->escapeString($message); + + return $this->requestAndResponse('APPEND', $tokens, true); + } + + /** + * copy message set from current folder to other folder + * + * @param string $folder destination folder + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message avaible + * @return bool success + * @throws Zend_Mail_Protocol_Exception + */ + public function copy($folder, $from, $to = null) + { + $set = (int)$from; + if ($to != null) { + $set .= ':' . ($to == INF ? '*' : (int)$to); + } + + return $this->requestAndResponse('COPY', array($set, $this->escapeString($folder)), true); + } + + /** + * create a new folder (and parent folders if needed) + * + * @param string $folder folder name + * @return bool success + */ + public function create($folder) + { + return $this->requestAndResponse('CREATE', array($this->escapeString($folder)), true); + } + + /** + * rename an existing folder + * + * @param string $old old name + * @param string $new new name + * @return bool success + */ + public function rename($old, $new) + { + return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true); + } + + /** + * remove a folder + * + * @param string $folder folder name + * @return bool success + */ + public function delete($folder) + { + return $this->requestAndResponse('DELETE', array($this->escapeString($folder)), true); + } + + /** + * permanently remove messages + * + * @return bool success + */ + public function expunge() + { + // TODO: parse response? + return $this->requestAndResponse('EXPUNGE'); + } + + /** + * send noop + * + * @return bool success + */ + public function noop() + { + // TODO: parse response + return $this->requestAndResponse('NOOP'); + } + + /** + * do a search request + * + * This method is currently marked as internal as the API might change and is not + * safe if you don't take precautions. + * + * @internal + * @return array message ids + */ + public function search(array $params) + { + $response = $this->requestAndResponse('SEARCH', $params); + if (!$response) { + return $response; + } + + foreach ($response as $ids) { + if ($ids[0] == 'SEARCH') { + array_shift($ids); + return $ids; + } + } + return array(); + } + +} diff --git a/lib/Zend/Mail/Protocol/Pop3.php b/lib/Zend/Mail/Protocol/Pop3.php new file mode 100644 index 0000000..c1f968a --- /dev/null +++ b/lib/Zend/Mail/Protocol/Pop3.php @@ -0,0 +1,471 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Pop3.php 12539 2008-11-11 02:47:17Z yoshida@zend.co.jp $ + */ + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Protocol_Pop3 +{ + /** + * Default timeout in seconds for initiating session + */ + const TIMEOUT_CONNECTION = 30; + + /** + * saves if server supports top + * @var null|bool + */ + public $hasTop = null; + + /** + * socket to pop3 + * @var null|resource + */ + protected $_socket; + + /** + * greeting timestamp for apop + * @var null|string + */ + protected $_timestamp; + + + /** + * Public constructor + * + * @param string $host hostname of IP address of POP3 server, if given connect() is called + * @param int|null $port port of POP3 server, null for default (110 or 995 for ssl) + * @param bool|string $ssl use ssl? 'SSL', 'TLS' or false + * @throws Zend_Mail_Protocol_Exception + */ + public function __construct($host = '', $port = null, $ssl = false) + { + if ($host) { + $this->connect($host, $port, $ssl); + } + } + + + /** + * Public destructor + */ + public function __destruct() + { + $this->logout(); + } + + + /** + * Open connection to POP3 server + * + * @param string $host hostname of IP address of POP3 server + * @param int|null $port of POP3 server, default is 110 (995 for ssl) + * @param string|bool $ssl use 'SSL', 'TLS' or false + * @return string welcome message + * @throws Zend_Mail_Protocol_Exception + */ + public function connect($host, $port = null, $ssl = false) + { + if ($ssl == 'SSL') { + $host = 'ssl://' . $host; + } + + if ($port === null) { + $port = $ssl == 'SSL' ? 995 : 110; + } + + $errno = 0; + $errstr = ''; + $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION); + if (!$this->_socket) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('cannot connect to host : ' . $errno . ' : ' . $errstr); + } + + $welcome = $this->readResponse(); + + strtok($welcome, '<'); + $this->_timestamp = strtok('>'); + if (!strpos($this->_timestamp, '@')) { + $this->_timestamp = null; + } else { + $this->_timestamp = '<' . $this->_timestamp . '>'; + } + + if ($ssl === 'TLS') { + $this->request('STLS'); + $result = stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + if (!$result) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('cannot enable TLS'); + } + } + + return $welcome; + } + + + /** + * Send a request + * + * @param string $request your request without newline + * @return null + * @throws Zend_Mail_Protocol_Exception + */ + public function sendRequest($request) + { + $result = @fputs($this->_socket, $request . "\r\n"); + if (!$result) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('send failed - connection closed?'); + } + } + + + /** + * read a response + * + * @param boolean $multiline response has multiple lines and should be read until "<nl>.<nl>" + * @return string response + * @throws Zend_Mail_Protocol_Exception + */ + public function readResponse($multiline = false) + { + $result = @fgets($this->_socket); + if (!is_string($result)) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('read failed - connection closed?'); + } + + $result = trim($result); + if (strpos($result, ' ')) { + list($status, $message) = explode(' ', $result, 2); + } else { + $status = $result; + $message = ''; + } + + if ($status != '+OK') { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('last request failed'); + } + + if ($multiline) { + $message = ''; + $line = fgets($this->_socket); + while ($line && rtrim($line, "\r\n") != '.') { + if ($line[0] == '.') { + $line = substr($line, 1); + } + $message .= $line; + $line = fgets($this->_socket); + }; + } + + return $message; + } + + + /** + * Send request and get resposne + * + * @see sendRequest(), readResponse() + * + * @param string $request request + * @param bool $multiline multiline response? + * @return string result from readResponse() + * @throws Zend_Mail_Protocol_Exception + */ + public function request($request, $multiline = false) + { + $this->sendRequest($request); + return $this->readResponse($multiline); + } + + + /** + * End communication with POP3 server (also closes socket) + * + * @return null + */ + public function logout() + { + if (!$this->_socket) { + return; + } + + try { + $this->request('QUIT'); + } catch (Zend_Mail_Protocol_Exception $e) { + // ignore error - we're closing the socket anyway + } + + fclose($this->_socket); + $this->_socket = null; + } + + + /** + * Get capabilities from POP3 server + * + * @return array list of capabilities + * @throws Zend_Mail_Protocol_Exception + */ + public function capa() + { + $result = $this->request('CAPA', true); + return explode("\n", $result); + } + + + /** + * Login to POP3 server. Can use APOP + * + * @param string $user username + * @param string $password password + * @param bool $try_apop should APOP be tried? + * @return void + * @throws Zend_Mail_Protocol_Exception + */ + public function login($user, $password, $tryApop = true) + { + if ($tryApop && $this->_timestamp) { + try { + $this->request("APOP $user " . md5($this->_timestamp . $password)); + return; + } catch (Zend_Mail_Protocol_Exception $e) { + // ignore + } + } + + $result = $this->request("USER $user"); + $result = $this->request("PASS $password"); + } + + + /** + * Make STAT call for message count and size sum + * + * @param int $messages out parameter with count of messages + * @param int $octets out parameter with size in octects of messages + * @return void + * @throws Zend_Mail_Protocol_Exception + */ + public function status(&$messages, &$octets) + { + $messages = 0; + $octets = 0; + $result = $this->request('STAT'); + + list($messages, $octets) = explode(' ', $result); + } + + + /** + * Make LIST call for size of message(s) + * + * @param int|null $msgno number of message, null for all + * @return int|array size of given message or list with array(num => size) + * @throws Zend_Mail_Protocol_Exception + */ + public function getList($msgno = null) + { + if ($msgno !== null) { + $result = $this->request("LIST $msgno"); + + list(, $result) = explode(' ', $result); + return (int)$result; + } + + $result = $this->request('LIST', true); + $messages = array(); + $line = strtok($result, "\n"); + while ($line) { + list($no, $size) = explode(' ', trim($line)); + $messages[(int)$no] = (int)$size; + $line = strtok("\n"); + } + + return $messages; + } + + + /** + * Make UIDL call for getting a uniqueid + * + * @param int|null $msgno number of message, null for all + * @return string|array uniqueid of message or list with array(num => uniqueid) + * @throws Zend_Mail_Protocol_Exception + */ + public function uniqueid($msgno = null) + { + if ($msgno !== null) { + $result = $this->request("UIDL $msgno"); + + list(, $result) = explode(' ', $result); + return $result; + } + + $result = $this->request('UIDL', true); + + $result = explode("\n", $result); + $messages = array(); + foreach ($result as $line) { + if (!$line) { + continue; + } + list($no, $id) = explode(' ', trim($line), 2); + $messages[(int)$no] = $id; + } + + return $messages; + + } + + + /** + * Make TOP call for getting headers and maybe some body lines + * This method also sets hasTop - before it it's not known if top is supported + * + * The fallback makes normale RETR call, which retrieves the whole message. Additional + * lines are not removed. + * + * @param int $msgno number of message + * @param int $lines number of wanted body lines (empty line is inserted after header lines) + * @param bool $fallback fallback with full retrieve if top is not supported + * @return string message headers with wanted body lines + * @throws Zend_Mail_Protocol_Exception + */ + public function top($msgno, $lines = 0, $fallback = false) + { + if ($this->hasTop === false) { + if ($fallback) { + return $this->retrieve($msgno); + } else { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('top not supported and no fallback wanted'); + } + } + $this->hasTop = true; + + $lines = (!$lines || $lines < 1) ? 0 : (int)$lines; + + try { + $result = $this->request("TOP $msgno $lines", true); + } catch (Zend_Mail_Protocol_Exception $e) { + $this->hasTop = false; + if ($fallback) { + $result = $this->retrieve($msgno); + } else { + throw $e; + } + } + + return $result; + } + + + /** + * Make a RETR call for retrieving a full message with headers and body + * + * @deprecated since 1.1.0; this method has a typo - please use retrieve() + * @param int $msgno message number + * @return string message + * @throws Zend_Mail_Protocol_Exception + */ + public function retrive($msgno) + { + return $this->retrieve($msgno); + } + + + /** + * Make a RETR call for retrieving a full message with headers and body + * + * @param int $msgno message number + * @return string message + * @throws Zend_Mail_Protocol_Exception + */ + public function retrieve($msgno) + { + $result = $this->request("RETR $msgno", true); + return $result; + } + + /** + * Make a NOOP call, maybe needed for keeping the server happy + * + * @return null + * @throws Zend_Mail_Protocol_Exception + */ + public function noop() + { + $this->request('NOOP'); + } + + + /** + * Make a DELE count to remove a message + * + * @return null + * @throws Zend_Mail_Protocol_Exception + */ + public function delete($msgno) + { + $this->request("DELE $msgno"); + } + + + /** + * Make RSET call, which rollbacks delete requests + * + * @return null + * @throws Zend_Mail_Protocol_Exception + */ + public function undelete() + { + $this->request('RSET'); + } +} diff --git a/lib/Zend/Mail/Protocol/Smtp.php b/lib/Zend/Mail/Protocol/Smtp.php new file mode 100644 index 0000000..18ac77a --- /dev/null +++ b/lib/Zend/Mail/Protocol/Smtp.php @@ -0,0 +1,443 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Smtp.php 11196 2008-09-02 00:56:25Z yoshida@zend.co.jp $ + */ + + +/** + * @see Zend_Mime + */ +require_once 'Zend/Mime.php'; + + +/** + * @see Zend_Mail_Protocol_Abstract + */ +require_once 'Zend/Mail/Protocol/Abstract.php'; + + +/** + * Smtp implementation of Zend_Mail_Protocol_Abstract + * + * Minimum implementation according to RFC2821: EHLO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, QUIT + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Protocol_Smtp extends Zend_Mail_Protocol_Abstract +{ + /** + * The transport method for the socket + * + * @var string + */ + protected $_transport = 'tcp'; + + + /** + * Indicates that a session is requested to be secure + * + * @var string + */ + protected $_secure; + + + /** + * Indicates an smtp session has been started by the HELO command + * + * @var boolean + */ + protected $_sess = false; + + + /** + * Indicates the HELO command has been issues + * + * @var unknown_type + */ + protected $_helo = false; + + + /** + * Indicates an smtp AUTH has been issued and authenticated + * + * @var unknown_type + */ + protected $_auth = false; + + + /** + * Indicates a MAIL command has been issued + * + * @var unknown_type + */ + protected $_mail = false; + + + /** + * Indicates one or more RCTP commands have been issued + * + * @var unknown_type + */ + protected $_rcpt = false; + + + /** + * Indicates that DATA has been issued and sent + * + * @var unknown_type + */ + protected $_data = null; + + + /** + * Constructor. + * + * @param string $host + * @param integer $port + * @param array $config + * @return void + * @throws Zend_Mail_Protocol_Exception + */ + public function __construct($host = '127.0.0.1', $port = null, array $config = array()) + { + if (isset($config['ssl'])) { + switch (strtolower($config['ssl'])) { + case 'tls': + $this->_secure = 'tls'; + break; + + case 'ssl': + $this->_transport = 'ssl'; + $this->_secure = 'ssl'; + if ($port == null) { + $port = 465; + } + break; + + default: + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception($config['ssl'] . ' is unsupported SSL type'); + break; + } + } + + // If no port has been specified then check the master PHP ini file. Defaults to 25 if the ini setting is null. + if ($port == null) { + if (($port = ini_get('smtp_port')) == '') { + $port = 25; + } + } + + parent::__construct($host, $port); + } + + + /** + * Connect to the server with the parameters given in the constructor. + * + * @return boolean + */ + public function connect() + { + return $this->_connect($this->_transport . '://' . $this->_host . ':'. $this->_port); + } + + + /** + * Initiate HELO/EHLO sequence and set flag to indicate valid smtp session + * + * @param string $host The client hostname or IP address (default: 127.0.0.1) + * @throws Zend_Mail_Protocol_Exception + * @return void + */ + public function helo($host = '127.0.0.1') + { + // Respect RFC 2821 and disallow HELO attempts if session is already initiated. + if ($this->_sess === true) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('Cannot issue HELO to existing session'); + } + + // Validate client hostname + if (!$this->_validHost->isValid($host)) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception(join(', ', $this->_validHost->getMessages())); + } + + // Initiate helo sequence + $this->_expect(220, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + $this->_ehlo($host); + + // If a TLS session is required, commence negotiation + if ($this->_secure == 'tls') { + $this->_send('STARTTLS'); + $this->_expect(220, 180); + if (!stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('Unable to connect via TLS'); + } + $this->_ehlo($host); + } + + $this->_startSession(); + $this->auth(); + } + + + /** + * Send EHLO or HELO depending on capabilities of smtp host + * + * @param string $host The client hostname or IP address (default: 127.0.0.1) + * @throws Zend_Mail_Protocol_Exception + * @return void + */ + protected function _ehlo($host) + { + // Support for older, less-compliant remote servers. Tries multiple attempts of EHLO or HELO. + try { + $this->_send('EHLO ' . $host); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } catch (Zend_Mail_Protocol_Exception $e) { + $this->_send('HELO ' . $host); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } catch (Zend_Mail_Protocol_Exception $e) { + throw $e; + } + } + + + /** + * Issues MAIL command + * + * @param string $from Sender mailbox + * @throws Zend_Mail_Protocol_Exception + * @return void + */ + public function mail($from) + { + if ($this->_sess !== true) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('A valid session has not been started'); + } + + $this->_send('MAIL FROM:<' . $from . '>'); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + + // Set mail to true, clear recipients and any existing data flags as per 4.1.1.2 of RFC 2821 + $this->_mail = true; + $this->_rcpt = false; + $this->_data = false; + } + + + /** + * Issues RCPT command + * + * @param string $to Receiver(s) mailbox + * @throws Zend_Mail_Protocol_Exception + * @return void + */ + public function rcpt($to) + { + if ($this->_mail !== true) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('No sender reverse path has been supplied'); + } + + // Set rcpt to true, as per 4.1.1.3 of RFC 2821 + $this->_send('RCPT TO:<' . $to . '>'); + $this->_expect(array(250, 251), 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + $this->_rcpt = true; + } + + + /** + * Issues DATA command + * + * @param string $data + * @throws Zend_Mail_Protocol_Exception + * @return void + */ + public function data($data) + { + // Ensure recipients have been set + if ($this->_rcpt !== true) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('No recipient forward path has been supplied'); + } + + $this->_send('DATA'); + $this->_expect(354, 120); // Timeout set for 2 minutes as per RFC 2821 4.5.3.2 + + foreach (explode(Zend_Mime::LINEEND, $data) as $line) { + if (strpos($line, '.') === 0) { + // Escape lines prefixed with a '.' + $line = '.' . $line; + } + $this->_send($line); + } + + $this->_send('.'); + $this->_expect(250, 600); // Timeout set for 10 minutes as per RFC 2821 4.5.3.2 + $this->_data = true; + } + + + /** + * Issues the RSET command end validates answer + * + * Can be used to restore a clean smtp communication state when a transaction has been cancelled or commencing a new transaction. + * + * @return void + */ + public function rset() + { + $this->_send('RSET'); + // MS ESMTP doesn't follow RFC, see [ZF-1377] + $this->_expect(array(250, 220)); + + $this->_mail = false; + $this->_rcpt = false; + $this->_data = false; + } + + + /** + * Issues the NOOP command end validates answer + * + * Not used by Zend_Mail, could be used to keep a connection alive or check if it is still open. + * + * @return void + */ + public function noop() + { + $this->_send('NOOP'); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + + + /** + * Issues the VRFY command end validates answer + * + * Not used by Zend_Mail. + * + * @param string $user User Name or eMail to verify + * @return void + */ + public function vrfy($user) + { + $this->_send('VRFY ' . $user); + $this->_expect(array(250, 251, 252), 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + + + /** + * Issues the QUIT command and clears the current session + * + * @return void + */ + public function quit() + { + if ($this->_sess) { + $this->_send('QUIT'); + $this->_expect(221, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + $this->_stopSession(); + } + } + + + /** + * Default authentication method + * + * This default method is implemented by AUTH adapters to properly authenticate to a remote host. + * + * @throws Zend_Mail_Protocol_Exception + * @return void + */ + public function auth() + { + if ($this->_auth === true) { + /** + * @see Zend_Mail_Protocol_Exception + */ + require_once 'Zend/Mail/Protocol/Exception.php'; + throw new Zend_Mail_Protocol_Exception('Already authenticated for this session'); + } + } + + + /** + * Closes connection + * + * @return void + */ + public function disconnect() + { + $this->_disconnect(); + } + + + /** + * Start mail session + * + * @return void + */ + protected function _startSession() + { + $this->_sess = true; + } + + + /** + * Stop mail session + * + * @return void + */ + protected function _stopSession() + { + $this->_sess = false; + } +} diff --git a/lib/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php b/lib/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php new file mode 100644 index 0000000..337c7eb --- /dev/null +++ b/lib/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php @@ -0,0 +1,108 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Crammd5.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Mail_Protocol_Smtp + */ +require_once 'Zend/Mail/Protocol/Smtp.php'; + + +/** + * Performs CRAM-MD5 authentication + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Protocol_Smtp_Auth_Crammd5 extends Zend_Mail_Protocol_Smtp +{ + /** + * Constructor. + * + * @param string $host (Default: 127.0.0.1) + * @param int $port (Default: null) + * @param array $config Auth-specific parameters + * @return void + */ + public function __construct($host = '127.0.0.1', $port = null, $config = null) + { + if (is_array($config)) { + if (isset($config['username'])) { + $this->_username = $config['username']; + } + if (isset($config['password'])) { + $this->_password = $config['password']; + } + } + + parent::__construct($host, $port, $config); + } + + + /** + * @todo Perform CRAM-MD5 authentication with supplied credentials + * + * @return void + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH CRAM-MD5'); + $challenge = $this->_expect(334); + $challenge = base64_decode($challenge); + $digest = $this->_hmacMd5($this->_password, $challenge); + $this->_send(base64_encode($this->_username . ' ' . $digest)); + $this->_expect(235); + $this->_auth = true; + } + + + /** + * Prepare CRAM-MD5 response to server's ticket + * + * @param string $key Challenge key (usually password) + * @param string $data Challenge data + * @param string $block Length of blocks + * @return string + */ + protected function _hmacMd5($key, $data, $block = 64) + { + if (strlen($key) > 64) { + $key = pack('H32', md5($key)); + } elseif (strlen($key) < 64) { + $key = str_pad($key, $block, chr(0)); + } + + $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64); + $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64); + + $inner = pack('H32', md5($k_ipad . $data)); + $digest = md5($k_opad . $inner); + + return $digest; + } +} diff --git a/lib/Zend/Mail/Protocol/Smtp/Auth/Login.php b/lib/Zend/Mail/Protocol/Smtp/Auth/Login.php new file mode 100644 index 0000000..6332207 --- /dev/null +++ b/lib/Zend/Mail/Protocol/Smtp/Auth/Login.php @@ -0,0 +1,98 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Login.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Mail_Protocol_Smtp + */ +require_once 'Zend/Mail/Protocol/Smtp.php'; + + +/** + * Performs LOGIN authentication + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Protocol_Smtp_Auth_Login extends Zend_Mail_Protocol_Smtp +{ + /** + * LOGIN username + * + * @var string + */ + protected $_username; + + + /** + * LOGIN password + * + * @var string + */ + protected $_password; + + + /** + * Constructor. + * + * @param string $host (Default: 127.0.0.1) + * @param int $port (Default: null) + * @param array $config Auth-specific parameters + * @return void + */ + public function __construct($host = '127.0.0.1', $port = null, $config = null) + { + if (is_array($config)) { + if (isset($config['username'])) { + $this->_username = $config['username']; + } + if (isset($config['password'])) { + $this->_password = $config['password']; + } + } + + parent::__construct($host, $port, $config); + } + + + /** + * Perform LOGIN authentication with supplied credentials + * + * @return void + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH LOGIN'); + $this->_expect(334); + $this->_send(base64_encode($this->_username)); + $this->_expect(334); + $this->_send(base64_encode($this->_password)); + $this->_expect(235); + $this->_auth = true; + } +} diff --git a/lib/Zend/Mail/Protocol/Smtp/Auth/Plain.php b/lib/Zend/Mail/Protocol/Smtp/Auth/Plain.php new file mode 100644 index 0000000..cb8d019 --- /dev/null +++ b/lib/Zend/Mail/Protocol/Smtp/Auth/Plain.php @@ -0,0 +1,96 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Plain.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Mail_Protocol_Smtp + */ +require_once 'Zend/Mail/Protocol/Smtp.php'; + + +/** + * Performs PLAIN authentication + * + * @category Zend + * @package Zend_Mail + * @subpackage Protocol + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Protocol_Smtp_Auth_Plain extends Zend_Mail_Protocol_Smtp +{ + /** + * PLAIN username + * + * @var string + */ + protected $_username; + + + /** + * PLAIN password + * + * @var string + */ + protected $_password; + + + /** + * Constructor. + * + * @param string $host (Default: 127.0.0.1) + * @param int $port (Default: null) + * @param array $config Auth-specific parameters + * @return void + */ + public function __construct($host = '127.0.0.1', $port = null, $config = null) + { + if (is_array($config)) { + if (isset($config['username'])) { + $this->_username = $config['username']; + } + if (isset($config['password'])) { + $this->_password = $config['password']; + } + } + + parent::__construct($host, $port, $config); + } + + + /** + * Perform PLAIN authentication with supplied credentials + * + * @return void + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH PLAIN'); + $this->_expect(334); + $this->_send(base64_encode(chr(0) . $this->_username . chr(0) . $this->_password)); + $this->_expect(235); + $this->_auth = true; + } +} diff --git a/lib/Zend/Mail/Storage.php b/lib/Zend/Mail/Storage.php new file mode 100644 index 0000000..227f496 --- /dev/null +++ b/lib/Zend/Mail/Storage.php @@ -0,0 +1,39 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Storage.php 9099 2008-03-30 19:35:47Z thomas $ + */ + +/** + * @category Zend + * @package Zend_Mail + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage +{ + // maildir and IMAP flags, using IMAP names, where possible to be able to distinguish between IMAP + // system flags and other flags + const FLAG_PASSED = 'Passed'; + const FLAG_SEEN = '\Seen'; + const FLAG_ANSWERED = '\Answered'; + const FLAG_FLAGGED = '\Flagged'; + const FLAG_DELETED = '\Deleted'; + const FLAG_DRAFT = '\Draft'; + const FLAG_RECENT = '\Recent'; +} diff --git a/lib/Zend/Mail/Storage/Abstract.php b/lib/Zend/Mail/Storage/Abstract.php new file mode 100644 index 0000000..9e84d93 --- /dev/null +++ b/lib/Zend/Mail/Storage/Abstract.php @@ -0,0 +1,366 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Abstract.php 9099 2008-03-30 19:35:47Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +abstract class Zend_Mail_Storage_Abstract implements Countable, ArrayAccess, SeekableIterator +{ + /** + * class capabilities with default values + * @var array + */ + protected $_has = array('uniqueid' => true, + 'delete' => false, + 'create' => false, + 'top' => false, + 'fetchPart' => true, + 'flags' => false); + + /** + * current iteration position + * @var int + */ + protected $_iterationPos = 0; + + /** + * maximum iteration position (= message count) + * @var null|int + */ + protected $_iterationMax = null; + + /** + * used message class, change it in an extened class to extend the returned message class + * @var string + */ + protected $_messageClass = 'Zend_Mail_Message'; + + /** + * Getter for has-properties. The standard has properties + * are: hasFolder, hasUniqueid, hasDelete, hasCreate, hasTop + * + * The valid values for the has-properties are: + * - true if a feature is supported + * - false if a feature is not supported + * - null is it's not yet known or it can't be know if a feature is supported + * + * @param string $var property name + * @return bool supported or not + * @throws Zend_Mail_Storage_Exception + */ + public function __get($var) + { + if (strpos($var, 'has') === 0) { + $var = strtolower(substr($var, 3)); + return isset($this->_has[$var]) ? $this->_has[$var] : null; + } + + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception($var . ' not found'); + } + + + /** + * Get a full list of features supported by the specific mail lib and the server + * + * @return array list of features as array(featurename => true|false[|null]) + */ + public function getCapabilities() + { + return $this->_has; + } + + + /** + * Count messages messages in current box/folder + * + * @return int number of messages + * @throws Zend_Mail_Storage_Exception + */ + abstract public function countMessages(); + + + /** + * Get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as array(num => size) + */ + abstract public function getSize($id = 0); + + + /** + * Get a message with headers and body + * + * @param $id int number of message + * @return Zend_Mail_Message + */ + abstract public function getMessage($id); + + + /** + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + */ + abstract public function getRawHeader($id, $part = null, $topLines = 0); + + /** + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage content + * @return string raw content + */ + abstract public function getRawContent($id, $part = null); + + /** + * Create instance with parameters + * + * @param array $params mail reader specific parameters + * @throws Zend_Mail_Storage_Exception + */ + abstract public function __construct($params); + + + /** + * Destructor calls close() and therefore closes the resource. + */ + public function __destruct() + { + $this->close(); + } + + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + * @return null + */ + abstract public function close(); + + + /** + * Keep the resource alive. + * + * @return null + */ + abstract public function noop(); + + /** + * delete a message from current box/folder + * + * @return null + */ + abstract public function removeMessage($id); + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Zend_Mail_Storage_Exception + */ + abstract public function getUniqueId($id = null); + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws Zend_Mail_Storage_Exception + */ + abstract public function getNumberByUniqueId($id); + + // interface implementations follows + + /** + * Countable::count() + * + * @return int + */ + public function count() + { + return $this->countMessages(); + } + + + /** + * ArrayAccess::offsetExists() + * + * @param int $id + * @return boolean + */ + public function offsetExists($id) + { + try { + if ($this->getMessage($id)) { + return true; + } + } catch(Zend_Mail_Storage_Exception $e) {} + + return false; + } + + + /** + * ArrayAccess::offsetGet() + * + * @param int $id + * @return Zend_Mail_Message message object + */ + public function offsetGet($id) + { + return $this->getMessage($id); + } + + + /** + * ArrayAccess::offsetSet() + * + * @param id $id + * @param mixed $value + * @throws Zend_Mail_Storage_Exception + * @return void + */ + public function offsetSet($id, $value) + { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot write mail messages via array access'); + } + + + /** + * ArrayAccess::offsetUnset() + * + * @param int $id + * @return boolean success + */ + public function offsetUnset($id) + { + return $this->removeMessage($id); + } + + + /** + * Iterator::rewind() + * + * Rewind always gets the new count from the storage. Thus if you use + * the interfaces and your scripts take long you should use reset() + * from time to time. + * + * @return void + */ + public function rewind() + { + $this->_iterationMax = $this->countMessages(); + $this->_iterationPos = 1; + } + + + /** + * Iterator::current() + * + * @return Zend_Mail_Message current message + */ + public function current() + { + return $this->getMessage($this->_iterationPos); + } + + + /** + * Iterator::key() + * + * @return int id of current position + */ + public function key() + { + return $this->_iterationPos; + } + + + /** + * Iterator::next() + * + * @return void + */ + public function next() + { + ++$this->_iterationPos; + } + + + /** + * Iterator::valid() + * + * @return boolean + */ + public function valid() + { + if ($this->_iterationMax === null) { + $this->_iterationMax = $this->countMessages(); + } + return $this->_iterationPos && $this->_iterationPos <= $this->_iterationMax; + } + + + /** + * SeekableIterator::seek() + * + * @param int $pos + * @return void + * @throws OutOfBoundsException + */ + public function seek($pos) + { + if ($this->_iterationMax === null) { + $this->_iterationMax = $this->countMessages(); + } + + if ($pos > $this->_iterationMax) { + throw new OutOfBoundsException('this position does not exist'); + } + $this->_iterationPos = $pos; + } + +} diff --git a/lib/Zend/Mail/Storage/Exception.php b/lib/Zend/Mail/Storage/Exception.php new file mode 100644 index 0000000..1aea4f9 --- /dev/null +++ b/lib/Zend/Mail/Storage/Exception.php @@ -0,0 +1,39 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Exception.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Mail_Exception + */ +require_once 'Zend/Mail/Exception.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Exception extends Zend_Mail_Exception +{} + diff --git a/lib/Zend/Mail/Storage/Folder.php b/lib/Zend/Mail/Storage/Folder.php new file mode 100644 index 0000000..6ee9abe --- /dev/null +++ b/lib/Zend/Mail/Storage/Folder.php @@ -0,0 +1,236 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Folder.php 9099 2008-03-30 19:35:47Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Folder implements RecursiveIterator +{ + /** + * subfolders of folder array(localName => Zend_Mail_Storage_Folder folder) + * @var array + */ + protected $_folders; + + /** + * local name (name of folder in parent folder) + * @var string + */ + protected $_localName; + + /** + * global name (absolute name of folder) + * @var string + */ + protected $_globalName; + + /** + * folder is selectable if folder is able to hold messages, else it's just a parent folder + * @var bool + */ + protected $_selectable = true; + + /** + * create a new mail folder instance + * + * @param string $localName name of folder in current subdirectory + * @param string $globalName absolute name of folder + * @param bool $selectable if true folder holds messages, if false it's just a parent for subfolders + * @param array $folders init with given instances of Zend_Mail_Storage_Folder as subfolders + */ + public function __construct($localName, $globalName = '', $selectable = true, array $folders = array()) + { + $this->_localName = $localName; + $this->_globalName = $globalName ? $globalName : $localName; + $this->_selectable = $selectable; + $this->_folders = $folders; + } + + /** + * implements RecursiveIterator::hasChildren() + * + * @return bool current element has children + */ + public function hasChildren() + { + $current = $this->current(); + return $current && $current instanceof Zend_Mail_Storage_Folder && !$current->isLeaf(); + } + + /** + * implements RecursiveIterator::getChildren() + * + * @return Zend_Mail_Storage_Folder same as self::current() + */ + public function getChildren() + { + return $this->current(); + } + + /** + * implements Iterator::valid() + * + * @return bool check if there's a current element + */ + public function valid() + { + return key($this->_folders) !== null; + } + + /** + * implements Iterator::next() + * + * @return null + */ + public function next() + { + next($this->_folders); + } + + /** + * implements Iterator::key() + * + * @return string key/local name of current element + */ + public function key() + { + return key($this->_folders); + } + + /** + * implements Iterator::current() + * + * @return Zend_Mail_Storage_Folder current folder + */ + public function current() + { + return current($this->_folders); + } + + /** + * implements Iterator::rewind() + * + * @return null + */ + public function rewind() + { + reset($this->_folders); + } + + /** + * get subfolder named $name + * + * @param string $name wanted subfolder + * @return Zend_Mail_Storage_Folder folder named $folder + * @throws Zend_Mail_Storage_Exception + */ + public function __get($name) + { + if (!isset($this->_folders[$name])) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("no subfolder named $name"); + } + + return $this->_folders[$name]; + } + + /** + * add or replace subfolder named $name + * + * @param string $name local name of subfolder + * @param Zend_Mail_Storage_Folder $folder instance for new subfolder + * @return null + */ + public function __set($name, Zend_Mail_Storage_Folder $folder) + { + $this->_folders[$name] = $folder; + } + + /** + * remove subfolder named $name + * + * @param string $name local name of subfolder + * @return null + */ + public function __unset($name) + { + unset($this->_folders[$name]); + } + + /** + * magic method for easy output of global name + * + * @return string global name of folder + */ + public function __toString() + { + return (string)$this->getGlobalName(); + } + + /** + * get local name + * + * @return string local name + */ + public function getLocalName() + { + return $this->_localName; + } + + /** + * get global name + * + * @return string global name + */ + public function getGlobalName() + { + return $this->_globalName; + } + + /** + * is this folder selectable? + * + * @return bool selectable + */ + public function isSelectable() + { + return $this->_selectable; + } + + /** + * check if folder has no subfolder + * + * @return bool true if no subfolders + */ + public function isLeaf() + { + return empty($this->_folders); + } +} diff --git a/lib/Zend/Mail/Storage/Folder/Interface.php b/lib/Zend/Mail/Storage/Folder/Interface.php new file mode 100644 index 0000000..cb148b7 --- /dev/null +++ b/lib/Zend/Mail/Storage/Folder/Interface.php @@ -0,0 +1,60 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Interface.php 9098 2008-03-30 19:29:10Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +interface Zend_Mail_Storage_Folder_Interface +{ + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @return Zend_Mail_Storage_Folder root or wanted folder + */ + public function getFolders($rootFolder = null); + + /** + * select given folder + * + * folder must be selectable! + * + * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function selectFolder($globalName); + + + /** + * get Zend_Mail_Storage_Folder instance for current folder + * + * @return Zend_Mail_Storage_Folder instance of current folder + * @throws Zend_Mail_Storage_Exception + */ + public function getCurrentFolder(); +} diff --git a/lib/Zend/Mail/Storage/Folder/Maildir.php b/lib/Zend/Mail/Storage/Folder/Maildir.php new file mode 100644 index 0000000..30b31aa --- /dev/null +++ b/lib/Zend/Mail/Storage/Folder/Maildir.php @@ -0,0 +1,265 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Maildir.php 9098 2008-03-30 19:29:10Z thomas $ + */ + + +/** + * @see Zend_Mail_Storage_Folder + */ +require_once 'Zend/Mail/Storage/Folder.php'; + +/** + * @see Zend_Mail_Storage_Folder_Interface + */ +require_once 'Zend/Mail/Storage/Folder/Interface.php'; + +/** + * @see Zend_Mail_Storage_Maildir + */ +require_once 'Zend/Mail/Storage/Maildir.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Folder_Maildir extends Zend_Mail_Storage_Maildir implements Zend_Mail_Storage_Folder_Interface +{ + /** + * Zend_Mail_Storage_Folder root folder for folder structure + * @var Zend_Mail_Storage_Folder + */ + protected $_rootFolder; + + /** + * rootdir of folder structure + * @var string + */ + protected $_rootdir; + + /** + * name of current folder + * @var string + */ + protected $_currentFolder; + + /** + * delim char for subfolders + * @var string + */ + protected $_delim; + + /** + * Create instance with parameters + * Supported parameters are: + * - dirname rootdir of maildir structure + * - delim delim char for folder structur, default is '.' + * - folder intial selected folder, default is 'INBOX' + * + * @param $params array mail reader specific parameters + * @throws Zend_Mail_Storage_Exception + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object)$params; + } + + if (!isset($params->dirname) || !is_dir($params->dirname)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('no valid dirname given in params'); + } + + $this->_rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + $this->_delim = isset($params->delim) ? $params->delim : '.'; + + $this->_buildFolderTree(); + $this->selectFolder(!empty($params->folder) ? $params->folder : 'INBOX'); + $this->_has['top'] = true; + $this->_has['flags'] = true; + } + + /** + * find all subfolders and mbox files for folder structure + * + * Result is save in Zend_Mail_Storage_Folder instances with the root in $this->_rootFolder. + * $parentFolder and $parentGlobalName are only used internally for recursion. + * + * @return null + * @throws Zend_Mail_Storage_Exception + */ + protected function _buildFolderTree() + { + $this->_rootFolder = new Zend_Mail_Storage_Folder('/', '/', false); + $this->_rootFolder->INBOX = new Zend_Mail_Storage_Folder('INBOX', 'INBOX', true); + + $dh = @opendir($this->_rootdir); + if (!$dh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("can't read folders in maildir"); + } + $dirs = array(); + while (($entry = readdir($dh)) !== false) { + // maildir++ defines folders must start with . + if ($entry[0] != '.' || $entry == '.' || $entry == '..') { + continue; + } + if ($this->_isMaildir($this->_rootdir . $entry)) { + $dirs[] = $entry; + } + } + closedir($dh); + + sort($dirs); + $stack = array(null); + $folderStack = array(null); + $parentFolder = $this->_rootFolder; + $parent = '.'; + + foreach ($dirs as $dir) { + do { + if (strpos($dir, $parent) === 0) { + $local = substr($dir, strlen($parent)); + if (strpos($local, $this->_delim) !== false) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('error while reading maildir'); + } + array_push($stack, $parent); + $parent = $dir . $this->_delim; + $folder = new Zend_Mail_Storage_Folder($local, substr($dir, 1), true); + $parentFolder->$local = $folder; + array_push($folderStack, $parentFolder); + $parentFolder = $folder; + break; + } else if ($stack) { + $parent = array_pop($stack); + $parentFolder = array_pop($folderStack); + } + } while ($stack); + if (!$stack) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('error while reading maildir'); + } + } + } + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @return Zend_Mail_Storage_Folder root or wanted folder + * @throws Zend_Mail_Storage_Exception + */ + public function getFolders($rootFolder = null) + { + if (!$rootFolder || $rootFolder == 'INBOX') { + return $this->_rootFolder; + } + + // rootdir is same as INBOX in maildir + if (strpos($rootFolder, 'INBOX' . $this->_delim) === 0) { + $rootFolder = substr($rootFolder, 6); + } + $currentFolder = $this->_rootFolder; + $subname = trim($rootFolder, $this->_delim); + while ($currentFolder) { + @list($entry, $subname) = @explode($this->_delim, $subname, 2); + $currentFolder = $currentFolder->$entry; + if (!$subname) { + break; + } + } + + if ($currentFolder->getGlobalName() != rtrim($rootFolder, $this->_delim)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("folder $rootFolder not found"); + } + return $currentFolder; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function selectFolder($globalName) + { + $this->_currentFolder = (string)$globalName; + + // getting folder from folder tree for validation + $folder = $this->getFolders($this->_currentFolder); + + try { + $this->_openMaildir($this->_rootdir . '.' . $folder->getGlobalName()); + } catch(Zend_Mail_Storage_Exception $e) { + // check what went wrong + if (!$folder->isSelectable()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("{$this->_currentFolder} is not selectable"); + } + // seems like file has vanished; rebuilding folder tree - but it's still an exception + $this->_buildFolderTree($this->_rootdir); + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('seems like the maildir has vanished, I\'ve rebuild the ' . + 'folder tree, search for an other folder and try again'); + } + } + + /** + * get Zend_Mail_Storage_Folder instance for current folder + * + * @return Zend_Mail_Storage_Folder instance of current folder + * @throws Zend_Mail_Storage_Exception + */ + public function getCurrentFolder() + { + return $this->_currentFolder; + } +} diff --git a/lib/Zend/Mail/Storage/Folder/Mbox.php b/lib/Zend/Mail/Storage/Folder/Mbox.php new file mode 100644 index 0000000..797148d --- /dev/null +++ b/lib/Zend/Mail/Storage/Folder/Mbox.php @@ -0,0 +1,264 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Mbox.php 9098 2008-03-30 19:29:10Z thomas $ + */ + + +/** + * @see Zend_Mail_Storage_Folder + */ +require_once 'Zend/Mail/Storage/Folder.php'; + +/** + * @see Zend_Mail_Storage_Folder_Interface + */ +require_once 'Zend/Mail/Storage/Folder/Interface.php'; + +/** + * @see Zend_Mail_Storage_Mbox + */ +require_once 'Zend/Mail/Storage/Mbox.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Folder_Mbox extends Zend_Mail_Storage_Mbox implements Zend_Mail_Storage_Folder_Interface +{ + /** + * Zend_Mail_Storage_Folder root folder for folder structure + * @var Zend_Mail_Storage_Folder + */ + protected $_rootFolder; + + /** + * rootdir of folder structure + * @var string + */ + protected $_rootdir; + + /** + * name of current folder + * @var string + */ + protected $_currentFolder; + + /** + * Create instance with parameters + * + * Disallowed parameters are: + * - filename use Zend_Mail_Storage_Mbox for a single file + * Supported parameters are: + * - dirname rootdir of mbox structure + * - folder intial selected folder, default is 'INBOX' + * + * @param $params array mail reader specific parameters + * @throws Zend_Mail_Storage_Exception + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object)$params; + } + + if (isset($params->filename)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('use Zend_Mail_Storage_Mbox for a single file'); + } + + if (!isset($params->dirname) || !is_dir($params->dirname)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('no valid dirname given in params'); + } + + $this->_rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + $this->_buildFolderTree($this->_rootdir); + $this->selectFolder(!empty($params->folder) ? $params->folder : 'INBOX'); + $this->_has['top'] = true; + $this->_has['uniqueid'] = false; + } + + /** + * find all subfolders and mbox files for folder structure + * + * Result is save in Zend_Mail_Storage_Folder instances with the root in $this->_rootFolder. + * $parentFolder and $parentGlobalName are only used internally for recursion. + * + * @param string $currentDir call with root dir, also used for recursion. + * @param Zend_Mail_Storage_Folder|null $parentFolder used for recursion + * @param string $parentGlobalName used for rescursion + * @return null + * @throws Zend_Mail_Storage_Exception + */ + protected function _buildFolderTree($currentDir, $parentFolder = null, $parentGlobalName = '') + { + if (!$parentFolder) { + $this->_rootFolder = new Zend_Mail_Storage_Folder('/', '/', false); + $parentFolder = $this->_rootFolder; + } + + $dh = @opendir($currentDir); + if (!$dh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("can't read dir $currentDir"); + } + while (($entry = readdir($dh)) !== false) { + // ignore hidden files for mbox + if ($entry[0] == '.') { + continue; + } + $absoluteEntry = $currentDir . $entry; + $globalName = $parentGlobalName . DIRECTORY_SEPARATOR . $entry; + if (is_file($absoluteEntry) && $this->_isMboxFile($absoluteEntry)) { + $parentFolder->$entry = new Zend_Mail_Storage_Folder($entry, $globalName); + continue; + } + if (!is_dir($absoluteEntry) /* || $entry == '.' || $entry == '..' */) { + continue; + } + $folder = new Zend_Mail_Storage_Folder($entry, $globalName, false); + $parentFolder->$entry = $folder; + $this->_buildFolderTree($absoluteEntry . DIRECTORY_SEPARATOR, $folder, $globalName); + } + + closedir($dh); + } + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @return Zend_Mail_Storage_Folder root or wanted folder + * @throws Zend_Mail_Storage_Exception + */ + public function getFolders($rootFolder = null) + { + if (!$rootFolder) { + return $this->_rootFolder; + } + + $currentFolder = $this->_rootFolder; + $subname = trim($rootFolder, DIRECTORY_SEPARATOR); + while ($currentFolder) { + @list($entry, $subname) = @explode(DIRECTORY_SEPARATOR, $subname, 2); + $currentFolder = $currentFolder->$entry; + if (!$subname) { + break; + } + } + + if ($currentFolder->getGlobalName() != DIRECTORY_SEPARATOR . trim($rootFolder, DIRECTORY_SEPARATOR)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("folder $rootFolder not found"); + } + return $currentFolder; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function selectFolder($globalName) + { + $this->_currentFolder = (string)$globalName; + + // getting folder from folder tree for validation + $folder = $this->getFolders($this->_currentFolder); + + try { + $this->_openMboxFile($this->_rootdir . $folder->getGlobalName()); + } catch(Zend_Mail_Storage_Exception $e) { + // check what went wrong + if (!$folder->isSelectable()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("{$this->_currentFolder} is not selectable"); + } + // seems like file has vanished; rebuilding folder tree - but it's still an exception + $this->_buildFolderTree($this->_rootdir); + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('seems like the mbox file has vanished, I\'ve rebuild the ' . + 'folder tree, search for an other folder and try again'); + } + } + + /** + * get Zend_Mail_Storage_Folder instance for current folder + * + * @return Zend_Mail_Storage_Folder instance of current folder + * @throws Zend_Mail_Storage_Exception + */ + public function getCurrentFolder() + { + return $this->_currentFolder; + } + + /** + * magic method for serialize() + * + * with this method you can cache the mbox class + * + * @return array name of variables + */ + public function __sleep() + { + return array_merge(parent::__sleep(), array('_currentFolder', '_rootFolder', '_rootdir')); + } + + /** + * magic method for unserialize() + * + * with this method you can cache the mbox class + * + * @return null + */ + public function __wakeup() + { + // if cache is stall selectFolder() rebuilds the tree on error + parent::__wakeup(); + } +} diff --git a/lib/Zend/Mail/Storage/Imap.php b/lib/Zend/Mail/Storage/Imap.php new file mode 100644 index 0000000..f4051f9 --- /dev/null +++ b/lib/Zend/Mail/Storage/Imap.php @@ -0,0 +1,644 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Imap.php 12519 2008-11-10 18:41:24Z alexander $ + */ + + +/** + * @see Zend_Mail_Storage_Abstract + */ +require_once 'Zend/Mail/Storage/Abstract.php'; + +/** + * @see Zend_Mail_Protocol_Imap + */ +require_once 'Zend/Mail/Protocol/Imap.php'; + +/** + * @see Zend_Mail_Storage_Writable_Interface + */ +require_once 'Zend/Mail/Storage/Writable/Interface.php'; + +/** + * @see Zend_Mail_Storage_Folder_Interface + */ +require_once 'Zend/Mail/Storage/Folder/Interface.php'; + +/** + * @see Zend_Mail_Storage_Folder + */ +require_once 'Zend/Mail/Storage/Folder.php'; + +/** + * @see Zend_Mail_Message + */ +require_once 'Zend/Mail/Message.php'; + +/** + * @see Zend_Mail_Storage + */ +require_once 'Zend/Mail/Storage.php'; + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Imap extends Zend_Mail_Storage_Abstract + implements Zend_Mail_Storage_Folder_Interface, Zend_Mail_Storage_Writable_Interface +{ + // TODO: with an internal cache we could optimize this class, or create an extra class with + // such optimizations. Especially the various fetch calls could be combined to one cache call + + /** + * protocol handler + * @var null|Zend_Mail_Protocol_Imap + */ + protected $_protocol; + + /** + * name of current folder + * @var string + */ + protected $_currentFolder = ''; + + /** + * imap flags to constants translation + * @var array + */ + protected static $_knownFlags = array('\Passed' => Zend_Mail_Storage::FLAG_PASSED, + '\Answered' => Zend_Mail_Storage::FLAG_ANSWERED, + '\Seen' => Zend_Mail_Storage::FLAG_SEEN, + '\Deleted' => Zend_Mail_Storage::FLAG_DELETED, + '\Draft' => Zend_Mail_Storage::FLAG_DRAFT, + '\Flagged' => Zend_Mail_Storage::FLAG_FLAGGED); + + /** + * map flags to search criterias + * @var array + */ + protected static $_searchFlags = array('\Recent' => 'RECENT', + '\Answered' => 'ANSWERED', + '\Seen' => 'SEEN', + '\Deleted' => 'DELETED', + '\Draft' => 'DRAFT', + '\Flagged' => 'FLAGGED'); + + /** + * Count messages all messages in current box + * + * @return int number of messages + * @throws Zend_Mail_Storage_Exception + * @throws Zend_Mail_Protocol_Exception + */ + public function countMessages($flags = null) + { + if (!$this->_currentFolder) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('No selected folder to count'); + } + + if ($flags === null) { + return count($this->_protocol->search(array('ALL'))); + } + + $params = array(); + foreach ((array)$flags as $flag) { + if (isset(self::$_searchFlags[$flag])) { + $params[] = self::$_searchFlags[$flag]; + } else { + $params[] = 'KEYWORD'; + $params[] = $this->_protocol->escapeString($flag); + } + } + return count($this->_protocol->search($params)); + } + + /** + * get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as array(num => size) + * @throws Zend_Mail_Protocol_Exception + */ + public function getSize($id = 0) + { + if ($id) { + return $this->_protocol->fetch('RFC822.SIZE', $id); + } + return $this->_protocol->fetch('RFC822.SIZE', 1, INF); + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return Zend_Mail_Message + * @throws Zend_Mail_Protocol_Exception + */ + public function getMessage($id) + { + $data = $this->_protocol->fetch(array('FLAGS', 'RFC822.HEADER'), $id); + $header = $data['RFC822.HEADER']; + + $flags = array(); + foreach ($data['FLAGS'] as $flag) { + $flags[] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag; + } + + return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $header, 'flags' => $flags)); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage header + * @param int $topLines include this many lines with header (after an empty line) + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws Zend_Mail_Protocol_Exception + * @throws Zend_Mail_Storage_Exception + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('not implemented'); + } + + // TODO: toplines + return $this->_protocol->fetch('RFC822.HEADER', $id); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage content + * @return string raw content + * @throws Zend_Mail_Protocol_Exception + * @throws Zend_Mail_Storage_Exception + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('not implemented'); + } + + return $this->_protocol->fetch('RFC822.TEXT', $id); + } + + /** + * create instance with parameters + * Supported paramters are + * - user username + * - host hostname or ip address of IMAP server [optional, default = 'localhost'] + * - password password for user 'username' [optional, default = ''] + * - port port for IMAP server [optional, default = 110] + * - ssl 'SSL' or 'TLS' for secure sockets + * - folder select this folder [optional, default = 'INBOX'] + * + * @param array $params mail reader specific parameters + * @throws Zend_Mail_Storage_Exception + * @throws Zend_Mail_Protocol_Exception + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object)$params; + } + + $this->_has['flags'] = true; + + if ($params instanceof Zend_Mail_Protocol_Imap) { + $this->_protocol = $params; + try { + $this->selectFolder('INBOX'); + } catch(Zend_Mail_Storage_Exception $e) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot select INBOX, is this a valid transport?'); + } + return; + } + + if (!isset($params->user)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('need at least user in params'); + } + + $host = isset($params->host) ? $params->host : 'localhost'; + $password = isset($params->password) ? $params->password : ''; + $port = isset($params->port) ? $params->port : null; + $ssl = isset($params->ssl) ? $params->ssl : false; + + $this->_protocol = new Zend_Mail_Protocol_Imap(); + $this->_protocol->connect($host, $port, $ssl); + if (!$this->_protocol->login($params->user, $password)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot login, user or password wrong'); + } + $this->selectFolder(isset($params->folder) ? $params->folder : 'INBOX'); + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + * @return null + */ + public function close() + { + $this->_currentFolder = ''; + $this->_protocol->logout(); + } + + /** + * Keep the server busy. + * + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function noop() + { + if (!$this->_protocol->noop()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('could not do nothing'); + } + } + + /** + * Remove a message from server. If you're doing that from a web enviroment + * you should be careful and use a uniqueid as parameter if possible to + * identify the message. + * + * @param int $id number of message + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function removeMessage($id) + { + if (!$this->_protocol->store(array(Zend_Mail_Storage::FLAG_DELETED), $id, null, '+')) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot set deleted flag'); + } + // TODO: expunge here or at close? we can handle an error here better and are more fail safe + if (!$this->_protocol->expunge()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('message marked as deleted, but could not expunge'); + } + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Zend_Mail_Storage_Exception + */ + public function getUniqueId($id = null) + { + if ($id) { + return $this->_protocol->fetch('UID', $id); + } + + return $this->_protocol->fetch('UID', 1, INF); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws Zend_Mail_Storage_Exception + */ + public function getNumberByUniqueId($id) + { + // TODO: use search to find number directly + $ids = $this->getUniqueId(); + foreach ($ids as $k => $v) { + if ($v == $id) { + return $k; + } + } + + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('unique id not found'); + } + + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @return Zend_Mail_Storage_Folder root or wanted folder + * @throws Zend_Mail_Storage_Exception + * @throws Zend_Mail_Protocol_Exception + */ + public function getFolders($rootFolder = null) + { + $folders = $this->_protocol->listMailbox((string)$rootFolder); + if (!$folders) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('folder not found'); + } + + ksort($folders, SORT_STRING); + $root = new Zend_Mail_Storage_Folder('/', '/', false); + $stack = array(null); + $folderStack = array(null); + $parentFolder = $root; + $parent = ''; + + foreach ($folders as $globalName => $data) { + do { + if (!$parent || strpos($globalName, $parent) === 0) { + $pos = strrpos($globalName, $data['delim']); + if ($pos === false) { + $localName = $globalName; + } else { + $localName = substr($globalName, $pos + 1); + } + $selectable = !$data['flags'] || !in_array('\\Noselect', $data['flags']); + + array_push($stack, $parent); + $parent = $globalName . $data['delim']; + $folder = new Zend_Mail_Storage_Folder($localName, $globalName, $selectable); + $parentFolder->$localName = $folder; + array_push($folderStack, $parentFolder); + $parentFolder = $folder; + break; + } else if ($stack) { + $parent = array_pop($stack); + $parentFolder = array_pop($folderStack); + } + } while ($stack); + if (!$stack) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('error while constructing folder tree'); + } + } + + return $root; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder + * @return null + * @throws Zend_Mail_Storage_Exception + * @throws Zend_Mail_Protocol_Exception + */ + public function selectFolder($globalName) + { + $this->_currentFolder = $globalName; + if (!$this->_protocol->select($this->_currentFolder)) { + $this->_currentFolder = ''; + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot change folder, maybe it does not exist'); + } + } + + + /** + * get Zend_Mail_Storage_Folder instance for current folder + * + * @return Zend_Mail_Storage_Folder instance of current folder + * @throws Zend_Mail_Storage_Exception + */ + public function getCurrentFolder() + { + return $this->_currentFolder; + } + + /** + * create a new folder + * + * This method also creates parent folders if necessary. Some mail storages may restrict, which folder + * may be used as parent or which chars may be used in the folder name + * + * @param string $name global name of folder, local name if $parentFolder is set + * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function createFolder($name, $parentFolder = null) + { + // TODO: we assume / as the hierarchy delim - need to get that from the folder class! + if ($parentFolder instanceof Zend_Mail_Storage_Folder) { + $folder = $parentFolder->getGlobalName() . '/' . $name; + } else if ($parentFolder != null) { + $folder = $parentFolder . '/' . $name; + } else { + $folder = $name; + } + + if (!$this->_protocol->create($folder)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot create folder'); + } + } + + /** + * remove a folder + * + * @param string|Zend_Mail_Storage_Folder $name name or instance of folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function removeFolder($name) + { + if ($name instanceof Zend_Mail_Storage_Folder) { + $name = $name->getGlobalName(); + } + + if (!$this->_protocol->delete($name)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot delete folder'); + } + } + + /** + * rename and/or move folder + * + * The new name has the same restrictions as in createFolder() + * + * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder + * @param string $newName new global name of folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function renameFolder($oldName, $newName) + { + if ($oldName instanceof Zend_Mail_Storage_Folder) { + $oldName = $oldName->getGlobalName(); + } + + if (!$this->_protocol->rename($oldName, $newName)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot rename folder'); + } + } + + /** + * append a new message to mail storage + * + * @param string $message message as string or instance of message class + * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken + * @param null|array $flags set flags for new message, else a default set is used + * @throws Zend_Mail_Storage_Exception + */ + // not yet * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class + public function appendMessage($message, $folder = null, $flags = null) + { + if ($folder === null) { + $folder = $this->_currentFolder; + } + + if ($flags === null) { + $flags = array(Zend_Mail_Storage::FLAG_SEEN); + } + + // TODO: handle class instances for $message + if (!$this->_protocol->append($folder, $message, $flags)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot create message, please check if the folder exists and your flags'); + } + } + + /** + * copy an existing message + * + * @param int $id number of message + * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function copyMessage($id, $folder) + { + if (!$this->_protocol->copy($folder, $id)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot copy message, does the folder exist?'); + } + } + + /** + * move an existing message + * + * NOTE: imap has no native move command, thus it's emulated with copy and delete + * + * @param int $id number of message + * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function moveMessage($id, $folder) { + $this->copyMessage($id, $folder); + $this->removeMessage($id); + } + + /** + * set flags for message + * + * NOTE: this method can't set the recent flag. + * + * @param int $id number of message + * @param array $flags new flags for message + * @throws Zend_Mail_Storage_Exception + */ + public function setFlags($id, $flags) + { + if (!$this->_protocol->store($flags, $id)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot set flags, have you tried to set the recent flag or special chars?'); + } + } +} + diff --git a/lib/Zend/Mail/Storage/Maildir.php b/lib/Zend/Mail/Storage/Maildir.php new file mode 100644 index 0000000..429636a --- /dev/null +++ b/lib/Zend/Mail/Storage/Maildir.php @@ -0,0 +1,475 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Maildir.php 12519 2008-11-10 18:41:24Z alexander $ + */ + + +/** + * @see Zend_Mail_Storage_Abstract + */ +require_once 'Zend/Mail/Storage/Abstract.php'; + +/** + * @see Zend_Mail_Message_File + */ +require_once 'Zend/Mail/Message/File.php'; + +/** + * @see Zend_Mail_Storage + */ +require_once 'Zend/Mail/Storage.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Maildir extends Zend_Mail_Storage_Abstract +{ + /** + * used message class, change it in an extened class to extend the returned message class + * @var string + */ + protected $_messageClass = 'Zend_Mail_Message_File'; + + /** + * data of found message files in maildir dir + * @var array + */ + protected $_files = array(); + + /** + * known flag chars in filenames + * + * This list has to be in alphabetical order for setFlags() + * + * @var array + */ + protected static $_knownFlags = array('D' => Zend_Mail_Storage::FLAG_DRAFT, + 'F' => Zend_Mail_Storage::FLAG_FLAGGED, + 'P' => Zend_Mail_Storage::FLAG_PASSED, + 'R' => Zend_Mail_Storage::FLAG_ANSWERED, + 'S' => Zend_Mail_Storage::FLAG_SEEN, + 'T' => Zend_Mail_Storage::FLAG_DELETED); + + // TODO: getFlags($id) for fast access if headers are not needed (i.e. just setting flags)? + + /** + * Count messages all messages in current box + * + * @return int number of messages + * @throws Zend_Mail_Storage_Exception + */ + public function countMessages($flags = null) + { + if ($flags === null) { + return count($this->_files); + } + + $count = 0; + if (!is_array($flags)) { + foreach ($this->_files as $file) { + if (isset($file['flaglookup'][$flags])) { + ++$count; + } + } + return $count; + } + + $flags = array_flip($flags); + foreach ($this->_files as $file) { + foreach ($flags as $flag => $v) { + if (!isset($file['flaglookup'][$flag])) { + continue 2; + } + } + ++$count; + } + return $count; + } + + /** + * Get one or all fields from file structure. Also checks if message is valid + * + * @param int $id message number + * @param string|null $field wanted field + * @return string|array wanted field or all fields as array + * @throws Zend_Mail_Storage_Exception + */ + protected function _getFileData($id, $field = null) + { + if (!isset($this->_files[$id - 1])) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('id does not exist'); + } + + if (!$field) { + return $this->_files[$id - 1]; + } + + if (!isset($this->_files[$id - 1][$field])) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('field does not exist'); + } + + return $this->_files[$id - 1][$field]; + } + + /** + * Get a list of messages with number and size + * + * @param int|null $id number of message or null for all messages + * @return int|array size of given message of list with all messages as array(num => size) + * @throws Zend_Mail_Storage_Exception + */ + public function getSize($id = null) + { + if ($id !== null) { + $filedata = $this->_getFileData($id); + return isset($filedata['size']) ? $filedata['size'] : filesize($filedata['filename']); + } + + $result = array(); + foreach ($this->_files as $num => $data) { + $result[$num + 1] = isset($data['size']) ? $data['size'] : filesize($data['filename']); + } + + return $result; + } + + + + /** + * Fetch a message + * + * @param int $id number of message + * @return Zend_Mail_Message_File + * @throws Zend_Mail_Storage_Exception + */ + public function getMessage($id) + { + // TODO that's ugly, would be better to let the message class decide + if (strtolower($this->_messageClass) == 'zend_mail_message_file' || is_subclass_of($this->_messageClass, 'zend_mail_message_file')) { + return new $this->_messageClass(array('file' => $this->_getFileData($id, 'filename'), + 'flags' => $this->_getFileData($id, 'flags'))); + } + + return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $this->getRawHeader($id), + 'flags' => $this->_getFileData($id, 'flags'))); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws Zend_Mail_Storage_Exception + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('not implemented'); + } + + $fh = fopen($this->_getFileData($id, 'filename'), 'r'); + + $content = ''; + while (!feof($fh)) { + $line = fgets($fh); + if (!trim($line)) { + break; + } + $content .= $line; + } + + fclose($fh); + return $content; + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage content + * @return string raw content + * @throws Zend_Mail_Storage_Exception + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('not implemented'); + } + + $fh = fopen($this->_getFileData($id, 'filename'), 'r'); + + while (!feof($fh)) { + $line = fgets($fh); + if (!trim($line)) { + break; + } + } + + $content = stream_get_contents($fh); + fclose($fh); + return $content; + } + + /** + * Create instance with parameters + * Supported parameters are: + * - dirname dirname of mbox file + * + * @param $params array mail reader specific parameters + * @throws Zend_Mail_Storage_Exception + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object)$params; + } + + if (!isset($params->dirname) || !is_dir($params->dirname)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('no valid dirname given in params'); + } + + if (!$this->_isMaildir($params->dirname)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('invalid maildir given'); + } + + $this->_has['top'] = true; + $this->_has['flags'] = true; + $this->_openMaildir($params->dirname); + } + + /** + * check if a given dir is a valid maildir + * + * @param string $dirname name of dir + * @return bool dir is valid maildir + */ + protected function _isMaildir($dirname) + { + if (file_exists($dirname . '/new') && !is_dir($dirname . '/new')) { + return false; + } + if (file_exists($dirname . '/tmp') && !is_dir($dirname . '/tmp')) { + return false; + } + return is_dir($dirname . '/cur'); + } + + /** + * open given dir as current maildir + * + * @param string $dirname name of maildir + * @return null + * @throws Zend_Mail_Storage_Exception + */ + protected function _openMaildir($dirname) + { + if ($this->_files) { + $this->close(); + } + + $dh = @opendir($dirname . '/cur/'); + if (!$dh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot open maildir'); + } + $this->_getMaildirFiles($dh, $dirname . '/cur/'); + closedir($dh); + + $dh = @opendir($dirname . '/new/'); + if ($dh) { + $this->_getMaildirFiles($dh, $dirname . '/new/', array(Zend_Mail_Storage::FLAG_RECENT)); + closedir($dh); + } else if (file_exists($dirname . '/new/')) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot read recent mails in maildir'); + } + } + + /** + * find all files in opened dir handle and add to maildir files + * + * @param resource $dh dir handle used for search + * @param string $dirname dirname of dir in $dh + * @param array $default_flags default flags for given dir + * @return null + */ + protected function _getMaildirFiles($dh, $dirname, $default_flags = array()) + { + while (($entry = readdir($dh)) !== false) { + if ($entry[0] == '.' || !is_file($dirname . $entry)) { + continue; + } + + @list($uniq, $info) = explode(':', $entry, 2); + @list(,$size) = explode(',', $uniq, 2); + if ($size && $size[0] == 'S' && $size[1] == '=') { + $size = substr($size, 2); + } + if (!ctype_digit($size)) { + $size = null; + } + @list($version, $flags) = explode(',', $info, 2); + if ($version != 2) { + $flags = ''; + } + + $named_flags = $default_flags; + $length = strlen($flags); + for ($i = 0; $i < $length; ++$i) { + $flag = $flags[$i]; + $named_flags[$flag] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag; + } + + $data = array('uniq' => $uniq, + 'flags' => $named_flags, + 'flaglookup' => array_flip($named_flags), + 'filename' => $dirname . $entry); + if ($size !== null) { + $data['size'] = (int)$size; + } + $this->_files[] = $data; + } + } + + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + * @return void + */ + public function close() + { + $this->_files = array(); + } + + + /** + * Waste some CPU cycles doing nothing. + * + * @return void + */ + public function noop() + { + return true; + } + + + /** + * stub for not supported message deletion + * + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function removeMessage($id) + { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('maildir is (currently) read-only'); + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Zend_Mail_Storage_Exception + */ + public function getUniqueId($id = null) + { + if ($id) { + return $this->_getFileData($id, 'uniq'); + } + + $ids = array(); + foreach ($this->_files as $num => $file) { + $ids[$num + 1] = $file['uniq']; + } + return $ids; + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws Zend_Mail_Storage_Exception + */ + public function getNumberByUniqueId($id) + { + foreach ($this->_files as $num => $file) { + if ($file['uniq'] == $id) { + return $num + 1; + } + } + + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('unique id not found'); + } +} diff --git a/lib/Zend/Mail/Storage/Mbox.php b/lib/Zend/Mail/Storage/Mbox.php new file mode 100644 index 0000000..8be42d8 --- /dev/null +++ b/lib/Zend/Mail/Storage/Mbox.php @@ -0,0 +1,447 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Mbox.php 12519 2008-11-10 18:41:24Z alexander $ + */ + + +/** + * @see Zend_Loader + * May be used in constructor, but commented out for now + */ +// require_once 'Zend/Loader.php'; + +/** + * @see Zend_Mail_Storage_Abstract + */ +require_once 'Zend/Mail/Storage/Abstract.php'; + +/** + * @see Zend_Mail_Message_File + */ +require_once 'Zend/Mail/Message/File.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Mbox extends Zend_Mail_Storage_Abstract +{ + /** + * file handle to mbox file + * @var null|resource + */ + protected $_fh; + + /** + * filename of mbox file for __wakeup + * @var string + */ + protected $_filename; + + /** + * modification date of mbox file for __wakeup + * @var int + */ + protected $_filemtime; + + /** + * start and end position of messages as array('start' => start, 'seperator' => headersep, 'end' => end) + * @var array + */ + protected $_positions; + + /** + * used message class, change it in an extened class to extend the returned message class + * @var string + */ + protected $_messageClass = 'Zend_Mail_Message_File'; + + /** + * Count messages all messages in current box + * + * @return int number of messages + * @throws Zend_Mail_Storage_Exception + */ + public function countMessages() + { + return count($this->_positions); + } + + + /** + * Get a list of messages with number and size + * + * @param int|null $id number of message or null for all messages + * @return int|array size of given message of list with all messages as array(num => size) + */ + public function getSize($id = 0) + { + if ($id) { + $pos = $this->_positions[$id - 1]; + return $pos['end'] - $pos['start']; + } + + $result = array(); + foreach ($this->_positions as $num => $pos) { + $result[$num + 1] = $pos['end'] - $pos['start']; + } + + return $result; + } + + + /** + * Get positions for mail message or throw exeption if id is invalid + * + * @param int $id number of message + * @return array positions as in _positions + * @throws Zend_Mail_Storage_Exception + */ + protected function _getPos($id) + { + if (!isset($this->_positions[$id - 1])) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('id does not exist'); + } + + return $this->_positions[$id - 1]; + } + + + /** + * Fetch a message + * + * @param int $id number of message + * @return Zend_Mail_Message_File + * @throws Zend_Mail_Storage_Exception + */ + public function getMessage($id) + { + // TODO that's ugly, would be better to let the message class decide + if (strtolower($this->_messageClass) == 'zend_mail_message_file' || is_subclass_of($this->_messageClass, 'zend_mail_message_file')) { + // TODO top/body lines + $messagePos = $this->_getPos($id); + return new $this->_messageClass(array('file' => $this->_fh, 'startPos' => $messagePos['start'], + 'endPos' => $messagePos['end'])); + } + + $bodyLines = 0; // TODO: need a way to change that + + $message = $this->getRawHeader($id); + // file pointer is after headers now + if ($bodyLines) { + $message .= "\n"; + while ($bodyLines-- && ftell($this->_fh) < $this->_positions[$id - 1]['end']) { + $message .= fgets($this->_fh); + } + } + + return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $message)); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws Zend_Mail_Protocol_Exception + * @throws Zend_Mail_Storage_Exception + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('not implemented'); + } + $messagePos = $this->_getPos($id); + // TODO: toplines + return stream_get_contents($this->_fh, $messagePos['separator'] - $messagePos['start'], $messagePos['start']); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage content + * @return string raw content + * @throws Zend_Mail_Protocol_Exception + * @throws Zend_Mail_Storage_Exception + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('not implemented'); + } + $messagePos = $this->_getPos($id); + return stream_get_contents($this->_fh, $messagePos['end'] - $messagePos['separator'], $messagePos['separator']); + } + + /** + * Create instance with parameters + * Supported parameters are: + * - filename filename of mbox file + * + * @param $params array mail reader specific parameters + * @throws Zend_Mail_Storage_Exception + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object)$params; + } + + if (!isset($params->filename) /* || Zend_Loader::isReadable($params['filename']) */) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('no valid filename given in params'); + } + + $this->_openMboxFile($params->filename); + $this->_has['top'] = true; + $this->_has['uniqueid'] = false; + } + + /** + * check if given file is a mbox file + * + * if $file is a resource its file pointer is moved after the first line + * + * @param resource|string $file stream resource of name of file + * @param bool $fileIsString file is string or resource + * @return bool file is mbox file + */ + protected function _isMboxFile($file, $fileIsString = true) + { + if ($fileIsString) { + $file = @fopen($file, 'r'); + if (!$file) { + return false; + } + } else { + fseek($file, 0); + } + + $result = false; + + $line = fgets($file); + if (strpos($line, 'From ') === 0) { + $result = true; + } + + if ($fileIsString) { + @fclose($file); + } + + return $result; + } + + /** + * open given file as current mbox file + * + * @param string $filename filename of mbox file + * @return null + * @throws Zend_Mail_Storage_Exception + */ + protected function _openMboxFile($filename) + { + if ($this->_fh) { + $this->close(); + } + + $this->_fh = @fopen($filename, 'r'); + if (!$this->_fh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot open mbox file'); + } + $this->_filename = $filename; + $this->_filemtime = filemtime($this->_filename); + + if (!$this->_isMboxFile($this->_fh, false)) { + @fclose($this->_fh); + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('file is not a valid mbox format'); + } + + $messagePos = array('start' => ftell($this->_fh), 'separator' => 0, 'end' => 0); + while (($line = fgets($this->_fh)) !== false) { + if (strpos($line, 'From ') === 0) { + $messagePos['end'] = ftell($this->_fh) - strlen($line) - 2; // + newline + if (!$messagePos['separator']) { + $messagePos['separator'] = $messagePos['end']; + } + $this->_positions[] = $messagePos; + $messagePos = array('start' => ftell($this->_fh), 'separator' => 0, 'end' => 0); + } + if (!$messagePos['separator'] && !trim($line)) { + $messagePos['separator'] = ftell($this->_fh); + } + } + + $messagePos['end'] = ftell($this->_fh); + if (!$messagePos['separator']) { + $messagePos['separator'] = $messagePos['end']; + } + $this->_positions[] = $messagePos; + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + * @return void + */ + public function close() + { + @fclose($this->_fh); + $this->_positions = array(); + } + + + /** + * Waste some CPU cycles doing nothing. + * + * @return void + */ + public function noop() + { + return true; + } + + + /** + * stub for not supported message deletion + * + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function removeMessage($id) + { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('mbox is read-only'); + } + + /** + * get unique id for one or all messages + * + * Mbox does not support unique ids (yet) - it's always the same as the message number. + * That shouldn't be a problem, because we can't change mbox files. Therefor the message + * number is save enough. + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Zend_Mail_Storage_Exception + */ + public function getUniqueId($id = null) + { + if ($id) { + // check if id exists + $this->_getPos($id); + return $id; + } + + $range = range(1, $this->countMessages()); + return array_combine($range, $range); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws Zend_Mail_Storage_Exception + */ + public function getNumberByUniqueId($id) + { + // check if id exists + $this->_getPos($id); + return $id; + } + + /** + * magic method for serialize() + * + * with this method you can cache the mbox class + * + * @return array name of variables + */ + public function __sleep() + { + return array('_filename', '_positions', '_filemtime'); + } + + /** + * magic method for unserialize() + * + * with this method you can cache the mbox class + * for cache validation the mtime of the mbox file is used + * + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function __wakeup() + { + if ($this->_filemtime != @filemtime($this->_filename)) { + $this->close(); + $this->_openMboxFile($this->_filename); + } else { + $this->_fh = @fopen($this->_filename, 'r'); + if (!$this->_fh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot open mbox file'); + } + } + } + +} diff --git a/lib/Zend/Mail/Storage/Pop3.php b/lib/Zend/Mail/Storage/Pop3.php new file mode 100644 index 0000000..693384d --- /dev/null +++ b/lib/Zend/Mail/Storage/Pop3.php @@ -0,0 +1,328 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Pop3.php 9099 2008-03-30 19:35:47Z thomas $ + */ + + +/** + * @see Zend_Mail_Storage_Abstract + */ +require_once 'Zend/Mail/Storage/Abstract.php'; + +/** + * @see Zend_Mail_Protocol_Pop3 + */ +require_once 'Zend/Mail/Protocol/Pop3.php'; + +/** + * @see Zend_Mail_Message + */ +require_once 'Zend/Mail/Message.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Pop3 extends Zend_Mail_Storage_Abstract +{ + /** + * protocol handler + * @var null|Zend_Mail_Protocol_Pop3 + */ + protected $_protocol; + + + /** + * Count messages all messages in current box + * + * @return int number of messages + * @throws Zend_Mail_Storage_Exception + * @throws Zend_Mail_Protocol_Exception + */ + public function countMessages() + { + $this->_protocol->status($count, $null); + return (int)$count; + } + + /** + * get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as array(num => size) + * @throws Zend_Mail_Protocol_Exception + */ + public function getSize($id = 0) + { + $id = $id ? $id : null; + return $this->_protocol->getList($id); + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return Zend_Mail_Message + * @throws Zend_Mail_Protocol_Exception + */ + public function getMessage($id) + { + $bodyLines = 0; + $message = $this->_protocol->top($id, $bodyLines, true); + + return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $message, + 'noToplines' => $bodyLines < 1)); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws Zend_Mail_Protocol_Exception + * @throws Zend_Mail_Storage_Exception + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('not implemented'); + } + + return $this->_protocol->top($id, 0, true); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for messsage content + * @return string raw content + * @throws Zend_Mail_Protocol_Exception + * @throws Zend_Mail_Storage_Exception + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('not implemented'); + } + + $content = $this->_protocol->retrieve($id); + // TODO: find a way to avoid decoding the headers + Zend_Mime_Decode::splitMessage($content, $null, $body); + return $body; + } + + /** + * create instance with parameters + * Supported paramters are + * - host hostname or ip address of POP3 server + * - user username + * - password password for user 'username' [optional, default = ''] + * - port port for POP3 server [optional, default = 110] + * - ssl 'SSL' or 'TLS' for secure sockets + * + * @param $params array mail reader specific parameters + * @throws Zend_Mail_Storage_Exception + * @throws Zend_Mail_Protocol_Exception + */ + public function __construct($params) + { + if (is_array($params)) { + $params = (object)$params; + } + + $this->_has['fetchPart'] = false; + $this->_has['top'] = null; + $this->_has['uniqueid'] = null; + + if ($params instanceof Zend_Mail_Protocol_Pop3) { + $this->_protocol = $params; + return; + } + + if (!isset($params->user)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('need at least user in params'); + } + + $host = isset($params->host) ? $params->host : 'localhost'; + $password = isset($params->password) ? $params->password : ''; + $port = isset($params->port) ? $params->port : null; + $ssl = isset($params->ssl) ? $params->ssl : false; + + $this->_protocol = new Zend_Mail_Protocol_Pop3(); + $this->_protocol->connect($host, $port, $ssl); + $this->_protocol->login($params->user, $password); + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + * @return null + */ + public function close() + { + $this->_protocol->logout(); + } + + /** + * Keep the server busy. + * + * @return null + * @throws Zend_Mail_Protocol_Exception + */ + public function noop() + { + return $this->_protocol->noop(); + } + + /** + * Remove a message from server. If you're doing that from a web enviroment + * you should be careful and use a uniqueid as parameter if possible to + * identify the message. + * + * @param int $id number of message + * @return null + * @throws Zend_Mail_Protocol_Exception + */ + public function removeMessage($id) + { + $this->_protocol->delete($id); + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Zend_Mail_Storage_Exception + */ + public function getUniqueId($id = null) + { + if (!$this->hasUniqueid) { + if ($id) { + return $id; + } + $count = $this->countMessages(); + if ($count < 1) { + return array(); + } + $range = range(1, $count); + return array_combine($range, $range); + } + + return $this->_protocol->uniqueid($id); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws Zend_Mail_Storage_Exception + */ + public function getNumberByUniqueId($id) + { + if (!$this->hasUniqueid) { + return $id; + } + + $ids = $this->getUniqueId(); + foreach ($ids as $k => $v) { + if ($v == $id) { + return $k; + } + } + + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('unique id not found'); + } + + /** + * Special handling for hasTop and hasUniqueid. The headers of the first message is + * retrieved if Top wasn't needed/tried yet. + * + * @see Zend_Mail_Storage_Abstract:__get() + * @param string $var + * @return string + * @throws Zend_Mail_Storage_Exception + */ + public function __get($var) + { + $result = parent::__get($var); + if ($result !== null) { + return $result; + } + + if (strtolower($var) == 'hastop') { + if ($this->_protocol->hasTop === null) { + // need to make a real call, because not all server are honest in their capas + try { + $this->_protocol->top(1, 0, false); + } catch(Zend_Mail_Exception $e) { + // ignoring error + } + } + $this->_has['top'] = $this->_protocol->hasTop; + return $this->_protocol->hasTop; + } + + if (strtolower($var) == 'hasuniqueid') { + $id = null; + try { + $id = $this->_protocol->uniqueid(1); + } catch(Zend_Mail_Exception $e) { + // ignoring error + } + $this->_has['uniqueid'] = $id ? true : false; + return $this->_has['uniqueid']; + } + + return $result; + } +} diff --git a/lib/Zend/Mail/Storage/Writable/Interface.php b/lib/Zend/Mail/Storage/Writable/Interface.php new file mode 100644 index 0000000..b49fc8e --- /dev/null +++ b/lib/Zend/Mail/Storage/Writable/Interface.php @@ -0,0 +1,108 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Interface.php 9098 2008-03-30 19:29:10Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +interface Zend_Mail_Storage_Writable_Interface +{ + /** + * create a new folder + * + * This method also creates parent folders if necessary. Some mail storages may restrict, which folder + * may be used as parent or which chars may be used in the folder name + * + * @param string $name global name of folder, local name if $parentFolder is set + * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function createFolder($name, $parentFolder = null); + + /** + * remove a folder + * + * @param string|Zend_Mail_Storage_Folder $name name or instance of folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function removeFolder($name); + + /** + * rename and/or move folder + * + * The new name has the same restrictions as in createFolder() + * + * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder + * @param string $newName new global name of folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function renameFolder($oldName, $newName); + + /** + * append a new message to mail storage + * + * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class + * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken + * @param null|array $flags set flags for new message, else a default set is used + * @throws Zend_Mail_Storage_Exception + */ + public function appendMessage($message, $folder = null, $flags = null); + + /** + * copy an existing message + * + * @param int $id number of message + * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function copyMessage($id, $folder); + + /** + * move an existing message + * + * @param int $id number of message + * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function moveMessage($id, $folder); + + /** + * set flags for message + * + * NOTE: this method can't set the recent flag. + * + * @param int $id number of message + * @param array $flags new flags for message + * @throws Zend_Mail_Storage_Exception + */ + public function setFlags($id, $flags); +}
\ No newline at end of file diff --git a/lib/Zend/Mail/Storage/Writable/Maildir.php b/lib/Zend/Mail/Storage/Writable/Maildir.php new file mode 100644 index 0000000..9ba7573 --- /dev/null +++ b/lib/Zend/Mail/Storage/Writable/Maildir.php @@ -0,0 +1,1049 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Maildir.php 12519 2008-11-10 18:41:24Z alexander $ + */ + + +/** + * @see Zend_Mail_Storage_Folder_Maildir + */ +require_once 'Zend/Mail/Storage/Folder/Maildir.php'; + +/** + * @see Zend_Mail_Storage_Writable_Interface + */ +require_once 'Zend/Mail/Storage/Writable/Interface.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Storage + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Storage_Writable_Maildir extends Zend_Mail_Storage_Folder_Maildir + implements Zend_Mail_Storage_Writable_Interface +{ + // TODO: init maildir (+ constructor option create if not found) + + /** + * use quota and size of quota if given + * @var bool|int + */ + protected $_quota; + + /** + * create a new maildir + * + * If the given dir is already a valid maildir this will not fail. + * + * @param string $dir directory for the new maildir (may already exist) + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public static function initMaildir($dir) + { + if (file_exists($dir)) { + if (!is_dir($dir)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('maildir must be a directory if already exists'); + } + } else { + if (!mkdir($dir)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + $dir = dirname($dir); + if (!file_exists($dir)) { + throw new Zend_Mail_Storage_Exception("parent $dir not found"); + } else if (!is_dir($dir)) { + throw new Zend_Mail_Storage_Exception("parent $dir not a directory"); + } else { + throw new Zend_Mail_Storage_Exception('cannot create maildir'); + } + } + } + + foreach (array('cur', 'tmp', 'new') as $subdir) { + if (!@mkdir($dir . DIRECTORY_SEPARATOR . $subdir)) { + // ignore if dir exists (i.e. was already valid maildir or two processes try to create one) + if (!file_exists($dir . DIRECTORY_SEPARATOR . $subdir)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('could not create subdir ' . $subdir); + } + } + } + } + + /** + * Create instance with parameters + * Additional parameters are (see parent for more): + * - create if true a new maildir is create if none exists + * + * @param $params array mail reader specific parameters + * @throws Zend_Mail_Storage_Exception + */ + public function __construct($params) { + if (is_array($params)) { + $params = (object)$params; + } + + if (!empty($params->create) && isset($params->dirname) && !file_exists($params->dirname . DIRECTORY_SEPARATOR . 'cur')) { + self::initMaildir($params->dirname); + } + + parent::__construct($params); + } + + /** + * create a new folder + * + * This method also creates parent folders if necessary. Some mail storages may restrict, which folder + * may be used as parent or which chars may be used in the folder name + * + * @param string $name global name of folder, local name if $parentFolder is set + * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent + * @return string only used internally (new created maildir) + * @throws Zend_Mail_Storage_Exception + */ + public function createFolder($name, $parentFolder = null) + { + if ($parentFolder instanceof Zend_Mail_Storage_Folder) { + $folder = $parentFolder->getGlobalName() . $this->_delim . $name; + } else if ($parentFolder != null) { + $folder = rtrim($parentFolder, $this->_delim) . $this->_delim . $name; + } else { + $folder = $name; + } + + $folder = trim($folder, $this->_delim); + + // first we check if we try to create a folder that does exist + $exists = null; + try { + $exists = $this->getFolders($folder); + } catch (Zend_Mail_Exception $e) { + // ok + } + if ($exists) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('folder already exists'); + } + + if (strpos($folder, $this->_delim . $this->_delim) !== false) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('invalid name - folder parts may not be empty'); + } + + if (strpos($folder, 'INBOX' . $this->_delim) === 0) { + $folder = substr($folder, 6); + } + + $fulldir = $this->_rootdir . '.' . $folder; + + // check if we got tricked and would create a dir outside of the rootdir or not as direct child + if (strpos($folder, DIRECTORY_SEPARATOR) !== false || strpos($folder, '/') !== false + || dirname($fulldir) . DIRECTORY_SEPARATOR != $this->_rootdir) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('invalid name - no directory seprator allowed in folder name'); + } + + // has a parent folder? + $parent = null; + if (strpos($folder, $this->_delim)) { + // let's see if the parent folder exists + $parent = substr($folder, 0, strrpos($folder, $this->_delim)); + try { + $this->getFolders($parent); + } catch (Zend_Mail_Exception $e) { + // does not - create parent folder + $this->createFolder($parent); + } + } + + if (!@mkdir($fulldir) || !@mkdir($fulldir . DIRECTORY_SEPARATOR . 'cur')) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('error while creating new folder, may be created incompletly'); + } + + mkdir($fulldir . DIRECTORY_SEPARATOR . 'new'); + mkdir($fulldir . DIRECTORY_SEPARATOR . 'tmp'); + + $localName = $parent ? substr($folder, strlen($parent) + 1) : $folder; + $this->getFolders($parent)->$localName = new Zend_Mail_Storage_Folder($localName, $folder, true); + + return $fulldir; + } + + /** + * remove a folder + * + * @param string|Zend_Mail_Storage_Folder $name name or instance of folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function removeFolder($name) + { + // TODO: This could fail in the middle of the task, which is not optimal. + // But there is no defined standard way to mark a folder as removed and there is no atomar fs-op + // to remove a directory. Also moving the folder to a/the trash folder is not possible, as + // all parent folders must be created. What we could do is add a dash to the front of the + // directory name and it should be ignored as long as other processes obey the standard. + + if ($name instanceof Zend_Mail_Storage_Folder) { + $name = $name->getGlobalName(); + } + + $name = trim($name, $this->_delim); + if (strpos($name, 'INBOX' . $this->_delim) === 0) { + $name = substr($name, 6); + } + + // check if folder exists and has no children + if (!$this->getFolders($name)->isLeaf()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('delete children first'); + } + + if ($name == 'INBOX' || $name == DIRECTORY_SEPARATOR || $name == '/') { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('wont delete INBOX'); + } + + if ($name == $this->getCurrentFolder()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('wont delete selected folder'); + } + + foreach (array('tmp', 'new', 'cur', '.') as $subdir) { + $dir = $this->_rootdir . '.' . $name . DIRECTORY_SEPARATOR . $subdir; + if (!file_exists($dir)) { + continue; + } + $dh = opendir($dir); + if (!$dh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("error opening $subdir"); + } + while (($entry = readdir($dh)) !== false) { + if ($entry == '.' || $entry == '..') { + continue; + } + if (!unlink($dir . DIRECTORY_SEPARATOR . $entry)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("error cleaning $subdir"); + } + } + closedir($dh); + if ($subdir !== '.') { + if (!rmdir($dir)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("error removing $subdir"); + } + } + } + + if (!rmdir($this->_rootdir . '.' . $name)) { + // at least we should try to make it a valid maildir again + mkdir($this->_rootdir . '.' . $name . DIRECTORY_SEPARATOR . 'cur'); + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("error removing maindir"); + } + + $parent = strpos($name, $this->_delim) ? substr($name, 0, strrpos($name, $this->_delim)) : null; + $localName = $parent ? substr($name, strlen($parent) + 1) : $name; + unset($this->getFolders($parent)->$localName); + } + + /** + * rename and/or move folder + * + * The new name has the same restrictions as in createFolder() + * + * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder + * @param string $newName new global name of folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function renameFolder($oldName, $newName) + { + // TODO: This is also not atomar and has similar problems as removeFolder() + + if ($oldName instanceof Zend_Mail_Storage_Folder) { + $oldName = $oldName->getGlobalName(); + } + + $oldName = trim($oldName, $this->_delim); + if (strpos($oldName, 'INBOX' . $this->_delim) === 0) { + $oldName = substr($oldName, 6); + } + + $newName = trim($newName, $this->_delim); + if (strpos($newName, 'INBOX' . $this->_delim) === 0) { + $newName = substr($newName, 6); + } + + if (strpos($newName, $oldName . $this->_delim) === 0) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('new folder cannot be a child of old folder'); + } + + // check if folder exists and has no children + $folder = $this->getFolders($oldName); + + if ($oldName == 'INBOX' || $oldName == DIRECTORY_SEPARATOR || $oldName == '/') { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('wont rename INBOX'); + } + + if ($oldName == $this->getCurrentFolder()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('wont rename selected folder'); + } + + $newdir = $this->createFolder($newName); + + if (!$folder->isLeaf()) { + foreach ($folder as $k => $v) { + $this->renameFolder($v->getGlobalName(), $newName . $this->_delim . $k); + } + } + + $olddir = $this->_rootdir . '.' . $folder; + foreach (array('tmp', 'new', 'cur') as $subdir) { + $subdir = DIRECTORY_SEPARATOR . $subdir; + if (!file_exists($olddir . $subdir)) { + continue; + } + // using copy or moving files would be even better - but also much slower + if (!rename($olddir . $subdir, $newdir . $subdir)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('error while moving ' . $subdir); + } + } + // create a dummy if removing fails - otherwise we can't read it next time + mkdir($olddir . DIRECTORY_SEPARATOR . 'cur'); + $this->removeFolder($oldName); + } + + /** + * create a uniqueid for maildir filename + * + * This is nearly the format defined in the maildir standard. The microtime() call should already + * create a uniqueid, the pid is for multicore/-cpu machine that manage to call this function at the + * exact same time, and uname() gives us the hostname for multiple machines accessing the same storage. + * + * If someone disables posix we create a random number of the same size, so this method should also + * work on Windows - if you manage to get maildir working on Windows. + * Microtime could also be disabled, altough I've never seen it. + * + * @return string new uniqueid + */ + protected function _createUniqueId() + { + $id = ''; + $id .= function_exists('microtime') ? microtime(true) : (time() . ' ' . rand(0, 100000)); + $id .= '.' . (function_exists('posix_getpid') ? posix_getpid() : rand(50, 65535)); + $id .= '.' . php_uname('n'); + + return $id; + } + + /** + * open a temporary maildir file + * + * makes sure tmp/ exists and create a file with a unique name + * you should close the returned filehandle! + * + * @param string $folder name of current folder without leading . + * @return array array('dirname' => dir of maildir folder, 'uniq' => unique id, 'filename' => name of create file + * 'handle' => file opened for writing) + * @throws Zend_Mail_Storage_Exception + */ + protected function _createTmpFile($folder = 'INBOX') + { + if ($folder == 'INBOX') { + $tmpdir = $this->_rootdir . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + } else { + $tmpdir = $this->_rootdir . '.' . $folder . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + } + if (!file_exists($tmpdir)) { + if (!mkdir($tmpdir)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('problems creating tmp dir'); + } + } + + // we should retry to create a unique id if a file with the same name exists + // to avoid a script timeout we only wait 1 second (instead of 2) and stop + // after a defined retry count + // if you change this variable take into account that it can take up to $max_tries seconds + // normally we should have a valid unique name after the first try, we're just following the "standard" here + $max_tries = 5; + for ($i = 0; $i < $max_tries; ++$i) { + $uniq = $this->_createUniqueId(); + if (!file_exists($tmpdir . $uniq)) { + // here is the race condition! - as defined in the standard + // to avoid having a long time between stat()ing the file and creating it we're opening it here + // to mark the filename as taken + $fh = fopen($tmpdir . $uniq, 'w'); + if (!$fh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('could not open temp file'); + } + break; + } + sleep(1); + } + + if (!$fh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception("tried $max_tries unique ids for a temp file, but all were taken" + . ' - giving up'); + } + + return array('dirname' => $this->_rootdir . '.' . $folder, 'uniq' => $uniq, 'filename' => $tmpdir . $uniq, + 'handle' => $fh); + } + + /** + * create an info string for filenames with given flags + * + * @param array $flags wanted flags, with the reference you'll get the set flags with correct key (= char for flag) + * @return string info string for version 2 filenames including the leading colon + * @throws Zend_Mail_Storage_Exception + */ + protected function _getInfoString(&$flags) + { + // accessing keys is easier, faster and it removes duplicated flags + $wanted_flags = array_flip($flags); + if (isset($wanted_flags[Zend_Mail_Storage::FLAG_RECENT])) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('recent flag may not be set'); + } + + $info = ':2,'; + $flags = array(); + foreach (Zend_Mail_Storage_Maildir::$_knownFlags as $char => $flag) { + if (!isset($wanted_flags[$flag])) { + continue; + } + $info .= $char; + $flags[$char] = $flag; + unset($wanted_flags[$flag]); + } + + if (!empty($wanted_flags)) { + $wanted_flags = implode(', ', array_keys($wanted_flags)); + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('unknown flag(s): ' . $wanted_flags); + } + + return $info; + } + + /** + * append a new message to mail storage + * + * @param string|stream $message message as string or stream resource + * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken + * @param null|array $flags set flags for new message, else a default set is used + * @param bool $recent handle this mail as if recent flag has been set, + * should only be used in delivery + * @throws Zend_Mail_Storage_Exception + */ + // not yet * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class + + public function appendMessage($message, $folder = null, $flags = null, $recent = false) + { + if ($this->_quota && $this->checkQuota()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('storage is over quota!'); + } + + if ($folder === null) { + $folder = $this->_currentFolder; + } + + if (!($folder instanceof Zend_Mail_Storage_Folder)) { + $folder = $this->getFolders($folder); + } + + if ($flags === null) { + $flags = array(Zend_Mail_Storage::FLAG_SEEN); + } + $info = $this->_getInfoString($flags); + $temp_file = $this->_createTmpFile($folder->getGlobalName()); + + // TODO: handle class instances for $message + if (is_resource($message) && get_resource_type($message) == 'stream') { + stream_copy_to_stream($message, $temp_file['handle']); + } else { + fputs($temp_file['handle'], $message); + } + fclose($temp_file['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($temp_file['filename']); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + $new_filename = $temp_file['dirname'] . DIRECTORY_SEPARATOR; + $new_filename .= $recent ? 'new' : 'cur'; + $new_filename .= DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (!link($temp_file['filename'], $new_filename)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + $exception = new Zend_Mail_Storage_Exception('cannot link message file to final dir'); + } + @unlink($temp_file['filename']); + + if ($exception) { + throw $exception; + } + + $this->_files[] = array('uniq' => $temp_file['uniq'], + 'flags' => $flags, + 'filename' => $new_filename); + if ($this->_quota) { + $this->_addQuotaEntry((int)$size, 1); + } + } + + /** + * copy an existing message + * + * @param int $id number of message + * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function copyMessage($id, $folder) + { + if ($this->_quota && $this->checkQuota()) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('storage is over quota!'); + } + + if (!($folder instanceof Zend_Mail_Storage_Folder)) { + $folder = $this->getFolders($folder); + } + + $filedata = $this->_getFileData($id); + $old_file = $filedata['filename']; + $flags = $filedata['flags']; + + // copied message can't be recent + while (($key = array_search(Zend_Mail_Storage::FLAG_RECENT, $flags)) !== false) { + unset($flags[$key]); + } + $info = $this->_getInfoString($flags); + + // we're creating the copy as temp file before moving to cur/ + $temp_file = $this->_createTmpFile($folder->getGlobalName()); + // we don't write directly to the file + fclose($temp_file['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($old_file); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + + $new_file = $temp_file['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (!copy($old_file, $temp_file['filename'])) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + $exception = new Zend_Mail_Storage_Exception('cannot copy message file'); + } else if (!link($temp_file['filename'], $new_file)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + $exception = new Zend_Mail_Storage_Exception('cannot link message file to final dir'); + } + @unlink($temp_file['filename']); + + if ($exception) { + throw $exception; + } + + if ($folder->getGlobalName() == $this->_currentFolder + || ($this->_currentFolder == 'INBOX' && $folder->getGlobalName() == '/')) { + $this->_files[] = array('uniq' => $temp_file['uniq'], + 'flags' => $flags, + 'filename' => $new_file); + } + + if ($this->_quota) { + $this->_addQuotaEntry((int)$size, 1); + } + } + + /** + * move an existing message + * + * @param int $id number of message + * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function moveMessage($id, $folder) { + if (!($folder instanceof Zend_Mail_Storage_Folder)) { + $folder = $this->getFolders($folder); + } + + if ($folder->getGlobalName() == $this->_currentFolder + || ($this->_currentFolder == 'INBOX' && $folder->getGlobalName() == '/')) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('target is current folder'); + } + + $filedata = $this->_getFileData($id); + $old_file = $filedata['filename']; + $flags = $filedata['flags']; + + // moved message can't be recent + while (($key = array_search(Zend_Mail_Storage::FLAG_RECENT, $flags)) !== false) { + unset($flags[$key]); + } + $info = $this->_getInfoString($flags); + + // reserving a new name + $temp_file = $this->_createTmpFile($folder->getGlobalName()); + fclose($temp_file['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($old_file); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + + $new_file = $temp_file['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (!rename($old_file, $new_file)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + $exception = new Zend_Mail_Storage_Exception('cannot move message file'); + } + @unlink($temp_file['filename']); + + if ($exception) { + throw $exception; + } + + unset($this->_files[$id - 1]); + // remove the gap + $this->_files = array_values($this->_files); + } + + + /** + * set flags for message + * + * NOTE: this method can't set the recent flag. + * + * @param int $id number of message + * @param array $flags new flags for message + * @throws Zend_Mail_Storage_Exception + */ + public function setFlags($id, $flags) + { + $info = $this->_getInfoString($flags); + $filedata = $this->_getFileData($id); + + // NOTE: double dirname to make sure we always move to cur. if recent flag has been set (message is in new) it will be moved to cur. + $new_filename = dirname(dirname($filedata['filename'])) . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . "$filedata[uniq]$info"; + + if (!@rename($filedata['filename'], $new_filename)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot rename file'); + } + + $filedata['flags'] = $flags; + $filedata['filename'] = $new_filename; + + $this->_files[$id - 1] = $filedata; + } + + + /** + * stub for not supported message deletion + * + * @return null + * @throws Zend_Mail_Storage_Exception + */ + public function removeMessage($id) + { + $filename = $this->_getFileData($id, 'filename'); + + if ($this->_quota) { + $size = filesize($filename); + } + + if (!@unlink($filename)) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot remove message'); + } + unset($this->_files[$id - 1]); + // remove the gap + $this->_files = array_values($this->_files); + if ($this->_quota) { + $this->_addQuotaEntry(0 - (int)$size, -1); + } + } + + /** + * enable/disable quota and set a quota value if wanted or needed + * + * You can enable/disable quota with true/false. If you don't have + * a MDA or want to enforce a quota value you can also set this value + * here. Use array('size' => SIZE_QUOTA, 'count' => MAX_MESSAGE) do + * define your quota. Order of these fields does matter! + * + * @param bool|array $value new quota value + * @return null + */ + public function setQuota($value) { + $this->_quota = $value; + } + + /** + * get currently set quota + * + * @see Zend_Mail_Storage_Writable_Maildir::setQuota() + * + * @return bool|array + */ + public function getQuota($fromStorage = false) { + if ($fromStorage) { + $fh = @fopen($this->_rootdir . 'maildirsize', 'r'); + if (!$fh) { + /** + * @see Zend_Mail_Storage_Exception + */ + require_once 'Zend/Mail/Storage/Exception.php'; + throw new Zend_Mail_Storage_Exception('cannot open maildirsize'); + } + $definition = fgets($fh); + fclose($fh); + $definition = explode(',', trim($definition)); + $quota = array(); + foreach ($definition as $member) { + $key = $member[strlen($member) - 1]; + if ($key == 'S' || $key == 'C') { + $key = $key == 'C' ? 'count' : 'size'; + } + $quota[$key] = substr($member, 0, -1); + } + return $quota; + } + + return $this->_quota; + } + + /** + * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating maildirsize" + */ + protected function _calculateMaildirsize() { + $timestamps = array(); + $messages = 0; + $total_size = 0; + + if (is_array($this->_quota)) { + $quota = $this->_quota; + } else { + try { + $quota = $this->getQuota(true); + } catch (Zend_Mail_Storage_Exception $e) { + throw new Zend_Mail_Storage_Exception('no quota defintion found'); + } + } + + $folders = new RecursiveIteratorIterator($this->getFolders(), RecursiveIteratorIterator::SELF_FIRST); + foreach ($folders as $folder) { + $subdir = $folder->getGlobalName(); + if ($subdir == 'INBOX') { + $subdir = ''; + } else { + $subdir = '.' . $subdir; + } + if ($subdir == 'Trash') { + continue; + } + + foreach (array('cur', 'new') as $subsubdir) { + $dirname = $this->_rootdir . $subdir . DIRECTORY_SEPARATOR . $subsubdir . DIRECTORY_SEPARATOR; + if (!file_exists($dirname)) { + continue; + } + // NOTE: we are using mtime instead of "the latest timestamp". The latest would be atime + // and as we are accessing the directory it would make the whole calculation useless. + $timestamps[$dirname] = filemtime($dirname); + + $dh = opendir($dirname); + // NOTE: Should have been checked in constructor. Not throwing an exception here, quotas will + // therefore not be fully enforeced, but next request will fail anyway, if problem persists. + if (!$dh) { + continue; + } + + + while (($entry = readdir()) !== false) { + if ($entry[0] == '.' || !is_file($dirname . $entry)) { + continue; + } + + if (strpos($entry, ',S=')) { + strtok($entry, '='); + $filesize = strtok(':'); + if (is_numeric($filesize)) { + $total_size += $filesize; + ++$messages; + continue; + } + } + $size = filesize($dirname . $entry); + if ($size === false) { + // ignore, as we assume file got removed + continue; + } + $total_size += $size; + ++$messages; + } + } + } + + $tmp = $this->_createTmpFile(); + $fh = $tmp['handle']; + $definition = array(); + foreach ($quota as $type => $value) { + if ($type == 'size' || $type == 'count') { + $type = $type == 'count' ? 'C' : 'S'; + } + $definition[] = $value . $type; + } + $definition = implode(',', $definition); + fputs($fh, "$definition\n"); + fputs($fh, "$total_size $messages\n"); + fclose($fh); + rename($tmp['filename'], $this->_rootdir . 'maildirsize'); + foreach ($timestamps as $dir => $timestamp) { + if ($timestamp < filemtime($dir)) { + unlink($this->_rootdir . 'maildirsize'); + break; + } + } + + return array('size' => $total_size, 'count' => $messages, 'quota' => $quota); + } + + /** + * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating the quota for a Maildir++" + */ + protected function _calculateQuota($forceRecalc = false) { + $fh = null; + $total_size = 0; + $messages = 0; + $maildirsize = ''; + if (!$forceRecalc && file_exists($this->_rootdir . 'maildirsize') && filesize($this->_rootdir . 'maildirsize') < 5120) { + $fh = fopen($this->_rootdir . 'maildirsize', 'r'); + } + if ($fh) { + $maildirsize = fread($fh, 5120); + if (strlen($maildirsize) >= 5120) { + fclose($fh); + $fh = null; + $maildirsize = ''; + } + } + if (!$fh) { + $result = $this->_calculateMaildirsize(); + $total_size = $result['size']; + $messages = $result['count']; + $quota = $result['quota']; + } else { + $maildirsize = explode("\n", $maildirsize); + if (is_array($this->_quota)) { + $quota = $this->_quota; + } else { + $definition = explode(',', $maildirsize[0]); + $quota = array(); + foreach ($definition as $member) { + $key = $member[strlen($member) - 1]; + if ($key == 'S' || $key == 'C') { + $key = $key == 'C' ? 'count' : 'size'; + } + $quota[$key] = substr($member, 0, -1); + } + } + unset($maildirsize[0]); + foreach ($maildirsize as $line) { + list($size, $count) = explode(' ', trim($line)); + $total_size += $size; + $messages += $count; + } + } + + $over_quota = false; + $over_quota = $over_quota || (isset($quota['size']) && $total_size > $quota['size']); + $over_quota = $over_quota || (isset($quota['count']) && $messages > $quota['count']); + // NOTE: $maildirsize equals false if it wasn't set (AKA we recalculated) or it's only + // one line, because $maildirsize[0] gets unsetted. + // Also we're using local time to calculate the 15 minute offset. Touching a file just for known the + // local time of the file storage isn't worth the hassle. + if ($over_quota && ($maildirsize || filemtime($this->_rootdir . 'maildirsize') > time() - 900)) { + $result = $this->_calculateMaildirsize(); + $total_size = $result['size']; + $messages = $result['count']; + $quota = $result['quota']; + $over_quota = false; + $over_quota = $over_quota || (isset($quota['size']) && $total_size > $quota['size']); + $over_quota = $over_quota || (isset($quota['count']) && $messages > $quota['count']); + } + + if ($fh) { + // TODO is there a safe way to keep the handle open for writing? + fclose($fh); + } + + return array('size' => $total_size, 'count' => $messages, 'quota' => $quota, 'over_quota' => $over_quota); + } + + protected function _addQuotaEntry($size, $count = 1) { + if (!file_exists($this->_rootdir . 'maildirsize')) { + // TODO: should get file handler from _calculateQuota + } + $size = (int)$size; + $count = (int)$count; + file_put_contents($this->_rootdir . 'maildirsize', "$size $count\n", FILE_APPEND); + } + + /** + * check if storage is currently over quota + * + * @param bool $detailedResponse return known data of quota and current size and message count @see _calculateQuota() + * @return bool|array over quota state or detailed response + */ + public function checkQuota($detailedResponse = false, $forceRecalc = false) { + $result = $this->_calculateQuota($forceRecalc); + return $detailedResponse ? $result : $result['over_quota']; + } +} diff --git a/lib/Zend/Mail/Transport/Abstract.php b/lib/Zend/Mail/Transport/Abstract.php new file mode 100644 index 0000000..603411a --- /dev/null +++ b/lib/Zend/Mail/Transport/Abstract.php @@ -0,0 +1,350 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Transport + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Abstract.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Mime + */ +require_once 'Zend/Mime.php'; + + +/** + * Abstract for sending eMails through different + * ways of transport + * + * @category Zend + * @package Zend_Mail + * @subpackage Transport + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +abstract class Zend_Mail_Transport_Abstract +{ + /** + * Mail body + * @var string + * @access public + */ + public $body = ''; + + /** + * MIME boundary + * @var string + * @access public + */ + public $boundary = ''; + + /** + * Mail header string + * @var string + * @access public + */ + public $header = ''; + + /** + * Array of message headers + * @var array + * @access protected + */ + protected $_headers = array(); + + /** + * Message is a multipart message + * @var boolean + * @access protected + */ + protected $_isMultipart = false; + + /** + * Zend_Mail object + * @var false|Zend_Mail + * @access protected + */ + protected $_mail = false; + + /** + * Array of message parts + * @var array + * @access protected + */ + protected $_parts = array(); + + /** + * Recipients string + * @var string + * @access public + */ + public $recipients = ''; + + /** + * EOL character string used by transport + * @var string + * @access public + */ + public $EOL = "\r\n"; + + /** + * Send an email independent from the used transport + * + * The requisite information for the email will be found in the following + * properties: + * + * - {@link $recipients} - list of recipients (string) + * - {@link $header} - message header + * - {@link $body} - message body + */ + abstract protected function _sendMail(); + + /** + * Return all mail headers as an array + * + * If a boundary is given, a multipart header is generated with a + * Content-Type of either multipart/alternative or multipart/mixed depending + * on the mail parts present in the {@link $_mail Zend_Mail object} present. + * + * @param string $boundary + * @return array + */ + protected function _getHeaders($boundary) + { + if (null !== $boundary) { + // Build multipart mail + $type = $this->_mail->getType(); + if (!$type) { + if ($this->_mail->hasAttachments) { + $type = Zend_Mime::MULTIPART_MIXED; + } elseif ($this->_mail->getBodyText() && $this->_mail->getBodyHtml()) { + $type = Zend_Mime::MULTIPART_ALTERNATIVE; + } else { + $type = Zend_Mime::MULTIPART_MIXED; + } + } + + $this->_headers['Content-Type'] = array( + $type . '; charset="' . $this->_mail->getCharset() . '";' + . $this->EOL + . " " . 'boundary="' . $boundary . '"' + ); + $this->_headers['MIME-Version'] = array('1.0'); + + $this->boundary = $boundary; + } + + return $this->_headers; + } + + /** + * Prepend header name to header value + * + * @param string $item + * @param string $key + * @param string $prefix + * @static + * @access protected + * @return void + */ + protected static function _formatHeader(&$item, $key, $prefix) + { + $item = $prefix . ': ' . $item; + } + + /** + * Prepare header string for use in transport + * + * Prepares and generates {@link $header} based on the headers provided. + * + * @param mixed $headers + * @access protected + * @return void + * @throws Zend_Mail_Transport_Exception if any header lines exceed 998 + * characters + */ + protected function _prepareHeaders($headers) + { + if (!$this->_mail) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Transport_Exception('Missing Zend_Mail object in _mail property'); + } + + $this->header = ''; + + foreach ($headers as $header => $content) { + if (isset($content['append'])) { + unset($content['append']); + $value = implode(',' . $this->EOL . ' ', $content); + $this->header .= $header . ': ' . $value . $this->EOL; + } else { + array_walk($content, array(get_class($this), '_formatHeader'), $header); + $this->header .= implode($this->EOL, $content) . $this->EOL; + } + } + + // Sanity check on headers -- should not be > 998 characters + $sane = true; + foreach (explode($this->EOL, $this->header) as $line) { + if (strlen(trim($line)) > 998) { + $sane = false; + break; + } + } + if (!$sane) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Exception('At least one mail header line is too long'); + } + } + + /** + * Generate MIME compliant message from the current configuration + * + * If both a text and HTML body are present, generates a + * multipart/alternative Zend_Mime_Part containing the headers and contents + * of each. Otherwise, uses whichever of the text or HTML parts present. + * + * The content part is then prepended to the list of Zend_Mime_Parts for + * this message. + * + * @return void + */ + protected function _buildBody() + { + if (($text = $this->_mail->getBodyText()) + && ($html = $this->_mail->getBodyHtml())) + { + // Generate unique boundary for multipart/alternative + $mime = new Zend_Mime(null); + $boundaryLine = $mime->boundaryLine($this->EOL); + $boundaryEnd = $mime->mimeEnd($this->EOL); + + $text->disposition = false; + $html->disposition = false; + + $body = $boundaryLine + . $text->getHeaders($this->EOL) + . $this->EOL + . $text->getContent($this->EOL) + . $this->EOL + . $boundaryLine + . $html->getHeaders($this->EOL) + . $this->EOL + . $html->getContent($this->EOL) + . $this->EOL + . $boundaryEnd; + + $mp = new Zend_Mime_Part($body); + $mp->type = Zend_Mime::MULTIPART_ALTERNATIVE; + $mp->boundary = $mime->boundary(); + + $this->_isMultipart = true; + + // Ensure first part contains text alternatives + array_unshift($this->_parts, $mp); + + // Get headers + $this->_headers = $this->_mail->getHeaders(); + return; + } + + // If not multipart, then get the body + if (false !== ($body = $this->_mail->getBodyHtml())) { + array_unshift($this->_parts, $body); + } elseif (false !== ($body = $this->_mail->getBodyText())) { + array_unshift($this->_parts, $body); + } + + if (!$body) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Transport_Exception('No body specified'); + } + + // Get headers + $this->_headers = $this->_mail->getHeaders(); + $headers = $body->getHeadersArray($this->EOL); + foreach ($headers as $header) { + // Headers in Zend_Mime_Part are kept as arrays with two elements, a + // key and a value + $this->_headers[$header[0]] = array($header[1]); + } + } + + /** + * Send a mail using this transport + * + * @param Zend_Mail $mail + * @access public + * @return void + * @throws Zend_Mail_Transport_Exception if mail is empty + */ + public function send(Zend_Mail $mail) + { + $this->_isMultipart = false; + $this->_mail = $mail; + $this->_parts = $mail->getParts(); + $mime = $mail->getMime(); + + // Build body content + $this->_buildBody(); + + // Determine number of parts and boundary + $count = count($this->_parts); + $boundary = null; + if ($count < 1) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Transport_Exception('Empty mail cannot be sent'); + } + + if ($count > 1) { + // Multipart message; create new MIME object and boundary + $mime = new Zend_Mime($this->_mail->getMimeBoundary()); + $boundary = $mime->boundary(); + } elseif ($this->_isMultipart) { + // multipart/alternative -- grab boundary + $boundary = $this->_parts[0]->boundary; + } + + // Determine recipients, and prepare headers + $this->recipients = implode(',', $mail->getRecipients()); + $this->_prepareHeaders($this->_getHeaders($boundary)); + + // Create message body + // This is done so that the same Zend_Mail object can be used in + // multiple transports + $message = new Zend_Mime_Message(); + $message->setParts($this->_parts); + $message->setMime($mime); + $this->body = $message->generateMessage($this->EOL); + + // Send to transport! + $this->_sendMail(); + } +} diff --git a/lib/Zend/Mail/Transport/Exception.php b/lib/Zend/Mail/Transport/Exception.php new file mode 100644 index 0000000..16ef240 --- /dev/null +++ b/lib/Zend/Mail/Transport/Exception.php @@ -0,0 +1,39 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Transport + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Exception.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Mail_Exception + */ +require_once 'Zend/Mail/Exception.php'; + + +/** + * @category Zend + * @package Zend_Mail + * @subpackage Transport + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Transport_Exception extends Zend_Mail_Exception +{} + diff --git a/lib/Zend/Mail/Transport/Sendmail.php b/lib/Zend/Mail/Transport/Sendmail.php new file mode 100644 index 0000000..651df3c --- /dev/null +++ b/lib/Zend/Mail/Transport/Sendmail.php @@ -0,0 +1,170 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Transport + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Sendmail.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Mail_Transport_Abstract + */ +require_once 'Zend/Mail/Transport/Abstract.php'; + + +/** + * Class for sending eMails via the PHP internal mail() function + * + * @category Zend + * @package Zend_Mail + * @subpackage Transport + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Transport_Sendmail extends Zend_Mail_Transport_Abstract +{ + /** + * Subject + * @var string + * @access public + */ + public $subject = null; + + + /** + * Config options for sendmail parameters + * + * @var string + */ + public $parameters; + + + /** + * EOL character string + * @var string + * @access public + */ + public $EOL = PHP_EOL; + + + /** + * Constructor. + * + * @param string $parameters OPTIONAL (Default: null) + * @return void + */ + public function __construct($parameters = null) + { + $this->parameters = $parameters; + } + + + /** + * Send mail using PHP native mail() + * + * @access public + * @return void + * @throws Zend_Mail_Transport_Exception on mail() failure + */ + public function _sendMail() + { + if ($this->parameters === null) { + $result = mail( + $this->recipients, + $this->_mail->getSubject(), + $this->body, + $this->header); + } else { + $result = mail( + $this->recipients, + $this->_mail->getSubject(), + $this->body, + $this->header, + $this->parameters); + } + if (!$result) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Transport_Exception('Unable to send mail'); + } + } + + + /** + * Format and fix headers + * + * mail() uses its $to and $subject arguments to set the To: and Subject: + * headers, respectively. This method strips those out as a sanity check to + * prevent duplicate header entries. + * + * @access protected + * @param array $headers + * @return void + * @throws Zend_Mail_Transport_Exception + */ + protected function _prepareHeaders($headers) + { + if (!$this->_mail) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Transport_Exception('_prepareHeaders requires a registered Zend_Mail object'); + } + + // mail() uses its $to parameter to set the To: header, and the $subject + // parameter to set the Subject: header. We need to strip them out. + if (0 === strpos(PHP_OS, 'WIN')) { + // If the current recipients list is empty, throw an error + if (empty($this->recipients)) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Transport_Exception('Missing To addresses'); + } + } else { + // All others, simply grab the recipients and unset the To: header + if (!isset($headers['To'])) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Transport_Exception('Missing To header'); + } + + unset($headers['To']['append']); + $this->recipients = implode(',', $headers['To']); + } + + // Remove recipient header + unset($headers['To']); + + // Remove subject header, if present + if (isset($headers['Subject'])) { + unset($headers['Subject']); + } + + // Prepare headers + parent::_prepareHeaders($headers); + } + +} + diff --git a/lib/Zend/Mail/Transport/Smtp.php b/lib/Zend/Mail/Transport/Smtp.php new file mode 100644 index 0000000..714266e --- /dev/null +++ b/lib/Zend/Mail/Transport/Smtp.php @@ -0,0 +1,241 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mail + * @subpackage Transport + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Smtp.php 12519 2008-11-10 18:41:24Z alexander $ + */ + + +/** + * @see Zend_Loader + */ +require_once 'Zend/Loader.php'; + +/** + * @see Zend_Mime + */ +require_once 'Zend/Mime.php'; + +/** + * @see Zend_Mail_Protocol_Smtp + */ +require_once 'Zend/Mail/Protocol/Smtp.php'; + +/** + * @see Zend_Mail_Transport_Abstract + */ +require_once 'Zend/Mail/Transport/Abstract.php'; + + +/** + * SMTP connection object + * + * Loads an instance of Zend_Mail_Protocol_Smtp and forwards smtp transactions + * + * @category Zend + * @package Zend_Mail + * @subpackage Transport + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mail_Transport_Smtp extends Zend_Mail_Transport_Abstract +{ + /** + * EOL character string used by transport + * @var string + * @access public + */ + public $EOL = "\n"; + + /** + * Remote smtp hostname or i.p. + * + * @var string + */ + protected $_host; + + + /** + * Port number + * + * @var integer|null + */ + protected $_port; + + + /** + * Local client hostname or i.p. + * + * @var string + */ + protected $_name = 'localhost'; + + + /** + * Authentication type OPTIONAL + * + * @var string + */ + protected $_auth; + + + /** + * Config options for authentication + * + * @var array + */ + protected $_config; + + + /** + * Instance of Zend_Mail_Protocol_Smtp + * + * @var Zend_Mail_Protocol_Smtp + */ + protected $_connection; + + + /** + * Constructor. + * + * @param string $host OPTIONAL (Default: 127.0.0.1) + * @param array|null $config OPTIONAL (Default: null) + * @return void + */ + public function __construct($host = '127.0.0.1', Array $config = array()) + { + if (isset($config['name'])) { + $this->_name = $config['name']; + } + if (isset($config['port'])) { + $this->_port = $config['port']; + } + if (isset($config['auth'])) { + $this->_auth = $config['auth']; + } + + $this->_host = $host; + $this->_config = $config; + } + + + /** + * Class destructor to ensure all open connections are closed + * + * @return void + */ + public function __destruct() + { + if ($this->_connection instanceof Zend_Mail_Protocol_Smtp) { + try { + $this->_connection->quit(); + } catch (Zend_Mail_Protocol_Exception $e) { + // ignore + } + $this->_connection->disconnect(); + } + } + + + /** + * Sets the connection protocol instance + * + * @param Zend_Mail_Protocol_Abstract $client + * + * @return void + */ + public function setConnection(Zend_Mail_Protocol_Abstract $connection) + { + $this->_connection = $connection; + } + + + /** + * Gets the connection protocol instance + * + * @return Zend_Mail_Protocol|null + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * Send an email via the SMTP connection protocol + * + * The connection via the protocol adapter is made just-in-time to allow a + * developer to add a custom adapter if required before mail is sent. + * + * @return void + */ + public function _sendMail() + { + // If sending multiple messages per session use existing adapter + if (!($this->_connection instanceof Zend_Mail_Protocol_Smtp)) { + // Check if authentication is required and determine required class + $connectionClass = 'Zend_Mail_Protocol_Smtp'; + if ($this->_auth) { + $connectionClass .= '_Auth_' . ucwords($this->_auth); + } + Zend_Loader::loadClass($connectionClass); + $this->setConnection(new $connectionClass($this->_host, $this->_port, $this->_config)); + $this->_connection->connect(); + $this->_connection->helo($this->_name); + } else { + // Reset connection to ensure reliable transaction + $this->_connection->rset(); + } + + // Set mail return path from sender email address + $this->_connection->mail($this->_mail->getReturnPath()); + + // Set recipient forward paths + foreach ($this->_mail->getRecipients() as $recipient) { + $this->_connection->rcpt($recipient); + } + + // Issue DATA command to client + $this->_connection->data($this->header . Zend_Mime::LINEEND . $this->body); + } + + /** + * Format and fix headers + * + * Some SMTP servers do not strip BCC headers. Most clients do it themselves as do we. + * + * @access protected + * @param array $headers + * @return void + * @throws Zend_Transport_Exception + */ + protected function _prepareHeaders($headers) + { + if (!$this->_mail) { + /** + * @see Zend_Mail_Transport_Exception + */ + require_once 'Zend/Mail/Transport/Exception.php'; + throw new Zend_Mail_Transport_Exception('_prepareHeaders requires a registered Zend_Mail object'); + } + + unset($headers['Bcc']); + + // Prepare headers + parent::_prepareHeaders($headers); + } +} diff --git a/lib/Zend/Mime.php b/lib/Zend/Mime.php new file mode 100644 index 0000000..414e541 --- /dev/null +++ b/lib/Zend/Mime.php @@ -0,0 +1,365 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + + +/** + * Support class for MultiPart Mime Messages + * + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mime +{ + const TYPE_OCTETSTREAM = 'application/octet-stream'; + const TYPE_TEXT = 'text/plain'; + const TYPE_HTML = 'text/html'; + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_QUOTEDPRINTABLE = 'quoted-printable'; + const ENCODING_BASE64 = 'base64'; + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + const LINELENGTH = 72; + const LINEEND = "\n"; + const MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const MULTIPART_MIXED = 'multipart/mixed'; + const MULTIPART_RELATED = 'multipart/related'; + + protected $_boundary; + protected static $makeUnique = 0; + + // lookup-Tables for QuotedPrintable + public static $qpKeys = array( + "\x00","\x01","\x02","\x03","\x04","\x05","\x06","\x07", + "\x08","\x09","\x0A","\x0B","\x0C","\x0D","\x0E","\x0F", + "\x10","\x11","\x12","\x13","\x14","\x15","\x16","\x17", + "\x18","\x19","\x1A","\x1B","\x1C","\x1D","\x1E","\x1F", + "\x7F","\x80","\x81","\x82","\x83","\x84","\x85","\x86", + "\x87","\x88","\x89","\x8A","\x8B","\x8C","\x8D","\x8E", + "\x8F","\x90","\x91","\x92","\x93","\x94","\x95","\x96", + "\x97","\x98","\x99","\x9A","\x9B","\x9C","\x9D","\x9E", + "\x9F","\xA0","\xA1","\xA2","\xA3","\xA4","\xA5","\xA6", + "\xA7","\xA8","\xA9","\xAA","\xAB","\xAC","\xAD","\xAE", + "\xAF","\xB0","\xB1","\xB2","\xB3","\xB4","\xB5","\xB6", + "\xB7","\xB8","\xB9","\xBA","\xBB","\xBC","\xBD","\xBE", + "\xBF","\xC0","\xC1","\xC2","\xC3","\xC4","\xC5","\xC6", + "\xC7","\xC8","\xC9","\xCA","\xCB","\xCC","\xCD","\xCE", + "\xCF","\xD0","\xD1","\xD2","\xD3","\xD4","\xD5","\xD6", + "\xD7","\xD8","\xD9","\xDA","\xDB","\xDC","\xDD","\xDE", + "\xDF","\xE0","\xE1","\xE2","\xE3","\xE4","\xE5","\xE6", + "\xE7","\xE8","\xE9","\xEA","\xEB","\xEC","\xED","\xEE", + "\xEF","\xF0","\xF1","\xF2","\xF3","\xF4","\xF5","\xF6", + "\xF7","\xF8","\xF9","\xFA","\xFB","\xFC","\xFD","\xFE", + "\xFF" + ); + + public static $qpReplaceValues = array( + "=00","=01","=02","=03","=04","=05","=06","=07", + "=08","=09","=0A","=0B","=0C","=0D","=0E","=0F", + "=10","=11","=12","=13","=14","=15","=16","=17", + "=18","=19","=1A","=1B","=1C","=1D","=1E","=1F", + "=7F","=80","=81","=82","=83","=84","=85","=86", + "=87","=88","=89","=8A","=8B","=8C","=8D","=8E", + "=8F","=90","=91","=92","=93","=94","=95","=96", + "=97","=98","=99","=9A","=9B","=9C","=9D","=9E", + "=9F","=A0","=A1","=A2","=A3","=A4","=A5","=A6", + "=A7","=A8","=A9","=AA","=AB","=AC","=AD","=AE", + "=AF","=B0","=B1","=B2","=B3","=B4","=B5","=B6", + "=B7","=B8","=B9","=BA","=BB","=BC","=BD","=BE", + "=BF","=C0","=C1","=C2","=C3","=C4","=C5","=C6", + "=C7","=C8","=C9","=CA","=CB","=CC","=CD","=CE", + "=CF","=D0","=D1","=D2","=D3","=D4","=D5","=D6", + "=D7","=D8","=D9","=DA","=DB","=DC","=DD","=DE", + "=DF","=E0","=E1","=E2","=E3","=E4","=E5","=E6", + "=E7","=E8","=E9","=EA","=EB","=EC","=ED","=EE", + "=EF","=F0","=F1","=F2","=F3","=F4","=F5","=F6", + "=F7","=F8","=F9","=FA","=FB","=FC","=FD","=FE", + "=FF" + ); + + public static $qpKeysString = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"; + + /** + * Check if the given string is "printable" + * + * Checks that a string contains no unprintable characters. If this returns + * false, encode the string for secure delivery. + * + * @param string $str + * @return boolean + */ + public static function isPrintable($str) + { + return (strcspn($str, self::$qpKeysString) == strlen($str)); + } + + /** + * Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines. + * + * @param string $str + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param int $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeQuotedPrintable($str, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND) + { + $out = ''; + $str = self::_encodeQuotedPrintable($str); + + // Split encoded text into separate lines + while ($str) { + $ptr = strlen($str); + if ($ptr > $lineLength) { + $ptr = $lineLength; + } + + // Ensure we are not splitting across an encoded character + $pos = strrpos(substr($str, 0, $ptr), '='); + if ($pos !== false && $pos >= $ptr - 2) { + $ptr = $pos; + } + + // Check if there is a space at the end of the line and rewind + if ($ptr > 0 && $str[$ptr - 1] == ' ') { + --$ptr; + } + + // Add string and continue + $out .= substr($str, 0, $ptr) . '=' . $lineEnd; + $str = substr($str, $ptr); + } + + $out = rtrim($out, $lineEnd); + $out = rtrim($out, '='); + return $out; + } + + /** + * Converts a string into quoted printable format. + * + * @param string $str + * @return string + */ + private static function _encodeQuotedPrintable($str) + { + $str = str_replace('=', '=3D', $str); + $str = str_replace(self::$qpKeys, self::$qpReplaceValues, $str); + $str = rtrim($str); + return $str; + } + + /** + * Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers. + * + * Mail headers depend on an extended quoted printable algorithm otherwise + * a range of bugs can occur. + * + * @param string $str + * @param string $charset + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param int $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeQuotedPrintableHeader($str, $charset, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND) + { + // Reduce line-length by the length of the required delimiter, charsets and encoding + $prefix = sprintf('=?%s?Q?', $charset); + $lineLength = $lineLength-strlen($prefix)-3; + + $str = self::_encodeQuotedPrintable($str); + + // Mail-Header required chars have to be encoded also: + $str = str_replace(array('?', ' ', '_'), array('=3F', '=20', '=5F'), $str); + + // initialize first line, we need it anyways + $lines = array(0 => ""); + + // Split encoded text into separate lines + $tmp = ""; + while(strlen($str) > 0) { + $currentLine = max(count($lines)-1, 0); + $token = self::getNextQuotedPrintableToken($str); + $str = substr($str, strlen($token)); + + $tmp .= $token; + if( (strlen($token) == 1 && strpbrk($token, '!%,.:;<>')) + || in_array($token, array("=3F", "=20", "=5F")) ) { + // only if we have a single char token or space, we can append the + // tempstring it to the current line or start a new line if necessary. + if(strlen($lines[$currentLine].$tmp) > $lineLength) { + $lines[$currentLine+1] = $tmp; + } else { + $lines[$currentLine] .= $tmp; + } + $tmp = ""; + } + // don't forget to append the rest to the last line + if(strlen($str) == 0) { + $lines[$currentLine] .= $tmp; + } + } + + // assemble the lines together by pre- and appending delimiters, charset, encoding. + for($i = 0; $i < count($lines); $i++) { + $lines[$i] = " ".$prefix.$lines[$i]."?="; + } + $str = trim(implode($lineEnd, $lines)); + return $str; + } + + /** + * Retrieves the first token from a quoted printable string. + * + * @param string $str + * @return string + */ + private static function getNextQuotedPrintableToken($str) + { + if(substr($str, 0, 1) == "=") { + $token = substr($str, 0, 3); + } else { + $token = substr($str, 0, 1); + } + return $token; + } + + /** + * Encode a given string in mail header compatible base64 encoding. + * + * @param string $str + * @param string $charset + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param int $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeBase64Header($str, + $charset, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND) + { + $prefix = '=?' . $charset . '?B?'; + $suffix = '?='; + $remainingLength = $lineLength - strlen($prefix) - strlen($suffix); + + $encodedValue = self::encodeBase64($str, $remainingLength, $lineEnd); + $encodedValue = str_replace($lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue); + $encodedValue = $prefix . $encodedValue . $suffix; + return $encodedValue; + } + + /** + * Encode a given string in base64 encoding and break lines + * according to the maximum linelength. + * + * @param string $str + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param int $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeBase64($str, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND) + { + return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd)); + } + + /** + * Constructor + * + * @param null|string $boundary + * @access public + * @return void + */ + public function __construct($boundary = null) + { + // This string needs to be somewhat unique + if ($boundary === null) { + $this->_boundary = '=_' . md5(microtime(1) . self::$makeUnique++); + } else { + $this->_boundary = $boundary; + } + } + + /** + * Encode the given string with the given encoding. + * + * @param string $str + * @param string $encoding + * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND} + * @return string + */ + public static function encode($str, $encoding, $EOL = self::LINEEND) + { + switch ($encoding) { + case self::ENCODING_BASE64: + return self::encodeBase64($str, self::LINELENGTH, $EOL); + + case self::ENCODING_QUOTEDPRINTABLE: + return self::encodeQuotedPrintable($str, self::LINELENGTH, $EOL); + + default: + /** + * @todo 7Bit and 8Bit is currently handled the same way. + */ + return $str; + } + } + + /** + * Return a MIME boundary + * + * @access public + * @return string + */ + public function boundary() + { + return $this->_boundary; + } + + /** + * Return a MIME boundary line + * + * @param mixed $EOL Defaults to {@link LINEEND} + * @access public + * @return string + */ + public function boundaryLine($EOL = self::LINEEND) + { + return $EOL . '--' . $this->_boundary . $EOL; + } + + /** + * Return MIME ending + * + * @access public + * @return string + */ + public function mimeEnd($EOL = self::LINEEND) + { + return $EOL . '--' . $this->_boundary . '--' . $EOL; + } +} diff --git a/lib/Zend/Mime/Decode.php b/lib/Zend/Mime/Decode.php new file mode 100644 index 0000000..cc4de8b --- /dev/null +++ b/lib/Zend/Mime/Decode.php @@ -0,0 +1,243 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +/** + * @see Zend_Mime + */ +require_once 'Zend/Mime.php'; + +/** + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mime_Decode +{ + /** + * Explode MIME multipart string into seperate parts + * + * Parts consist of the header and the body of each MIME part. + * + * @param string $body raw body of message + * @param string $boundary boundary as found in content-type + * @return array parts with content of each part, empty if no parts found + * @throws Zend_Exception + */ + public static function splitMime($body, $boundary) + { + // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r? + $body = str_replace("\r", '', $body); + + $start = 0; + $res = array(); + // find every mime part limiter and cut out the + // string before it. + // the part before the first boundary string is discarded: + $p = strpos($body, '--' . $boundary . "\n", $start); + if ($p === false) { + // no parts found! + return array(); + } + + // position after first boundary line + $start = $p + 3 + strlen($boundary); + + while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) { + $res[] = substr($body, $start, $p-$start); + $start = $p + 3 + strlen($boundary); + } + + // no more parts, find end boundary + $p = strpos($body, '--' . $boundary . '--', $start); + if ($p===false) { + throw new Zend_Exception('Not a valid Mime Message: End Missing'); + } + + // the remaining part also needs to be parsed: + $res[] = substr($body, $start, $p-$start); + return $res; + } + + /** + * decodes a mime encoded String and returns a + * struct of parts with header and body + * + * @param string $message raw message content + * @param string $boundary boundary as found in content-type + * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND} + * @return array|null parts as array('header' => array(name => value), 'body' => content), null if no parts found + * @throws Zend_Exception + */ + public static function splitMessageStruct($message, $boundary, $EOL = Zend_Mime::LINEEND) + { + $parts = self::splitMime($message, $boundary); + if (count($parts) <= 0) { + return null; + } + $result = array(); + foreach ($parts as $part) { + self::splitMessage($part, $headers, $body, $EOL); + $result[] = array('header' => $headers, + 'body' => $body ); + } + return $result; + } + + /** + * split a message in header and body part, if no header or an + * invalid header is found $headers is empty + * + * The charset of the returned headers depend on your iconv settings. + * + * @param string $message raw message with header and optional content + * @param array $headers output param, array with headers as array(name => value) + * @param string $body output param, content of message + * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND} + * @return null + */ + public static function splitMessage($message, &$headers, &$body, $EOL = Zend_Mime::LINEEND) + { + // check for valid header at first line + $firstline = strtok($message, "\n"); + if (!preg_match('%^[^\s]+[^:]*:%', $firstline)) { + $headers = array(); + // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r? + $body = str_replace(array("\r", "\n"), array('', $EOL), $message); + return; + } + + // find an empty line between headers and body + // default is set new line + if (strpos($message, $EOL . $EOL)) { + list($headers, $body) = explode($EOL . $EOL, $message, 2); + // next is the standard new line + } else if ($EOL != "\r\n" && strpos($message, "\r\n\r\n")) { + list($headers, $body) = explode("\r\n\r\n", $message, 2); + // next is the other "standard" new line + } else if ($EOL != "\n" && strpos($message, "\n\n")) { + list($headers, $body) = explode("\n\n", $message, 2); + // at last resort find anything that looks like a new line + } else { + @list($headers, $body) = @preg_split("%([\r\n]+)\\1%U", $message, 2); + } + + $headers = iconv_mime_decode_headers($headers, ICONV_MIME_DECODE_CONTINUE_ON_ERROR); + + if ($headers === false ) { + // an error occurs during the decoding + return; + } + + // normalize header names + foreach ($headers as $name => $header) { + $lower = strtolower($name); + if ($lower == $name) { + continue; + } + unset($headers[$name]); + if (!isset($headers[$lower])) { + $headers[$lower] = $header; + continue; + } + if (is_array($headers[$lower])) { + $headers[$lower][] = $header; + continue; + } + $headers[$lower] = array($headers[$lower], $header); + } + } + + /** + * split a content type in its different parts + * + * @param string $type content-type + * @param string $wantedPart the wanted part, else an array with all parts is returned + * @return string|array wanted part or all parts as array('type' => content-type, partname => value) + */ + public static function splitContentType($type, $wantedPart = null) + { + return self::splitHeaderField($type, $wantedPart, 'type'); + } + + /** + * split a header field like content type in its different parts + * + * @param string $type header field + * @param string $wantedPart the wanted part, else an array with all parts is returned + * @param string $firstName key name for the first part + * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) + * @throws Zend_Exception + */ + public static function splitHeaderField($field, $wantedPart = null, $firstName = 0) + { + $wantedPart = strtolower($wantedPart); + $firstName = strtolower($firstName); + + // special case - a bit optimized + if ($firstName === $wantedPart) { + $field = strtok($field, ';'); + return $field[0] == '"' ? substr($field, 1, -1) : $field; + } + + $field = $firstName . '=' . $field; + if (!preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) { + throw new Zend_Exception('not a valid header field'); + } + + if ($wantedPart) { + foreach ($matches[1] as $key => $name) { + if (strcasecmp($name, $wantedPart)) { + continue; + } + if ($matches[2][$key][0] != '"') { + return $matches[2][$key]; + } + return substr($matches[2][$key], 1, -1); + } + return null; + } + + $split = array(); + foreach ($matches[1] as $key => $name) { + $name = strtolower($name); + if ($matches[2][$key][0] == '"') { + $split[$name] = substr($matches[2][$key], 1, -1); + } else { + $split[$name] = $matches[2][$key]; + } + } + + return $split; + } + + /** + * decode a quoted printable encoded string + * + * The charset of the returned string depends on your iconv settings. + * + * @param string encoded string + * @return string decoded string + */ + public static function decodeQuotedPrintable($string) + { + return iconv_mime_decode($string, ICONV_MIME_DECODE_CONTINUE_ON_ERROR); + } +} diff --git a/lib/Zend/Mime/Exception.php b/lib/Zend/Mime/Exception.php new file mode 100644 index 0000000..2c22e9b --- /dev/null +++ b/lib/Zend/Mime/Exception.php @@ -0,0 +1,36 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + + +/** + * Zend_Exception + */ +require_once 'Zend/Exception.php'; + + +/** + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mime_Exception extends Zend_Exception +{} + diff --git a/lib/Zend/Mime/Message.php b/lib/Zend/Mime/Message.php new file mode 100644 index 0000000..613be1d --- /dev/null +++ b/lib/Zend/Mime/Message.php @@ -0,0 +1,285 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + + +/** + * Zend_Mime + */ +require_once 'Zend/Mime.php'; + +/** + * Zend_Mime_Part + */ +require_once 'Zend/Mime/Part.php'; + + +/** + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mime_Message +{ + + protected $_parts = array(); + protected $_mime = null; + + /** + * Returns the list of all Zend_Mime_Parts in the message + * + * @return array of Zend_Mime_Part + */ + public function getParts() + { + return $this->_parts; + } + + /** + * Sets the given array of Zend_Mime_Parts as the array for the message + * + * @param array $parts + */ + public function setParts($parts) + { + $this->_parts = $parts; + } + + /** + * Append a new Zend_Mime_Part to the current message + * + * @param Zend_Mime_Part $part + */ + public function addPart(Zend_Mime_Part $part) + { + /** + * @todo check for duplicate object handle + */ + $this->_parts[] = $part; + } + + /** + * Check if message needs to be sent as multipart + * MIME message or if it has only one part. + * + * @return boolean + */ + public function isMultiPart() + { + return (count($this->_parts) > 1); + } + + /** + * Set Zend_Mime object for the message + * + * This can be used to set the boundary specifically or to use a subclass of + * Zend_Mime for generating the boundary. + * + * @param Zend_Mime $mime + */ + public function setMime(Zend_Mime $mime) + { + $this->_mime = $mime; + } + + /** + * Returns the Zend_Mime object in use by the message + * + * If the object was not present, it is created and returned. Can be used to + * determine the boundary used in this message. + * + * @return Zend_Mime + */ + public function getMime() + { + if ($this->_mime === null) { + $this->_mime = new Zend_Mime(); + } + + return $this->_mime; + } + + /** + * Generate MIME-compliant message from the current configuration + * + * This can be a multipart message if more than one MIME part was added. If + * only one part is present, the content of this part is returned. If no + * part had been added, an empty string is returned. + * + * Parts are seperated by the mime boundary as defined in Zend_Mime. If + * {@link setMime()} has been called before this method, the Zend_Mime + * object set by this call will be used. Otherwise, a new Zend_Mime object + * is generated and used. + * + * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND} + * @return string + */ + public function generateMessage($EOL = Zend_Mime::LINEEND) + { + if (! $this->isMultiPart()) { + $body = array_shift($this->_parts); + $body = $body->getContent($EOL); + } else { + $mime = $this->getMime(); + + $boundaryLine = $mime->boundaryLine($EOL); + $body = 'This is a message in Mime Format. If you see this, ' + . "your mail reader does not support this format." . $EOL; + + foreach (array_keys($this->_parts) as $p) { + $body .= $boundaryLine + . $this->getPartHeaders($p, $EOL) + . $EOL + . $this->getPartContent($p, $EOL); + } + + $body .= $mime->mimeEnd($EOL); + } + + return trim($body); + } + + /** + * Get the headers of a given part as an array + * + * @param int $partnum + * @return array + */ + public function getPartHeadersArray($partnum) + { + return $this->_parts[$partnum]->getHeadersArray(); + } + + /** + * Get the headers of a given part as a string + * + * @param int $partnum + * @return string + */ + public function getPartHeaders($partnum, $EOL = Zend_Mime::LINEEND) + { + return $this->_parts[$partnum]->getHeaders($EOL); + } + + /** + * Get the (encoded) content of a given part as a string + * + * @param int $partnum + * @return string + */ + public function getPartContent($partnum, $EOL = Zend_Mime::LINEEND) + { + return $this->_parts[$partnum]->getContent($EOL); + } + + /** + * Explode MIME multipart string into seperate parts + * + * Parts consist of the header and the body of each MIME part. + * + * @param string $body + * @param string $boundary + * @return array + */ + protected static function _disassembleMime($body, $boundary) + { + $start = 0; + $res = array(); + // find every mime part limiter and cut out the + // string before it. + // the part before the first boundary string is discarded: + $p = strpos($body, '--'.$boundary."\n", $start); + if ($p === false) { + // no parts found! + return array(); + } + + // position after first boundary line + $start = $p + 3 + strlen($boundary); + + while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) { + $res[] = substr($body, $start, $p-$start); + $start = $p + 3 + strlen($boundary); + } + + // no more parts, find end boundary + $p = strpos($body, '--' . $boundary . '--', $start); + if ($p===false) { + throw new Zend_Exception('Not a valid Mime Message: End Missing'); + } + + // the remaining part also needs to be parsed: + $res[] = substr($body, $start, $p-$start); + return $res; + } + + /** + * Decodes a MIME encoded string and returns a Zend_Mime_Message object with + * all the MIME parts set according to the given string + * + * @param string $message + * @param string $boundary + * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND} + * @return Zend_Mime_Message + */ + public static function createFromMessage($message, $boundary, $EOL = Zend_Mime::LINEEND) + { + require_once 'Zend/Mime/Decode.php'; + $parts = Zend_Mime_Decode::splitMessageStruct($message, $boundary, $EOL); + + $res = new self(); + foreach ($parts as $part) { + // now we build a new MimePart for the current Message Part: + $newPart = new Zend_Mime_Part($part['body']); + foreach ($part['header'] as $key => $value) { + /** + * @todo check for characterset and filename + */ + switch(strtolower($key)) { + case 'content-type': + $newPart->type = $value; + break; + case 'content-transfer-encoding': + $newPart->encoding = $value; + break; + case 'content-id': + $newPart->id = trim($value,'<>'); + break; + case 'content-disposition': + $newPart->disposition = $value; + break; + case 'content-description': + $newPart->description = $value; + break; + case 'content-location': + $newPart->location = $value; + break; + case 'content-language': + $newPart->language = $value; + break; + default: + throw new Zend_Exception('Unknown header ignored for MimePart:' . $key); + } + } + $res->addPart($newPart); + } + return $res; + } +} diff --git a/lib/Zend/Mime/Part.php b/lib/Zend/Mime/Part.php new file mode 100644 index 0000000..a3e261b --- /dev/null +++ b/lib/Zend/Mime/Part.php @@ -0,0 +1,216 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ + +/** + * Zend_Mime + */ +require_once 'Zend/Mime.php'; + +/** + * Class representing a MIME part. + * + * @category Zend + * @package Zend_Mime + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Mime_Part { + + public $type = Zend_Mime::TYPE_OCTETSTREAM; + public $encoding = Zend_Mime::ENCODING_8BIT; + public $id; + public $disposition; + public $filename; + public $description; + public $charset; + public $boundary; + public $location; + public $language; + protected $_content; + protected $_isStream = false; + + + /** + * create a new Mime Part. + * The (unencoded) content of the Part as passed + * as a string or stream + * + * @param mixed $content String or Stream containing the content + */ + public function __construct($content) + { + $this->_content = $content; + if (is_resource($content)) { + $this->_isStream = true; + } + } + + /** + * @todo setters/getters + * @todo error checking for setting $type + * @todo error checking for setting $encoding + */ + + /** + * check if this part can be read as a stream. + * if true, getEncodedStream can be called, otherwise + * only getContent can be used to fetch the encoded + * content of the part + * + * @return bool + */ + public function isStream() + { + return $this->_isStream; + } + + /** + * if this was created with a stream, return a filtered stream for + * reading the content. very useful for large file attachments. + * + * @return stream + * @throws Zend_Mime_Exception if not a stream or unable to append filter + */ + public function getEncodedStream() + { + if (!$this->_isStream) { + require_once 'Zend/Mime/Exception.php'; + throw new Zend_Mime_Exception('Attempt to get a stream from a string part'); + } + + //stream_filter_remove(); // ??? is that right? + switch ($this->encoding) { + case Zend_Mime::ENCODING_QUOTEDPRINTABLE: + $filter = stream_filter_append( + $this->_content, + 'convert.quoted-printable-encode', + STREAM_FILTER_READ, + array( + 'line-length' => 76, + 'line-break-chars' => Zend_Mime::LINEEND + ) + ); + if (!is_resource($filter)) { + require_once 'Zend/Mime/Exception.php'; + throw new Zend_Mime_Exception('Failed to append quoted-printable filter'); + } + break; + case Zend_Mime::ENCODING_BASE64: + $filter = stream_filter_append( + $this->_content, + 'convert.base64-encode', + STREAM_FILTER_READ, + array( + 'line-length' => 76, + 'line-break-chars' => Zend_Mime::LINEEND + ) + ); + if (!is_resource($filter)) { + require_once 'Zend/Mime/Exception.php'; + throw new Zend_Mime_Exception('Failed to append base64 filter'); + } + break; + default: + } + return $this->_content; + } + + /** + * Get the Content of the current Mime Part in the given encoding. + * + * @return String + */ + public function getContent($EOL = Zend_Mime::LINEEND) + { + if ($this->_isStream) { + return stream_get_contents($this->getEncodedStream()); + } else { + return Zend_Mime::encode($this->_content, $this->encoding, $EOL); + } + } + + /** + * Create and return the array of headers for this MIME part + * + * @access public + * @return array + */ + public function getHeadersArray($EOL = Zend_Mime::LINEEND) + { + $headers = array(); + + $contentType = $this->type; + if ($this->charset) { + $contentType .= '; charset="' . $this->charset . '"'; + } + + if ($this->boundary) { + $contentType .= ';' . $EOL + . " boundary=\"" . $this->boundary . '"'; + } + + $headers[] = array('Content-Type', $contentType); + + if ($this->encoding) { + $headers[] = array('Content-Transfer-Encoding', $this->encoding); + } + + if ($this->id) { + $headers[] = array('Content-ID', '<' . $this->id . '>'); + } + + if ($this->disposition) { + $disposition = $this->disposition; + if ($this->filename) { + $disposition .= '; filename="' . $this->filename . '"'; + } + $headers[] = array('Content-Disposition', $disposition); + } + + if ($this->description) { + $headers[] = array('Content-Description', $this->description); + } + + if ($this->location) { + $headers[] = array('Content-Location', $this->location); + } + + if ($this->language){ + $headers[] = array('Content-Language', $this->language); + } + + return $headers; + } + + /** + * Return the headers for this part as a string + * + * @return String + */ + public function getHeaders($EOL = Zend_Mime::LINEEND) + { + $res = ''; + foreach ($this->getHeadersArray($EOL) as $header) { + $res .= $header[0] . ': ' . $header[1] . $EOL; + } + + return $res; + } +} diff --git a/lib/Zend/Registry.php b/lib/Zend/Registry.php new file mode 100644 index 0000000..2bc8814 --- /dev/null +++ b/lib/Zend/Registry.php @@ -0,0 +1,207 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Registry + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Registry.php 12065 2008-10-21 20:56:32Z doctorrock83 $ + */ + +/** + * Generic storage class helps to manage global data. + * + * @category Zend + * @package Zend_Registry + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Registry extends ArrayObject +{ + /** + * Class name of the singleton registry object. + * @var string + */ + private static $_registryClassName = 'Zend_Registry'; + + /** + * Registry object provides storage for shared objects. + * @var Zend_Registry + */ + private static $_registry = null; + + /** + * Retrieves the default registry instance. + * + * @return Zend_Registry + */ + public static function getInstance() + { + if (self::$_registry === null) { + self::init(); + } + + return self::$_registry; + } + + /** + * Set the default registry instance to a specified instance. + * + * @param Zend_Registry $registry An object instance of type Zend_Registry, + * or a subclass. + * @return void + * @throws Zend_Exception if registry is already initialized. + */ + public static function setInstance(Zend_Registry $registry) + { + if (self::$_registry !== null) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception('Registry is already initialized'); + } + + self::setClassName(get_class($registry)); + self::$_registry = $registry; + } + + /** + * Initialize the default registry instance. + * + * @return void + */ + protected static function init() + { + self::setInstance(new self::$_registryClassName()); + } + + /** + * Set the class name to use for the default registry instance. + * Does not affect the currently initialized instance, it only applies + * for the next time you instantiate. + * + * @param string $registryClassName + * @return void + * @throws Zend_Exception if the registry is initialized or if the + * class name is not valid. + */ + public static function setClassName($registryClassName = 'Zend_Registry') + { + if (self::$_registry !== null) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception('Registry is already initialized'); + } + + if (!is_string($registryClassName)) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception("Argument is not a class name"); + } + + /** + * @see Zend_Loader + */ + require_once 'Zend/Loader.php'; + Zend_Loader::loadClass($registryClassName); + + self::$_registryClassName = $registryClassName; + } + + /** + * Unset the default registry instance. + * Primarily used in tearDown() in unit tests. + * @returns void + */ + public static function _unsetInstance() + { + self::$_registry = null; + } + + /** + * getter method, basically same as offsetGet(). + * + * This method can be called from an object of type Zend_Registry, or it + * can be called statically. In the latter case, it uses the default + * static instance stored in the class. + * + * @param string $index - get the value associated with $index + * @return mixed + * @throws Zend_Exception if no entry is registerd for $index. + */ + public static function get($index) + { + $instance = self::getInstance(); + + if (!$instance->offsetExists($index)) { + require_once 'Zend/Exception.php'; + throw new Zend_Exception("No entry is registered for key '$index'"); + } + + return $instance->offsetGet($index); + } + + /** + * setter method, basically same as offsetSet(). + * + * This method can be called from an object of type Zend_Registry, or it + * can be called statically. In the latter case, it uses the default + * static instance stored in the class. + * + * @param string $index The location in the ArrayObject in which to store + * the value. + * @param mixed $value The object to store in the ArrayObject. + * @return void + */ + public static function set($index, $value) + { + $instance = self::getInstance(); + $instance->offsetSet($index, $value); + } + + /** + * Returns TRUE if the $index is a named value in the registry, + * or FALSE if $index was not found in the registry. + * + * @param string $index + * @return boolean + */ + public static function isRegistered($index) + { + if (self::$_registry === null) { + return false; + } + return self::$_registry->offsetExists($index); + } + + /** + * Constructs a parent ArrayObject with default + * ARRAY_AS_PROPS to allow acces as an object + * + * @param array $array data array + * @param integer $flags ArrayObject flags + */ + public function __construct($array = array(), $flags = parent::ARRAY_AS_PROPS) + { + parent::__construct($array, $flags); + } + + /** + * @param string $index + * @returns mixed + * + * Workaround for http://bugs.php.net/bug.php?id=40442 (ZF-960). + */ + public function offsetExists($index) + { + return array_key_exists($index, $this); + } + +} diff --git a/lib/Zend/Validate.php b/lib/Zend/Validate.php new file mode 100644 index 0000000..b57930a --- /dev/null +++ b/lib/Zend/Validate.php @@ -0,0 +1,171 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Validate.php 8729 2008-03-10 11:44:10Z thomas $ + */ + + +/** + * @see Zend_Validate_Interface + */ +require_once 'Zend/Validate/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate implements Zend_Validate_Interface +{ + /** + * Validator chain + * + * @var array + */ + protected $_validators = array(); + + /** + * Array of validation failure messages + * + * @var array + */ + protected $_messages = array(); + + /** + * Array of validation failure message codes + * + * @var array + * @deprecated Since 1.5.0 + */ + protected $_errors = array(); + + /** + * Adds a validator to the end of the chain + * + * If $breakChainOnFailure is true, then if the validator fails, the next validator in the chain, + * if one exists, will not be executed. + * + * @param Zend_Validate_Interface $validator + * @param boolean $breakChainOnFailure + * @return Zend_Validate Provides a fluent interface + */ + public function addValidator(Zend_Validate_Interface $validator, $breakChainOnFailure = false) + { + $this->_validators[] = array( + 'instance' => $validator, + 'breakChainOnFailure' => (boolean) $breakChainOnFailure + ); + return $this; + } + + /** + * Returns true if and only if $value passes all validations in the chain + * + * Validators are run in the order in which they were added to the chain (FIFO). + * + * @param mixed $value + * @return boolean + */ + public function isValid($value) + { + $this->_messages = array(); + $this->_errors = array(); + $result = true; + foreach ($this->_validators as $element) { + $validator = $element['instance']; + if ($validator->isValid($value)) { + continue; + } + $result = false; + $messages = $validator->getMessages(); + $this->_messages = array_merge($this->_messages, $messages); + $this->_errors = array_merge($this->_errors, array_keys($messages)); + if ($element['breakChainOnFailure']) { + break; + } + } + return $result; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns array of validation failure messages + * + * @return array + */ + public function getMessages() + { + return $this->_messages; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns array of validation failure message codes + * + * @return array + * @deprecated Since 1.5.0 + */ + public function getErrors() + { + return $this->_errors; + } + + /** + * @param mixed $value + * @param string $classBaseName + * @param array $args OPTIONAL + * @param mixed $namespaces OPTIONAL + * @return boolean + * @throws Zend_Validate_Exception + */ + public static function is($value, $classBaseName, array $args = array(), $namespaces = array()) + { + $namespaces = array_merge(array('Zend_Validate'), (array) $namespaces); + foreach ($namespaces as $namespace) { + $className = $namespace . '_' . ucfirst($classBaseName); + try { + require_once 'Zend/Loader.php'; + @Zend_Loader::loadClass($className); + if (class_exists($className, false)) { + $class = new ReflectionClass($className); + if ($class->implementsInterface('Zend_Validate_Interface')) { + if ($class->hasMethod('__construct')) { + $object = $class->newInstanceArgs($args); + } else { + $object = $class->newInstance(); + } + return $object->isValid($value); + } + } + } catch (Zend_Validate_Exception $ze) { + // if there is an exception while validating throw it + throw $ze; + } catch (Zend_Exception $ze) { + // fallthrough and continue for missing validation classes + } + } + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("Validate class not found from basename '$classBaseName'"); + } + +} diff --git a/lib/Zend/Validate/Abstract.php b/lib/Zend/Validate/Abstract.php new file mode 100644 index 0000000..66566a9 --- /dev/null +++ b/lib/Zend/Validate/Abstract.php @@ -0,0 +1,358 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Abstract.php 13351 2008-12-18 15:26:14Z alexander $ + */ + + +/** + * @see Zend_Validate_Interface + */ +require_once 'Zend/Validate/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +abstract class Zend_Validate_Abstract implements Zend_Validate_Interface +{ + /** + * The value to be validated + * + * @var mixed + */ + protected $_value; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $_messageVariables = array(); + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array(); + + /** + * Array of validation failure messages + * + * @var array + */ + protected $_messages = array(); + + /** + * Flag indidcating whether or not value should be obfuscated in error + * messages + * @var bool + */ + protected $_obscureValue = false; + + /** + * Array of validation failure message codes + * + * @var array + * @deprecated Since 1.5.0 + */ + protected $_errors = array(); + + /** + * Translation object + * @var Zend_Translate + */ + protected $_translator; + + /** + * Default translation object for all validate objects + * @var Zend_Translate + */ + protected static $_defaultTranslator; + + /** + * Returns array of validation failure messages + * + * @return array + */ + public function getMessages() + { + return $this->_messages; + } + + /** + * Returns an array of the names of variables that are used in constructing validation failure messages + * + * @return array + */ + public function getMessageVariables() + { + return array_keys($this->_messageVariables); + } + + /** + * Sets the validation failure message template for a particular key + * + * @param string $messageString + * @param string $messageKey OPTIONAL + * @return Zend_Validate_Abstract Provides a fluent interface + * @throws Zend_Validate_Exception + */ + public function setMessage($messageString, $messageKey = null) + { + if ($messageKey === null) { + $keys = array_keys($this->_messageTemplates); + $messageKey = current($keys); + } + if (!isset($this->_messageTemplates[$messageKey])) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("No message template exists for key '$messageKey'"); + } + $this->_messageTemplates[$messageKey] = $messageString; + return $this; + } + + /** + * Sets validation failure message templates given as an array, where the array keys are the message keys, + * and the array values are the message template strings. + * + * @param array $messages + * @return Zend_Validate_Abstract + */ + public function setMessages(array $messages) + { + foreach ($messages as $key => $message) { + $this->setMessage($message, $key); + } + return $this; + } + + /** + * Magic function returns the value of the requested property, if and only if it is the value or a + * message variable. + * + * @param string $property + * @return mixed + * @throws Zend_Validate_Exception + */ + public function __get($property) + { + if ($property == 'value') { + return $this->_value; + } + if (array_key_exists($property, $this->_messageVariables)) { + return $this->{$this->_messageVariables[$property]}; + } + /** + * @see Zend_Validate_Exception + */ + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("No property exists by the name '$property'"); + } + + /** + * Constructs and returns a validation failure message with the given message key and value. + * + * Returns null if and only if $messageKey does not correspond to an existing template. + * + * If a translator is available and a translation exists for $messageKey, + * the translation will be used. + * + * @param string $messageKey + * @param string $value + * @return string + */ + protected function _createMessage($messageKey, $value) + { + if (!isset($this->_messageTemplates[$messageKey])) { + return null; + } + + $message = $this->_messageTemplates[$messageKey]; + + if (null !== ($translator = $this->getTranslator())) { + if ($translator->isTranslated($message)) { + $message = $translator->translate($message); + } elseif ($translator->isTranslated($messageKey)) { + $message = $translator->translate($messageKey); + } + } + + if (is_object($value)) { + if (!in_array('__toString', get_class_methods($value))) { + $value = get_class($value) . ' object'; + } else { + $value = $value->__toString(); + } + } else { + $value = (string)$value; + } + + if ($this->getObscureValue()) { + $value = str_repeat('*', strlen($value)); + } + + $message = str_replace('%value%', (string) $value, $message); + foreach ($this->_messageVariables as $ident => $property) { + $message = str_replace("%$ident%", (string) $this->$property, $message); + } + return $message; + } + + /** + * @param string $messageKey OPTIONAL + * @param string $value OPTIONAL + * @return void + */ + protected function _error($messageKey = null, $value = null) + { + if ($messageKey === null) { + $keys = array_keys($this->_messageTemplates); + $messageKey = current($keys); + } + if ($value === null) { + $value = $this->_value; + } + $this->_errors[] = $messageKey; + $this->_messages[$messageKey] = $this->_createMessage($messageKey, $value); + } + + /** + * Sets the value to be validated and clears the messages and errors arrays + * + * @param mixed $value + * @return void + */ + protected function _setValue($value) + { + $this->_value = $value; + $this->_messages = array(); + $this->_errors = array(); + } + + /** + * Returns array of validation failure message codes + * + * @return array + * @deprecated Since 1.5.0 + */ + public function getErrors() + { + return $this->_errors; + } + + /** + * Set flag indicating whether or not value should be obfuscated in messages + * + * @param bool $flag + * @return Zend_Validate_Abstract + */ + public function setObscureValue($flag) + { + $this->_obscureValue = (bool) $flag; + return $this; + } + + /** + * Retrieve flag indicating whether or not value should be obfuscated in + * messages + * + * @return bool + */ + public function getObscureValue() + { + return $this->_obscureValue; + } + + /** + * Set translation object + * + * @param Zend_Translate|Zend_Translate_Adapter|null $translator + * @return Zend_Validate_Abstract + */ + public function setTranslator($translator = null) + { + if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) { + $this->_translator = $translator; + } elseif ($translator instanceof Zend_Translate) { + $this->_translator = $translator->getAdapter(); + } else { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Invalid translator specified'); + } + return $this; + } + + /** + * Return translation object + * + * @return Zend_Translate_Adapter|null + */ + public function getTranslator() + { + if (null === $this->_translator) { + return self::getDefaultTranslator(); + } + + return $this->_translator; + } + + /** + * Set default translation object for all validate objects + * + * @param Zend_Translate|Zend_Translate_Adapter|null $translator + * @return void + */ + public static function setDefaultTranslator($translator = null) + { + if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) { + self::$_defaultTranslator = $translator; + } elseif ($translator instanceof Zend_Translate) { + self::$_defaultTranslator = $translator->getAdapter(); + } else { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Invalid translator specified'); + } + } + + /** + * Get default translation object for all validate objects + * + * @return Zend_Translate_Adapter|null + */ + public static function getDefaultTranslator() + { + if (null === self::$_defaultTranslator) { + require_once 'Zend/Registry.php'; + if (Zend_Registry::isRegistered('Zend_Translate')) { + $translator = Zend_Registry::get('Zend_Translate'); + if ($translator instanceof Zend_Translate_Adapter) { + return $translator; + } elseif ($translator instanceof Zend_Translate) { + return $translator->getAdapter(); + } + } + } + return self::$_defaultTranslator; + } +} diff --git a/lib/Zend/Validate/Alnum.php b/lib/Zend/Validate/Alnum.php new file mode 100644 index 0000000..36b0adc --- /dev/null +++ b/lib/Zend/Validate/Alnum.php @@ -0,0 +1,120 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Alnum.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Alnum extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value contains non-alphabetic or non-digit characters + */ + const NOT_ALNUM = 'notAlnum'; + + /** + * Validation failure message key for when the value is an empty string + */ + const STRING_EMPTY = 'stringEmpty'; + + /** + * Whether to allow white space characters; off by default + * + * @var boolean + */ + public $allowWhiteSpace; + + /** + * Alphanumeric filter used for validation + * + * @var Zend_Filter_Alnum + */ + protected static $_filter = null; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::NOT_ALNUM => "'%value%' has not only alphabetic and digit characters", + self::STRING_EMPTY => "'%value%' is an empty string" + ); + + /** + * Sets default option values for this instance + * + * @param boolean $allowWhiteSpace + * @return void + */ + public function __construct($allowWhiteSpace = false) + { + $this->allowWhiteSpace = (boolean) $allowWhiteSpace; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value contains only alphabetic and digit characters + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + if ('' === $valueString) { + $this->_error(self::STRING_EMPTY); + return false; + } + + if (null === self::$_filter) { + /** + * @see Zend_Filter_Alnum + */ + require_once 'Zend/Filter/Alnum.php'; + self::$_filter = new Zend_Filter_Alnum(); + } + + self::$_filter->allowWhiteSpace = $this->allowWhiteSpace; + + if ($valueString !== self::$_filter->filter($valueString)) { + $this->_error(self::NOT_ALNUM); + return false; + } + + return true; + } + +} diff --git a/lib/Zend/Validate/Alpha.php b/lib/Zend/Validate/Alpha.php new file mode 100644 index 0000000..0f2298e --- /dev/null +++ b/lib/Zend/Validate/Alpha.php @@ -0,0 +1,120 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Alpha.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Alpha extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value contains non-alphabetic characters + */ + const NOT_ALPHA = 'notAlpha'; + + /** + * Validation failure message key for when the value is an empty string + */ + const STRING_EMPTY = 'stringEmpty'; + + /** + * Whether to allow white space characters; off by default + * + * @var boolean + */ + public $allowWhiteSpace; + + /** + * Alphabetic filter used for validation + * + * @var Zend_Filter_Alpha + */ + protected static $_filter = null; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::NOT_ALPHA => "'%value%' has not only alphabetic characters", + self::STRING_EMPTY => "'%value%' is an empty string" + ); + + /** + * Sets default option values for this instance + * + * @param boolean $allowWhiteSpace + * @return void + */ + public function __construct($allowWhiteSpace = false) + { + $this->allowWhiteSpace = (boolean) $allowWhiteSpace; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value contains only alphabetic characters + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + if ('' === $valueString) { + $this->_error(self::STRING_EMPTY); + return false; + } + + if (null === self::$_filter) { + /** + * @see Zend_Filter_Alpha + */ + require_once 'Zend/Filter/Alpha.php'; + self::$_filter = new Zend_Filter_Alpha(); + } + + self::$_filter->allowWhiteSpace = $this->allowWhiteSpace; + + if ($valueString !== self::$_filter->filter($valueString)) { + $this->_error(self::NOT_ALPHA); + return false; + } + + return true; + } + +} diff --git a/lib/Zend/Validate/Barcode.php b/lib/Zend/Validate/Barcode.php new file mode 100644 index 0000000..d51f11b --- /dev/null +++ b/lib/Zend/Validate/Barcode.php @@ -0,0 +1,99 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Barcode.php 8211 2008-02-20 14:29:24Z darby $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Barcode extends Zend_Validate_Abstract +{ + /** + * Barcode validator + * + * @var Zend_Validate_Abstract + */ + protected $_barcodeValidator; + + /** + * Generates the standard validator object + * + * @param string $barcodeType - Barcode validator to use + * @return void + * @throws Zend_Validate_Exception + */ + public function __construct($barcodeType) + { + $this->setType($barcodeType); + } + + /** + * Sets a new barcode validator + * + * @param string $barcodeType - Barcode validator to use + * @return void + * @throws Zend_Validate_Exception + */ + public function setType($barcodeType) + { + switch (strtolower($barcodeType)) { + case 'upc': + case 'upc-a': + $className = 'UpcA'; + break; + case 'ean13': + case 'ean-13': + $className = 'Ean13'; + break; + default: + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("Barcode type '$barcodeType' is not supported'"); + break; + } + + require_once 'Zend/Validate/Barcode/' . $className . '.php'; + + $class = 'Zend_Validate_Barcode_' . $className; + $this->_barcodeValidator = new $class; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value contains a valid barcode + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + return call_user_func(array($this->_barcodeValidator, 'isValid'), $value); + } +} diff --git a/lib/Zend/Validate/Barcode/Ean13.php b/lib/Zend/Validate/Barcode/Ean13.php new file mode 100644 index 0000000..393091d --- /dev/null +++ b/lib/Zend/Validate/Barcode/Ean13.php @@ -0,0 +1,112 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Ean13.php 11791 2008-10-09 18:19:13Z andries $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Barcode_Ean13 extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value is + * an invalid barcode + */ + const INVALID = 'invalid'; + + /** + * Validation failure message key for when the value is + * not 13 characters long + */ + const INVALID_LENGTH = 'invalidLength'; + + /** + * Validation failure message key for when the value + * does not only contain numeric characters + */ + const NOT_NUMERIC = 'ean13NotNumeric'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::INVALID => "'%value%' is an invalid EAN-13 barcode", + self::INVALID_LENGTH => "'%value%' should be 13 characters", + self::NOT_NUMERIC => "'%value%' should contain only numeric characters", + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value contains a valid barcode + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + if (false === ctype_digit($value)) { + $this->_error(self::NOT_NUMERIC); + return false; + } + + $valueString = (string) $value; + $this->_setValue($valueString); + + if (strlen($valueString) !== 13) { + $this->_error(self::INVALID_LENGTH); + return false; + } + + $barcode = strrev(substr($valueString, 0, -1)); + $oddSum = 0; + $evenSum = 0; + + for ($i = 0; $i < 12; $i++) { + if ($i % 2 === 0) { + $oddSum += $barcode[$i] * 3; + } elseif ($i % 2 === 1) { + $evenSum += $barcode[$i]; + } + } + + $calculation = ($oddSum + $evenSum) % 10; + $checksum = ($calculation === 0) ? 0 : 10 - $calculation; + + if ($valueString[12] != $checksum) { + $this->_error(self::INVALID); + return false; + } + + return true; + } +} diff --git a/lib/Zend/Validate/Barcode/UpcA.php b/lib/Zend/Validate/Barcode/UpcA.php new file mode 100644 index 0000000..c584e81 --- /dev/null +++ b/lib/Zend/Validate/Barcode/UpcA.php @@ -0,0 +1,100 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: UpcA.php 8210 2008-02-20 14:09:05Z andries $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Barcode_UpcA extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value is + * an invalid barcode + */ + const INVALID = 'invalid'; + + /** + * Validation failure message key for when the value is + * not 12 characters long + */ + const INVALID_LENGTH = 'invalidLength'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::INVALID => "'%value%' is an invalid UPC-A barcode", + self::INVALID_LENGTH => "'%value%' should be 12 characters", + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value contains a valid barcode + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + $this->_setValue($valueString); + + if (strlen($valueString) !== 12) { + $this->_error(self::INVALID_LENGTH); + return false; + } + + $barcode = substr($valueString, 0, -1); + $oddSum = 0; + $evenSum = 0; + + for ($i = 0; $i < 11; $i++) { + if ($i % 2 === 0) { + $oddSum += $barcode[$i] * 3; + } elseif ($i % 2 === 1) { + $evenSum += $barcode[$i]; + } + } + + $calculation = ($oddSum + $evenSum) % 10; + $checksum = ($calculation === 0) ? 0 : 10 - $calculation; + + if ($valueString[11] != $checksum) { + $this->_error(self::INVALID); + return false; + } + + return true; + } +} diff --git a/lib/Zend/Validate/Between.php b/lib/Zend/Validate/Between.php new file mode 100644 index 0000000..bb0b726 --- /dev/null +++ b/lib/Zend/Validate/Between.php @@ -0,0 +1,200 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Between.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Between extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value is not between the min and max, inclusively + */ + const NOT_BETWEEN = 'notBetween'; + + /** + * Validation failure message key for when the value is not strictly between the min and max + */ + const NOT_BETWEEN_STRICT = 'notBetweenStrict'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::NOT_BETWEEN => "'%value%' is not between '%min%' and '%max%', inclusively", + self::NOT_BETWEEN_STRICT => "'%value%' is not strictly between '%min%' and '%max%'" + ); + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $_messageVariables = array( + 'min' => '_min', + 'max' => '_max' + ); + + /** + * Minimum value + * + * @var mixed + */ + protected $_min; + + /** + * Maximum value + * + * @var mixed + */ + protected $_max; + + /** + * Whether to do inclusive comparisons, allowing equivalence to min and/or max + * + * If false, then strict comparisons are done, and the value may equal neither + * the min nor max options + * + * @var boolean + */ + protected $_inclusive; + + /** + * Sets validator options + * + * @param mixed $min + * @param mixed $max + * @param boolean $inclusive + * @return void + */ + public function __construct($min, $max, $inclusive = true) + { + $this->setMin($min) + ->setMax($max) + ->setInclusive($inclusive); + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->_min; + } + + /** + * Sets the min option + * + * @param mixed $min + * @return Zend_Validate_Between Provides a fluent interface + */ + public function setMin($min) + { + $this->_min = $min; + return $this; + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->_max; + } + + /** + * Sets the max option + * + * @param mixed $max + * @return Zend_Validate_Between Provides a fluent interface + */ + public function setMax($max) + { + $this->_max = $max; + return $this; + } + + /** + * Returns the inclusive option + * + * @return boolean + */ + public function getInclusive() + { + return $this->_inclusive; + } + + /** + * Sets the inclusive option + * + * @param boolean $inclusive + * @return Zend_Validate_Between Provides a fluent interface + */ + public function setInclusive($inclusive) + { + $this->_inclusive = $inclusive; + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is between min and max options, inclusively + * if inclusive option is true. + * + * @param mixed $value + * @return boolean + */ + public function isValid($value) + { + $this->_setValue($value); + + if ($this->_inclusive) { + if ($this->_min > $value || $value > $this->_max) { + $this->_error(self::NOT_BETWEEN); + return false; + } + } else { + if ($this->_min >= $value || $value >= $this->_max) { + $this->_error(self::NOT_BETWEEN_STRICT); + return false; + } + } + return true; + } + +} diff --git a/lib/Zend/Validate/Ccnum.php b/lib/Zend/Validate/Ccnum.php new file mode 100644 index 0000000..227a4ec --- /dev/null +++ b/lib/Zend/Validate/Ccnum.php @@ -0,0 +1,111 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Ccnum.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Ccnum extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value is not of valid length + */ + const LENGTH = 'ccnumLength'; + + /** + * Validation failure message key for when the value fails the mod-10 checksum + */ + const CHECKSUM = 'ccnumChecksum'; + + /** + * Digits filter for input + * + * @var Zend_Filter_Digits + */ + protected static $_filter = null; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::LENGTH => "'%value%' must contain between 13 and 19 digits", + self::CHECKSUM => "Luhn algorithm (mod-10 checksum) failed on '%value%'" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum) + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $this->_setValue($value); + + if (null === self::$_filter) { + /** + * @see Zend_Filter_Digits + */ + require_once 'Zend/Filter/Digits.php'; + self::$_filter = new Zend_Filter_Digits(); + } + + $valueFiltered = self::$_filter->filter($value); + + $length = strlen($valueFiltered); + + if ($length < 13 || $length > 19) { + $this->_error(self::LENGTH); + return false; + } + + $sum = 0; + $weight = 2; + + for ($i = $length - 2; $i >= 0; $i--) { + $digit = $weight * $valueFiltered[$i]; + $sum += floor($digit / 10) + $digit % 10; + $weight = $weight % 2 + 1; + } + + if ((10 - $sum % 10) % 10 != $valueFiltered[$length - 1]) { + $this->_error(self::CHECKSUM, $valueFiltered); + return false; + } + + return true; + } + +} diff --git a/lib/Zend/Validate/Date.php b/lib/Zend/Validate/Date.php new file mode 100644 index 0000000..156c584 --- /dev/null +++ b/lib/Zend/Validate/Date.php @@ -0,0 +1,235 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Date.php 13687 2009-01-18 15:33:52Z thomas $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Date extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value does not follow the YYYY-MM-DD format + */ + const NOT_YYYY_MM_DD = 'dateNotYYYY-MM-DD'; + + /** + * Validation failure message key for when the value does not appear to be a valid date + */ + const INVALID = 'dateInvalid'; + + /** + * Validation failure message key for when the value does not fit the given dateformat or locale + */ + const FALSEFORMAT = 'dateFalseFormat'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::NOT_YYYY_MM_DD => "'%value%' is not of the format YYYY-MM-DD", + self::INVALID => "'%value%' does not appear to be a valid date", + self::FALSEFORMAT => "'%value%' does not fit given date format" + ); + + /** + * Optional format + * + * @var string|null + */ + protected $_format; + + /** + * Optional locale + * + * @var string|Zend_Locale|null + */ + protected $_locale; + + /** + * Sets validator options + * + * @param string $format OPTIONAL + * @param string|Zend_Locale $locale OPTIONAL + * @return void + */ + public function __construct($format = null, $locale = null) + { + $this->setFormat($format); + if ($locale !== null) { + $this->setLocale($locale); + } + } + + /** + * Returns the locale option + * + * @return string|Zend_Locale|null + */ + public function getLocale() + { + return $this->_locale; + } + + /** + * Sets the locale option + * + * @param string|Zend_Locale $locale + * @return Zend_Validate_Date provides a fluent interface + */ + public function setLocale($locale = null) + { + require_once 'Zend/Locale.php'; + $this->_locale = Zend_Locale::findLocale($locale); + return $this; + } + + /** + * Returns the locale option + * + * @return string|null + */ + public function getFormat() + { + return $this->_format; + } + + /** + * Sets the format option + * + * @param string $format + * @return Zend_Validate_Date provides a fluent interface + */ + public function setFormat($format = null) + { + $this->_format = $format; + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if $value is a valid date of the format YYYY-MM-DD + * If optional $format or $locale is set the date format is checked + * according to Zend_Date, see Zend_Date::isDate() + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + if (($this->_format !== null) or ($this->_locale !== null)) { + require_once 'Zend/Date.php'; + if (!Zend_Date::isDate($value, $this->_format, $this->_locale)) { + if ($this->_checkFormat($value) === false) { + $this->_error(self::FALSEFORMAT); + } else { + $this->_error(self::INVALID); + } + return false; + } + } else { + if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $valueString)) { + $this->_error(self::NOT_YYYY_MM_DD); + return false; + } + + list($year, $month, $day) = sscanf($valueString, '%d-%d-%d'); + + if (!checkdate($month, $day, $year)) { + $this->_error(self::INVALID); + return false; + } + } + + return true; + } + + /** + * Check if the given date fits the given format + * + * @param string $value Date to check + * @return boolean False when date does not fit the format + */ + private function _checkFormat($value) + { + try { + require_once 'Zend/Locale/Format.php'; + $parsed = Zend_Locale_Format::getDate($value, array( + 'date_format' => $this->_format, 'format_type' => 'iso', + 'fix_date' => false)); + if (isset($parsed['year']) and ((strpos(strtoupper($this->_format), 'YY') !== false) and + (strpos(strtoupper($this->_format), 'YYYY') === false))) { + $parsed['year'] = Zend_Date::getFullYear($parsed['year']); + } + } catch (Exception $e) { + // Date can not be parsed + return false; + } + + if (((strpos($this->_format, 'Y') !== false) or (strpos($this->_format, 'y') !== false)) and + (!isset($parsed['year']))) { + // Year expected but not found + return false; + } + + if ((strpos($this->_format, 'M') !== false) and (!isset($parsed['month']))) { + // Month expected but not found + return false; + } + + if ((strpos($this->_format, 'd') !== false) and (!isset($parsed['day']))) { + // Day expected but not found + return false; + } + + if (((strpos($this->_format, 'H') !== false) or (strpos($this->_format, 'h') !== false)) and + (!isset($parsed['hour']))) { + // Hour expected but not found + return false; + } + + if ((strpos($this->_format, 'm') !== false) and (!isset($parsed['minute']))) { + // Minute expected but not found + return false; + } + + if ((strpos($this->_format, 's') !== false) and (!isset($parsed['second']))) { + // Second expected but not found + return false; + } + + // Date fits the format + return true; + } +} diff --git a/lib/Zend/Validate/Digits.php b/lib/Zend/Validate/Digits.php new file mode 100644 index 0000000..c42ec0a --- /dev/null +++ b/lib/Zend/Validate/Digits.php @@ -0,0 +1,100 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Digits.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Digits extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value contains non-digit characters + */ + const NOT_DIGITS = 'notDigits'; + + /** + * Validation failure message key for when the value is an empty string + */ + const STRING_EMPTY = 'stringEmpty'; + + /** + * Digits filter used for validation + * + * @var Zend_Filter_Digits + */ + protected static $_filter = null; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::NOT_DIGITS => "'%value%' contains not only digit characters", + self::STRING_EMPTY => "'%value%' is an empty string" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value only contains digit characters + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + if ('' === $valueString) { + $this->_error(self::STRING_EMPTY); + return false; + } + + if (null === self::$_filter) { + /** + * @see Zend_Filter_Digits + */ + require_once 'Zend/Filter/Digits.php'; + self::$_filter = new Zend_Filter_Digits(); + } + + if ($valueString !== self::$_filter->filter($valueString)) { + $this->_error(self::NOT_DIGITS); + return false; + } + + return true; + } + +} diff --git a/lib/Zend/Validate/EmailAddress.php b/lib/Zend/Validate/EmailAddress.php new file mode 100644 index 0000000..82ed91b --- /dev/null +++ b/lib/Zend/Validate/EmailAddress.php @@ -0,0 +1,247 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: EmailAddress.php 13253 2008-12-14 20:28:06Z thomas $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * @see Zend_Validate_Hostname + */ +require_once 'Zend/Validate/Hostname.php'; + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_EmailAddress extends Zend_Validate_Abstract +{ + const INVALID = 'emailAddressInvalid'; + const INVALID_HOSTNAME = 'emailAddressInvalidHostname'; + const INVALID_MX_RECORD = 'emailAddressInvalidMxRecord'; + const DOT_ATOM = 'emailAddressDotAtom'; + const QUOTED_STRING = 'emailAddressQuotedString'; + const INVALID_LOCAL_PART = 'emailAddressInvalidLocalPart'; + const LENGTH_EXCEEDED = 'emailAddressLengthExceeded'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::INVALID => "'%value%' is not a valid email address in the basic format local-part@hostname", + self::INVALID_HOSTNAME => "'%hostname%' is not a valid hostname for email address '%value%'", + self::INVALID_MX_RECORD => "'%hostname%' does not appear to have a valid MX record for the email address '%value%'", + self::DOT_ATOM => "'%localPart%' not matched against dot-atom format", + self::QUOTED_STRING => "'%localPart%' not matched against quoted-string format", + self::INVALID_LOCAL_PART => "'%localPart%' is not a valid local part for email address '%value%'", + self::LENGTH_EXCEEDED => "'%value%' exceeds the allowed length" + ); + + /** + * @var array + */ + protected $_messageVariables = array( + 'hostname' => '_hostname', + 'localPart' => '_localPart' + ); + + /** + * Local object for validating the hostname part of an email address + * + * @var Zend_Validate_Hostname + */ + public $hostnameValidator; + + /** + * Whether we check for a valid MX record via DNS + * + * @var boolean + */ + protected $_validateMx = false; + + /** + * @var string + */ + protected $_hostname; + + /** + * @var string + */ + protected $_localPart; + + /** + * Instantiates hostname validator for local use + * + * You can pass a bitfield to determine what types of hostnames are allowed. + * These bitfields are defined by the ALLOW_* constants in Zend_Validate_Hostname + * The default is to allow DNS hostnames only + * + * @param integer $allow OPTIONAL + * @param bool $validateMx OPTIONAL + * @param Zend_Validate_Hostname $hostnameValidator OPTIONAL + * @return void + */ + public function __construct($allow = Zend_Validate_Hostname::ALLOW_DNS, $validateMx = false, Zend_Validate_Hostname $hostnameValidator = null) + { + $this->setValidateMx($validateMx); + $this->setHostnameValidator($hostnameValidator, $allow); + } + + /** + * @param Zend_Validate_Hostname $hostnameValidator OPTIONAL + * @param int $allow OPTIONAL + * @return void + */ + public function setHostnameValidator(Zend_Validate_Hostname $hostnameValidator = null, $allow = Zend_Validate_Hostname::ALLOW_DNS) + { + if ($hostnameValidator === null) { + $hostnameValidator = new Zend_Validate_Hostname($allow); + } + $this->hostnameValidator = $hostnameValidator; + } + + /** + * Whether MX checking via dns_get_mx is supported or not + * + * This currently only works on UNIX systems + * + * @return boolean + */ + public function validateMxSupported() + { + return function_exists('dns_get_mx'); + } + + /** + * Set whether we check for a valid MX record via DNS + * + * This only applies when DNS hostnames are validated + * + * @param boolean $allowed Set allowed to true to validate for MX records, and false to not validate them + */ + public function setValidateMx($allowed) + { + $this->_validateMx = (bool) $allowed; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is a valid email address + * according to RFC2822 + * + * @link http://www.ietf.org/rfc/rfc2822.txt RFC2822 + * @link http://www.columbia.edu/kermit/ascii.html US-ASCII characters + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + $matches = array(); + $length = true; + + $this->_setValue($valueString); + + // Split email address up and disallow '..' + if ((strpos($valueString, '..') !== false) or + (!preg_match('/^(.+)@([^@]+)$/', $valueString, $matches))) { + $this->_error(self::INVALID); + return false; + } + + $this->_localPart = $matches[1]; + $this->_hostname = $matches[2]; + + if ((strlen($this->_localPart) > 64) || (strlen($this->_hostname) > 255)) { + $length = false; + $this->_error(self::LENGTH_EXCEEDED); + } + + // Match hostname part + $hostnameResult = $this->hostnameValidator->setTranslator($this->getTranslator()) + ->isValid($this->_hostname); + if (!$hostnameResult) { + $this->_error(self::INVALID_HOSTNAME); + + // Get messages and errors from hostnameValidator + foreach ($this->hostnameValidator->getMessages() as $code => $message) { + $this->_messages[$code] = $message; + } + foreach ($this->hostnameValidator->getErrors() as $error) { + $this->_errors[] = $error; + } + } else if ($this->_validateMx) { + // MX check on hostname via dns_get_record() + if ($this->validateMxSupported()) { + $result = dns_get_mx($this->_hostname, $mxHosts); + if (count($mxHosts) < 1) { + $hostnameResult = false; + $this->_error(self::INVALID_MX_RECORD); + } + } else { + /** + * MX checks are not supported by this system + * @see Zend_Validate_Exception + */ + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Internal error: MX checking not available on this system'); + } + } + + // First try to match the local part on the common dot-atom format + $localResult = false; + + // Dot-atom characters are: 1*atext *("." 1*atext) + // atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*", + // "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~" + $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d'; + if (preg_match('/^[' . $atext . ']+(\x2e+[' . $atext . ']+)*$/', $this->_localPart)) { + $localResult = true; + } else { + // Try quoted string format + + // Quoted-string characters are: DQUOTE *([FWS] qtext/quoted-pair) [FWS] DQUOTE + // qtext: Non white space controls, and the rest of the US-ASCII characters not + // including "\" or the quote character + $noWsCtl = '\x01-\x08\x0b\x0c\x0e-\x1f\x7f'; + $qtext = $noWsCtl . '\x21\x23-\x5b\x5d-\x7e'; + $ws = '\x20\x09'; + if (preg_match('/^\x22([' . $ws . $qtext . '])*[$ws]?\x22$/', $this->_localPart)) { + $localResult = true; + } else { + $this->_error(self::DOT_ATOM); + $this->_error(self::QUOTED_STRING); + $this->_error(self::INVALID_LOCAL_PART); + } + } + + // If both parts valid, return true + if ($localResult && $hostnameResult && $length) { + return true; + } else { + return false; + } + } +} diff --git a/lib/Zend/Validate/Exception.php b/lib/Zend/Validate/Exception.php new file mode 100644 index 0000000..a38077e --- /dev/null +++ b/lib/Zend/Validate/Exception.php @@ -0,0 +1,37 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Exception.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Exception + */ +require_once 'Zend/Exception.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Exception extends Zend_Exception +{} diff --git a/lib/Zend/Validate/File/Count.php b/lib/Zend/Validate/File/Count.php new file mode 100644 index 0000000..97667ab --- /dev/null +++ b/lib/Zend/Validate/File/Count.php @@ -0,0 +1,275 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * Validator for counting all given files + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Count extends Zend_Validate_Abstract +{ + /**#@+ + * @const string Error constants + */ + const TOO_MUCH = 'fileCountTooMuch'; + const TOO_LESS = 'fileCountTooLess'; + /**#@-*/ + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::TOO_MUCH => "Too much files, maximum '%max%' are allowed but '%count%' are given", + self::TOO_LESS => "Too less files, minimum '%min%' are expected but '%count%' are given" + ); + + /** + * @var array Error message template variables + */ + protected $_messageVariables = array( + 'min' => '_min', + 'max' => '_max', + 'count' => '_count' + ); + + /** + * Minimum file count + * + * If null, there is no minimum file count + * + * @var integer + */ + protected $_min; + + /** + * Maximum file count + * + * If null, there is no maximum file count + * + * @var integer|null + */ + protected $_max; + + /** + * Actual filecount + * + * @var integer + */ + protected $_count; + + /** + * Internal file array + * @var array + */ + protected $_files; + + /** + * Sets validator options + * + * Min limits the file count, when used with max=null it is the maximum file count + * It also accepts an array with the keys 'min' and 'max' + * + * If $options is a integer, it will be used as maximum file count + * As Array is accepts the following keys: + * 'min': Minimum filecount + * 'max': Maximum filecount + * + * @param integer|array $options Options for the adapter + * @param integer $max (Deprecated) Maximum value (implies $options is the minimum) + * @return void + */ + public function __construct($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (is_string($options) || is_numeric($options)) { + $options = array('max' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + if (1 < func_num_args()) { + trigger_error('Multiple arguments are deprecated in favor of an array of named arguments', E_USER_NOTICE); + $options['min'] = func_get_arg(0); + $options['max'] = func_get_arg(1); + } + + if (isset($options['min'])) { + $this->setMin($options); + } + + if (isset($options['max'])) { + $this->setMax($options); + } + } + + /** + * Returns the minimum file count + * + * @return integer + */ + public function getMin() + { + return $this->_min; + } + + /** + * Sets the minimum file count + * + * @param integer|array $min The minimum file count + * @return Zend_Validate_File_Count Provides a fluent interface + * @throws Zend_Validate_Exception When min is greater than max + */ + public function setMin($min) + { + if (is_array($min) and isset($min['min'])) { + $min = $min['min']; + } + + if (!is_string($min) and !is_numeric($min)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + $min = (integer) $min; + if (($this->_max !== null) && ($min > $this->_max)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The minimum must be less than or equal to the maximum file count, but $min >" + . " {$this->_max}"); + } + + $this->_min = $min; + return $this; + } + + /** + * Returns the maximum file count + * + * @return integer + */ + public function getMax() + { + return $this->_max; + } + + /** + * Sets the maximum file count + * + * @param integer|array $max The maximum file count + * @return Zend_Validate_StringLength Provides a fluent interface + * @throws Zend_Validate_Exception When max is smaller than min + */ + public function setMax($max) + { + if (is_array($max) and isset($max['max'])) { + $max = $max['max']; + } + + if (!is_string($max) and !is_numeric($max)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + $max = (integer) $max; + if (($this->_min !== null) && ($max < $this->_min)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum file count, but " + . "$max < {$this->_min}"); + } + + $this->_max = $max; + return $this; + } + + /** + * Adds a file for validation + * + * @param string|array $file + */ + public function addFile($file) + { + if (is_string($file)) { + $file = array($file); + } + + if (is_array($file)) { + foreach ($file as $name) { + if (!isset($this->_files[$name])) { + $this->_files[$name] = $name; + } + } + } + + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the file count of all checked files is at least min and + * not bigger than max (when max is not null). Attention: When checking with set min you + * must give all files with the first call, otherwise you will get an false. + * + * @param string|array $value Filenames to check for count + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + $this->addFile($value); + $this->_count = count($this->_files); + if (($this->_max !== null) && ($this->_count > $this->_max)) { + return $this->_throw($file, self::TOO_MUCH); + } + + if (($this->_min !== null) && ($this->_count < $this->_min)) { + return $this->_throw($file, self::TOO_LESS); + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function _throw($file, $errorType) + { + if ($file !== null) { + $this->_value = $file['name']; + } + + $this->_error($errorType); + return false; + } +} diff --git a/lib/Zend/Validate/File/Crc32.php b/lib/Zend/Validate/File/Crc32.php new file mode 100644 index 0000000..47ace9d --- /dev/null +++ b/lib/Zend/Validate/File/Crc32.php @@ -0,0 +1,179 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_File_Hash + */ +require_once 'Zend/Validate/File/Hash.php'; + +/** + * Validator for the crc32 hash of given files + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Crc32 extends Zend_Validate_File_Hash +{ + /** + * @const string Error constants + */ + const DOES_NOT_MATCH = 'fileCrc32DoesNotMatch'; + const NOT_DETECTED = 'fileCrc32NotDetected'; + const NOT_FOUND = 'fileCrc32NotFound'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::DOES_NOT_MATCH => "The file '%value%' does not match the given crc32 hashes", + self::NOT_DETECTED => "There was no crc32 hash detected for the given file", + self::NOT_FOUND => "The file '%value%' could not be found" + ); + + /** + * Hash of the file + * + * @var string + */ + protected $_hash; + + /** + * Sets validator options + * + * @param string|array $options + * @return void + */ + public function __construct($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (is_scalar($options)) { + $options = array('hash1' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Invalid options to validator provided'); + } + + $this->setCrc32($options); + } + + /** + * Returns all set crc32 hashes + * + * @return array + */ + public function getCrc32() + { + return $this->getHash(); + } + + /** + * Sets the crc32 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function setHash($options) + { + if (!is_array($options)) { + $options = array($options); + } + + $options['algorithm'] = 'crc32'; + parent::setHash($options); + return $this; + } + + /** + * Sets the crc32 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function setCrc32($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the crc32 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function addHash($options) + { + if (!is_array($options)) { + $options = array($options); + } + + $options['algorithm'] = 'crc32'; + parent::addHash($options); + return $this; + } + + /** + * Adds the crc32 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function addCrc32($options) + { + $this->addHash($options); + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the given file confirms the set hash + * + * @param string $value Filename to check for hash + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_FOUND); + } + + $hashes = array_unique(array_keys($this->_hash)); + $filehash = hash_file('crc32', $value); + if ($filehash === false) { + return $this->_throw($file, self::NOT_DETECTED); + } + + foreach($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + return $this->_throw($file, self::DOES_NOT_MATCH); + } +}
\ No newline at end of file diff --git a/lib/Zend/Validate/File/ExcludeExtension.php b/lib/Zend/Validate/File/ExcludeExtension.php new file mode 100644 index 0000000..2a442ab --- /dev/null +++ b/lib/Zend/Validate/File/ExcludeExtension.php @@ -0,0 +1,94 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/File/Extension.php'; + +/** + * Validator for the excluding file extensions + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_ExcludeExtension extends Zend_Validate_File_Extension +{ + /** + * @const string Error constants + */ + const FALSE_EXTENSION = 'fileExcludeExtensionFalse'; + const NOT_FOUND = 'fileExcludeExtensionNotFound'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::FALSE_EXTENSION => "The file '%value%' has a false extension", + self::NOT_FOUND => "The file '%value%' was not found" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the fileextension of $value is not included in the + * set extension list + * + * @param string $value Real file to check for extension + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_FOUND); + } + + if ($file !== null) { + $info['extension'] = substr($file['name'], strrpos($file['name'], '.') + 1); + } else { + $info = pathinfo($value); + } + + $extensions = $this->getExtension(); + + if ($this->_case and (!in_array($info['extension'], $extensions))) { + return true; + } else if (!$this->_case) { + $found = false; + foreach ($extensions as $extension) { + if (strtolower($extension) == strtolower($info['extension'])) { + $found = true; + } + } + + if (!$found) { + return true; + } + } + + return $this->_throw($file, self::FALSE_EXTENSION); + } +} diff --git a/lib/Zend/Validate/File/ExcludeMimeType.php b/lib/Zend/Validate/File/ExcludeMimeType.php new file mode 100644 index 0000000..d69dc06 --- /dev/null +++ b/lib/Zend/Validate/File/ExcludeMimeType.php @@ -0,0 +1,91 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_File_MimeType + */ +require_once 'Zend/Validate/File/MimeType.php'; + +/** + * Validator for the mime type of a file + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_ExcludeMimeType extends Zend_Validate_File_MimeType +{ + const FALSE_TYPE = 'fileExcludeMimeTypeFalse'; + const NOT_DETECTED = 'fileExcludeMimeTypeNotDetected'; + const NOT_READABLE = 'fileExcludeMimeTypeNotReadable'; + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if the mimetype of the file does not matche the given ones. Also parts + * of mimetypes can be checked. If you give for example "image" all image + * mime types will not be accepted like "image/gif", "image/jpeg" and so on. + * + * @param string $value Real file to check for mimetype + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_READABLE); + } + + if ($file !== null) { + if (class_exists('finfo', false) && defined('MAGIC')) { + $mime = new finfo(FILEINFO_MIME); + $this->_type = $mime->file($value); + unset($mime); + } elseif (function_exists('mime_content_type') && ini_get('mime_magic.magicfile')) { + $this->_type = mime_content_type($value); + } else { + $this->_type = $file['type']; + } + } + + if (empty($this->_type)) { + return $this->_throw($file, self::NOT_DETECTED); + } + + $mimetype = $this->getMimeType(true); + if (in_array($this->_type, $mimetype)) { + return $this->_throw($file, self::FALSE_TYPE); + } + + $types = explode('/', $this->_type); + $types = array_merge($types, explode('-', $this->_type)); + foreach($mimetype as $mime) { + if (in_array($mime, $types)) { + return $this->_throw($file, self::FALSE_TYPE); + } + } + + return true; + } +} diff --git a/lib/Zend/Validate/File/Exists.php b/lib/Zend/Validate/File/Exists.php new file mode 100644 index 0000000..cf509aa --- /dev/null +++ b/lib/Zend/Validate/File/Exists.php @@ -0,0 +1,203 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * Validator which checks if the file already exists in the directory + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Exists extends Zend_Validate_Abstract +{ + /** + * @const string Error constants + */ + const DOES_NOT_EXIST = 'fileExistsDoesNotExist'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::DOES_NOT_EXIST => "The file '%value%' does not exist" + ); + + /** + * Internal list of directories + * @var string + */ + protected $_directory = ''; + + /** + * @var array Error message template variables + */ + protected $_messageVariables = array( + 'directory' => '_directory' + ); + + /** + * Sets validator options + * + * @param string|array $directory + * @return void + */ + public function __construct($directory = array()) + { + if ($directory instanceof Zend_Config) { + $directory = $directory->toArray(); + } else if (is_string($directory)) { + $directory = explode(',', $directory); + } else if (!is_array($directory)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + $this->setDirectory($directory); + } + + /** + * Returns the set file directories which are checked + * + * @param boolean $asArray Returns the values as array, when false an concated string is returned + * @return string + */ + public function getDirectory($asArray = false) + { + $asArray = (bool) $asArray; + $directory = (string) $this->_directory; + if ($asArray) { + $directory = explode(',', $directory); + } + + return $directory; + } + + /** + * Sets the file directory which will be checked + * + * @param string|array $directory The directories to validate + * @return Zend_Validate_File_Extension Provides a fluent interface + */ + public function setDirectory($directory) + { + $this->_directory = null; + $this->addDirectory($directory); + return $this; + } + + /** + * Adds the file directory which will be checked + * + * @param string|array $directory The directory to add for validation + * @return Zend_Validate_File_Extension Provides a fluent interface + */ + public function addDirectory($directory) + { + $directories = $this->getDirectory(true); + + if (is_string($directory)) { + $directory = explode(',', $directory); + } else if (!is_array($directory)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + foreach ($directory as $content) { + if (empty($content) || !is_string($content)) { + continue; + } + + $directories[] = trim($content); + } + $directories = array_unique($directories); + + // Sanity check to ensure no empty values + foreach ($directories as $key => $dir) { + if (empty($dir)) { + unset($directories[$key]); + } + } + + $this->_directory = implode(',', $directories); + + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the file already exists in the set directories + * + * @param string $value Real file to check for existance + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + $directories = $this->getDirectory(true); + if (($file !== null) and (!empty($file['destination']))) { + $directories[] = $file['destination']; + } else if (!isset($file['name'])) { + $file['name'] = $value; + } + + $check = false; + foreach ($directories as $directory) { + if (empty($directory)) { + continue; + } + + $check = true; + if (!file_exists($directory . DIRECTORY_SEPARATOR . $file['name'])) { + return $this->_throw($file, self::DOES_NOT_EXIST); + } + } + + if (!$check) { + return $this->_throw($file, self::DOES_NOT_EXIST); + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function _throw($file, $errorType) + { + if ($file !== null) { + $this->_value = $file['name']; + } + + $this->_error($errorType); + return false; + } +} diff --git a/lib/Zend/Validate/File/Extension.php b/lib/Zend/Validate/File/Extension.php new file mode 100644 index 0000000..49411b3 --- /dev/null +++ b/lib/Zend/Validate/File/Extension.php @@ -0,0 +1,234 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * Validator for the file extension of a file + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Extension extends Zend_Validate_Abstract +{ + /** + * @const string Error constants + */ + const FALSE_EXTENSION = 'fileExtensionFalse'; + const NOT_FOUND = 'fileExtensionNotFound'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::FALSE_EXTENSION => "The file '%value%' has a false extension", + self::NOT_FOUND => "The file '%value%' was not found" + ); + + /** + * Internal list of extensions + * @var string + */ + protected $_extension = ''; + + /** + * Validate case sensitive + * + * @var boolean + */ + protected $_case = false; + + /** + * @var array Error message template variables + */ + protected $_messageVariables = array( + 'extension' => '_extension' + ); + + /** + * Sets validator options + * + * @param string|array $extension + * @param boolean $case If true validation is done case sensitive + * @return void + */ + public function __construct($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } + + if (1 < func_num_args()) { + trigger_error('Multiple arguments to constructor are deprecated in favor of options array', E_USER_NOTICE); + $case = func_get_arg(1); + $this->setCase($case); + } + + if (is_array($options) and isset($options['case'])) { + $this->setCase($options['case']); + unset($options['case']); + } + + $this->setExtension($options); + } + + /** + * Returns the case option + * + * @return boolean + */ + public function getCase() + { + return $this->_case; + } + + /** + * Sets the case to use + * + * @param boolean $case + * @return Zend_Validate_File_Extension Provides a fluent interface + */ + public function setCase($case) + { + $this->_case = (boolean) $case; + return $this; + } + + /** + * Returns the set file extension + * + * @return array + */ + public function getExtension() + { + $extension = explode(',', $this->_extension); + + return $extension; + } + + /** + * Sets the file extensions + * + * @param string|array $extension The extensions to validate + * @return Zend_Validate_File_Extension Provides a fluent interface + */ + public function setExtension($extension) + { + $this->_extension = null; + $this->addExtension($extension); + return $this; + } + + /** + * Adds the file extensions + * + * @param string|array $extension The extensions to add for validation + * @return Zend_Validate_File_Extension Provides a fluent interface + */ + public function addExtension($extension) + { + $extensions = $this->getExtension(); + if (is_string($extension)) { + $extension = explode(',', $extension); + } + + foreach ($extension as $content) { + if (empty($content) || !is_string($content)) { + continue; + } + + $extensions[] = trim($content); + } + $extensions = array_unique($extensions); + + // Sanity check to ensure no empty values + foreach ($extensions as $key => $ext) { + if (empty($ext)) { + unset($extensions[$key]); + } + } + + $this->_extension = implode(',', $extensions); + + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the fileextension of $value is included in the + * set extension list + * + * @param string $value Real file to check for extension + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_FOUND); + } + + if ($file !== null) { + $info['extension'] = substr($file['name'], strrpos($file['name'], '.') + 1); + } else { + $info = pathinfo($value); + } + + $extensions = $this->getExtension(); + + if ($this->_case && (in_array($info['extension'], $extensions))) { + return true; + } else if (!$this->getCase()) { + foreach ($extensions as $extension) { + if (strtolower($extension) == strtolower($info['extension'])) { + return true; + } + } + } + + return $this->_throw($file, self::FALSE_EXTENSION); + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function _throw($file, $errorType) + { + if (null !== $file) { + $this->_value = $file['name']; + } + + $this->_error($errorType); + return false; + } +} diff --git a/lib/Zend/Validate/File/FilesSize.php b/lib/Zend/Validate/File/FilesSize.php new file mode 100644 index 0000000..ea8d085 --- /dev/null +++ b/lib/Zend/Validate/File/FilesSize.php @@ -0,0 +1,163 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_File_Size + */ +require_once 'Zend/Validate/File/Size.php'; + +/** + * Validator for the size of all files which will be validated in sum + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_FilesSize extends Zend_Validate_File_Size +{ + /** + * @const string Error constants + */ + const TOO_BIG = 'fileFilesSizeTooBig'; + const TOO_SMALL = 'fileFilesSizeTooSmall'; + const NOT_READABLE = 'fileFilesSizeNotReadable'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::TOO_BIG => "All files in sum should have a maximum size of '%max%' but '%size%' were detected", + self::TOO_SMALL => "All files in sum should have a minimum size of '%min%' but '%size%' were detected", + self::NOT_READABLE => "One or more files can not be read" + ); + + /** + * Internal file array + * + * @var array + */ + protected $_files; + + /** + * Sets validator options + * + * Min limits the used diskspace for all files, when used with max=null it is the maximum filesize + * It also accepts an array with the keys 'min' and 'max' + * + * @param integer|array $min Minimum diskspace for all files + * @param integer $max Maximum diskspace for all files (deprecated) + * @param boolean $bytestring Use bytestring or real size ? (deprecated) + * @return void + */ + public function __construct($options) + { + $this->_files = array(); + $this->_setSize(0); + + if (1 < func_num_args()) { + trigger_error('Multiple constructor options are deprecated in favor of a single options array', E_USER_NOTICE); + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (is_scalar($options)) { + $options = array('min' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Invalid options to validator provided'); + } + + $argv = func_get_args(); + array_shift($argv); + $options['max'] = array_shift($argv); + if (!empty($argv)) { + $options['bytestring'] = array_shift($argv); + } + } + + parent::__construct($options); + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the disk usage of all files is at least min and + * not bigger than max (when max is not null). + * + * @param string|array $value Real file to check for size + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + require_once 'Zend/Loader.php'; + if (is_string($value)) { + $value = array($value); + } + + $min = $this->getMin(true); + $max = $this->getMax(true); + $size = $this->_getSize(); + foreach ($value as $files) { + // Is file readable ? + if (!Zend_Loader::isReadable($files)) { + $this->_throw($file, self::NOT_READABLE); + continue; + } + + if (!isset($this->_files[$files])) { + $this->_files[$files] = $files; + } else { + // file already counted... do not count twice + continue; + } + + // limited to 2GB files + $size += @filesize($files); + $this->_setSize($size); + if (($max !== null) && ($max < $size)) { + if ($this->useByteString()) { + $this->setMax($this->_toByteString($max)); + $this->_throw($file, self::TOO_BIG); + $this->setMax($max); + } else { + $this->_throw($file, self::TOO_BIG); + } + } + } + + // Check that aggregate files are >= minimum size + if (($min !== null) && ($size < $min)) { + if ($this->useByteString()) { + $this->setMin($this->_toByteString($min)); + $this->_throw($file, self::TOO_SMALL); + $this->setMin($min); + } else { + $this->_throw($file, self::TOO_SMALL); + } + } + + if (count($this->_messages) > 0) { + return false; + } + + return true; + } +} diff --git a/lib/Zend/Validate/File/Hash.php b/lib/Zend/Validate/File/Hash.php new file mode 100644 index 0000000..1702593 --- /dev/null +++ b/lib/Zend/Validate/File/Hash.php @@ -0,0 +1,195 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * Validator for the hash of given files + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Hash extends Zend_Validate_Abstract +{ + /** + * @const string Error constants + */ + const DOES_NOT_MATCH = 'fileHashDoesNotMatch'; + const NOT_DETECTED = 'fileHashHashNotDetected'; + const NOT_FOUND = 'fileHashNotFound'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::DOES_NOT_MATCH => "The file '%value%' does not match the given hashes", + self::NOT_DETECTED => "There was no hash detected for the given file", + self::NOT_FOUND => "The file '%value%' could not be found" + ); + + /** + * Hash of the file + * + * @var string + */ + protected $_hash; + + /** + * Sets validator options + * + * @param string|array $options + * @return void + */ + public function __construct($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (is_scalar($options)) { + $options = array('hash1' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Invalid options to validator provided'); + } + + if (1 < func_num_args()) { + trigger_error('Multiple constructor options are deprecated in favor of a single options array', E_USER_NOTICE); + $options['algorithm'] = func_get_arg(1); + } + + $this->setHash($options); + } + + /** + * Returns the set hash values as array, the hash as key and the algorithm the value + * + * @return array + */ + public function getHash() + { + return $this->_hash; + } + + /** + * Sets the hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function setHash($options) + { + $this->_hash = null; + $this->addHash($options); + + return $this; + } + + /** + * Adds the hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function addHash($options) + { + if (is_string($options)) { + $options = array($options); + } else if (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("False parameter given"); + } + + $known = hash_algos(); + if (!isset($options['algorithm'])) { + $algorithm = 'crc32'; + } else { + $algorithm = $options['algorithm']; + unset($options['algorithm']); + } + + if (!in_array($algorithm, $known)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("Unknown algorithm '{$algorithm}'"); + } + + foreach ($options as $value) { + $this->_hash[$value] = $algorithm; + } + + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the given file confirms the set hash + * + * @param string $value Filename to check for hash + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_FOUND); + } + + $algos = array_unique(array_values($this->_hash)); + $hashes = array_unique(array_keys($this->_hash)); + foreach ($algos as $algorithm) { + $filehash = hash_file($algorithm, $value); + if ($filehash === false) { + return $this->_throw($file, self::NOT_DETECTED); + } + + foreach($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + } + + return $this->_throw($file, self::DOES_NOT_MATCH); + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function _throw($file, $errorType) + { + if ($file !== null) { + $this->_value = $file['name']; + } + + $this->_error($errorType); + return false; + } +} diff --git a/lib/Zend/Validate/File/ImageSize.php b/lib/Zend/Validate/File/ImageSize.php new file mode 100644 index 0000000..ada8bd3 --- /dev/null +++ b/lib/Zend/Validate/File/ImageSize.php @@ -0,0 +1,370 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * Validator for the image size of a image file + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_ImageSize extends Zend_Validate_Abstract +{ + /** + * @const string Error constants + */ + const WIDTH_TOO_BIG = 'fileImageSizeWidthTooBig'; + const WIDTH_TOO_SMALL = 'fileImageSizeWidthTooSmall'; + const HEIGHT_TOO_BIG = 'fileImageSizeHeightTooBig'; + const HEIGHT_TOO_SMALL = 'fileImageSizeHeightTooSmall'; + const NOT_DETECTED = 'fileImageSizeNotDetected'; + const NOT_READABLE = 'fileImageSizeNotReadable'; + + /** + * @var array Error message template + */ + protected $_messageTemplates = array( + self::WIDTH_TOO_BIG => "Maximum allowed width for image '%value%' should be '%maxwidth%' but '%width%' detected", + self::WIDTH_TOO_SMALL => "Minimum expected width for image '%value%' should be '%minwidth%' but '%width%' detected", + self::HEIGHT_TOO_BIG => "Maximum allowed height for image '%value%' should be '%maxheight%' but '%height%' detected", + self::HEIGHT_TOO_SMALL => "Minimum expected height for image '%value%' should be '%minheight%' but '%height%' detected", + self::NOT_DETECTED => "The size of image '%value%' could not be detected", + self::NOT_READABLE => "The image '%value%' can not be read" + ); + + /** + * @var array Error message template variables + */ + protected $_messageVariables = array( + 'minwidth' => '_minwidth', + 'maxwidth' => '_maxwidth', + 'minheight' => '_minheight', + 'maxheight' => '_maxheight', + 'width' => '_width', + 'height' => '_height' + ); + + /** + * Minimum image width + * + * @var integer + */ + protected $_minwidth; + + /** + * Maximum image width + * + * @var integer + */ + protected $_maxwidth; + + /** + * Minimum image height + * + * @var integer + */ + protected $_minheight; + + /** + * Maximum image height + * + * @var integer + */ + protected $_maxheight; + + /** + * Detected width + * + * @var integer + */ + protected $_width; + + /** + * Detected height + * + * @var integer + */ + protected $_height; + + /** + * Sets validator options + * + * Accepts the following option keys: + * - minheight + * - minwidth + * - maxheight + * - maxwidth + * + * @param Zend_Config|array $options + * @return void + */ + public function __construct($options) + { + $minwidth = 0; + $minheight = 0; + $maxwidth = null; + $maxheight = null; + + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (1 < func_num_args()) { + trigger_error('Multiple constructor options are deprecated in favor of a single options array', E_USER_NOTICE); + if (!is_array($options)) { + $options = array('minwidth' => $options); + } + $argv = func_get_args(); + array_shift($argv); + $options['minheight'] = array_shift($argv); + if (!empty($argv)) { + $options['maxwidth'] = array_shift($argv); + if (!empty($argv)) { + $options['maxheight'] = array_shift($argv); + } + } + } else if (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + if (isset($options['minheight']) || isset($options['minwidth'])) { + $this->setImageMin($options); + } + + if (isset($options['maxheight']) || isset($options['maxwidth'])) { + $this->setImageMax($options); + } + } + + /** + * Returns the set minimum image sizes + * + * @return array + */ + public function getImageMin() + { + return array('minwidth' => $this->_minwidth, 'minheight' => $this->_minheight); + } + + /** + * Returns the set maximum image sizes + * + * @return array + */ + public function getImageMax() + { + return array('maxwidth' => $this->_maxwidth, 'maxheight' => $this->_maxheight); + } + + /** + * Returns the set image width sizes + * + * @return array + */ + public function getImageWidth() + { + return array('minwidth' => $this->_minwidth, 'maxwidth' => $this->_maxwidth); + } + + /** + * Returns the set image height sizes + * + * @return array + */ + public function getImageHeight() + { + return array('minheight' => $this->_minheight, 'maxheight' => $this->_maxheight); + } + + /** + * Sets the minimum image size + * + * @param array $options The minimum image dimensions + * @throws Zend_Validate_Exception When minwidth is greater than maxwidth + * @throws Zend_Validate_Exception When minheight is greater than maxheight + * @return Zend_Validate_File_ImageSize Provides a fluent interface + */ + public function setImageMin($options) + { + if (isset($options['minwidth'])) { + if (($this->_maxwidth !== null) and ($options['minwidth'] > $this->_maxwidth)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The minimum image width must be less than or equal to the " + . " maximum image width, but {$options['minwidth']} > {$this->_maxwidth}"); + } + } + + if (isset($options['maxheight'])) { + if (($this->_maxheight !== null) and ($options['minheight'] > $this->_maxheight)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The minimum image height must be less than or equal to the " + . " maximum image height, but {$options['minheight']} > {$this->_maxheight}"); + } + } + + if (isset($options['minwidth'])) { + $this->_minwidth = (int) $options['minwidth']; + } + + if (isset($options['minheight'])) { + $this->_minheight = (int) $options['minheight']; + } + + return $this; + } + + /** + * Sets the maximum image size + * + * @param array $options The maximum image dimensions + * @throws Zend_Validate_Exception When maxwidth is smaller than minwidth + * @throws Zend_Validate_Exception When maxheight is smaller than minheight + * @return Zend_Validate_StringLength Provides a fluent interface + */ + public function setImageMax($options) + { + if (isset($options['maxwidth'])) { + if (($this->_minwidth !== null) and ($options['maxwidth'] < $this->_minwidth)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The maximum image width must be greater than or equal to the " + . "minimum image width, but {$options['maxwidth']} < {$this->_minwidth}"); + } + } + + if (isset($options['maxheight'])) { + if (($this->_minheight !== null) and ($options['maxheight'] < $this->_minheight)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The maximum image height must be greater than or equal to the " + . "minimum image height, but {$options['maxheight']} < {$this->_minwidth}"); + } + } + + if (isset($options['maxwidth'])) { + $this->_maxwidth = (int) $options['maxwidth']; + } + + if (isset($options['maxheight'])) { + $this->_maxheight = (int) $options['maxheight']; + } + + return $this; + } + + /** + * Sets the mimimum and maximum image width + * + * @param array $options The image width dimensions + * @return Zend_Validate_File_ImageSize Provides a fluent interface + */ + public function setImageWidth($options) + { + $this->setImageMin($options); + $this->setImageMax($options); + + return $this; + } + + /** + * Sets the mimimum and maximum image height + * + * @param array $options The image height dimensions + * @return Zend_Validate_File_ImageSize Provides a fluent interface + */ + public function setImageHeight($options) + { + $this->setImageMin($options); + $this->setImageMax($options); + + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the imagesize of $value is at least min and + * not bigger than max + * + * @param string $value Real file to check for image size + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_READABLE); + } + + $size = @getimagesize($value); + $this->_setValue($file); + + if (empty($size) or ($size[0] === 0) or ($size[1] === 0)) { + return $this->_throw($file, self::NOT_DETECTED); + } + + $this->_width = $size[0]; + $this->_height = $size[1]; + if ($this->_width < $this->_minwidth) { + $this->_throw($file, self::WIDTH_TOO_SMALL); + } + + if (($this->_maxwidth !== null) and ($this->_maxwidth < $this->_width)) { + $this->_throw($file, self::WIDTH_TOO_BIG); + } + + if ($this->_height < $this->_minheight) { + $this->_throw($file, self::HEIGHT_TOO_SMALL); + } + + if (($this->_maxheight !== null) and ($this->_maxheight < $this->_height)) { + $this->_throw($file, self::HEIGHT_TOO_BIG); + } + + if (count($this->_messages) > 0) { + return false; + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function _throw($file, $errorType) + { + if ($file !== null) { + $this->_value = $file['name']; + } + + $this->_error($errorType); + return false; + } +} diff --git a/lib/Zend/Validate/File/IsCompressed.php b/lib/Zend/Validate/File/IsCompressed.php new file mode 100644 index 0000000..050eb1c --- /dev/null +++ b/lib/Zend/Validate/File/IsCompressed.php @@ -0,0 +1,133 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_File_MimeType + */ +require_once 'Zend/Validate/File/MimeType.php'; + +/** + * Validator which checks if the file already exists in the directory + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_IsCompressed extends Zend_Validate_File_MimeType +{ + /** + * @const string Error constants + */ + const FALSE_TYPE = 'fileIsCompressedFalseType'; + const NOT_DETECTED = 'fileIsCompressedNotDetected'; + const NOT_READABLE = 'fileIsCompressedNotReadable'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::FALSE_TYPE => "The file '%value%' is not compressed, '%type%' detected", + self::NOT_DETECTED => "The mimetype of file '%value%' has not been detected", + self::NOT_READABLE => "The file '%value%' can not be read" + ); + + /** + * Sets validator options + * + * @param string|array $compression + * @return void + */ + public function __construct($mimetype = array()) + { + if (empty($mimetype)) { + $mimetype = array( + 'application/x-tar', + 'application/x-cpio', + 'application/x-debian-package', + 'application/x-archive', + 'application/x-arc', + 'application/x-arj', + 'application/x-lharc', + 'application/x-lha', + 'application/x-rar', + 'application/zip', + 'application/zoo', + 'application/x-eet', + 'application/x-java-pack200', + 'application/x-compress', + 'application/x-gzip', + 'application/x-bzip2' + ); + } + + $this->setMimeType($mimetype); + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the file is compression with the set compression types + * + * @param string $value Real file to check for compression + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_READABLE); + } + + if ($file !== null) { + if (class_exists('finfo', false) && defined('MAGIC')) { + $mime = new finfo(FILEINFO_MIME); + $this->_type = $mime->file($value); + unset($mime); + } elseif (function_exists('mime_content_type') && ini_get('mime_magic.magicfile')) { + $this->_type = mime_content_type($value); + } else { + $this->_type = $file['type']; + } + } + + if (empty($this->_type)) { + return $this->_throw($file, self::NOT_DETECTED); + } + + $compressions = $this->getMimeType(true); + if (in_array($this->_type, $compressions)) { + return true; + } + + $types = explode('/', $this->_type); + $types = array_merge($types, explode('-', $this->_type)); + foreach ($compressions as $mime) { + if (in_array($mime, $types)) { + return true; + } + } + + return $this->_throw($file, self::FALSE_TYPE); + } +} diff --git a/lib/Zend/Validate/File/IsImage.php b/lib/Zend/Validate/File/IsImage.php new file mode 100644 index 0000000..045c7fe --- /dev/null +++ b/lib/Zend/Validate/File/IsImage.php @@ -0,0 +1,137 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_File_MimeType + */ +require_once 'Zend/Validate/File/MimeType.php'; + +/** + * Validator which checks if the file already exists in the directory + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_IsImage extends Zend_Validate_File_MimeType +{ + /** + * @const string Error constants + */ + const FALSE_TYPE = 'fileIsImageFalseType'; + const NOT_DETECTED = 'fileIsImageNotDetected'; + const NOT_READABLE = 'fileIsImageNotReadable'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::FALSE_TYPE => "The file '%value%' is no image, '%type%' detected", + self::NOT_DETECTED => "The mimetype of file '%value%' has not been detected", + self::NOT_READABLE => "The file '%value%' can not be read" + ); + + /** + * Sets validator options + * + * @param string|array $mimetype + * @return void + */ + public function __construct($mimetype = array()) + { + if (empty($mimetype)) { + $mimetype = array( + 'image/x-quicktime', + 'image/jp2', + 'image/x-xpmi', + 'image/x-portable-bitmap', + 'image/x-portable-greymap', + 'image/x-portable-pixmap', + 'image/x-niff', + 'image/tiff', + 'image/png', + 'image/x-unknown', + 'image/gif', + 'image/x-ms-bmp', + 'application/dicom', + 'image/vnd.adobe.photoshop', + 'image/vnd.djvu', + 'image/x-cpi', + 'image/jpeg', + 'image/x-ico', + 'image/x-coreldraw', + 'image/svg+xml' + ); + } + + $this->setMimeType($mimetype); + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the file is compression with the set compression types + * + * @param string $value Real file to check for compression + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_READABLE); + } + + if ($file !== null) { + if (class_exists('finfo', false) && defined('MAGIC')) { + $mime = new finfo(FILEINFO_MIME); + $this->_type = $mime->file($value); + unset($mime); + } elseif (function_exists('mime_content_type') && ini_get('mime_magic.magicfile')) { + $this->_type = mime_content_type($value); + } else { + $this->_type = $file['type']; + } + } + + if (empty($this->_type)) { + return $this->_throw($file, self::NOT_DETECTED); + } + + $compressions = $this->getMimeType(true); + if (in_array($this->_type, $compressions)) { + return true; + } + + $types = explode('/', $this->_type); + $types = array_merge($types, explode('-', $this->_type)); + foreach($compressions as $mime) { + if (in_array($mime, $types)) { + return true; + } + } + + return $this->_throw($file, self::FALSE_TYPE); + } +} diff --git a/lib/Zend/Validate/File/Md5.php b/lib/Zend/Validate/File/Md5.php new file mode 100644 index 0000000..4b612d0 --- /dev/null +++ b/lib/Zend/Validate/File/Md5.php @@ -0,0 +1,183 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_File_Hash + */ +require_once 'Zend/Validate/File/Hash.php'; + +/** + * Validator for the md5 hash of given files + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Md5 extends Zend_Validate_File_Hash +{ + /** + * @const string Error constants + */ + const DOES_NOT_MATCH = 'fileMd5DoesNotMatch'; + const NOT_DETECTED = 'fileMd5NotDetected'; + const NOT_FOUND = 'fileMd5NotFound'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::DOES_NOT_MATCH => "The file '%value%' does not match the given md5 hashes", + self::NOT_DETECTED => "There was no md5 hash detected for the given file", + self::NOT_FOUND => "The file '%value%' could not be found" + ); + + /** + * Hash of the file + * + * @var string + */ + protected $_hash; + + /** + * Sets validator options + * + * $hash is the hash we accept for the file $file + * + * @param string|array $options + * @return void + */ + public function __construct($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (is_scalar($options)) { + $options = array('hash1' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Invalid options to validator provided'); + } + + $this->setMd5($options); + } + + /** + * Returns all set md5 hashes + * + * @return array + */ + public function getMd5() + { + return $this->getHash(); + } + + /** + * Sets the md5 hash for one or multiple files + * + * @param string|array $options + * @param string $algorithm (Deprecated) Algorithm to use, fixed to md5 + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function setHash($options) + { + if (!is_array($options)) { + $options = (array) $options; + } + + $options['algorithm'] = 'md5'; + parent::setHash($options); + return $this; + } + + /** + * Sets the md5 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function setMd5($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the md5 hash for one or multiple files + * + * @param string|array $options + * @param string $algorithm (Depreciated) Algorithm to use, fixed to md5 + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function addHash($options) + { + if (!is_array($options)) { + $options = (array) $options; + } + + $options['algorithm'] = 'md5'; + parent::addHash($options); + return $this; + } + + /** + * Adds the md5 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function addMd5($options) + { + $this->addHash($options); + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the given file confirms the set hash + * + * @param string $value Filename to check for hash + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_FOUND); + } + + $hashes = array_unique(array_keys($this->_hash)); + $filehash = hash_file('md5', $value); + if ($filehash === false) { + return $this->_throw($file, self::NOT_DETECTED); + } + + foreach($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + return $this->_throw($file, self::DOES_NOT_MATCH); + } +} diff --git a/lib/Zend/Validate/File/MimeType.php b/lib/Zend/Validate/File/MimeType.php new file mode 100644 index 0000000..1605dad --- /dev/null +++ b/lib/Zend/Validate/File/MimeType.php @@ -0,0 +1,283 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * Validator for the mime type of a file + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_MimeType extends Zend_Validate_Abstract +{ + /**#@+ + * @const Error type constants + */ + const FALSE_TYPE = 'fileMimeTypeFalse'; + const NOT_DETECTED = 'fileMimeTypeNotDetected'; + const NOT_READABLE = 'fileMimeTypeNotReadable'; + /**#@-*/ + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::FALSE_TYPE => "The file '%value%' has a false mimetype of '%type%'", + self::NOT_DETECTED => "The mimetype of file '%value%' could not been detected", + self::NOT_READABLE => "The file '%value%' can not be read" + ); + + /** + * @var array + */ + protected $_messageVariables = array( + 'type' => '_type' + ); + + /** + * @var string + */ + protected $_type; + + /** + * Mimetypes + * + * If null, there is no mimetype + * + * @var string|null + */ + protected $_mimetype; + + /** + * Magicfile to use + * + * @var string|null + */ + protected $_magicfile; + + /** + * Sets validator options + * + * Mimetype to accept + * + * @param string|array $mimetype MimeType + * @return void + */ + public function __construct($mimetype) + { + if ($mimetype instanceof Zend_Config) { + $mimetype = $mimetype->toArray(); + } elseif (is_string($mimetype)) { + $mimetype = explode(',', $mimetype); + } elseif (!is_array($mimetype)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("Invalid options to validator provided"); + } + + if (isset($mimetype['magicfile'])) { + $this->setMagicFile($mimetype['magicfile']); + } + + $this->setMimeType($mimetype); + } + + /** + * Returna the actual set magicfile + * + * @return string + */ + public function getMagicFile() + { + return $this->_magicfile; + } + + /** + * Sets the magicfile to use + * if null, the MAGIC constant from php is used + * + * @param string $file + * @return Zend_Validate_File_MimeType Provides fluid interface + */ + public function setMagicFile($file) + { + if (empty($file)) { + $this->_magicfile = null; + } else if (!is_readable($file)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('The given magicfile can not be read'); + } else { + $this->_magicfile = (string) $file; + } + + return $this; + } + + /** + * Returns the set mimetypes + * + * @param boolean $asArray Returns the values as array, when false an concated string is returned + * @return string|array + */ + public function getMimeType($asArray = false) + { + $asArray = (bool) $asArray; + $mimetype = (string) $this->_mimetype; + if ($asArray) { + $mimetype = explode(',', $mimetype); + } + + return $mimetype; + } + + /** + * Sets the mimetypes + * + * @param string|array $mimetype The mimetypes to validate + * @return Zend_Validate_File_Extension Provides a fluent interface + */ + public function setMimeType($mimetype) + { + $this->_mimetype = null; + $this->addMimeType($mimetype); + return $this; + } + + /** + * Adds the mimetypes + * + * @param string|array $mimetype The mimetypes to add for validation + * @return Zend_Validate_File_Extension Provides a fluent interface + */ + public function addMimeType($mimetype) + { + $mimetypes = $this->getMimeType(true); + + if (is_string($mimetype)) { + $mimetype = explode(',', $mimetype); + } elseif (!is_array($mimetype)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("Invalid options to validator provided"); + } + + if (isset($mimetype['magicfile'])) { + unset($mimetype['magicfile']); + } + + foreach ($mimetype as $content) { + if (empty($content) || !is_string($content)) { + continue; + } + $mimetypes[] = trim($content); + } + $mimetypes = array_unique($mimetypes); + + // Sanity check to ensure no empty values + foreach ($mimetypes as $key => $mt) { + if (empty($mt)) { + unset($mimetypes[$key]); + } + } + + $this->_mimetype = implode(',', $mimetypes); + + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if the mimetype of the file matches the given ones. Also parts + * of mimetypes can be checked. If you give for example "image" all image + * mime types will be accepted like "image/gif", "image/jpeg" and so on. + * + * @param string $value Real file to check for mimetype + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_READABLE); + } + + if ($file !== null) { + $mimefile = $this->getMagicFile(); + if (class_exists('finfo', false) && ((!empty($mimefile)) or (defined('MAGIC')))) { + if (!empty($mimefile)) { + $mime = new finfo(FILEINFO_MIME, $mimefile); + } else { + $mime = new finfo(FILEINFO_MIME); + } + + $this->_type = $mime->file($value); + unset($mime); + } elseif (function_exists('mime_content_type') && ini_get('mime_magic.magicfile')) { + $this->_type = mime_content_type($value); + } else { + $this->_type = $file['type']; + } + } + + if (empty($this->_type)) { + return $this->_throw($file, self::NOT_DETECTED); + } + + $mimetype = $this->getMimeType(true); + if (in_array($this->_type, $mimetype)) { + return true; + } + + $types = explode('/', $this->_type); + $types = array_merge($types, explode('-', $this->_type)); + foreach($mimetype as $mime) { + if (in_array($mime, $types)) { + return true; + } + } + + return $this->_throw($file, self::FALSE_TYPE); + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function _throw($file, $errorType) + { + if ($file !== null) { + $this->_value = $file['name']; + } + + $this->_error($errorType); + return false; + } +} diff --git a/lib/Zend/Validate/File/NotExists.php b/lib/Zend/Validate/File/NotExists.php new file mode 100644 index 0000000..8aeef9f --- /dev/null +++ b/lib/Zend/Validate/File/NotExists.php @@ -0,0 +1,84 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_File_Exists + */ +require_once 'Zend/Validate/File/Exists.php'; + +/** + * Validator which checks if the destination file does not exist + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_NotExists extends Zend_Validate_File_Exists +{ + /** + * @const string Error constants + */ + const DOES_EXIST = 'fileNotExistsDoesExist'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::DOES_EXIST => "The file '%value%' does exist" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the file does not exist in the set destinations + * + * @param string $value Real file to check for + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + $directories = $this->getDirectory(true); + if (($file !== null) and (!empty($file['destination']))) { + $directories[] = $file['destination']; + } else if (!isset($file['name'])) { + $file['name'] = $value; + } + + foreach ($directories as $directory) { + if (empty($directory)) { + continue; + } + + $check = true; + if (file_exists($directory . DIRECTORY_SEPARATOR . $file['name'])) { + return $this->_throw($file, self::DOES_EXIST); + } + } + + if (!isset($check)) { + return $this->_throw($file, self::DOES_EXIST); + } + + return true; + } +} diff --git a/lib/Zend/Validate/File/Sha1.php b/lib/Zend/Validate/File/Sha1.php new file mode 100644 index 0000000..b02a837 --- /dev/null +++ b/lib/Zend/Validate/File/Sha1.php @@ -0,0 +1,181 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_File_Hash + */ +require_once 'Zend/Validate/File/Hash.php'; + +/** + * Validator for the sha1 hash of given files + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Sha1 extends Zend_Validate_File_Hash +{ + /** + * @const string Error constants + */ + const DOES_NOT_MATCH = 'fileSha1DoesNotMatch'; + const NOT_DETECTED = 'fileSha1NotDetected'; + const NOT_FOUND = 'fileSha1NotFound'; + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::DOES_NOT_MATCH => "The file '%value%' does not match the given sha1 hashes", + self::NOT_DETECTED => "There was no sha1 hash detected for the given file", + self::NOT_FOUND => "The file '%value%' could not be found" + ); + + /** + * Hash of the file + * + * @var string + */ + protected $_hash; + + /** + * Sets validator options + * + * $hash is the hash we accept for the file $file + * + * @param string|array $options + * @return void + */ + public function __construct($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (is_scalar($options)) { + $options = array('hash1' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Invalid options to validator provided'); + } + + $this->setHash($options); + } + + /** + * Returns all set sha1 hashes + * + * @return array + */ + public function getSha1() + { + return $this->getHash(); + } + + /** + * Sets the sha1 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function setHash($options) + { + if (!is_array($options)) { + $options = (array) $options; + } + + $options['algorithm'] = 'sha1'; + parent::setHash($options); + return $this; + } + + /** + * Sets the sha1 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function setSha1($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the sha1 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function addHash($options) + { + if (!is_array($options)) { + $options = (array) $options; + } + + $options['algorithm'] = 'sha1'; + parent::addHash($options); + return $this; + } + + /** + * Adds the sha1 hash for one or multiple files + * + * @param string|array $options + * @return Zend_Validate_File_Hash Provides a fluent interface + */ + public function addSha1($options) + { + $this->addHash($options); + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the given file confirms the set hash + * + * @param string $value Filename to check for hash + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_FOUND); + } + + $hashes = array_unique(array_keys($this->_hash)); + $filehash = hash_file('sha1', $value); + if ($filehash === false) { + return $this->_throw($file, self::NOT_DETECTED); + } + + foreach ($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + return $this->_throw($file, self::DOES_NOT_MATCH); + } +} diff --git a/lib/Zend/Validate/File/Size.php b/lib/Zend/Validate/File/Size.php new file mode 100644 index 0000000..a52d499 --- /dev/null +++ b/lib/Zend/Validate/File/Size.php @@ -0,0 +1,404 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * Validator for the maximum size of a file up to a max of 2GB + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Size extends Zend_Validate_Abstract +{ + /**#@+ + * @const string Error constants + */ + const TOO_BIG = 'fileSizeTooBig'; + const TOO_SMALL = 'fileSizeTooSmall'; + const NOT_FOUND = 'fileSizeNotFound'; + /**#@-*/ + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::TOO_BIG => "Maximum allowed size for file '%value%' is '%max%' but '%size%' detected", + self::TOO_SMALL => "Minimum expected size for file '%value%' is '%min%' but '%size%' detected", + self::NOT_FOUND => "The file '%value%' could not be found" + ); + + /** + * @var array Error message template variables + */ + protected $_messageVariables = array( + 'min' => '_min', + 'max' => '_max', + 'size' => '_size', + ); + + /** + * Minimum filesize + * @var integer + */ + protected $_min; + + /** + * Maximum filesize + * + * If null, there is no maximum filesize + * + * @var integer|null + */ + protected $_max; + + /** + * Detected size + * + * @var integer + */ + protected $_size; + + /** + * Use bytestring ? + * + * @var boolean + */ + protected $_useByteString = true; + + /** + * Sets validator options + * + * If $options is a integer, it will be used as maximum filesize + * As Array is accepts the following keys: + * 'min': Minimum filesize + * 'max': Maximum filesize + * 'bytestring': Use bytestring or real size for messages + * + * @param integer|array $options Options for the adapter + */ + public function __construct($options) + { + if ($options instanceof Zend_Config) { + $options = $options->toArray(); + } elseif (is_string($options) || is_numeric($options)) { + $options = array('max' => $options); + } elseif (!is_array($options)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + if (1 < func_num_args()) { + trigger_error('Multiple constructor options are deprecated in favor of a single options array', E_USER_NOTICE); + $argv = func_get_args(); + array_shift($argv); + $options['max'] = array_shift($argv); + if (!empty($argv)) { + $options['bytestring'] = array_shift($argv); + } + } + + if (isset($options['bytestring'])) { + $this->setUseByteString($options['bytestring']); + } + + if (isset($options['min'])) { + $this->setMin($options['min']); + } + + if (isset($options['max'])) { + $this->setMax($options['max']); + } + } + + /** + * Returns the minimum filesize + * + * @param boolean $byteString Use bytestring ? + * @return integer + */ + public function setUseByteString($byteString = true) + { + $this->_useByteString = (bool) $byteString; + return $this; + } + + /** + * Will bytestring be used? + * + * @return boolean + */ + public function useByteString() + { + return $this->_useByteString; + } + + /** + * Returns the minimum filesize + * + * @param bool $raw Whether or not to force return of the raw value (defaults off) + * @return integer|string + */ + public function getMin($raw = false) + { + $min = $this->_min; + if (!$raw && $this->useByteString()) { + $min = $this->_toByteString($min); + } + + return $min; + } + + /** + * Sets the minimum filesize + * + * @param integer $min The minimum filesize + * @throws Zend_Validate_Exception When min is greater than max + * @return Zend_Validate_File_Size Provides a fluent interface + */ + public function setMin($min) + { + if (!is_string($min) and !is_numeric($min)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + $min = (integer) $this->_fromByteString($min); + $max = $this->getMax(true); + if (($max !== null) && ($min > $max)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The minimum must be less than or equal to the maximum filesize, but $min >" + . " $max"); + } + + $this->_min = $min; + return $this; + } + + /** + * Returns the maximum filesize + * + * @param bool $raw Whether or not to force return of the raw value (defaults off) + * @return integer|string + */ + public function getMax($raw = false) + { + $max = $this->_max; + if (!$raw && $this->useByteString()) { + $max = $this->_toByteString($max); + } + + return $max; + } + + /** + * Sets the maximum filesize + * + * @param integer $max The maximum filesize + * @throws Zend_Validate_Exception When max is smaller than min + * @return Zend_Validate_StringLength Provides a fluent interface + */ + public function setMax($max) + { + if (!is_string($max) && !is_numeric($max)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception ('Invalid options to validator provided'); + } + + $max = (integer) $this->_fromByteString($max); + $min = $this->getMin(true); + if (($min !== null) && ($max < $min)) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum filesize, but " + . "$max < $min"); + } + + $this->_max = $max; + return $this; + } + + /** + * Retrieve current detected file size + * + * @return int + */ + protected function _getSize() + { + return $this->_size; + } + + /** + * Set current size + * + * @param int $size + * @return Zend_Validate_File_Size + */ + protected function _setSize($size) + { + $this->_size = $size; + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the filesize of $value is at least min and + * not bigger than max (when max is not null). + * + * @param string $value Real file to check for size + * @param array $file File data from Zend_File_Transfer + * @return boolean + */ + public function isValid($value, $file = null) + { + // Is file readable ? + require_once 'Zend/Loader.php'; + if (!Zend_Loader::isReadable($value)) { + return $this->_throw($file, self::NOT_FOUND); + } + + // limited to 4GB files + $size = sprintf("%u", @filesize($value)); + + // Check to see if it's smaller than min size + $min = $this->getMin(true); + $max = $this->getMax(true); + if (($min !== null) && ($size < $min)) { + if ($this->useByteString()) { + $this->_min = $this->_toByteString($min); + $this->_size = $this->_toByteString($size); + $this->_throw($file, self::TOO_SMALL); + $this->_min = $min; + $this->_size = $size; + } else { + $this->_throw($file, self::TOO_SMALL); + } + } + + // Check to see if it's larger than max size + if (($max !== null) && ($max < $size)) { + if ($this->useByteString()) { + $this->_max = $this->_toByteString($max); + $this->_size = $this->_toByteString($size); + $this->_throw($file, self::TOO_BIG); + $this->_max = $max; + $this->_size = $size; + } else { + $this->_throw($file, self::TOO_BIG); + } + } + + if (count($this->_messages) > 0) { + return false; + } + + return true; + } + + /** + * Returns the formatted size + * + * @param integer $size + * @return string + */ + protected function _toByteString($size) + { + $sizes = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); + for ($i=0; $size >= 1024 && $i < 9; $i++) { + $size /= 1024; + } + + return round($size, 2) . $sizes[$i]; + } + + /** + * Returns the unformatted size + * + * @param string $size + * @return integer + */ + protected function _fromByteString($size) + { + if (is_numeric($size)) { + return (integer) $size; + } + + $type = trim(substr($size, -2, 1)); + + $value = substr($size, 0, -1); + if (!is_numeric($value)) { + $value = substr($value, 0, -1); + } + + switch (strtoupper($type)) { + case 'Y': + $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024); + break; + case 'Z': + $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024); + break; + case 'E': + $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024); + break; + case 'P': + $value *= (1024 * 1024 * 1024 * 1024 * 1024); + break; + case 'T': + $value *= (1024 * 1024 * 1024 * 1024); + break; + case 'G': + $value *= (1024 * 1024 * 1024); + break; + case 'M': + $value *= (1024 * 1024); + break; + case 'K': + $value *= 1024; + break; + default: + break; + } + + return $value; + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function _throw($file, $errorType) + { + if ($file !== null) { + $this->_value = $file['name']; + } + + $this->_error($errorType); + return false; + } +} diff --git a/lib/Zend/Validate/File/Upload.php b/lib/Zend/Validate/File/Upload.php new file mode 100644 index 0000000..ddeb328 --- /dev/null +++ b/lib/Zend/Validate/File/Upload.php @@ -0,0 +1,234 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * Validator for the maximum size of a file up to a max of 2GB + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_File_Upload extends Zend_Validate_Abstract +{ + /**@#+ + * @const string Error constants + */ + const INI_SIZE = 'fileUploadErrorIniSize'; + const FORM_SIZE = 'fileUploadErrorFormSize'; + const PARTIAL = 'fileUploadErrorPartial'; + const NO_FILE = 'fileUploadErrorNoFile'; + const NO_TMP_DIR = 'fileUploadErrorNoTmpDir'; + const CANT_WRITE = 'fileUploadErrorCantWrite'; + const EXTENSION = 'fileUploadErrorExtension'; + const ATTACK = 'fileUploadErrorAttack'; + const FILE_NOT_FOUND = 'fileUploadErrorFileNotFound'; + const UNKNOWN = 'fileUploadErrorUnknown'; + /**@#-*/ + + /** + * @var array Error message templates + */ + protected $_messageTemplates = array( + self::INI_SIZE => "The file '%value%' exceeds the defined ini size", + self::FORM_SIZE => "The file '%value%' exceeds the defined form size", + self::PARTIAL => "The file '%value%' was only partially uploaded", + self::NO_FILE => "The file '%value%' was not uploaded", + self::NO_TMP_DIR => "No temporary directory was found for the file '%value%'", + self::CANT_WRITE => "The file '%value%' can't be written", + self::EXTENSION => "The extension returned an error while uploading the file '%value%'", + self::ATTACK => "The file '%value%' was illegal uploaded, possible attack", + self::FILE_NOT_FOUND => "The file '%value%' was not found", + self::UNKNOWN => "Unknown error while uploading the file '%value%'" + ); + + /** + * Internal array of files + * @var array + */ + protected $_files = array(); + + /** + * Sets validator options + * + * The array $files must be given in syntax of Zend_File_Transfer to be checked + * If no files are given the $_FILES array will be used automatically. + * NOTE: This validator will only work with HTTP POST uploads! + * + * @param array $files Array of files in syntax of Zend_File_Transfer + * @return void + */ + public function __construct($files = array()) + { + $this->setFiles($files); + } + + /** + * Returns the array of set files + * + * @param string $files (Optional) The file to return in detail + * @return array + * @throws Zend_Validate_Exception If file is not found + */ + public function getFiles($file = null) + { + if ($file !== null) { + $return = array(); + foreach ($this->_files as $name => $content) { + if ($name === $file) { + $return[$file] = $this->_files[$name]; + } + + if ($content['name'] === $file) { + $return[$name] = $this->_files[$name]; + } + } + + if (count($return) === 0) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The file '$file' was not found"); + } + + return $return; + } + + return $this->_files; + } + + /** + * Sets the minimum filesize + * + * @param array $files The files to check in syntax of Zend_File_Transfer + * @return Zend_Validate_File_Upload Provides a fluent interface + */ + public function setFiles($files = array()) + { + if (count($files) === 0) { + $this->_files = $_FILES; + } else { + $this->_files = $files; + } + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the file was uploaded without errors + * + * @param string $value Single file to check for upload errors, when giving null the $_FILES array + * from initialization will be used + * @return boolean + */ + public function isValid($value, $file = null) + { + if (array_key_exists($value, $this->_files)) { + $files[$value] = $this->_files[$value]; + } else { + foreach ($this->_files as $file => $content) { + if ($content['name'] === $value) { + $files[$file] = $this->_files[$file]; + } + + if ($content['tmp_name'] === $value) { + $files[$file] = $this->_files[$file]; + } + } + } + + if (empty($files)) { + return $this->_throw($file, self::FILE_NOT_FOUND); + } + + foreach ($files as $file => $content) { + $this->_value = $file; + switch($content['error']) { + case 0: + if (!is_uploaded_file($content['tmp_name'])) { + $this->_throw($file, self::ATTACK); + } + break; + + case 1: + $this->_throw($file, self::INI_SIZE); + break; + + case 2: + $this->_throw($file, self::FORM_SIZE); + break; + + case 3: + $this->_throw($file, self::PARTIAL); + break; + + case 4: + $this->_throw($file, self::NO_FILE); + break; + + case 6: + $this->_throw($file, self::NO_TMP_DIR); + break; + + case 7: + $this->_throw($file, self::CANT_WRITE); + break; + + case 8: + $this->_throw($file, self::EXTENSION); + break; + + default: + $this->_throw($file, self::UNKNOWN); + break; + } + } + + if (count($this->_messages) > 0) { + return false; + } else { + return true; + } + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function _throw($file, $errorType) + { + if ($file !== null) { + if (is_array($file) and !empty($file['name'])) { + $this->_value = $file['name']; + } + } + + $this->_error($errorType); + return false; + } +} diff --git a/lib/Zend/Validate/Float.php b/lib/Zend/Validate/Float.php new file mode 100644 index 0000000..0405161 --- /dev/null +++ b/lib/Zend/Validate/Float.php @@ -0,0 +1,75 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Float.php 8714 2008-03-09 20:03:45Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Float extends Zend_Validate_Abstract +{ + + const NOT_FLOAT = 'notFloat'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::NOT_FLOAT => "'%value%' does not appear to be a float" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is a floating-point value + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + $locale = localeconv(); + + $valueFiltered = str_replace($locale['thousands_sep'], '', $valueString); + $valueFiltered = str_replace($locale['decimal_point'], '.', $valueFiltered); + + if (strval(floatval($valueFiltered)) != $valueFiltered) { + $this->_error(); + return false; + } + + return true; + } + +} diff --git a/lib/Zend/Validate/GreaterThan.php b/lib/Zend/Validate/GreaterThan.php new file mode 100644 index 0000000..35e658c --- /dev/null +++ b/lib/Zend/Validate/GreaterThan.php @@ -0,0 +1,114 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: GreaterThan.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_GreaterThan extends Zend_Validate_Abstract +{ + + const NOT_GREATER = 'notGreaterThan'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::NOT_GREATER => "'%value%' is not greater than '%min%'" + ); + + /** + * @var array + */ + protected $_messageVariables = array( + 'min' => '_min' + ); + + /** + * Minimum value + * + * @var mixed + */ + protected $_min; + + /** + * Sets validator options + * + * @param mixed $min + * @return void + */ + public function __construct($min) + { + $this->setMin($min); + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->_min; + } + + /** + * Sets the min option + * + * @param mixed $min + * @return Zend_Validate_GreaterThan Provides a fluent interface + */ + public function setMin($min) + { + $this->_min = $min; + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is greater than min option + * + * @param mixed $value + * @return boolean + */ + public function isValid($value) + { + $this->_setValue($value); + + if ($this->_min >= $value) { + $this->_error(); + return false; + } + return true; + } + +} diff --git a/lib/Zend/Validate/Hex.php b/lib/Zend/Validate/Hex.php new file mode 100644 index 0000000..9512eda --- /dev/null +++ b/lib/Zend/Validate/Hex.php @@ -0,0 +1,74 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Hex.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hex extends Zend_Validate_Abstract +{ + /** + * Validation failure message key for when the value contains characters other than hexadecimal digits + */ + const NOT_HEX = 'notHex'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::NOT_HEX => "'%value%' has not only hexadecimal digit characters" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value contains only hexadecimal digit characters + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + if (!ctype_xdigit($valueString)) { + $this->_error(); + return false; + } + + return true; + } + +} diff --git a/lib/Zend/Validate/Hostname.php b/lib/Zend/Validate/Hostname.php new file mode 100644 index 0000000..61b0b23 --- /dev/null +++ b/lib/Zend/Validate/Hostname.php @@ -0,0 +1,444 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Hostname.php 12274 2008-11-03 12:50:50Z yoshida@zend.co.jp $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * @see Zend_Loader + */ +require_once 'Zend/Loader.php'; + +/** + * @see Zend_Validate_Ip + */ +require_once 'Zend/Validate/Ip.php'; + +/** + * Please note there are two standalone test scripts for testing IDN characters due to problems + * with file encoding. + * + * The first is tests/Zend/Validate/HostnameTestStandalone.php which is designed to be run on + * the command line. + * + * The second is tests/Zend/Validate/HostnameTestForm.php which is designed to be run via HTML + * to allow users to test entering UTF-8 characters in a form. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname extends Zend_Validate_Abstract +{ + + const IP_ADDRESS_NOT_ALLOWED = 'hostnameIpAddressNotAllowed'; + const UNKNOWN_TLD = 'hostnameUnknownTld'; + const INVALID_DASH = 'hostnameDashCharacter'; + const INVALID_HOSTNAME_SCHEMA = 'hostnameInvalidHostnameSchema'; + const UNDECIPHERABLE_TLD = 'hostnameUndecipherableTld'; + const INVALID_HOSTNAME = 'hostnameInvalidHostname'; + const INVALID_LOCAL_NAME = 'hostnameInvalidLocalName'; + const LOCAL_NAME_NOT_ALLOWED = 'hostnameLocalNameNotAllowed'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::IP_ADDRESS_NOT_ALLOWED => "'%value%' appears to be an IP address, but IP addresses are not allowed", + self::UNKNOWN_TLD => "'%value%' appears to be a DNS hostname but cannot match TLD against known list", + self::INVALID_DASH => "'%value%' appears to be a DNS hostname but contains a dash (-) in an invalid position", + self::INVALID_HOSTNAME_SCHEMA => "'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'", + self::UNDECIPHERABLE_TLD => "'%value%' appears to be a DNS hostname but cannot extract TLD part", + self::INVALID_HOSTNAME => "'%value%' does not match the expected structure for a DNS hostname", + self::INVALID_LOCAL_NAME => "'%value%' does not appear to be a valid local network name", + self::LOCAL_NAME_NOT_ALLOWED => "'%value%' appears to be a local network name but local network names are not allowed" + ); + + /** + * @var array + */ + protected $_messageVariables = array( + 'tld' => '_tld' + ); + + /** + * Allows Internet domain names (e.g., example.com) + */ + const ALLOW_DNS = 1; + + /** + * Allows IP addresses + */ + const ALLOW_IP = 2; + + /** + * Allows local network names (e.g., localhost, www.localdomain) + */ + const ALLOW_LOCAL = 4; + + /** + * Allows all types of hostnames + */ + const ALLOW_ALL = 7; + + /** + * Whether IDN domains are validated + * + * @var boolean + */ + private $_validateIdn = true; + + /** + * Whether TLDs are validated against a known list + * + * @var boolean + */ + private $_validateTld = true; + + /** + * Bit field of ALLOW constants; determines which types of hostnames are allowed + * + * @var integer + */ + protected $_allow; + + /** + * Bit field of CHECK constants; determines what additional hostname checks to make + * + * @var unknown_type + */ + // protected $_check; + + /** + * Array of valid top-level-domains + * + * @var array + * @see ftp://data.iana.org/TLD/tlds-alpha-by-domain.txt List of all TLDs by domain + */ + protected $_validTlds = array( + 'ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', + 'aq', 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', + 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bm', 'bn', 'bo', + 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd', + 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', + 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', + 'dz', 'ec', 'edu', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', + 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', + 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', + 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', + 'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', + 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', + 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', + 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', + 'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', + 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng', + 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe', + 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', + 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', + 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', + 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj', + 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', + 'tz', 'ua', 'ug', 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', + 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm', + 'zw' + ); + + /** + * @var string + */ + protected $_tld; + + /** + * Sets validator options + * + * @param integer $allow OPTIONAL Set what types of hostname to allow (default ALLOW_DNS) + * @param boolean $validateIdn OPTIONAL Set whether IDN domains are validated (default true) + * @param boolean $validateTld OPTIONAL Set whether the TLD element of a hostname is validated (default true) + * @param Zend_Validate_Ip $ipValidator OPTIONAL + * @return void + * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm Technical Specifications for ccTLDs + */ + public function __construct($allow = self::ALLOW_DNS, $validateIdn = true, $validateTld = true, Zend_Validate_Ip $ipValidator = null) + { + // Set allow options + $this->setAllow($allow); + + // Set validation options + $this->_validateIdn = $validateIdn; + $this->_validateTld = $validateTld; + + $this->setIpValidator($ipValidator); + } + + /** + * @param Zend_Validate_Ip $ipValidator OPTIONAL + * @return void; + */ + public function setIpValidator(Zend_Validate_Ip $ipValidator = null) + { + if ($ipValidator === null) { + $ipValidator = new Zend_Validate_Ip(); + } + $this->_ipValidator = $ipValidator; + } + + /** + * Returns the allow option + * + * @return integer + */ + public function getAllow() + { + return $this->_allow; + } + + /** + * Sets the allow option + * + * @param integer $allow + * @return Zend_Validate_Hostname Provides a fluent interface + */ + public function setAllow($allow) + { + $this->_allow = $allow; + return $this; + } + + /** + * Set whether IDN domains are validated + * + * This only applies when DNS hostnames are validated + * + * @param boolean $allowed Set allowed to true to validate IDNs, and false to not validate them + */ + public function setValidateIdn ($allowed) + { + $this->_validateIdn = (bool) $allowed; + } + + /** + * Set whether the TLD element of a hostname is validated + * + * This only applies when DNS hostnames are validated + * + * @param boolean $allowed Set allowed to true to validate TLDs, and false to not validate them + */ + public function setValidateTld ($allowed) + { + $this->_validateTld = (bool) $allowed; + } + + /** + * Sets the check option + * + * @param integer $check + * @return Zend_Validate_Hostname Provides a fluent interface + */ + /* + public function setCheck($check) + { + $this->_check = $check; + return $this; + } + */ + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the $value is a valid hostname with respect to the current allow option + * + * @param string $value + * @throws Zend_Validate_Exception if a fatal error occurs for validation process + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + // Check input against IP address schema + if ($this->_ipValidator->setTranslator($this->getTranslator())->isValid($valueString)) { + if (!($this->_allow & self::ALLOW_IP)) { + $this->_error(self::IP_ADDRESS_NOT_ALLOWED); + return false; + } else{ + return true; + } + } + + // Check input against DNS hostname schema + $domainParts = explode('.', $valueString); + if ((count($domainParts) > 1) && (strlen($valueString) >= 4) && (strlen($valueString) <= 254)) { + $status = false; + + do { + // First check TLD + if (preg_match('/([a-z]{2,10})$/i', end($domainParts), $matches)) { + + reset($domainParts); + + // Hostname characters are: *(label dot)(label dot label); max 254 chars + // label: id-prefix [*ldh{61} id-prefix]; max 63 chars + // id-prefix: alpha / digit + // ldh: alpha / digit / dash + + // Match TLD against known list + $this->_tld = strtolower($matches[1]); + if ($this->_validateTld) { + if (!in_array($this->_tld, $this->_validTlds)) { + $this->_error(self::UNKNOWN_TLD); + $status = false; + break; + } + } + + /** + * Match against IDN hostnames + * @see Zend_Validate_Hostname_Interface + */ + $labelChars = 'a-z0-9'; + $utf8 = false; + $classFile = 'Zend/Validate/Hostname/' . ucfirst($this->_tld) . '.php'; + if ($this->_validateIdn) { + if (Zend_Loader::isReadable($classFile)) { + + // Load additional characters + $className = 'Zend_Validate_Hostname_' . ucfirst($this->_tld); + Zend_Loader::loadClass($className); + $labelChars .= call_user_func(array($className, 'getCharacters')); + $utf8 = true; + } + } + + // Keep label regex short to avoid issues with long patterns when matching IDN hostnames + $regexLabel = '/^[' . $labelChars . '\x2d]{1,63}$/i'; + if ($utf8) { + $regexLabel .= 'u'; + } + + // Check each hostname part + $valid = true; + foreach ($domainParts as $domainPart) { + + // Check dash (-) does not start, end or appear in 3rd and 4th positions + if (strpos($domainPart, '-') === 0 || + (strlen($domainPart) > 2 && strpos($domainPart, '-', 2) == 2 && strpos($domainPart, '-', 3) == 3) || + strrpos($domainPart, '-') === strlen($domainPart) - 1) { + + $this->_error(self::INVALID_DASH); + $status = false; + break 2; + } + + // Check each domain part + $status = @preg_match($regexLabel, $domainPart); + if ($status === false) { + /** + * Regex error + * @see Zend_Validate_Exception + */ + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Internal error: DNS validation failed'); + } elseif ($status === 0) { + $valid = false; + } + } + + // If all labels didn't match, the hostname is invalid + if (!$valid) { + $this->_error(self::INVALID_HOSTNAME_SCHEMA); + $status = false; + } + + } else { + // Hostname not long enough + $this->_error(self::UNDECIPHERABLE_TLD); + $status = false; + } + } while (false); + + // If the input passes as an Internet domain name, and domain names are allowed, then the hostname + // passes validation + if ($status && ($this->_allow & self::ALLOW_DNS)) { + return true; + } + } else { + $this->_error(self::INVALID_HOSTNAME); + } + + // Check input against local network name schema; last chance to pass validation + $regexLocal = '/^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}){1,254}$/'; + $status = @preg_match($regexLocal, $valueString); + if (false === $status) { + /** + * Regex error + * @see Zend_Validate_Exception + */ + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Internal error: local network name validation failed'); + } + + // If the input passes as a local network name, and local network names are allowed, then the + // hostname passes validation + $allowLocal = $this->_allow & self::ALLOW_LOCAL; + if ($status && $allowLocal) { + return true; + } + + // If the input does not pass as a local network name, add a message + if (!$status) { + $this->_error(self::INVALID_LOCAL_NAME); + } + + // If local network names are not allowed, add a message + if ($status && !$allowLocal) { + $this->_error(self::LOCAL_NAME_NOT_ALLOWED); + } + + return false; + } + + /** + * Throws an exception if a regex for $type does not exist + * + * @param string $type + * @throws Zend_Validate_Exception + * @return Zend_Validate_Hostname Provides a fluent interface + */ + /* + protected function _checkRegexType($type) + { + if (!isset($this->_regex[$type])) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("'$type' must be one of ('" . implode(', ', array_keys($this->_regex)) + . "')"); + } + return $this; + } + */ + +} diff --git a/lib/Zend/Validate/Hostname/At.php b/lib/Zend/Validate/Hostname/At.php new file mode 100644 index 0000000..fff6bf2 --- /dev/null +++ b/lib/Zend/Validate/Hostname/At.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: At.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Hostname_Interface + */ +require_once 'Zend/Validate/Hostname/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname_At implements Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * @see http://www.nic.at/en/service/technical_information/idn/charset_converter/ Austria (.AT) + * @return string + */ + static function getCharacters() + { + return '\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}\x{0161}\x{017E}'; + } + +}
\ No newline at end of file diff --git a/lib/Zend/Validate/Hostname/Ch.php b/lib/Zend/Validate/Hostname/Ch.php new file mode 100644 index 0000000..72fae7b --- /dev/null +++ b/lib/Zend/Validate/Hostname/Ch.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Ch.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Hostname_Interface + */ +require_once 'Zend/Validate/Hostname/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname_Ch implements Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * @see https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1 Switzerland (.CH) + * @return string + */ + static function getCharacters() + { + return '\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}'; + } + +}
\ No newline at end of file diff --git a/lib/Zend/Validate/Hostname/De.php b/lib/Zend/Validate/Hostname/De.php new file mode 100644 index 0000000..cfea068 --- /dev/null +++ b/lib/Zend/Validate/Hostname/De.php @@ -0,0 +1,58 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: De.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Hostname_Interface + */ +require_once 'Zend/Validate/Hostname/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname_De implements Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * @see http://www.denic.de/en/domains/idns/liste.html Germany (.DE) alllowed characters + * @return string + */ + static function getCharacters() + { + return '\x{00E1}\x{00E0}\x{0103}\x{00E2}\x{00E5}\x{00E4}\x{00E3}\x{0105}\x{0101}\x{00E6}\x{0107}' . + '\x{0109}\x{010D}\x{010B}\x{00E7}\x{010F}\x{0111}\x{00E9}\x{00E8}\x{0115}\x{00EA}\x{011B}' . + '\x{00EB}\x{0117}\x{0119}\x{0113}\x{011F}\x{011D}\x{0121}\x{0123}\x{0125}\x{0127}\x{00ED}' . + '\x{00EC}\x{012D}\x{00EE}\x{00EF}\x{0129}\x{012F}\x{012B}\x{0131}\x{0135}\x{0137}\x{013A}' . + '\x{013E}\x{013C}\x{0142}\x{0144}\x{0148}\x{00F1}\x{0146}\x{014B}\x{00F3}\x{00F2}\x{014F}' . + '\x{00F4}\x{00F6}\x{0151}\x{00F5}\x{00F8}\x{014D}\x{0153}\x{0138}\x{0155}\x{0159}\x{0157}' . + '\x{015B}\x{015D}\x{0161}\x{015F}\x{0165}\x{0163}\x{0167}\x{00FA}\x{00F9}\x{016D}\x{00FB}' . + '\x{016F}\x{00FC}\x{0171}\x{0169}\x{0173}\x{016B}\x{0175}\x{00FD}\x{0177}\x{00FF}\x{017A}' . + '\x{017E}\x{017C}\x{00F0}\x{00FE}'; + } + +}
\ No newline at end of file diff --git a/lib/Zend/Validate/Hostname/Fi.php b/lib/Zend/Validate/Hostname/Fi.php new file mode 100644 index 0000000..a28ba56 --- /dev/null +++ b/lib/Zend/Validate/Hostname/Fi.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Fi.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Hostname_Interface + */ +require_once 'Zend/Validate/Hostname/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname_Fi implements Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * @see http://www.ficora.fi/en/index/palvelut/fiverkkotunnukset/aakkostenkaytto.html Finland (.FI) + * @return string + */ + static function getCharacters() + { + return '\x{00E5}\x{00E4}\x{00F6}'; + } + +}
\ No newline at end of file diff --git a/lib/Zend/Validate/Hostname/Hu.php b/lib/Zend/Validate/Hostname/Hu.php new file mode 100644 index 0000000..e6f9795 --- /dev/null +++ b/lib/Zend/Validate/Hostname/Hu.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Hu.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Hostname_Interface + */ +require_once 'Zend/Validate/Hostname/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname_Hu implements Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * @see http://www.domain.hu/domain/English/szabalyzat.html Hungary (.HU) + * @return string + */ + static function getCharacters() + { + return '\x{00E1}\x{00E9}\x{00ED}\x{00F3}\x{00F6}\x{0151}\x{00FA}\x{00FC}\x{0171}'; + } + +}
\ No newline at end of file diff --git a/lib/Zend/Validate/Hostname/Interface.php b/lib/Zend/Validate/Hostname/Interface.php new file mode 100644 index 0000000..4426d8b --- /dev/null +++ b/lib/Zend/Validate/Hostname/Interface.php @@ -0,0 +1,52 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Interface.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +interface Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * UTF-8 characters should be written as four character hex codes \x{XXXX} + * For example é (lowercase e with acute) is represented by the hex code \x{00E9} + * + * You only need to include lower-case equivalents of characters since the hostname + * check is case-insensitive + * + * Please document the supported TLDs in the documentation file at: + * manual/en/module_specs/Zend_Validate-Hostname.xml + * + * @see http://en.wikipedia.org/wiki/Internationalized_domain_name + * @see http://www.iana.org/cctld/ Country-Code Top-Level Domains (TLDs) + * @see http://www.columbia.edu/kermit/utf8-t1.html UTF-8 characters + * @return string + */ + static function getCharacters(); + +}
\ No newline at end of file diff --git a/lib/Zend/Validate/Hostname/Li.php b/lib/Zend/Validate/Hostname/Li.php new file mode 100644 index 0000000..0739c93 --- /dev/null +++ b/lib/Zend/Validate/Hostname/Li.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Li.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Hostname_Interface + */ +require_once 'Zend/Validate/Hostname/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname_Li implements Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * @see https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1 Liechtenstein (.LI) + * @return string + */ + static function getCharacters() + { + return '\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}'; + } + +}
\ No newline at end of file diff --git a/lib/Zend/Validate/Hostname/No.php b/lib/Zend/Validate/Hostname/No.php new file mode 100644 index 0000000..d0d4c67 --- /dev/null +++ b/lib/Zend/Validate/Hostname/No.php @@ -0,0 +1,52 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: No.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Hostname_Interface + */ +require_once 'Zend/Validate/Hostname/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname_No implements Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * @see http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html Norway (.NO) + * @return string + */ + static function getCharacters() + { + return '\x00E1\x00E0\x00E4\x010D\x00E7\x0111\x00E9\x00E8\x00EA\x\x014B' . + '\x0144\x00F1\x00F3\x00F2\x00F4\x00F6\x0161\x0167\x00FC\x017E\x00E6' . + '\x00F8\x00E5'; + } + +} diff --git a/lib/Zend/Validate/Hostname/Se.php b/lib/Zend/Validate/Hostname/Se.php new file mode 100644 index 0000000..6f181cb --- /dev/null +++ b/lib/Zend/Validate/Hostname/Se.php @@ -0,0 +1,50 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Se.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Hostname_Interface + */ +require_once 'Zend/Validate/Hostname/Interface.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Hostname_Se implements Zend_Validate_Hostname_Interface +{ + + /** + * Returns UTF-8 characters allowed in DNS hostnames for the specified Top-Level-Domain + * + * @see http://www.iis.se/english/IDN_campaignsite.shtml?lang=en Sweden (.SE) + * @return string + */ + static function getCharacters() + { + return '\x{00E5}\x{00E4}\x{00F6}\x{00FC}\x{00E9}'; + } + +}
\ No newline at end of file diff --git a/lib/Zend/Validate/Identical.php b/lib/Zend/Validate/Identical.php new file mode 100644 index 0000000..02a5366 --- /dev/null +++ b/lib/Zend/Validate/Identical.php @@ -0,0 +1,117 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Identical.php 8118 2008-02-18 16:10:32Z matthew $ + */ + +/** Zend_Validate_Abstract */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Identical extends Zend_Validate_Abstract +{ + /**#@+ + * Error codes + * @const string + */ + const NOT_SAME = 'notSame'; + const MISSING_TOKEN = 'missingToken'; + /**#@-*/ + + /** + * Error messages + * @var array + */ + protected $_messageTemplates = array( + self::NOT_SAME => 'Tokens do not match', + self::MISSING_TOKEN => 'No token was provided to match against', + ); + + /** + * Original token against which to validate + * @var string + */ + protected $_token; + + /** + * Sets validator options + * + * @param string $token + * @return void + */ + public function __construct($token = null) + { + if (null !== $token) { + $this->setToken($token); + } + } + + /** + * Set token against which to compare + * + * @param string $token + * @return Zend_Validate_Identical + */ + public function setToken($token) + { + $this->_token = (string) $token; + return $this; + } + + /** + * Retrieve token + * + * @return string + */ + public function getToken() + { + return $this->_token; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if a token has been set and the provided value + * matches that token. + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $this->_setValue($value); + $token = $this->getToken(); + + if (empty($token)) { + $this->_error(self::MISSING_TOKEN); + return false; + } + + if ($value !== $token) { + $this->_error(self::NOT_SAME); + return false; + } + + return true; + } +} diff --git a/lib/Zend/Validate/InArray.php b/lib/Zend/Validate/InArray.php new file mode 100644 index 0000000..1c7725a --- /dev/null +++ b/lib/Zend/Validate/InArray.php @@ -0,0 +1,138 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: InArray.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_InArray extends Zend_Validate_Abstract +{ + + const NOT_IN_ARRAY = 'notInArray'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::NOT_IN_ARRAY => "'%value%' was not found in the haystack" + ); + + /** + * Haystack of possible values + * + * @var array + */ + protected $_haystack; + + /** + * Whether a strict in_array() invocation is used + * + * @var boolean + */ + protected $_strict; + + /** + * Sets validator options + * + * @param array $haystack + * @param boolean $strict + * @return void + */ + public function __construct(array $haystack, $strict = false) + { + $this->setHaystack($haystack) + ->setStrict($strict); + } + + /** + * Returns the haystack option + * + * @return mixed + */ + public function getHaystack() + { + return $this->_haystack; + } + + /** + * Sets the haystack option + * + * @param mixed $haystack + * @return Zend_Validate_InArray Provides a fluent interface + */ + public function setHaystack(array $haystack) + { + $this->_haystack = $haystack; + return $this; + } + + /** + * Returns the strict option + * + * @return boolean + */ + public function getStrict() + { + return $this->_strict; + } + + /** + * Sets the strict option + * + * @param boolean $strict + * @return Zend_Validate_InArray Provides a fluent interface + */ + public function setStrict($strict) + { + $this->_strict = $strict; + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is contained in the haystack option. If the strict + * option is true, then the type of $value is also checked. + * + * @param mixed $value + * @return boolean + */ + public function isValid($value) + { + $this->_setValue($value); + if (!in_array($value, $this->_haystack, $this->_strict)) { + $this->_error(); + return false; + } + return true; + } + +} diff --git a/lib/Zend/Validate/Int.php b/lib/Zend/Validate/Int.php new file mode 100644 index 0000000..1fa0744 --- /dev/null +++ b/lib/Zend/Validate/Int.php @@ -0,0 +1,73 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Int.php 12336 2008-11-06 19:11:46Z thomas $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Int extends Zend_Validate_Abstract +{ + const NOT_INT = 'notInt'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::NOT_INT => "'%value%' does not appear to be an integer" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is a valid integer + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + $this->_setValue($valueString); + if (is_bool($value)) { + $this->_error(); + return false; + } + + $locale = localeconv(); + $valueFiltered = str_replace($locale['decimal_point'], '.', $valueString); + $valueFiltered = str_replace($locale['thousands_sep'], '', $valueFiltered); + + if (strval(intval($valueFiltered)) != $valueFiltered) { + $this->_error(); + return false; + } + + return true; + } + +} diff --git a/lib/Zend/Validate/Interface.php b/lib/Zend/Validate/Interface.php new file mode 100644 index 0000000..4fcd525 --- /dev/null +++ b/lib/Zend/Validate/Interface.php @@ -0,0 +1,71 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Interface.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +interface Zend_Validate_Interface +{ + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param mixed $value + * @return boolean + * @throws Zend_Valid_Exception If validation of $value is impossible + */ + public function isValid($value); + + /** + * Returns an array of messages that explain why the most recent isValid() + * call returned false. The array keys are validation failure message identifiers, + * and the array values are the corresponding human-readable message strings. + * + * If isValid() was never called or if the most recent isValid() call + * returned true, then this method returns an empty array. + * + * @return array + */ + public function getMessages(); + + /** + * Returns an array of message codes that explain why a previous isValid() call + * returned false. + * + * If isValid() was never called or if the most recent isValid() call + * returned true, then this method returns an empty array. + * + * This is now the same as calling array_keys() on the return value from getMessages(). + * + * @return array + * @deprecated Since 1.5.0 + */ + public function getErrors(); + +} diff --git a/lib/Zend/Validate/Ip.php b/lib/Zend/Validate/Ip.php new file mode 100644 index 0000000..67dc95f --- /dev/null +++ b/lib/Zend/Validate/Ip.php @@ -0,0 +1,71 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Ip.php 13289 2008-12-15 23:18:58Z tjohns $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Ip extends Zend_Validate_Abstract +{ + const NOT_IP_ADDRESS = 'notIpAddress'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::NOT_IP_ADDRESS => "'%value%' does not appear to be a valid IP address" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is a valid IP address + * + * @param mixed $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + if ((ip2long($valueString) === false) || (long2ip(ip2long($valueString)) !== $valueString)) { + if (!function_exists('inet_pton')) { + $this->_error(); + return false; + } else if ((@inet_pton($value) === false) ||(inet_ntop(@inet_pton($value)) !== $valueString)) { + $this->_error(); + return false; + } + } + + return true; + } + +} diff --git a/lib/Zend/Validate/LessThan.php b/lib/Zend/Validate/LessThan.php new file mode 100644 index 0000000..9f7b72c --- /dev/null +++ b/lib/Zend/Validate/LessThan.php @@ -0,0 +1,113 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: LessThan.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_LessThan extends Zend_Validate_Abstract +{ + + const NOT_LESS = 'notLessThan'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::NOT_LESS => "'%value%' is not less than '%max%'" + ); + + /** + * @var array + */ + protected $_messageVariables = array( + 'max' => '_max' + ); + + /** + * Maximum value + * + * @var mixed + */ + protected $_max; + + /** + * Sets validator options + * + * @param mixed $max + * @return void + */ + public function __construct($max) + { + $this->setMax($max); + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->_max; + } + + /** + * Sets the max option + * + * @param mixed $max + * @return Zend_Validate_LessThan Provides a fluent interface + */ + public function setMax($max) + { + $this->_max = $max; + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is less than max option + * + * @param mixed $value + * @return boolean + */ + public function isValid($value) + { + $this->_setValue($value); + if ($this->_max <= $value) { + $this->_error(); + return false; + } + return true; + } + +} diff --git a/lib/Zend/Validate/NotEmpty.php b/lib/Zend/Validate/NotEmpty.php new file mode 100644 index 0000000..ce5b1d3 --- /dev/null +++ b/lib/Zend/Validate/NotEmpty.php @@ -0,0 +1,70 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: NotEmpty.php 13249 2008-12-14 19:29:40Z thomas $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_NotEmpty extends Zend_Validate_Abstract +{ + const IS_EMPTY = 'isEmpty'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::IS_EMPTY => "Value is required and can't be empty" + ); + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is not an empty value. + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $this->_setValue((string) $value); + + if (is_string($value) + && (('' === $value) + || preg_match('/^\s+$/s', $value)) + ) { + $this->_error(); + return false; + } elseif (!is_string($value) && empty($value)) { + $this->_error(); + return false; + } + + return true; + } + +} diff --git a/lib/Zend/Validate/Regex.php b/lib/Zend/Validate/Regex.php new file mode 100644 index 0000000..1566f07 --- /dev/null +++ b/lib/Zend/Validate/Regex.php @@ -0,0 +1,125 @@ +<?php + +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: Regex.php 8064 2008-02-16 10:58:39Z thomas $ + */ + + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_Regex extends Zend_Validate_Abstract +{ + + const NOT_MATCH = 'regexNotMatch'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::NOT_MATCH => "'%value%' does not match against pattern '%pattern%'" + ); + + /** + * @var array + */ + protected $_messageVariables = array( + 'pattern' => '_pattern' + ); + + /** + * Regular expression pattern + * + * @var string + */ + protected $_pattern; + + /** + * Sets validator options + * + * @param string $pattern + * @return void + */ + public function __construct($pattern) + { + $this->setPattern($pattern); + } + + /** + * Returns the pattern option + * + * @return string + */ + public function getPattern() + { + return $this->_pattern; + } + + /** + * Sets the pattern option + * + * @param string $pattern + * @return Zend_Validate_Regex Provides a fluent interface + */ + public function setPattern($pattern) + { + $this->_pattern = (string) $pattern; + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value matches against the pattern option + * + * @param string $value + * @throws Zend_Validate_Exception if there is a fatal error in pattern matching + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + $this->_setValue($valueString); + + $status = @preg_match($this->_pattern, $valueString); + if (false === $status) { + /** + * @see Zend_Validate_Exception + */ + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("Internal error matching pattern '$this->_pattern' against value '$valueString'"); + } + if (!$status) { + $this->_error(); + return false; + } + return true; + } + +} diff --git a/lib/Zend/Validate/StringLength.php b/lib/Zend/Validate/StringLength.php new file mode 100644 index 0000000..caf3d4d --- /dev/null +++ b/lib/Zend/Validate/StringLength.php @@ -0,0 +1,223 @@ +<?php +/** + * Zend Framework + * + * LICENSE + * + * This source file is subject to the new BSD license that is bundled + * with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://framework.zend.com/license/new-bsd + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@zend.com so we can send you a copy immediately. + * + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id: StringLength.php 13278 2008-12-15 19:55:17Z thomas $ + */ + +/** + * @see Zend_Validate_Abstract + */ +require_once 'Zend/Validate/Abstract.php'; + +/** + * @category Zend + * @package Zend_Validate + * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + */ +class Zend_Validate_StringLength extends Zend_Validate_Abstract +{ + const TOO_SHORT = 'stringLengthTooShort'; + const TOO_LONG = 'stringLengthTooLong'; + + /** + * @var array + */ + protected $_messageTemplates = array( + self::TOO_SHORT => "'%value%' is less than %min% characters long", + self::TOO_LONG => "'%value%' is greater than %max% characters long" + ); + + /** + * @var array + */ + protected $_messageVariables = array( + 'min' => '_min', + 'max' => '_max' + ); + + /** + * Minimum length + * + * @var integer + */ + protected $_min; + + /** + * Maximum length + * + * If null, there is no maximum length + * + * @var integer|null + */ + protected $_max; + + /** + * Encoding to use + * + * @var string|null + */ + protected $_encoding; + + /** + * Sets validator options + * + * @param integer $min + * @param integer $max + * @return void + */ + public function __construct($min = 0, $max = null, $encoding = null) + { + $this->setMin($min); + $this->setMax($max); + $this->setEncoding($encoding); + } + + /** + * Returns the min option + * + * @return integer + */ + public function getMin() + { + return $this->_min; + } + + /** + * Sets the min option + * + * @param integer $min + * @throws Zend_Validate_Exception + * @return Zend_Validate_StringLength Provides a fluent interface + */ + public function setMin($min) + { + if (null !== $this->_max && $min > $this->_max) { + /** + * @see Zend_Validate_Exception + */ + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The minimum must be less than or equal to the maximum length, but $min >" + . " $this->_max"); + } + $this->_min = max(0, (integer) $min); + return $this; + } + + /** + * Returns the max option + * + * @return integer|null + */ + public function getMax() + { + return $this->_max; + } + + /** + * Sets the max option + * + * @param integer|null $max + * @throws Zend_Validate_Exception + * @return Zend_Validate_StringLength Provides a fluent interface + */ + public function setMax($max) + { + if (null === $max) { + $this->_max = null; + } else if ($max < $this->_min) { + /** + * @see Zend_Validate_Exception + */ + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum length, but " + . "$max < $this->_min"); + } else { + $this->_max = (integer) $max; + } + + return $this; + } + + /** + * Returns the actual encoding + * + * @return string + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Sets a new encoding to use + * + * @param string $encoding + * @return Zend_Validate_StringLength + */ + public function setEncoding($encoding = null) + { + if ($encoding !== null) { + $orig = iconv_get_encoding('internal_encoding'); + $result = iconv_set_encoding('internal_encoding', $encoding); + if (!$result) { + require_once 'Zend/Validate/Exception.php'; + throw new Zend_Validate_Exception('Given encoding not supported on this OS!'); + } + + iconv_set_encoding('internal_encoding', $orig); + } + + $this->_encoding = $encoding; + return $this; + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if the string length of $value is at least the min option and + * no greater than the max option (when the max option is not null). + * + * @param string $value + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + $this->_setValue($valueString); + if ($this->_encoding !== null) { + $length = iconv_strlen($valueString, $this->_encoding); + } else { + $length = iconv_strlen($valueString); + } + + if ($length < $this->_min) { + $this->_error(self::TOO_SHORT); + } + + if (null !== $this->_max && $this->_max < $length) { + $this->_error(self::TOO_LONG); + } + + if (count($this->_messages)) { + return false; + } else { + return true; + } + } +} |